Laden...

keine Änderung button.Text bei ViewModel INotifyPropertyChanged binding

Erstellt von Winfried vor einem Jahr Letzter Beitrag vor einem Jahr 758 Views
W
Winfried Themenstarter:in
18 Beiträge seit 2020
vor einem Jahr
keine Änderung button.Text bei ViewModel INotifyPropertyChanged binding

Ich bin trotz langer Suche, sowohl im gesamten Web als auch hier im Forum, bei meinem Problem nicht fündig geworden. Auch die Umschreibung ist etwas schwierig, da sie eigentlich heissen müsste "geht einfach nicht".
Ich habe in Vorbereitung eines größeren Projektes angefangen, Versuche mit dem "alten" Winforms, Framework 4.7 und der Möglichkeit mit einer Art MVVM das Verhalten von GUI Elementen über ein ViewModel zu kapseln und an Daten zu binden. Ich habe viel in WPF damit gearbeitet und dort hat das ganze Konzept einfach "irgendwie" funktioniert. Ganze Tabellen und einzelne Properties waren null Probelm. Der Hinergrund warum ich auf das Framework und Forms zurückgehen muss/möchte ist, daß nur damit erstellte Releases auf einem Raspberry mit mono funktionieren. (Ich habe die serielle Schnittstelle am Start und auch GPIO, das funktioniert soweit erst mal)
Nun scheiter ich aber schon beim ersten Versuch, den Text eines Buttons über das ViewModel zu ändern. Ich habe den Code auf das absolut wesentliche reduziert um irgendwie den Fehler im Detail zu finden, ich schaffe es nicht.
Die Form besteht nur aus einem Label: labelPlayStatus
und einem Button: buttonPlayStop

Beim Button Klick soll sich nicht nur der TExt des Labels (über den Delegaten) sondern auch der Text des Buttons über die Bindung ändern
Das der Delegat UpdateLabel aufgerufen wird zeigt, daß das Event abgefeuert und im Maincode erkannt wird, oder sehe ich das falsch?

Ich habe das Gefühl, daß der Button nicht die Benachrichtigung über das Ereignis erhält oder nicht weiß was er damit machen soll.
Was mache ich falsch? Wo ist mein grundsätzlicher Denkfehler?

Datei 1 MainForm


using System;
using System.Windows.Forms;

namespace NewMVTest
{
    public partial class Form1 : Form
    {
        ViewModel viewModel;
        public Form1()
        {
            InitializeComponent();
            viewModel = new ViewModel();
            buttonPlayStop.DataBindings.Add("Text", viewModel, "SetPlayButtonText");
            viewModel.PropertyChanged += UpdateLabel;
            
        }
        private void UpdateLabel(object sender, System.EventArgs e)
        {
            labelPlayStatus.Text = labelPlayStatus.Text == "-PLAY-" ? "-STOP-" : "-PLAY-";
        }
        private void buttonPlayStop_Click(object sender, EventArgs e)
        {
            viewModel.SetPlayButtonText = viewModel.SetPlayButtonText == "PLAY" ? viewModel.SetPlayButtonText = "STOP" : viewModel.SetPlayButtonText = "PLAY";
        }
    }
}



Datei 2 ViewModelClass


using System;
using System.ComponentModel;

namespace NewMVTest
{
    public class ViewModel : INotifyPropertyChanged
    {
        private string _buttonPlayText;
        public string SetPlayButtonText
        {
            get
            {
                return _buttonPlayText;
            }
            set
            {
                if (_buttonPlayText == value) return;
                _buttonPlayText = value;
                RaisePropertyChanged("Text");
            }
        }
        public event PropertyChangedEventHandler PropertyChanged;
        protected void RaisePropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

4.931 Beiträge seit 2008
vor einem Jahr

Sollte es nicht RaisePropertyChanged("SetPlayButtonText") (bzw. RaisePropertyChanged(nameof(SetPlayButtonText)) heißen?

W
Winfried Themenstarter:in
18 Beiträge seit 2020
vor einem Jahr

Fehler in Zeile 23 Datei 1 - es müssen ja nur die Strings sein - aber keine Änderung am Gesamtverhalten
viewModel.SetPlayButtonText = viewModel.SetPlayButtonText == "PLAY" ? "STOP" : "PLAY";


        private void buttonPlayStop_Click(object sender, EventArgs e)
        {
            viewModel.SetPlayButtonText = viewModel.SetPlayButtonText == "PLAY" ? "STOP" : "PLAY";
        }

W
Winfried Themenstarter:in
18 Beiträge seit 2020
vor einem Jahr

DANKE! Genau das war es

RaisePropertyChanged("SetPlayButtonText")

Da war mein Denkfehler: die Sichtweise wer was wie sieht oder erwartet

W
Winfried Themenstarter:in
18 Beiträge seit 2020
vor einem Jahr

Ich habe jetzt noch mal das Beispiel nach dem ich gearbeitet habe angeschaut, ganz klarer Flüchtigkeitsfehler von mir weil ich es noch nicht ganz verstanden hatte.
Die Kontexterklärung von Bindings.Add in VS ist für den Anfängerblick auch nicht immer hilfreich. Da ich dann fast alle Variablen für meine Zwecke umbenannt habe, bin ich da in die Falle getappt.
Danke für die schnelle Hilfe.

Aber jetzt wirds schwieriger.....

Leider wird der Text auf dem Raspberry immer noch nicht angezeigt. Das Label wechselt den Text, aber der Button zeigt gar keinen Text.
Schade, ich hoffe kein Konflikt der durch die mono Umgebung auftritt

4.931 Beiträge seit 2008
vor einem Jahr

Warum bindest du nicht den Label-Text auch an die Eigenschaft?
Wenn der Text dort unterschiedlich zum Button-Text sein soll, dann füge eine zweite Eigenschaft (evtl. nur als Getter) im ViewModel hinzu.

Du solltest jedliche View-Logik im ViewModel haben, d.h. auch das Umschalten der Texte (so daß, bis auf das Binding, kein Code in der View-Klasse ist).

PS: Für Unterschiede bzgl. Mono habe ich noch Winforms data binding and INotifyPropertyChanged on mono gefunden.

W
Winfried Themenstarter:in
18 Beiträge seit 2020
vor einem Jahr

Der Labeltext war nur der Nebenschauplatz und Test ob das Event überhaupt abgefeuert wird und die Logik nur um direkten Zugriff auf das Control zu haben.
Aber trotzdem Danke für den Hinweis. Ich bin seit langem sehr darum bemüht, genau diese Trennung immer und überall gleich beim Neuschreiben eines Programmes einzuführen. Das hat begonnen, als ich vor zwei Jahren das Dreischichtenmodell entdeckt hatte und natürlich mit WPF das MVVM Pattern. Das erscheint bei kleinen Programmen erst etwas umständlich, entpuppt sich aber später immer als Gold wert.

Ganz besonder Dank für Deine Suche nach dem mono Problem, das ist genau die Schublade die ich nun auch aufgemacht habe...
Meine Erkenntnisse dazu werde ich dort beschreiben und hier kurz verlinken/andeuten

W
Winfried Themenstarter:in
18 Beiträge seit 2020
vor einem Jahr

Ich habe leider keine Lösung für die fehlende "automatische" Signalisierung bei PropertyChanged gefunden. Dafür habe ich mir nun eine eigene Signalisierung und Auswertung programmiert.
Das Event selber wird ja abgefeuert und in der Main wird es empfangen. Ich werte einfach in einer switch Logic die Property aus und rufe meine entsrpechenden GUI Routinen auf. Da kann ich auch gleich mehrere Dinge auf einmal mit dem Control machen, Backgrond Color wechseln etc.

Das Problem mit threadübergreifenden Zugriffen ist auch mit InvokeRequired gelöst.



VModel.PropertyChanged += UpdateGUI;


  private void UpdateGUI(object sender, System.EventArgs e)
        {
            if (InvokeRequired)
            {
                this.Invoke(new Action<object, System.EventArgs>(UpdateGUI), new object[] { sender, e });
                return;
            }

            var VMProperty = sender as ViewModelClass;

            switch((e as PropertyChangedEventArgs).PropertyName)
                {
                case "ActivePortName":
                    GUIChangePortName(VModel.ActivePortName);
                    break;
                case "ProcessorName":
                    GUIChangeProcessorName(VModel.ProcessorName);
                    break;
                case "ReadLine":
                    GUIChangeReadLine(VModel.ReadLine);
                    break;
                case "ActiveBaudRate":
                    GUIChangeBaudRate(VModel.ActiveBaudRate);
                    break;
                case "ProtocollLine":
                    GUIAddProtocollText(VModel.ProtocollLine);
                    break;
            }

        }

Aber ich bin über die nächste fehlende Event Signalisierung in mono gestolpert, der serialPort feuert kein Event bei


VM.ActiveSerialPort.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);

Mein DataReceiveHandler wird nie aufgerufen. Das ist leider, wenn man etwas sucht, auch so beschrieben.
https://www.mono-project.com/archived/howtosystemioports/

Aber die ReadByte Lösung wie sie hier beschrieben ist funktioniert.

Meine Modifikation um bei einem CR das Lesen zu beenden:


      public string ReadData()
        {
            byte tmpByte;
            var s = '\r'; 
            var b = (byte)s;
            string rxString = "";

            tmpByte = (byte)VM.ActiveSerialPort.ReadByte();
            
            while (tmpByte != b)
            {
                rxString += ((char)tmpByte);
                
                tmpByte = (byte)VM.ActiveSerialPort.ReadByte();
            }

            return rxString;
        }

Ich hoffe der Eine oder Andere verirrt sich hierher über das ursprüngliche Thema und hat einen Erkenntnissgewinn 😉