Laden...

Propertybinding eines kompletten Objekts

Erstellt von nekron vor 2 Jahren Letzter Beitrag vor 2 Jahren 383 Views
N
nekron Themenstarter:in
14 Beiträge seit 2007
vor 2 Jahren
Propertybinding eines kompletten Objekts

Hi ...

kann mir mal einer helfen - ich komm hier nicht weiter ...

ich habe ein Usercontrol, an diesem hängt ein Objekt (einer Klasse) per Bindung - hier kommt ein ChangedEvent beim Erstellen - aber wie kann ich Änderungen der Klassenmember mitbekommen ?
Wenn ich an das Usercontrol ein Member der Klasse hänge dann klappt das, ich habe dann schon versucht ein self zu erstellen, und dann an mit NotifyPropertyChanged("self"); einen Changeevent auszulösen - klappt aber irgendwie auch nicht ...

Sorry - ich weiss nicht so ganz wie ichs umschreiben soll - bin zu doof dazu 🙂

Gruss,
nekron

2.078 Beiträge seit 2012
vor 2 Jahren

Wenn das Control über Änderungen einer Property informiert werden soll, muss die Klasse mit dieser Property INotifyPropertyChanged implementieren und das Event entsprechend werfen.
Das kannst Du beliebig weit verschachteln.

Oder worauf willst Du hinaus?

N
nekron Themenstarter:in
14 Beiträge seit 2007
vor 2 Jahren

Ich habe eine Klasse, diese implementiert INotifyPropertychanged, funktioniert auch prinzipiell (mit einzelnen Variablen).

Nun binde ich ein Objekt dieser Klasse an ein Usercontrol und möchte prinzipiell über alle geänderten Properties in der Klasse informiert werden, bekomme aber nur zur Erstellung ein Notify.

2.078 Beiträge seit 2012
vor 2 Jahren

Dann wirfst Du scheinbar das Event nicht richtig.
So ganz ohne Code lässt sich dazu aber nichts sagen.

N
nekron Themenstarter:in
14 Beiträge seit 2007
vor 2 Jahren

Mal stark abgekürzt zur Erläuterung...


    public class Tag : INotifyPropertyChanged
    {
        public Tag Self
        {
            get { return this; }
        }
        public Area DataSource
        {
            get
            {
                return this.DataSource;
            }
            set
            {
                this.DataSource = value;
                NotifyPropertyChanged("Self");  NotifyPropertyChanged("DataSource");
            }
        }
        public string ItemAsString
        {
            get
            {
                return this.ItemAsString;
            }
            set
            {
                this.ItemAsString = value;
                NotifyPropertyChanged("Self");  NotifyPropertyChanged("ItemAsString");
            }
        }
[...]
        #region PropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;
        protected void NotifyPropertyChanged(String info)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }
        #endregion

Im Usercontrol:


		public static readonly DependencyProperty AddressProperty = DependencyProperty.Register("Address", typeof(Tag), typeof(TagAdressBox), new FrameworkPropertyMetadata(null, OnAddressChanged)
		{
			BindsTwoWayByDefault = true
		});

		public Tag Address
		{
			get
			{
				return (Tag)GetValue(AddressProperty);
			}
			set
			{
				SetValue(AddressProperty, value);
			}
		}

		public static void OnAddressChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
		{
                [...]
                }


Im View:


                    <userctrl:TagAdressBox x:Name="Edit_Tag" Height="32" HorizontalAlignment="Left" Grid.Column="1" Grid.Row="3" Address="{Binding _editTag.Self, Mode=TwoWay}"/>

hab ich im XAML ein (Usercontrol entsprechen angepasst, das es einen String frisst) funktioniert das Notify


                    <userctrl:TagAdressBox x:Name="Edit_Tag" Height="32" HorizontalAlignment="Left" Grid.Column="1" Grid.Row="3" Address="{Binding _editTag.ItemAsString, Mode=TwoWay}"/>

zu deutsch will ich ein Change-Event haben wenn ItemAsString oder auch Datasource sich ändert.
Vielleicht reichts ja so schon ...

2.078 Beiträge seit 2012
vor 2 Jahren

Das ViewModel:*Die Self-Property kann weg, die ist Mist, genauso auch das PropertyChanged dafür. *Die beiden anderen Properties rufen sich selbst auf, ergo: StackOverflowException. Du brauchst private Variablen. *Und anstelle der Property-Namen als string kannst Du auch einfach nameof(DataSource) nutzen, dann sagt dir der Compiler, wenn Du dich verschrieben hast. *Die Implementierung vom NotifyPropertyChanged sollte man so nicht machen. Entweder Du legst das vorher als Variable ab, oder Du nutzt PropertyChanged?.Invoke(...) - ich tendiere zu Letzterem.

Das Control:*Die DependencyProperty kann komplett weg, für das, was Du damit erreichen möchtest, nimmt man den DataContext. Oder Du definierst je Wert eine DependencyProperty, dann bist Du flexibler, hast aber auch mehr Arbeit. Wenn es an vielen verschiedenen Stellen genutzt werden soll, würde ich zu letzterem tendieren, ansonsten tut's auch der DataContext.

Das XAML:*Was soll "_editTag" sein, wo ist das definiert? Ein DataBinding braucht eine Source, die kann man angeben, tut man das nicht, ist der DataContext der Default. Überlege dir also, was Du für einen DataContext hast, im Binding kannst Du dann Properties aus dieser Klasse nutzen. *Das "Mode=TwoWay" brauchst Du meines Wissens nach nicht, bei veränderlichen DependencyProperties (z.B. TextBox.Text) ist das der Default.

Könnte so aussehen:

public class Tag : INotifyPropertyChanged
{
    private string _itemAsString;

    public event PropertyChangedEventHandler PropertyChanged;

    public string ItemAsString
    {
        get { return _itemAsString; }
        set
        {
            if (_itemAsString != value)
            {
                _itemAsString= value;
                NotifyPropertyChanged(nameof(ItemAsString));
            }
        }
    }

    protected void NotifyPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

        // Ist die Kurzform von:

        var propertyChanged = PropertyChanged;
        if (propertyChanged != null)
            propertyChanged(this, new PropertyChangedEventArgs(propertyName));

        // Der Grund für die Zwischenvariable ist, dass sich zwischen der Prüfung und dem Aufruf der Inhalt des Events ändern kann
        // Mit der Zwischenvariable ziehst Du sozusagen einen Zwischenstand, der sich danach nicht mehr ändert.
        // Oder Du nutzt den ?.-Operator, der macht das automatisch.
    }
}
<userctrl:TagAdressBox DataContext="{Binding DiePropertyWoDieTagInstantHerKommt}" Item="{Binding ItemAsString}" />

Der Code geht davon aus, dass Du für die einzelnen Inhalte je eine DependencyProperty hast, einfach, weil's besser als Beispiel passt.
Hast Du das nicht, dann muss das Control intern selber die Werte binden und davon ausgehen, dass der DataContext eine gültige Instanz hat.

N
nekron Themenstarter:in
14 Beiträge seit 2007
vor 2 Jahren

Das Self habe ich nur zum probieren eingefügt, ob es eventuell so funktioniert 🙂 Dachte mit einfach ,wenn ich das NotifyPropertyChanged("Self") loslasse kommt das ChangeEvent im Usercontrol an. Hat leider nicht funktioniert.

Variablen sind natürlich _[...] als private deklariert, habe ich wohl beim raustippern vergessen...

Ich baue ein Usercontrol, welches Verschiedene Objekte aus dieser Klasse (_editTag ist ein gefülltes Objekt hiervon) bearbeiten bzw. zum Teil nur darstellen soll. Meine Idee war also ich bringe das nicht einzeln zum Usercontrol (also mehrer Bindings) sondern als Sammelobjekt.

Ich kann im Codebehind darauf auch zugreifen, dann passt das, aber ich bekomme keine Changenotifications.
Die ganze Tag Klasse ist eine etwas angestaubte Library, wurde wahrscheinlich historisch so programmiert. Aber werde das Stückchenweise umstellen, macht ja sinn.

Danke Dir, Sorry, C# ist nicht meine Muttersprache 🙂

2.078 Beiträge seit 2012
vor 2 Jahren

Woher soll das Binding denn von den privaten Variablen wissen?
Wie gesagt: Das Binding braucht eine Source, die kannst Du am Binding direkt (ElementName, Source, RelativeSource) mit geben, oder Du gibst keine Source an, brauchst dann aber einen DataContext..

Du kannst auch deine eigene DependencyProperty nutzen, allerdings musst Du das dann auf den DataContext umleiten (würde ich nicht machen, führt nur zu Chaos) oder Du verwendest überall diese DependencyProperty als Source, dann funktioniert es.
Besser wäre aber, Du nimmst den DataContext und castest darauf, die View ist sowieso fest davon abhängig, dann stört das Casting auch nicht mehr.

Viel spannender finde ich aber die Frage, wozu Du überhaupt aus dem CodeBehind darauf zugreifen willst, das ist meistens ein Hinweis auf falsch umgesetztes und/oder nicht verstandenes MVVM.
Logik (außer reine UI-Logik) gehört nicht in die View, dafür ist das ViewModel da. Aufgerufen wird besagte Logik im ViewModel mit Hilfe von Commands.
und die reine UI-Logik kann man meistens in Form von AttachedDependencyProperties, Triggern, Behaviors, etc. auslagern.
Reine UI-Logik lagere ich nur dann nicht aus, wenn diese UI-Logik zu aufwändig auszulagern oder sehr spezifisch für das Control ist und sonst nirgendwo benötigt wird.

Für mich klingt das ein bisschen so, als würdest Du Erfahrung von WinForms mit bringen, oder von einem ähnlichen Framework von einer anderen Sprache.
MVVM war mit WPF ganz neu, das macht den Einstieg etwas schwieriger, weil man viele Erfahrungen nicht anwenden kann.

N
nekron Themenstarter:in
14 Beiträge seit 2007
vor 2 Jahren

Stimmt genau - ich komme rein aus der Richtung Winforms ... wobei ich hier schon versuche Logik und View zu trennen.

da im Usercontrol jedoch nur Werte für den Benutzer zur Eingabe aufbereitet werden - dachte ich wieso soll ich das nicht im Codebehind machen ? Funktioniert ja prinzipiell auch fast - aber auch nur fast.

Im Codebehind entscheide ich quasi -
wenn Tag.isABC -> txtBox1.Visibility = Hidden, textEdit1.Visibility ...
wenn Tag.isDEF -> txtBox1.Visibility = Visible

deswegen möchte ich möglichst mitgekommen wenn sich irgendein Property in _editTag ändert. Somit kann ich das dann für den Benutzer aufbereiten.

2.078 Beiträge seit 2012
vor 2 Jahren

Die zwei von dir genannten Beispiele sind problemlos mittels DataTrigger (die vom Style) lösbar, bei einfachen Fällen brauchst Du dann auch keine IsXYZ-Properties.
Du definierst dann ein Binding auf irgendeine Property, einen Wert, bei dem der Trigger gelten soll und die Style-Setter, die definieren, welche Werte sich dann ändern sollen. Wenn der Trigger nicht gilt, greifen die alten Werte, die kannst Du normal im Style setzen.

Überleg dir aber gut, ob Du die IsXYZ-Properties nutzen willst.
Sie können den Code schnell größer machen, besonders wenn Du für alle möglichen Fälle eine IsXYZ-Property brauchst.
Diese IsXYZ-Properties können aber auch ein Vorteil sein, da es damit bedeutend leichter ist, Änderungen umzusetzen, besonders wenn nicht mehr nur einfache Wert-Vergleiche notwendig sind.

Oder Du greifst auf Converter zurück, wie der Name schon sagt, convertieren die einen Wert in einen Anderen.
Die sind eine einfache Klasse, die IValueConverter implementiert, eine Instanz davon kann dann im Binding gesetzt werden.
Ich habe z.B. immer einen BoolToVisibilityConverter, den ich für genau solche Fälle nutze.

N
nekron Themenstarter:in
14 Beiträge seit 2007
vor 2 Jahren

Danke dir - du hast mir sehr weitergeholfen und mich dem Weg ein ganzes Stück weiter gebracht ...

Vor allem bin ich auf den Multidatatrigger gestossen, mit welchem sich eine entsprechende Logik ohne probleme erstellen lies.

Viele Grüße,
michael