| 

.NET C# Java Javascript Exception

5
Hallo Gemeinde
ich will mir den Umstieg von WinForms nach WPF und MVVM mit einem kleinen eigenen Framework erleichtern.
Dazu müsste ich Propertys dynamisch, also wärend der Laufzeit erzeugen können.

zB MakeProperty("NeuesProperty", vomType String)

Nun sollte folgendes mit dem Property möglich sein

PropertyInfo pi = this.GetType().GetProperty("NeuesProperty");
pi.SetValue(this, Value, null);
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("NeuesProperty"));

Verständlich ausgedrückt, was ich will? und geht das überhaupt?
16.08.2012
Brainy 85 5
5 Antworten
4
Hi Brainy,

meiner Meinung nach kannst Du das Ganze wesentlich einfacher lösen.

Die Labels benötigen eine Eigenschaft, die PB benötigen 3. Also 15 Eigenschaften deklarieren ... zB pb_Statusanzeige_Maximum
Das es aber nun ProgessBars sind, zeigt mir die Entwicklungsumgebung beim Tippen von 'pb' mindestens 12 Einträge. pb_Statusanzeige_Maximum = 100 ist für mich persönlich auch nicht so lesbar (bzw weiss man nicht sofort, dass sich dieser Wert auf ein Controll bezieht oder eine einfache blöde Variabel ist).
Es ist auch nicht interessant, ob sich die Property auf ein Control bezieht oder nicht. MVVM versucht gerade, diese Trennung möglichst so hinzubekommen.
Dass Du den Fortschritt mit einer ProgressBar visualisierst, ist ein Implementierungsdetail. Genausogut könntest Du dafür drei Labels verwenden, einen Slider, ein eigens entworfenes Control oder Farben.

Ich möchte das es zB so aussieht pb_Statusanzeige.Maximum = 100. Die Vorschläge im Editor (intel...weiss jetzt nicht, wie das heisst :-() reduzieren sich auf die 4 pb's, und nach dem Punkt hab ich dann eben die Eigenschaften, die Verändert werden können ..

Das löst Du ganz einfach dadurch, indem Du Dir eine Klasse erzeugst, die die 3 Properties Deiner Statusbar enthält. z.B. so:
// INotifyPropertyChanged.PropertyChanged Event auslösen habe ich hier ausgelassen
public class ProgressViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public int Maximum {get;set;}
public int Minimum {get;set;}
public int Value {get;set;}
}

Das Ganze verwendest Du dann je einmal pro ProgressBar im ViewModel des Hauptfensters:
// INotifyPropertyChanged.PropertyChanged Event auslösen habe ich hier ausgelassen
public class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
Progress1 = new ProgressViewModel();
Progress2 = new ProgressViewModel();
Progress3 = new ProgressViewModel();
// ...
}

public event PropertyChangedEventHandler PropertyChanged;
public ProgressViewModel Progress1 { get; private set; } // setter nicht vergessen!!
public ProgressViewModel Progress2 { get; private set; }
public ProgressViewModel Progress3 { get; private set; }
public string Text1 { get; set; }
public string Text2 { get; set; }

private void ExampleAccess()
{
Progress1.Maximum = 100; // mit '.' und Intellisense
}
}

Und im XAML (MainViewModel als DataContext des Fensters festlegen)
<StackPanel>
<ProgressBar Maximum="{Binding Progress1.Maximum}"
Minimum="{Binding Progress1.Minimum}"
Value="{Binding Progress1.Value}"/>
<!-- usw... -->
</StackPanel>


Das ist nicht viel Arbeit und im Gegensatz zu Laufzeitgenerierung oder irgenwelchen anderen Ideen immer noch für jeden verständlich. Schlussendlich brauchst Du mit Sicherheit auch weniger Code, als für die Laufzeit-Voodoo Lösung.

.. Wie bei Forms eben
Diese Haltung musst Du ablegen. Du machst jetzt WPF.

HTH
Florian
17.08.2012
ffordermaier 8,3k 2 9
Ähem, sollte eigentlich ein Edit meiner Antwort werden. Sorry.
ffordermaier 17.08.2012
+1 Geht schon in Ordnung. Und schön auf den Punkt gebracht. Eigentlich waren es ja zwei Fragen:
"Wie mache ich dynamische Properties?"
und
"Wie löse ich das eigentliche Problem?"
Da haben zwei Antworten auf jeden Fall ihre Daseinsberechtigung.
Matthias Hlawatsch 17.08.2012
3
Ein Ansatz ist die Verwendung von ICustomTypeDescriptor, wie auch in diesem Artikel beschrieben wird. Bekanntes Beispiel sind die Row/Col Properties im VS Designer die dazukommen, sobald ein Control in ein TableLayout gesetzt wird.
16.08.2012
puls200 3,8k 6
+1, coole Idee. Mit nem TypeDescriptor hab ich seit WinForms nicht mehr rumgetrickst, ist aber ein plausibler Ansatz. Für die WPF Infrastruktur sicher verständlich, aber im eigenen nutzenden Code damit hantieren zu müssen, stell ich mir irgendwie eklig vor...
ffordermaier 16.08.2012
Einmal hab ich das gemacht und ja, ist irgendwie eklig :)
puls200 16.08.2012
+1 Ja, so könnte es gehen. Der Vollständigkeit halber, und umzu verhindern, dass der letzte Satz des Artikels überlesen wird, hier noch der Link zum zweiten Teil: http://msdn.microsoft.com/en-us/magazine/cc163804.aspx
Matthias Hlawatsch 17.08.2012
2
Laufzeitcodegenerierung ist prinzipiell möglich, siehe dazu alles was sich im System.Reflection.Emit Namespace befindet. Die API ist aber alles andere als schön. Und ohne tiefergehende Kenntnisse der CLR und IL sicher nicht mal eben schnell zu lernen. Dafür stehen Dir hier alle Möglichkeiten offen. In meinen Augen oft die beste Wahl für RuntimeCodeGeneration Aufgaben. Man gewöhnt sich an alles ;-)
Damit kannst Du zur Laufzeit Typen erzeugen, die die von Dir gewünschten Properties haben. Nur Properties ohne zugehörigen Typ ist nicht möglich. Das geht nur mit Methoden, siehe hierzu DynamicMethod.
Etwas einfacher als Reflection.Emit ist die Generierung mit Expressions, siehe hier. Für größere Vorhaben aber auch nicht optimal. Wird schlussendlich auf Wunsch auch via DynamicMethod in ein Delegate kompiliert, also wieder keine Property.

Eine weitere Möglichkeit wäre, die DynamicLanguageRuntime dafür einzusetzen. Erstell Dir eine Klasse, die von DynamicObject ableitet und überschreib die entprechenden Methoden für Properties (TryGetMember, TrySetMember). Benutze eine Instanz davon dann so:
dynamic myDynPropertyHost = new MyDynamicObject();
myDynPropertyHost.SomeProperty = 123;

Die DLR lenkt alle Aufrufe (late-bound) an Dein DynamicObject weiter. Ich habe so das Gefühl, Dir schwebt ein DynamicViewModel vor, siehe hier für eine Beispielimplementierung.

Wenn Du Dein Vorhaben etwas genauer spezifizieren kannst, findet sich vielleicht noch eine andere (mit hoher Wahrscheinlichkeit einfachere) Lösung.

Florian
16.08.2012
ffordermaier 8,3k 2 9
Dem letzten Satz schließe ich mich an: wäre interessant zu wissen, was Du mit diesen Properties eigentlich anstellen willst - insbesondere, wie Dir die Möglichkeit, dynamisch Properties zu ergänzen, den Umstieg von Windows Forms zu WPF erleichtert.
Matthias Hlawatsch 17.08.2012
1
Hallo zusammen
ich werde mir den beschriebenen Ansatz von Florian am Wochenende mal genau ansehen. Ich vermute, dass ist das, was ich brauche.
Wenn Du Dein Vorhaben etwas genauer spezifizieren kannst, findet sich vielleicht noch eine andere (mit hoher Wahrscheinlichkeit einfachere) Lösung.

Naja, ich will ein Property zur Laufzeit erzeugen .... ;-)
Aber der Reihe nach. Ich hab noch nicht viel mit WPF gemacht, und wenn dann natürlich alles im Codebehind programmiert. Nun hab ich ein ganz kleines Projekt, Welches nur aus ein paar Label und ProgressBars besteht. In den Label wird der Text geändert, und die ProgressBars sind zur Fortschrittanzeige .. keine Clicks, kein sonstwas.
Also etwas leichtes zum üben von MVVM. Also schnell eine UI gebastellt und ab in den Code ... und da fing es schon wieder an zu nerven.
Die Labels benötigen eine Eigenschaft, die PB benötigen 3. Also 15 Eigenschaften deklarieren ... zB pb_Statusanzeige_Maximum
Das es aber nun ProgessBars sind, zeigt mir die Entwicklungsumgebung beim Tippen von 'pb' mindestens 12 Einträge. pb_Statusanzeige_Maximum = 100 ist für mich persönlich auch nicht so lesbar (bzw weiss man nicht sofort, dass sich dieser Wert auf ein Controll bezieht oder eine einfache blöde Variabel ist). Ich möchte das es zB so aussieht pb_Statusanzeige.Maximum = 100. Die Vorschläge im Editor (intel...weiss jetzt nicht, wie das heisst :-() reduzieren sich auf die 4 pb's, und nach dem Punkt hab ich dann eben die Eigenschaften, die Verändert werden können .. Wie bei Forms eben. Also hab ich mir folgendes überlegt (auch wenn es solche Ansätze schon gibt, will ichs eben selbst machen, um es zu verstehen). Wir basteln uns eine UI mit nur einem Label:
<Label Content="{Binding label_anzeige_Content, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Height="45" HorizontalAlignment="Left" Margin="92,84,0,0" Name="label_anzeige" VerticalAlignment="Top" Width="209" />

Die Bindingvariabel heisst immer Controllname + _ + Attributname.
Codebehind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var view = new mvvm();
DataContext = view;
}
}

Dann zum MVVM ( hier soll er in 2 Sekunden ein Text in das Label Schreiben
class mvvm :PForms
{
public mvvm()
{
label_anzeige = pLabel("label_anzeige"); // <-- die wichtige Zeile für das Pseudolabel. Als Übergabewert den Namen des Labels in der UI

Timer t = new Timer(2000);
t.Elapsed += new ElapsedEventHandler(t_Elapsed);
t.Start();
}

void t_Elapsed(object sender, ElapsedEventArgs e)
{
Timer t = (Timer)sender;
t.Stop();
label_anzeige.Content = "test";

}
}


Nun kommen noch 2 Klassen .. einmal die Classe, die alles steuert (eigene dll, die ich dann immer mitschleppen und erweitern kann)

pforms (Pseudoforms)
public class PForms : INotifyPropertyChanged
{


public PLabel pLabel(string Name)
{
var lb = new PLabel(Name);
PropertyInfo[] pi = lb.GetType().GetProperties();


foreach (PropertyInfo propertyInfo in lb.GetType().GetProperties())
{
// Und hier müsste nun für jede Eigenschaft in PLabel ein Properts in Form von Name + _ + Eigenschaftsname erzeugt werden
}

lb.PropertyChanged += new PLabel.PropertyChangedEvent(P_PropertyChanged);
return lb;
}


void P_PropertyChanged(string Propertyname, object Value)
{
try
{

PropertyInfo pi = this.GetType().GetProperty(Propertyname);
pi.SetValue(this, Value, null);
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(Propertyname));
}
catch
{ }
}
public event PropertyChangedEventHandler PropertyChanged;
}

forms
public class PLabel
{

public delegate void PropertyChangedEvent(string PropertyName, object Value);
public event PropertyChangedEvent PropertyChanged;
public String Name { get; set; }
public PLabel(string name)
{
Name = name;
}
private string _content;
public string Content
{
get { return _content; }
set
{
_content = value;
PropertyChanged(Name + "_Content", Content);
}
}
}


Ist eigentlich ganz einfach, ausser der fehlende Teil in der Klasse PForms.
Ich hoffe, das war nicht zu blöd alles von mir ...

Grüsse und schönes sonniges WE
17.08.2012
Brainy 85 5
1
Danke für den ausführlichen Blick hinter die Kulissen.
Ich hab's vielleicht noch nicht ganz verstanden, aber mein Bauchgefühl sagt: don't fight the framework.
Die vielen Properties, die Dich nerven, entstehen in der ViewModel-Klasse?
Hast Du schon mal überlegt, ob Dir die Index-Syntax beim Binding helfen könnte? (Binding=MyDict[MaxStatus]...)
Siehe z.B. http://msdn.microsoft.com/de-de/library/system.windows.data.binding.path.aspx
Matthias Hlawatsch 17.08.2012
0
Hallo zusammen
entweder ist die Sonne zu stark, die Hitze zu gross, oder ich sollte einfach Wochenende machen.
Von Florian die Lösung ist ja total simpel, und eigentlich genau das, was ich erreichen wollte ... hab aber irgendwie nen Knoten im Hirn ...
// INotifyPropertyChanged.PropertyChanged Event auslösen habe ich hier ausgelassenpublic 
class MainViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ProgressViewModel Progress1 { get; }
public ProgressViewModel Progress2 { get; }
public ProgressViewModel Progress3 { get; }
public string Text1 { get; set; }
public string Text2 { get; set; }
private void ExampleAccess()
{
ProgressBar1.Maximum = 100; // mit '.' und Intellisense
}
}

Bei ProgressBar1.Maximum = 100 fliegt das Programm um die Ohren, weil irgendwo das Schlüsselwort new fehlt. Ist wohl nicht mein Tag :-(
17.08.2012
Brainy 85 5
Muss auch
Progress1.Maximum
heißen. Du beziehst Dich ja damit auf die Property MainViewModel.Progress1
Sorry, war wohl ein Tippfehler. Habs in meinem Post editiert.
ffordermaier 17.08.2012
Achja, und im Konstruktor des MainViewModels solltest Du die Properties auch initialisieren. Hab meinen Post nochmal angepasst.
ffordermaier 17.08.2012
Das initialieren im Konstruktor hatte ich auch schon versucht. Hatte dann aber noch einen kleinen Fehler, so das die Anzeige sich nicht aktuallisierte. Einen starken Espresso später klappte es.
Mein Ansatz, den ich aus deinem ersten Post versucht hab, klappt auch schon (zumindest in eine Richtung).

Die Handhabung, wie man nun die Eigenschaften anspricht, ist gleich.
Brainy 17.08.2012
Der Aufwand ist auch relativ gleich ... mal übers WE drüber nachdenken, was für Vorteile sich ergeben.
Vielen lieben dank für die Hilfe
Brainy 17.08.2012
Gern geschehen. Wenn Dich nach dem Wochenende des Grübelns das Gefühl beschleichen sollte, dass meine Antwort Dein Problem hinreichend gelöst hat, würde ich mich freuen, wenn Du sie durch Klicken des grünen Häkchens als "akzeptiert" kennzeichnest.
ffordermaier 17.08.2012
Deine Lösung ist so genial einfach , da kann man nur das grüne Häckchen drücken.
Wobei ich meine Lösung auch nicht soooooo schlecht schlecht finde. Vodoo ist sie auf keinen Fall .. werde meinen Ansatz vielleicht noch ein wenig weiter denken und evtl hier vorstellen
Brainy 17.08.2012
Danke :-) Voodoo hin oder her, "Keep it simple" ist nie schlecht. Bin schon gespannt auf Deine Lösung...
ffordermaier 17.08.2012
Hi, ich hab meine Lösung nicht weiter verfolgt. Deine Lösung brachte mir genau das, was ich haben wollte. Vielmehr hat mich beschäftigt, wieso ich nicht drauf gekommen bin. Ich wusste einfach nicht, dass es mit der Bindung "pb.Content" (also dieser Punkt) funktioniert. So bekomm ich mein 'Feel like Forms' hin und verstoss in keinem Punkt dem MVVM. Also nochmal Danke
Brainy 20.08.2012

Stelle deine .net-Frage jetzt!