Benutzer mit den meisten Antworten
WPF - Binding und Vererbung

Frage
-
Hallo zusammen
Ich habe folgendes Problem:
Folgende Klassen erben von Contact:
- Company
- Person
- BranchNun ist es so, dass die Klassen Company und Person ein Property Salutation besitzen. Contact besitzt ein Property Name.
Ich habe nun ein WPF-Window erstellt mit Textboxen die ich nun binden möchte. Als DataContext dient das Objekt vom Typ Contact. Wenn ich nun das Property Name (das in Contact existiert) binde, funktioniert das einwandfrei. Ich möchte nun aber auch das Property Salutation (das nur in Company und Person existiert) an eine Textbox binden. Leider funktioniert das nicht. Die Frage ist nun, wie kriege ich es hin, das Property Salutation zu binden? Wenn Contact vom konkreten Typ Branch ist, soll einfach nichts angezeigt werden - bzw. später dann noch auf Visible=false gesetzt werden.
Danke für eure Hilfe.
Antworten
-
Hallo Thomas,
Kernaspekt des ViewModel ist es, das Model um das zu ergänzen, was die View benötigt,
aber nicht (immer) im Model vorhanden ist. Als reine 1 : 1 Abbildung würde das
ViewModel seinen Zweck verlieren (und Du hättest MV ;-).Was aber auch bedeutet, dass Du nicht direkt Contact aus dem Model durchreichen solltest,
sondern ein (kombiniertes) ViewModel für alle verwendeten Eigenschaften entwerfen -
umgekehrt auch alles streichen, das die View(s) nicht verwendet.Und so wäre eine Salutation Eigenschaft im VM sinnvoll,
ergänzt um eine boolsche Eigenschaft wie IsCompanyOrPerson [1]
die zur Steuerung der Visible Eigenschaft, Auswahl eines anderen DataTemplates
uam. dienen könnte (für die Details der Darstellung wäre wiederum die View zuständig).Und es könnte in angedeutet in etwa so aussehen:
public class ContactViewModel : ViewModelBase // INotifyPropertyChanged und Co. { private Contact _model; public ContactViewModel(Contact model) { this._model = model; } public string Name { get { return _model.Name; } set { if (_model.Name != value) { this._model.Name = value; OnPropertyChanged("Name"); } } } public string Salutation { get { // Ein Interface mag es einfacher machen if (_model is Person) return ((Person)_model).Salutation; if (_model is Company) return ((Company)_model).Salutation; return null; } set { string oldValue = this.Salutation; if (oldValue != value) { if (_model is Person) ((Person)_model).Salutation = value; else if (_model is Company) ((Company)_model).Salutation = value; OnPropertyChanged("Salutation"); } } } public bool IsCompanyOrPerson { get { return ((_model is Person) || (_model is Company)); } } }
Wie oben im Kommentar angedeutet könnte ein Interface für Eigenschaften / -gruppen
sinnvoll sein, ebenso gilt das für das angedeutete IsCompanyOrPerson.Das wäre jetzt die einfachste Fassung. Variationen wären viele denkbar.
So z. B.: Wenn Deine von Contact abgeleiteten Klassen bereits INotifyPropertyChanged
implementieren, könnte sich das ViewModel dort einhängen und die Ereignisse weiterleiten,
anstatt sie selbst zu definieren.Ist ein Wechsel von Branch zu Company/Person möglich, müsste eine Benachrichtigung
Gruß Elmar
für IsCompanyOrPerson implementiert werden.
[1] Der Eigenschaftsname sollte den Zweck widerspiegeln
und wäre u. U. besser benannt werden.- Als Antwort markiert Robert BreitenhoferModerator Dienstag, 20. Juli 2010 14:16
-
Hallo Thomas,
Du schriebst, Du hättest ein ViewModel.
Ja, das nehme ich heutzutage auch fast als Standardfall an und meine Antwort würde sich im ~Prinzip nicht ändern. Und ich ich stimme Dir 100% zu, dass zusätzliche Eigenschaften im ViewModel unschön wären (also ein Quellcode wie bei Elmar muss eben nicht sein).
In so einem Fall ist ja normal der DataContext an die ViewModel-Instanz gebunden (mit oder ohne MEF, das sei mal dahingestellt). Nehmen wir mal als Basisklasse [InpcBase] an, dann kannst Du im XAML zum Beispiel so etwas schreiben:<TextBox [...] Text="{Binding Path=Kontakt.Salutation}" [...]
der Code könnte zum Beispiel (ganz rudimentär) so aussehen:
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); DataContext = new ViewModelMain { Kontakt = new Contact { Name = "Neumann" } }; } private void btnTest_Click(object sender, RoutedEventArgs e) { var modell = DataContext as ViewModelMain; modell.Kontakt = new Person {Salutation = "Person Anrede" }; } } // ....
mit dem ViewModel:
public class ViewModelMain : INPC.InpcBase { private Contact kontakt = new Contact { Name = "<ungesetzt>" }; /// <summary>Beschreibung für Kontakt</summary> public Contact Kontakt { get { return kontakt; } set { kontakt = value; this.RaisePropertyChanged(()=>Kontakt); } } }
ciao Frank- Als Antwort markiert Robert BreitenhoferModerator Dienstag, 20. Juli 2010 14:16
Alle Antworten
-
Hallo Thomas,
nehmen wir an, dass Du im XAML einfach etwas wie folgendes gesetzt hast:
<TextBox [...] Text="{Binding Path=Salutation}" />
dann bräuchtest Du für eine Neu-Bindung "zum Beispiel" einfach nur:
DataContext = new Person() { Name = "HauptPerson", Salutation = "Hallo" }; // oder DataContext = new Branch { Name = "Verwandter" };
ausführen.
ciao Frank -
Hallo Frank
Das "Problem" ist, dass ich mittels ViewModel arbeite. Nun ist es so, dass ich ein ViewModel habe. Dieses hat wiederum ein Property Model vom Typ Contact. Als DataContext wird effektiv das ViewModel gesetzt. Das Binding dann
<TextBox [...] Text="{Binding Path=Model.Salutation}" />
Ich könnte nun hingehen und das Property Salutation im ViewModel zusätzlich implementieren und zum Model weiterleiten - was ich aber überhaupt nicht schön finden - vor allem gibt es noch mehrere solche Properties.
Direkt zum Model binden geht auch nicht, da es einige Properties gibt, die ich im ViewModel implementieren muss.
Evt. ist mit dieser Ausführung mein Problem klarer geworden.
Viele Grüsse, Thomas
-
Hallo Thomas,
Kernaspekt des ViewModel ist es, das Model um das zu ergänzen, was die View benötigt,
aber nicht (immer) im Model vorhanden ist. Als reine 1 : 1 Abbildung würde das
ViewModel seinen Zweck verlieren (und Du hättest MV ;-).Was aber auch bedeutet, dass Du nicht direkt Contact aus dem Model durchreichen solltest,
sondern ein (kombiniertes) ViewModel für alle verwendeten Eigenschaften entwerfen -
umgekehrt auch alles streichen, das die View(s) nicht verwendet.Und so wäre eine Salutation Eigenschaft im VM sinnvoll,
ergänzt um eine boolsche Eigenschaft wie IsCompanyOrPerson [1]
die zur Steuerung der Visible Eigenschaft, Auswahl eines anderen DataTemplates
uam. dienen könnte (für die Details der Darstellung wäre wiederum die View zuständig).Und es könnte in angedeutet in etwa so aussehen:
public class ContactViewModel : ViewModelBase // INotifyPropertyChanged und Co. { private Contact _model; public ContactViewModel(Contact model) { this._model = model; } public string Name { get { return _model.Name; } set { if (_model.Name != value) { this._model.Name = value; OnPropertyChanged("Name"); } } } public string Salutation { get { // Ein Interface mag es einfacher machen if (_model is Person) return ((Person)_model).Salutation; if (_model is Company) return ((Company)_model).Salutation; return null; } set { string oldValue = this.Salutation; if (oldValue != value) { if (_model is Person) ((Person)_model).Salutation = value; else if (_model is Company) ((Company)_model).Salutation = value; OnPropertyChanged("Salutation"); } } } public bool IsCompanyOrPerson { get { return ((_model is Person) || (_model is Company)); } } }
Wie oben im Kommentar angedeutet könnte ein Interface für Eigenschaften / -gruppen
sinnvoll sein, ebenso gilt das für das angedeutete IsCompanyOrPerson.Das wäre jetzt die einfachste Fassung. Variationen wären viele denkbar.
So z. B.: Wenn Deine von Contact abgeleiteten Klassen bereits INotifyPropertyChanged
implementieren, könnte sich das ViewModel dort einhängen und die Ereignisse weiterleiten,
anstatt sie selbst zu definieren.Ist ein Wechsel von Branch zu Company/Person möglich, müsste eine Benachrichtigung
Gruß Elmar
für IsCompanyOrPerson implementiert werden.
[1] Der Eigenschaftsname sollte den Zweck widerspiegeln
und wäre u. U. besser benannt werden.- Als Antwort markiert Robert BreitenhoferModerator Dienstag, 20. Juli 2010 14:16
-
Hallo Thomas,
Du schriebst, Du hättest ein ViewModel.
Ja, das nehme ich heutzutage auch fast als Standardfall an und meine Antwort würde sich im ~Prinzip nicht ändern. Und ich ich stimme Dir 100% zu, dass zusätzliche Eigenschaften im ViewModel unschön wären (also ein Quellcode wie bei Elmar muss eben nicht sein).
In so einem Fall ist ja normal der DataContext an die ViewModel-Instanz gebunden (mit oder ohne MEF, das sei mal dahingestellt). Nehmen wir mal als Basisklasse [InpcBase] an, dann kannst Du im XAML zum Beispiel so etwas schreiben:<TextBox [...] Text="{Binding Path=Kontakt.Salutation}" [...]
der Code könnte zum Beispiel (ganz rudimentär) so aussehen:
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); DataContext = new ViewModelMain { Kontakt = new Contact { Name = "Neumann" } }; } private void btnTest_Click(object sender, RoutedEventArgs e) { var modell = DataContext as ViewModelMain; modell.Kontakt = new Person {Salutation = "Person Anrede" }; } } // ....
mit dem ViewModel:
public class ViewModelMain : INPC.InpcBase { private Contact kontakt = new Contact { Name = "<ungesetzt>" }; /// <summary>Beschreibung für Kontakt</summary> public Contact Kontakt { get { return kontakt; } set { kontakt = value; this.RaisePropertyChanged(()=>Kontakt); } } }
ciao Frank- Als Antwort markiert Robert BreitenhoferModerator Dienstag, 20. Juli 2010 14:16