Laden...

MVVM: Textbox in UserControl wird nach Änderung nicht mehr geupdatet

Letzter Beitrag vor einem Jahr 13 Posts 674 Views
MVVM: Textbox in UserControl wird nach Änderung nicht mehr geupdatet

Hallo Forum, ich habe hier ein kurioses Problem und weiß nicht mehr weiter...

In meiner Anwendung habe ich ein UserControl(NumInput) bestehend aus 2 Buttons (+ und -) und einer Textbox erstellt, um einen Wert mittels Klick auf einen der 2 Buttons zu ändern. 2 dieser UCs sind mit noch weiteren Standard-UCs in einem übergeordneten UserControl(ControlBox) zusammengefasst, das an eine Eigenschaft (ViewmodelData - eigene Klasse) gebunden ist.

UserControl "ControlBox"

-> Textbox "Name"

-> Textbox "Drehzahl"

-> UserControl "NumInput"

->-> Button "+"

->-> Button "-"

->-> Textbox "Wert"

-> UserControl "NumInput"

->-> Button "+"

->-> Button "-"

->-> Textbox "Wert"

Wenn sich nun die Eigenschaft "ViewmodelData" ändert, werden auch alle Werte korrekt aktualisiert - passt.

Das Ändern der Werte über die Buttons funktioniert auch wie es soll. Wenn ich aber einen Wert über die Buttons geändert habe und dann "ViewmodelData" ändere, wird der neue Wert nicht mehr im Textfeld des "NumInput" aktualisiert (die anderen Textboxen aber schon). Es bleibt der Wert nach der letzten Änderung im Textfeld des "NumInput".

Hat jemand eine Idee an was das liegt? Mir scheint, dass das UC "NumInput" das Textfeld nach Manipulation "sperrt".

Danke euch für jeden Hinweis.

Hallo,

wie schauen denn die XAML-Codes der TextBoxen aus?

Und hat diese eine TextBox gerade den Tastatur-Fokus?

Hallo,

Tastaturfokus sollte die Textbox nicht haben, da der Wert ja normalerweise nur über die beiden Buttons geändert wird. Im Klick-Event der Buttons wird dann der "Value" manipuliert. Wenn ich einen Wert in die Textbox schreibe tritt das Problem aber genauso auf.

Hier der XAML-Code der Textbox im "NumInput" Control.

            <TextBox 
               x:Name="TB_Value"
               Grid.Row="0"
               Grid.Column="0"
               Height="24"
               TextAlignment="Right"
               FontSize="16"
               BorderThickness="1,1,0,1">
                <TextBox.Text>
                   <Binding Path="Value"
                  UpdateSourceTrigger="PropertyChanged"/>
               </TextBox.Text>
           </TextBox>
Hinweis von Abt vor einem Jahr

Nutz doch einfach Multi-Line Code-Highlight - dann kann mans auch lesen 😉
Editiert.

Schreiben die Buttons direkt in die Text-Eigenschaft (d.h. im Codebehind) oder aber änderst du den Wert von Value? Bei ersterem löscht du ja das Binding darauf.

Hallo Th69,

nein, die Buttons schreiben auf Value in der NumInput.xaml.cs, aber das könnte trotzdem die richtige Spur sein.

Der Aufbau ist ja so:

ViewmodelData → Binding → ControlBox → ViewmodelData.Value → Binding → NumInput.xaml.cs → Binding →NumInput.xaml

Die Manipulation von Value durch die Buttons passiert direkt im NumInput.xaml.cs.

Kann es sein, dass durch die Manipulation die Bindung ViewmodelData.Value zu NumInput gelöscht wird?

Ziel soll es sein, dass nur bei Änderung von ViewmodelData die Usercontrols aktualisiert werden (nach DB Zugriff)

Dann zeige mal den Code für die Buttons.

Dies ist aber kein reines MVVM, wenn du im Codebehind Daten änderst - hier solltest du Commands benutzen, s. [Artikel] MVVM und DataBinding ("3. Commands").

Button in NumInput.xaml

       <Button x:Name="RB_Dec"
            Margin="1 1"
            Padding="0"
            Grid.Column="0"
            PreviewMouseDown="RB_Dec_PreviewMouseDown" 
            PreviewMouseUp="RB_Dec_PreviewMouseUp" >
            <Viewbox Height="37" Width="37">
                <Path
                    Data="M 7 17 L 27 17"
                    Stroke="White"
                    StrokeThickness="5"
                    Fill="White"
                    Stretch="Fill" />
            </Viewbox> 
        </Button>

NumInput.xaml.cs

        private void RB_Dec_PreviewMouseDown(object sender, MouseButtonEventArgs e)
        {
            Tmr_Down.Start();
            DecValue(factorDown);
            IsChanged = true;
        }

Im "Tick" des Timers wird die Schrittweite nach Länge des Tastendrucks angepasst

        private void Tmr_Down_tick(object? sender, EventArgs e)
        {
            
            elapsedTime_Down += 1;
            if(elapsedTime_Down == 10)
            {
                //Tmr_Down_Calc.Interval = StartInterval / 2;
                factorDown = 2;
            }
            else if (elapsedTime_Down == 25)
            {
                //Tmr_Down_Calc.Interval = StartInterval / 5;
                factorDown = 5;
            }
            else if (elapsedTime_Down == 50)
            {
                //Tmr_Down_Calc.Interval = StartInterval / 10;
                factorDown = 10;
            }
            DecValue(factorDown);
        }

Die Manipulation von Value dann in der Funktion

        private void DecValue(int factor)
        {
            Value -= (StepValue * (float)factor);
            if (Value < MinValue) Value = MinValue;
        }

Value ist im UserControl NumInput.xaml.cs als eine DependancyProperty angelegt

        public float Value
        {
            get { 
                    return (float)GetValue(ValueProperty); 
                }
            set { 
                    SetValue(ValueProperty, value); 
                }
        }

        // Using a DependencyProperty as the backing store for Value.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ValueProperty =
            DependencyProperty.Register("Value", typeof(float), typeof(NumericInput), new PropertyMetadata(0f, new 		   PropertyChangedCallback(OnPropertyThisChanged)));

        private static void OnPropertyThisChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            //throw new NotImplementedException();
        }

Bindung im UserControl "ControlBox"

            <local:NumInput
                x:Name="InSetSpeed1"
                Grid.Row="8"
                Grid.Column="1"
                Grid.ColumnSpan="4"
                Margin="0 2"
                MaxValue="100.0"
                MinValue="0.0"
                Value="{Binding Path=FfuListItem.Speed1, ElementName=root, Mode=OneWay}"
                StepValue="0.1"
                Accuracy="1"
                Unit="%"
                ValueName="{lex:Loc SetSpeed1}"
                Stylus.IsPressAndHoldEnabled="False"
                Role="{Binding Role, ElementName=root, Mode=OneWay}">
            </local:NumericInput>

Danke Dir.

Hast du 2 verschiedenen Value-Eigenschaften, einmal in ViewmodelData und zusätzlich in NumInput? Wie sind diese denn verknüpft bzgl.

Value="{Binding Path=FfuListItem.Speed1, ElementName=root, Mode=OneWay}"

?

Und was hast du denn als DataContext (jeweils) gesetzt?

Du solltest mal den Debugger dazu benutzen: [Artikel] Debugger: Wie verwende ich den von Visual Studio? (d.h. Haltepunkte in den Eigenschaften setzen und überprüfen, welche Werte dort gesetzt bzw. gelesen werden).

Edit:

Oder alternativ per Logging: .NET logging and tracing

Läuft evtl. der Timer noch und überschreibt immer wieder den von Hand geänderten Wert?

Zitat von Th69

Hast du 2 verschiedenen Value-Eigenschaften, einmal in ViewmodelData und zusätzlich in NumInput? Wie sind diese denn verknüpft bzgl.

Value="{Binding Path=FfuListItem.Speed1, ElementName=root, Mode=OneWay}"

?

FfuListItem ist ViewmodelData und das wird als DataContext an "ControlBox" gesetzt. Innerhalb von "ControlBox" ist dann nur der Teil "Speed1" an NumInput.Value gebunden.

Ziel ist es, den Wert von ViewmodelData.Value einmalig bei Änderung von ViewmodelData in NumInput.Value zu schreiben, quasi als Startwert für die Eingabe.

Eine Änderung von ViewmodelData.Value soll nicht passieren

Und was hast du denn als DataContext (jeweils) gesetzt?

Du solltest mal den Debugger dazu benutzen: [Artikel] Debugger: Wie verwende ich den von Visual Studio? (d.h. Haltepunkte in den Eigenschaften setzen und überprüfen, welche Werte dort gesetzt bzw. gelesen werden).

Edit:

Oder alternativ per Logging: .NET logging and tracing

Läuft evtl. der Timer noch und überschreibt immer wieder den von Hand geänderten Wert?

Nein, der Timer ist gestoppt.

Zitat von DanHue

Wenn ich aber einen Wert über die Buttons geändert habe und dann "ViewmodelData" ändere, wird der neue Wert nicht mehr im Textfeld des "NumInput" aktualisiert (die anderen Textboxen aber schon).

Dann hatte ich dies evtl. falsch verstanden - du tauschst also das ViewmodelData-Objekt aus (und änderst nicht nur die Value-Eigenschaft)?

Dann zeige auch dazu mal den Code.

Genau, ViewmodelData enthält mehrere Variablen, die in ControlBox angezeigt werden. Eine davon, Value, soll bei einem Change von ViewmodelData als Preset in das Control "NumInput" (Child von ControlBox) geschrieben werden. Dort kann Value dann geändert werden und soll dann über ein Command in die DB geschrieben werden (nur Value).

Das alles funktioniert soweit, nur sobald ich Value im Control NumInput ändere, funktioniert das Schreiben von Value als Preset bei einer Änderung von ViewmodelData nicht mehr. Das Change Event von ViewmodelData in der Depen.Property von ControlBox wird aber ausgelöst und alle anderen Variablen in ControlBox werden auch aktualisiert.

Ich denke Du hattest recht, dass durch die Änderung von Value in NumInput das Binding von Value zwischen ControlBox und NumInput gelöscht wird.

Bindung ViewmodelData an ControlBox

                <ctrl:ControlBox
                   x:Name="CtrlBox"
                   VerticalAlignment="Stretch" 
                   HorizontalAlignment="Center"
                   Height="500"
                   ViewmodelData ="{Binding SelectedViewModelData,Mode=OneWay}" 
                   SetSpeedCommand="{Binding SetSpeedCommand}"
                   Role="{Binding UserRole}">
               </ctrl:ControlBox>

Bindung Value an NumInput

            <local:NumInput
               x:Name="InSetSpeed1"
               MaxValue="100.0"
               MinValue="0.0"
               Value="{Binding Path=ViewmodelData.Value, ElementName=root, Mode=OneWay}"
               Role="{Binding Role, ElementName=root, Mode=OneWay}">
           </local:NumInput>

NumInput besteht aus 2 Buttons (+, -) und einer Textbox zum anzeigen von Value. Jeder Button hat die Methoden "PreviewMouseDown" zum Starten eines Timers in NumInput.xaml.cs und "PreviewMouseUp" zum Stoppen des Timers. In den Tick Events des Timers wird dann Value jeweils vergrößert oder verkleinert. Das ist gedacht für Touch Bedienung → Finger bleibt drauf bzw Mouse links gedrückt, Value "läuft " runter / hoch.

Value ist als DependencyProperty angelegt. Das IsChanged Event wird bei Änderung von ViewmodelData ausgelöst, aber sobald ich einmal Value über die Buttons geändert habe nicht mehr.

        public float Value
       {
           get { return (float)GetValue(ValueProperty); }
           set { SetValue(ValueProperty, value); }
       }
       public static readonly DependencyProperty ValueProperty =
           DependencyProperty.Register("Value", typeof(float), typeof(NumericInput), new PropertyMetadata(0f, new PropertyChangedCallback(OnPropertyThisChanged)));
       private static void OnPropertyThisChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
       {
           //throw new NotImplementedException();
       }

Button xaml

        <Button x:Name="RB_Inc"
           PreviewMouseDown="RB_Inc_PreviewMouseDown" 
           PreviewMouseUp="RB_Inc_PreviewMouseUp" >
           <Viewbox Height="37" Width="37">
               <Path
                   Data="M 7 17 L 27 17 M 17 7 L 17 27"
                   Stroke="White"
                   StrokeThickness="5"
                   Fill="White"
                   Stretch="Fill" />
           </Viewbox>
       </Button>

Textbox:

            <TextBox 
               Text="{Binding Value}">
               <!--<TextBox.Text>
                   <Binding Path="Value"
                  UpdateSourceTrigger="PropertyChanged"/>
           </TextBox>

PreviewMouseDown

        private void RB_Dec_PreviewMouseDown(object sender, MouseButtonEventArgs e)
       {
           Tmr_Down.Start();
       }

Tmr_Down_tick - da wird noch ein Faktor je nach Dauer der Haltezeit erhöht

        private void Tmr_Down_tick(object? sender, EventArgs e)
       {
           
           elapsedTime_Down += 1;
           if(elapsedTime_Down == 10)
           {
               factorDown = 2;
           }
           else if (elapsedTime_Down == 25)
           {
               factorDown = 5;
           }
           else if (elapsedTime_Down == 50)
           {
               factorDown = 10;
           }
           DecValue(factorDown);
       }

DecValue hier wird dann Value geändert

        private void DecValue(int factor)
       {
           Value -= (StepValue * (float)factor);
           if (Value < MinValue) Value = MinValue;
       }

Ich bin langsam am verzweifeln...

Gibt es vielleicht ein Idee wie man das anders lösen könnte?

Danke und viele Grüße.

Könntest du evtl. ein Testprojekt (d.h. auf das Wesentliche reduziert) erzeugen und hier als Anhang hinzufügen?

Zitat von DanHue

Ich bin langsam am verzweifeln...

Anscheinend noch nicht genug.

Gibt es vielleicht ein Idee wie man das anders lösen könnte?

Wenn du ernsthaft an einer Lösung interessiert bist dann hättest du schon längst (gleich von Anfang an) ein kleines Demo-Projekt gezeigt anstatt größtmöglichst kompliziert verklausuliert den Ablauf beschrieben bzw. immer nur kleinste Fragmente gezeigt, die aber nicht den gesamten Zusammenhang darstellen. Es gibt viele falsche Wege und auf jeden Fall erheblich weniger richtige Wege. Woher sollen wir wissen, wo genau dein Fehler liegt ohne den gesamten (relevanten) Code zu sehen?

Hat die Blume einen Knick, war der Schmetterling zu dick.