Laden...

Wie Klasse benachrichtigen, sobald sich Variable im ViewModel ändert?

Erstellt von CodeHamster vor 4 Jahren Letzter Beitrag vor 4 Jahren 1.468 Views
CodeHamster Themenstarter:in
6 Beiträge seit 2019
vor 4 Jahren
Wie Klasse benachrichtigen, sobald sich Variable im ViewModel ändert?

Hi Leute, ich hätte folgendes Problem:

Ich habe ein einfaches WPF-Fenster, welches ein Textfeld beinhaltet. Dieses ist an eine Variable im MainViewModel gebunden, diese nennt sich MeinText.

Soweit funktioniert meine Anwendung schon mal 😉

Anbei das MainViewModel:

public class MainViewModel : INotifyPropertyChanged
    {
        private string meinText;
        public string MeinText { set { meinText = value; OnPropertyChanged(); } 
            get
            {
                return meinText;
            }
        }

        public MainViewModel()
        {
            MeinText = "blah";
        }

        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            var handler = PropertyChanged;
            if (handler != null)
            {

                handler(this, new PropertyChangedEventArgs(propertyName));
               
            }
        }
    }

Nun möchte ich eine weitere Klasse schreiben, diese soll von ModelVisual3D abgeleitet sein (da sie später mal ein 3D-Objekt darstellen wird).

Der Klasse habe ich eine DependencyProperty spendiert; diese nennt sich: MyProperty. Hier ist die Klasse:

public class Bena : ModelVisual3D
    {
        private string text;

        public string Text { get => text; set => text = value; }

        public static readonly DependencyProperty MyProperty = DependencyProperty.Register("BetonHoehe", typeof(string), typeof(Bena), new PropertyMetadata("leer")); //new UIPropertyMetadata(0, (sender, e) => OnMyPropertyChanged(sender, e)));

        public string BetonHoehe
        {
            get
            {
                Console.WriteLine("Lese Wert aus");
                return (string)this.GetValue(MyProperty);
            }
            set { this.SetValue(MyProperty, value); Console.WriteLine("Setze den Wert neu"); }
        }


        public Bena()
        {
            text = "";
        }

    }

Nun habe ich ein Databinding Objekt erzeugt, welches mir die Bindung zwischen meiner Variable "MeinText" im MainViewModel und der DependencyProperty myProperty erzeugen soll.

Ist im Moment alles noch im CodeBehind:

 public partial class MainWindow : Window
    {
        public static MainViewModel mvm;
        public MainWindow()
        {
            mvm = new MainViewModel();
            DataContext = mvm;
            InitializeComponent();

            Bena bena = new Bena();

            Binding myBinding = new Binding("MeinText");
            myBinding.Source = mvm;
            myBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
            myBinding.Mode = BindingMode.TwoWay;
            BindingOperations.SetBinding(bena, Bena.MyProperty, myBinding);
        }
    }

Der tiefere Sinn hierbei ist dass meine Klasse namens _Bena _benachrichtigt wird sobald sich meine Variable **MeinText **im _MainViewModel _ändert.
Funktioniert nur leider nicht. meine Debugging-Ausgaben kommen nicht.

Hat jemand eine Idee warum das so ist, oder eine bessere Idee wie ich das Binding zwischen MVM und meiner Custom-Klasse bewerkstelligen kann?

16.807 Beiträge seit 2008
vor 4 Jahren

Der tiefere Sinn hierbei ist dass meine Klasse namens Bena benachrichtigt wird sobald sich meine Variable MeinText im MainViewModel ändert.

Sauber kann man das schnell und einfach zB via Reactive Extensions umsetzen.
Auf der einen Seite publishst Du Ereignisse, auf der anderen Seite abonnierst Du diese.

5.657 Beiträge seit 2006
vor 4 Jahren

Du mußt bei der DependencyProperty die Naming Conventions beachten, siehe Dependency properties overview:

The naming convention of the property and its backing DependencyProperty field is important. The name of the field is always the name of the property, with the suffix Property appended. For more information about this convention and the reasons for it, see Custom Dependency Properties.

Davon abgesehen ist dort der falsche Ort für die Logik der View. Die gehört ins ViewModel.

Weeks of programming can save you hours of planning

CodeHamster Themenstarter:in
6 Beiträge seit 2019
vor 4 Jahren

Du mußt bei der DependencyProperty die Naming Conventions beachten, siehe
>
:

okay, das habe ich nun gemacht. Bena sieht nun so aus


public class Bena : ModelVisual3D
    {
        private string text;

        public string Text { get => text; set => text = value; }

        public static readonly DependencyProperty MyProperty = DependencyProperty.Register("My", typeof(string), typeof(Bena), new PropertyMetadata("leer")); 

        public string My
        {
            get
            {
                Console.WriteLine("Lese Wert aus");
                return (string)this.GetValue(MyProperty);
            }
            set { this.SetValue(MyProperty, value); Console.WriteLine("Setze den Wert neu"); }
        }


        public Bena()
        {
            text = "";
        }

    }

Funktioniert leider immer noch nicht. Mein Objekt wird nicht benachrichtigt.

Davon abgesehen ist dort der falsche Ort für die Logik der View. Die gehört ins ViewModel.

hmm... wie sollte das denn aussehen...
selbst wenn ich mein Objekt vom Typ Bena in den ViewModel setze, so wird es nicht automatisch benachrichtigt wenn sich die Variable MyText ändert. Es gibt schließlich keine Bindung zwischen den beiden.

3.170 Beiträge seit 2006
vor 4 Jahren

Hallo,

Mein Objekt wird nicht benachrichtigt.

Bist Du Dir da sicher? Hast Du versucht, den Wert nach der Änderung im MainViewModel aus der gebundenen Property in Bena mal abzurufen?

meine Debugging-Ausgaben kommen nicht. Das ist jedenfalls nicht verwunderlich, denn über das Binding wird sicherlich nicht Dein Setter aufgerufen, sondern direkt die SetValue-Methode der DependencyProperty. Du kannst also nicht erwarten, dass die Ausgabe automatisch bei jeder Änderung kommt. Die kommt nur, wenn der Wert tatsächlich über Deinen Setter gesetzt wird.
oder eine bessere Idee wie ich das Binding zwischen MVM und meiner Custom-Klasse bewerkstelligen kann?

Ja. Das Gefrickel im CodeBehind sein lassen und das Binding in XAML umsetzen.

Davon abgesehen ist dort der falsche Ort für die Logik der View. Die gehört ins ViewModel. Warum denn? Wenn Bena ein Control ist, an das er einen Wert aus dem ViewModel binden will, dann muss das doch da umgesetzt werden...

Gruß, MarsStein

Non quia difficilia sunt, non audemus, sed quia non audemus, difficilia sunt! - Seneca

5.657 Beiträge seit 2006
vor 4 Jahren

Lies nochmal die Doku zu den DependencyPropertys. Es müßte so aussehen:


public class Bena : ModelVisual3D
    {

        public string Text { get; set; }

        public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(Bena), new PropertyMetadata("leer")); 
    }

Davon abgesehen ist das, wie schon gesagt, der falsche Weg. Keine Ahnung, was du genau vor hast, aber ModelVisual3D verwendest du in der View, und bindest die Geometrie-Daten an die ModelVisual3D.Content-Eigenschaft. Siehe die Beispiele in der Doku: ModelVisual3D Class. Die Geometrie-Daten kannst du im ViewModel erzeugen und dann an die View binden.

Erzähl mal, was du eigentlich vor hast.

Weeks of programming can save you hours of planning

CodeHamster Themenstarter:in
6 Beiträge seit 2019
vor 4 Jahren

Bist Du Dir da sicher? Hast Du versucht, den Wert nach der Änderung im MainViewModel aus der gebundenen Property in Bena mal abzurufen?

Hey, du hast Recht! ich habe quick 'n dirty einen Button mit einem Event in die Oberfläche eingebaut:

private void Button_Click(object sender, RoutedEventArgs e)
        {
            Console.WriteLine(mvm.My + " und nun die MyPropery aus Bena: " + bena.My);
        }

und tatsächlich:

blah22 und nun die MyPropery aus Bena: blah22

nur blöd dass mein Setter nicht aufgerufen wird. Es soll schließlich eine Routine aufgerufen werden, welche das 3D-Objekt u.U. anpasst.

Ich habe versucht das INotifyPropertyChanged Interface in Bena zu implementieren, aber das wird auch nicht aufgerufen, obwohl sich die Propery namens My sich ja offensichtlich ändert.

So sieht bena nun aus:

public class Bena : DependencyObject, INotifyPropertyChanged
    {
        private string text;

        public string Text { get => text; set => text = value; }

        public static readonly DependencyProperty MyProperty = DependencyProperty.Register("My", typeof(string), typeof(Bena), new PropertyMetadata("leer")); //new UIPropertyMetadata(0, (sender, e) => OnMyPropertyChanged(sender, e)));

        public event PropertyChangedEventHandler PropertyChanged;

        public string My
        {
            get
            {
                Console.WriteLine("Lese Wert aus");
                return (string)this.GetValue(MyProperty);
            }
            set { this.SetValue(MyProperty, value); Console.WriteLine("Setze den Wert neu"); }
        }


        public Bena()
        {
            text = "";
        }

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            Console.WriteLine("INotofypropertychanged wird in Bena ausgeführt");
            var handler = PropertyChanged;
            if (handler != null)
            {

                handler(this, new PropertyChangedEventArgs(propertyName));

            }
        }

    }

Jemand noch dafür ne Idee?

5.657 Beiträge seit 2006
vor 4 Jahren

Also solange du die Naming-Conventions für DependencyPropertys und die Hinweise auf die Doku, und sogar die Beispiele ignorierst, kann dir natürlich niemand weiterhelfen.

Bitte beachte [Hinweis] Wie poste ich richtig?

Weeks of programming can save you hours of planning

CodeHamster Themenstarter:in
6 Beiträge seit 2019
vor 4 Jahren

Lies nochmal die Doku zu den DependencyPropertys. Es müßte so aussehen:

  
public class Bena : ModelVisual3D  
    {  
  
        public string Text { get; set; }  
  
        public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(Bena), new PropertyMetadata("leer"));   
    }  
  

das ist quasi das gleich dass auch bei mir steht, nur habe ich es nicht mit der Text-Variable umgesetzt, sondern mit der Variable namens My

Davon abgesehen ist das, wie schon gesagt, der falsche Weg.

sehe ich ehrlich gesagt nicht so. Bena ist im Moment ein Platzhalter für ein bestimmtes, dynamisch generiertes Objekt, welches unter bestimmten Bedingungen seine Form verändert. Es besteht aus hunderten Dreiecken, welche algorithmisch, dynamisch erzeugt werden.
Es stellt sich da die Frage ob man so etwas überhaupt in XAML implementieren kann.

Keine Ahnung, was du genau vor hast, aber ModelVisual3D verwendest du in der View, und bindest die Geometrie-Daten an die ModelVisual3D.Content-Eigenschaft.

Wie gesagt, Bena ist nur ein Platzhalter. Die richtige Klasse setzt sich die Content-Eigenschaft selbst.
Außerdem gibt es in der View ein Viewport3D und Bena wird diesem über Children.add hinzugefügt.

Die Geometrie-Daten kannst du im ViewModel erzeugen und dann an die View binden.

Und dann soll ich mich also von der Objektorientierung verabschieden und alles in einem komplett überladenen Viewmodel reinpacken?
Ich möchte Bena mit verschiedenen Facetten des Viewmodels koppeln und auch mehrere, aufgrund ihrer Bindung und der daraus generierten Darstellung unterschiedliche Objekte darstellen.

Erzähl mal, was du eigentlich vor hast.

Ganz einfach, ich will das mein Custom Objekt an Variable(n) im Viewmodel gebunden wird. Wird eine Variable im ViewModel geändert, muß ich eine Funktion aufrufen können, damit mein Objekt u.U. angepasst wird.

Hier ein Beispiel:
Nimm mal z.B. drei Mauern. du hast die linke, rechte und mittlere Mauer.
die linke ist an materialLinks im Viewmodel, die mittlere ist an MaterialMitte und die rechte an MaterialRechts gebunden.
_MaterialLinks _hat den Wert Backstein, _MaterialMitte _und _MaterialRechts _haben _Beton _als Wert.

Da erzeuge ich mir drei Objekte meiner Klasse und binde sie entsprechend an meine Variablen im Viewmodel.
_MauerRechts _sei noch an _MauerRechtsRandRechts _gebunden und der Wert ist nun "ausgerissen". Von der Darstellung wird sich der rechte MauerRand schon deutlich von den anderen Rändern der Mauern unterscheiden. Es ist dennoch ein Objekt der Klasse Mauer und diese Klasse muß das Leisten was ich von ihr will, weil ich sie dahingehend entsprechend programmiere.
Die Logik der Darstellung des rechten Randes gehört genau in meine Klasse.

Repariert mir nun jemand den rechten Rand der rechten Mauer, indem er die Variable _MauerRechtsRandRechts _ auf "gerade" setzt, so muß ich eine Funktion aufrufen können, welche das 3D-Objekt anpasst.

Das nun der interne Wert von Bena über BindingOperations.SetBinding bei Änderung der bestimmten Variable im Viewmodel auch geändert wird ist schon mal ein Fortschritt.
Dennoch muß es eine Möglichkeit geben über die Änderung auch informiert zu werden.
INotifyPropertyChanged hilft hier seltsamerweise nicht.

CodeHamster Themenstarter:in
6 Beiträge seit 2019
vor 4 Jahren

Also solange du die Naming-Conventions für DependencyPropertys und die Hinweise auf die Doku, und sogar die Beispiele ignorierst, kann dir natürlich niemand weiterhelfen.

Bitte beachte
>

wieso ignoriere ich die? ich habe doch den Namen der Property entsprechend geändert

5.657 Beiträge seit 2006
vor 4 Jahren

Vielleicht solltest du erstmal in Ruhe eine Nacht darüber schlafen. Dann schau nochmal in die Doku zu den DependencyPropertys, und dann schau dir nochmal den Code an, den ich gepostet habe.

Deine Eigenschaft heißt Text und dein DependencyProperty MyProperty, das kann nicht funktionieren.

Und die Geometrie, die du erzeugst, gehört ins ViewModel. Genau wie Daten einer Liste ins ViewModel gehören und an die Liste in der View gebunden werden, gehören die Geometrie-Daten ins ViewModel und werden an den Viewport3d in der View gebunden.

Ich empfehle auch den Helix-Toolkit, der nimmt dir bei der 3D-Darstellung einiges an Arbeit ab.

Weeks of programming can save you hours of planning

CodeHamster Themenstarter:in
6 Beiträge seit 2019
vor 4 Jahren

Deine Eigenschaft heißt Text und dein DependencyProperty MyProperty, das kann nicht funktionieren.

Darauf habe ich schon vorher reagiert, aber du hast es offenbar überlesen. Lies dir mal den Quelltext durch, die Variable Text hat nichts mit der Property zu tun. Ich binde die Variable **My **an die Property

hier der Codeschnipsel:

public static readonly DependencyProperty MyProperty = DependencyProperty.Register("My", typeof(string), typeof(Bena), new PropertyMetadata("leer")); 

        public string My
        {
            get
            {
                Console.WriteLine("Lese Wert aus");
                return (string)this.GetValue(MyProperty);
            }
            set { this.SetValue(MyProperty, value); Console.WriteLine("Setze den Wert neu"); }
        }

Und die Geometrie, die du erzeugst, gehört ins ViewModel. Genau wie Daten einer Liste ins ViewModel gehören und an die Liste in der View gebunden werden, gehören die Geometrie-Daten ins ViewModel und werden an den Viewport3d in der View gebunden.

Nicht bös sein, aber du übertreibst es. Du siehst es so, ich sehe es anders.
Die für die Darstellung benötigte technische Berechnung gehört in die Klasse, welche das Objekt representiert. Viewmodel hin oder her.

Ich empfehle auch den
>
, der nimmt dir bei der 3D-Darstellung einiges an Arbeit ab.

Das Helix-Toolkit kenne ich bereits. Nützt mir bei den Objekten die ich brauche nur nichts.

Mal davon abgesehen, habe ich es gefunden
und zwar hier:

https://docs.microsoft.com/de-de/dotnet/framework/wpf/advanced/dependency-property-callbacks-and-validation

tatsächlich muß die Deklaration der Property angepasst werden, speziell muß ein anderer Konstruktor für die PropertyMetadata, welches die **PropertyChangedCallback **unterstützt gewählt werden. So sieht es nun aus:

public static readonly DependencyProperty MyProperty = DependencyProperty.Register("My", typeof(string), typeof(Bena), new PropertyMetadata("leer", new PropertyChangedCallback(OnMyChanged)));

Folgende Funktion wird beim Setzen meiner Variable My aufgerufen:

        private static void OnMyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            //Was auch immer hier aufgerufen wird, dies passiert wenn meine Variable My geändert wird
            Console.WriteLine(d.ToString() + " " + e.ToString());
        }

da geht sogar noch mehr, ich kann **CoarcedValueCallback **und **ValidateValueCallback **nutzen 😃

Sieht gut aus 😮)

PS: ich habe das kleine Beispiel-Projekt hochgeladen falls noch jemand auf der Suche nach dieser Lösung ist