2016-03-23 7 views
2

Ich implementiere eine Undo/Redo-Funktionalität für meine Anwendung und habe ein Problem festgestellt. Grundsätzlich habe ich ein Kombinationsfeld an eine Liste von Elementen gebunden. Dann habe ich mehrere Textfelder, die einen DataContext des ausgewählten Elements in dem Kombinationsfeld und Text an Eigenschaften dieses Elements gebunden haben. Wenn ein Benutzer einen Rückgängig-Befehl ausgibt und das Element, das rückgängig gemacht werden soll, die Textänderungen eines Textfelds ist, möchte ich zuerst das verknüpfte Element im Kombinationsfeld auswählen. Dann möchte ich die Textbox sofort mit dem Originaltext aktualisieren. Dies sollte die datengebundene Eigenschaft des ausgewählten Elements theoretisch aktualisieren. Die Auswahl ändert sich jedoch, aber die datengebundenen Textfelder werden nicht aktualisiert, bevor ich die Texteigenschaft des Textfelds ändere und daher den Eigenschaftswert für das zuvor ausgewählte Element im Kombinationsfeld ändert.Kann ich eine Aktualisierung eines Textfeld-DataContext erzwingen, das an ein ComboBox.SelectedItem gebunden ist?

Das ist mein Kombinationsfeld:

<ComboBox x:Name="myComboBox" 
      ItemsSource="{Binding MyItems, UpdateSourceTrigger=PropertyChanged}" 
      DisplayMemberPath="Name" 
      SelectionChanged="myComboBox_SelectionChanged" /> 

Das ist mein Textfeld (die Datacontext wird auf dem übergeordneten Netz Das gleiche Verhalten tritt auf, wenn die Datacontext auf das Textfeld gesetzt ist selbst.)

<TextBox x:Name="purposeTxtBox" 
     Text="{Binding Purpose, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> 

der Code (snippet) verwende ich einmal die Undo initiiert wird, ist:

case UndoAction.PURPOSE_CHANGE: 
    SelectComboBoxItem(myComboBox, itemToSelect); 
    purposeTxtBox.Text = itemValue; 
    FocusAndSelect(purposeTxtBox); 

Und die SelectComboBoxItem Methode:

private void SelectComboBoxItem(ComboBox box, object item) 
{ 
    Dispatcher.BeginInvoke(DispatcherPriority.Input, 
          new Action(delegate() 
          { 
           box.SelectedItem = item; 
          })); 
} 

Ich weiß, ich kann die ‚Zweck‘ Eigenschaft auf meiner Datacontext aktualisieren. Das funktioniert, aber es konzentriert sich nicht und wählt aus, wenn ich es so mache. Da alle Bindungen bereits vorhanden sind und einige von ihnen an Ganzzahlen gebunden sind, wollte ich nur den Text aktualisieren und der "Magie" erlauben, mit den Übersetzungen umzugehen. So könnte ich eine allgemeine Lösung für jede Textbox haben. Der Vollständigkeit halber, hier ist meine FocusAndSelect Funktion:

private void FocusAndSelect(TextBox box) 
{ 
    box.SelectAll(); 
    Dispatcher.BeginInvoke(DispatcherPriority.Input, 
          new Action(delegate() 
          { 
           box.Focus();   // Set Logical Focus 
           Keyboard.Focus(box); // Set Keyboard Focus 
          })); 
} 
+0

Seitenkommentar: Der Undo kann organisiert werden, indem der Delegat zur Liste der Delegaten hinzugefügt wird, wodurch ein Eigenschaftswert einfach zurück gesetzt wird. Dann rückgängig machen ist ein einfacher Aufruf dieses Delegaten und es aus der Liste zu entfernen. – Sinatr

+0

@Sinatr, hast du ein Beispiel von dem, was du beschreibst, auf das du mich verweisen könntest? – JLB

Antwort

2

ich glaube, das Problem dabei ist:

Dispatcher.BeginInvoke(DispatcherPriority.Input, 
         new Action(delegate() 
         { 
          box.SelectedItem = item; 
         })); 

Sie sagen dem System, die Combo-Box-Wert zu aktualisieren, aber Sie verwenden eine asynchrone Methode (glaube ich). Das heißt, es wird es tun, wenn es auf dem UIhread möglich ist (obwohl ich denke, dass Sie es über den UInthread aufrufen), aber trotzdem - es wird nicht passieren jetzt.

Dann setzen Sie in der nächsten Zeile purposeTxtBox.Text = itemValue direkt, aber das Kontrollkästchen hat den ausgewählten Wert noch nicht aktualisiert, so dass der Wert noch nicht geändert wurde.

Letztendlich ist das Problem, dass Sie Ihre Logik mit Ihrer Benutzeroberfläche mischen. Sie sollten den ausgewählten Wert nicht ändern, indem Sie in die Benutzeroberfläche wechseln und etwas ändern, damit Ihre Daten aktualisiert werden. Sie sollten die Daten direkt ändern und die Benutzeroberfläche aktualisieren, wenn sie benötigt wird.

Um ein einfaches Beispiel zu nehmen, mit einem Kontrollkästchen boolean anzuzeigen, möchten Sie kein Kontrollkästchen in der Benutzeroberfläche erstellen und es in Ihrer Geschäftslogik oder Datenlogik deaktivieren, das Kontrollkästchen sollte nicht halten, dass Wert - Ihr Programm sollte, und das Kontrollkästchen sollte nur daran binden. Sie müssen diesen Wert in false ändern, Sie machen den Boolean-Wert false, gehen nicht zum Ankreuzfeld, setzen das auf unchecked und lassen die Bindung den Booleschen Wert aktualisieren. Das geht irgendwie rückwärts!

Was sollten Sie dies tun, ist mit:

private Item selectedItem; 
    public Item SelectedItem 
    { 
     get { return selectedItem; } 
     set 
     { 
      if (selectedItem != value) 
      { 
       selectedItem = value; 
       //perform your ItemChange events here, if you have any 
       OnPropertyChanged("SelectedItem"); 
      } 
     } 
    } 

Ihr Kombinationsfeld sollte es binden SelectedItem auf diese Eigenschaft. Ihre Textfelder sollten ihre Texteigenschaften an die Datenkontext-SelectedItem.SomeText-Eigenschaften des Rasters binden.

Wenn Sie die getroffene Auswahl zu ändern, Sie nicht gehen, dann auf der Benutzeroberfläche, um die Komponente ändern, für den UI-Thread warten, das zu verarbeiten, kommen wieder, ein Ereignis auslösen etc etc etc

Sie haben soeben das ändern Objekt - die Benutzeroberfläche reagiert.

Das ist viel bessere Praxis. Aus diesem Grund haben wir auch Modelle wie MVVM und MVC, um diese Trennung zwischen UI und Logik zu erzwingen.

+0

Hallo @Joe, Vielen Dank für Ihre ausführliche Antwort. Ich versuche zu tun, was du gesagt hast. Ich habe die Bindung an die ComboBox und das Grid mithilfe einer SelectedItem-Eigenschaft aktualisiert. Das scheint für die Bindung gut zu funktionieren. Wenn ich das mache und dann SelectedItem = item; item.Purpose = "Etwas Text"; Die Benutzeroberfläche reagiert nicht. Ich sollte erwähnen, da Sie es angesprochen haben, dass diese Anrufe tatsächlich in einer anderen Klasse sind. Deshalb habe ich den Dispatcher benutzt. Invoke ... vielleicht habe ich das falsch verstanden. – JLB

+0

Wird die Benutzeroberfläche nicht aktualisiert, da das ausgewählte Element in der Combobox nicht geändert wird? Haben Sie INotifyPropertyChanged für Ihren Datenkontext implementiert? Wie binden Sie es? Verwenden Sie Mode = TwoWay? – Joe

+0

Sie müssen Dispatcher.Invoke verwenden, wenn Sie versuchen, die Benutzeroberfläche in einem anderen Thread (nicht im UI-Thread) zu ändern. Wenn Sie nur an eine Eigenschaft binden, können Sie diese Eigenschaft aus einem beliebigen Thread aktualisieren, und die Benutzeroberfläche verwaltet die Aktualisierung selbst. Sie sollte aktualisiert werden, wenn INotifyPropertyChanged und der Modus korrekt ist! – Joe

1

nicht direkt mit Ihrem Problem zu tun hat (ich hoffe, dass es nützlich sein wird), aber Sie können mit den Delegierten organisieren rückgängig machen:

readonly Stack<Action> _undo = new Stack<Action>(); 

string _someProperty; 
// property to bind 
public string SomeProperty 
{ 
    get { return _someProperty; } 
    set 
    { 
     var old = _someProperty; // capture old value 
     _undo.Push(() => 
     { 
      _someProperty = old; 
      OnPropertyChanged(); 
     }); 
     _someProperty = value; 
     OnPropertyChanged(); 
    } 
} 

void Undo() 
{ 
    if (_undo.Count > 0) 
     _undo.Pop()(); 
} 

ändern Eigenschaftswert (über die Bindung) wird Datensatz Delegat des vorherigen Wert gesetzt . Rufen Sie Undo() wird einfach spielen Delegierten von der letzten bis zum ersten (LIFO).

Sie können auch Auswahl- und Fokusmanipulationen in Delegaten hinzufügen (nicht sicher, ob dies eine gute Idee ist).

Verwandte Themen