Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 | Suche | FAQ

Hauptmenü
myCSharp.de
» Startseite
» Forum
» Suche
» Regeln
» Wie poste ich richtig?

Mitglieder
» Liste / Suche
» Wer ist online?

Ressourcen
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Microsoft Docs

Team
» Kontakt
» Cookies
» Spenden
» Datenschutz
» Impressum

  • »
  • Community
  • |
  • Diskussionsforum
Wie Klasse benachrichtigen, sobald sich Variable im ViewModel ändert?
CodeHamster
myCSharp.de - Member

Avatar #avatar-4131.jpg


Dabei seit:
Beiträge: 6

Themenstarter:

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

beantworten | zitieren | melden

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?
Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von CodeHamster am .
private Nachricht | Beiträge des Benutzers
Abt
myCSharp.de - Team

Avatar #avatar-4119.png


Dabei seit:
Beiträge: 15705
Herkunft: BW

beantworten | zitieren | melden

Zitat von CodeHamster
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.
private Nachricht | Beiträge des Benutzers
MrSparkle
myCSharp.de - Team

Avatar #avatar-2159.gif


Dabei seit:
Beiträge: 5963
Herkunft: Leipzig

beantworten | zitieren | melden

Du mußt bei der DependencyProperty die Naming Conventions beachten, siehe Dependency properties overview:
Zitat
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
private Nachricht | Beiträge des Benutzers
CodeHamster
myCSharp.de - Member

Avatar #avatar-4131.jpg


Dabei seit:
Beiträge: 6

Themenstarter:

beantworten | zitieren | melden

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

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.

Zitat von MrSparkle
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.
private Nachricht | Beiträge des Benutzers
MarsStein
myCSharp.de - Experte

Avatar #avatar-3191.gif


Dabei seit:
Beiträge: 3429
Herkunft: Trier -> München

beantworten | zitieren | melden

Hallo,
Zitat von CodeHamster
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?
Zitat von CodeHamster
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.
Zitat von CodeHamster
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.
Zitat von MrSparkle
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
Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von MarsStein am .
Non quia difficilia sunt, non audemus, sed quia non audemus, difficilia sunt! - Seneca
private Nachricht | Beiträge des Benutzers
MrSparkle
myCSharp.de - Team

Avatar #avatar-2159.gif


Dabei seit:
Beiträge: 5963
Herkunft: Leipzig

beantworten | zitieren | melden

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
private Nachricht | Beiträge des Benutzers
CodeHamster
myCSharp.de - Member

Avatar #avatar-4131.jpg


Dabei seit:
Beiträge: 6

Themenstarter:

beantworten | zitieren | melden

Zitat von MarsStein
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?
private Nachricht | Beiträge des Benutzers
MrSparkle
myCSharp.de - Team

Avatar #avatar-2159.gif


Dabei seit:
Beiträge: 5963
Herkunft: Leipzig

beantworten | zitieren | melden

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
private Nachricht | Beiträge des Benutzers
CodeHamster
myCSharp.de - Member

Avatar #avatar-4131.jpg


Dabei seit:
Beiträge: 6

Themenstarter:

beantworten | zitieren | melden

Zitat von MrSparkle
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

Zitat von MrSparkle
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.
Zitat von MrSparkle
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.
Zitat von MrSparkle
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.
Zitat von MrSparkle
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.
private Nachricht | Beiträge des Benutzers
CodeHamster
myCSharp.de - Member

Avatar #avatar-4131.jpg


Dabei seit:
Beiträge: 6

Themenstarter:

beantworten | zitieren | melden

Zitat von MrSparkle
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?

wieso ignoriere ich die? ich habe doch den Namen der Property entsprechend geändert
private Nachricht | Beiträge des Benutzers
MrSparkle
myCSharp.de - Team

Avatar #avatar-2159.gif


Dabei seit:
Beiträge: 5963
Herkunft: Leipzig

beantworten | zitieren | melden

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
private Nachricht | Beiträge des Benutzers
CodeHamster
myCSharp.de - Member

Avatar #avatar-4131.jpg


Dabei seit:
Beiträge: 6

Themenstarter:

beantworten | zitieren | melden

Zitat von MrSparkle
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"); }
        }

Zitat von MrSparkle
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.
Zitat von MrSparkle
Ich empfehle auch den Helix-Toolkit, 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 :o)

PS: ich habe das kleine Beispiel-Projekt hochgeladen falls noch jemand auf der Suche nach dieser Lösung ist
Dieser Beitrag wurde 3 mal editiert, zum letzten Mal von CodeHamster am .
Attachments
private Nachricht | Beiträge des Benutzers