myCSharp.de - DIE C# und .NET Community
Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 
 | Suche | FAQ

» Hauptmenü
myCSharp.de
» Startseite
» Forum
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Suche
» Regeln
» Wie poste ich richtig?
» Forum-FAQ

Mitglieder
» Liste / Suche
» Wer ist wo online?

Ressourcen
» openbook: Visual C#
» openbook: OO
» Microsoft Docs

Team
» Kontakt
» Übersicht
» Wir über uns

» myCSharp.de Diskussionsforum
Du befindest Dich hier: Community-Index » Diskussionsforum » Entwicklung » GUI: WPF und XAML » Wie Klasse benachrichtigen, sobald sich Variable im ViewModel ändert?
Letzter Beitrag | Erster ungelesener Beitrag Druckvorschau | Thema zu Favoriten hinzufügen

Antwort erstellen
Zum Ende der Seite springen  

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

 
Autor
Beitrag « Vorheriges Thema | Nächstes Thema »
CodeHamster
myCSharp.de-Mitglied

avatar-4131.jpg


Dabei seit: 03.11.2019
Beiträge: 6
Entwicklungsumgebung: Visual Studio


CodeHamster ist offline

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

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

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:

C#-Code:
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:

C#-Code:
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:

C#-Code:
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 03.11.2019 21:46.

03.11.2019 21:44 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Abt
myCSharp.de-Team

avatar-4119.png


Dabei seit: 20.07.2008
Beiträge: 13.172
Herkunft: Stuttgart/Stockholm


Abt ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

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.
03.11.2019 21:51 Beiträge des Benutzers | zu Buddylist hinzufügen
MrSparkle MrSparkle ist männlich
myCSharp.de-Team

avatar-2159.gif


Dabei seit: 16.05.2006
Beiträge: 5.214
Herkunft: Leipzig


MrSparkle ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

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.
04.11.2019 17:51 Beiträge des Benutzers | zu Buddylist hinzufügen
CodeHamster
myCSharp.de-Mitglied

avatar-4131.jpg


Dabei seit: 03.11.2019
Beiträge: 6
Entwicklungsumgebung: Visual Studio

Themenstarter Thema begonnen von CodeHamster

CodeHamster ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

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

C#-Code:
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.
04.11.2019 21:31 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
MarsStein MarsStein ist männlich
myCSharp.de-Poweruser/ Experte

avatar-3191.gif


Dabei seit: 27.06.2006
Beiträge: 3.130
Entwicklungsumgebung: VS 2013, MonoDevelop
Herkunft: Trier -> München


MarsStein ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

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 05.11.2019 09:20.

05.11.2019 09:19 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
MrSparkle MrSparkle ist männlich
myCSharp.de-Team

avatar-2159.gif


Dabei seit: 16.05.2006
Beiträge: 5.214
Herkunft: Leipzig


MrSparkle ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

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

C#-Code:
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.
05.11.2019 13:56 Beiträge des Benutzers | zu Buddylist hinzufügen
CodeHamster
myCSharp.de-Mitglied

avatar-4131.jpg


Dabei seit: 03.11.2019
Beiträge: 6
Entwicklungsumgebung: Visual Studio

Themenstarter Thema begonnen von CodeHamster

CodeHamster ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

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:

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

und tatsächlich:

Code:
1:
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:

C#-Code:
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?
06.11.2019 20:56 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
MrSparkle MrSparkle ist männlich
myCSharp.de-Team

avatar-2159.gif


Dabei seit: 16.05.2006
Beiträge: 5.214
Herkunft: Leipzig


MrSparkle ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

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?
06.11.2019 21:09 Beiträge des Benutzers | zu Buddylist hinzufügen
CodeHamster
myCSharp.de-Mitglied

avatar-4131.jpg


Dabei seit: 03.11.2019
Beiträge: 6
Entwicklungsumgebung: Visual Studio

Themenstarter Thema begonnen von CodeHamster

CodeHamster ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

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

C#-Code:
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.
06.11.2019 21:27 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
CodeHamster
myCSharp.de-Mitglied

avatar-4131.jpg


Dabei seit: 03.11.2019
Beiträge: 6
Entwicklungsumgebung: Visual Studio

Themenstarter Thema begonnen von CodeHamster

CodeHamster ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

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
06.11.2019 21:30 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
MrSparkle MrSparkle ist männlich
myCSharp.de-Team

avatar-2159.gif


Dabei seit: 16.05.2006
Beiträge: 5.214
Herkunft: Leipzig


MrSparkle ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

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.
06.11.2019 21:41 Beiträge des Benutzers | zu Buddylist hinzufügen
CodeHamster
myCSharp.de-Mitglied

avatar-4131.jpg


Dabei seit: 03.11.2019
Beiträge: 6
Entwicklungsumgebung: Visual Studio

Themenstarter Thema begonnen von CodeHamster

CodeHamster ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

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:

C#-Code:
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:

C#-Code:
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:

C#-Code:
        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


Dateianhang:
unknown benachrichtige_mich.rar (241,09 KB, 0 mal heruntergeladen)

Dieser Beitrag wurde 3 mal editiert, zum letzten Mal von CodeHamster am 06.11.2019 21:59.

06.11.2019 21:56 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Baumstruktur | Brettstruktur       | Top 
myCSharp.de | Forum
Antwort erstellen


© Copyright 2003-2019 myCSharp.de-Team | Impressum | Datenschutz | Alle Rechte vorbehalten. | Dieses Portal verwendet zum korrekten Betrieb Cookies. 15.11.2019 03:31