2010-07-19 15 views
5

Mam formularz WPF z combobox i textbox (oba są databound do właściwości obiektu). Zmiana danych wejściowych combobox lub textbox powoduje aktualizację właściwości obiektu, a powiązanie danych rozpoczyna się i aktualizuje interfejs użytkownika. Problem polega na tym, zaimplementowałem sposób na anulowanie zmiany, która działa, ale przykręca aktualizację interfejsu użytkownika. Jeśli dokonam zmiany z combobox i anuluję, combobox nie przywróci wybranej wartości z powrotem do tego, co powinno być (ograniczone przez wartość obiektu). Jeśli dokonam zmiany z pola tekstowego i anuluję ją, zarówno pole tekstowe, jak i pole wyboru pokażą właściwe dane, ale wtedy ostrość jest natychmiast przekazywana do pola wyboru (kiedy powinno pozostać w polu tekstowym, ponieważ jest to ostatnie miejsce, które miałem to). Naprawdę nie wiem, jak rozwiązać ten problem w ogólnym aspekcie, ponieważ jest on przeznaczony do obsługi zdarzeń zmian i weryfikacja zmiany nie została później anulowana (ponieważ w takim razie do czego służy punktowanie danych?) ...WPF DataBinding: Anulowana zmiana właściwości - Combobox misaligns

//User.cs 

using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Text; 

namespace MyTesting 
{ 
    public class User : AbstractEntity 
    { 
     public User() 
     { 
      Rankings = new Dictionary<int,string>(); 

      Rankings.Add(1, "Newbie"); 
      Rankings.Add(10, "Novice"); 
      Rankings.Add(25, "Adept User"); 
      Rankings.Add(50, "Power User"); 
      Rankings.Add(100, "Admin God"); 
     } 

     public Dictionary<Int32, String> Rankings { get; set; } 

     private Int32 _rank; 
     public Int32 Rank 
     { 
      get 
      { 
       return _rank; 
      } 
      set 
      { 
       SetProperty<Int32>("Rank", ref _rank, value); 
      } 
     } 
    } 
} 


//AbstractEntity.cs 

using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Text; 

namespace MyTesting 
{ 
    public abstract class AbstractEntity : INotifyPropertyChanging, INotifyPropertyChanged 
    { 
     protected void SetProperty<T>(String propertyName, ref T property, T value) 
     { 
      if (!Object.Equals(property, value)) 
      { 
       if (OnPropertyChanging(propertyName, property, value)) 
       { 
        T oldValue = (T)property; 
        property = value; 
        OnPropertyChanged(propertyName, property, value); 
       } 
      } 
     } 

     [field: NonSerialized] 
     public event PropertyChangingEventHandler PropertyChanging; 

     protected virtual Boolean OnPropertyChanging(String propertyName, Object oldValue = null, Object newValue = null) 
     { 
      CancellablePropertyChangingEventArgs e; 

      if ((oldValue != null) || (newValue != null)) 
       e = new CancellablePropertyChangingEventArgs(propertyName, oldValue, newValue); 
      else 
       e = new CancellablePropertyChangingEventArgs(propertyName); 

      return OnPropertyChanging(e); 
     } 
     protected virtual Boolean OnPropertyChanging(CancellablePropertyChangingEventArgs e) 
     { 
      if (PropertyChanging != null) 
       PropertyChanging(this, e as PropertyChangingEventArgs); 

      return !e.IsCancelled; 
     } 

     [field: NonSerialized] 
     public event PropertyChangedEventHandler PropertyChanged; 

     protected virtual void OnPropertyChanged(String propertyName, Object oldValue = null, Object newValue = null) 
     { 
      ExtendedPropertyChangedEventArgs e; 

      if ((oldValue != null) || (newValue != null)) 
       e = new ExtendedPropertyChangedEventArgs(propertyName, oldValue, newValue); 
      else 
       e = new ExtendedPropertyChangedEventArgs(propertyName); 

      OnPropertyChanged(e); 
     } 
     protected virtual void OnPropertyChanged(ExtendedPropertyChangedEventArgs e) 
     { 
      if (PropertyChanged != null) 
       PropertyChanged(this, e as PropertyChangedEventArgs); 
     } 
    } 

    public class ExtendedPropertyChangedEventArgs : PropertyChangedEventArgs 
    { 
     public ExtendedPropertyChangedEventArgs(String propertyName) 
      : base(propertyName) 
     { 
     } 

     public ExtendedPropertyChangedEventArgs(String propertyName, Object oldValue, Object newValue) 
      : base(propertyName) 
     { 
      OldValue = oldValue; 
      NewValue = newValue; 
     } 

     public Object OldValue { get; private set; } 
     public Object NewValue { get; private set; } 
    } 

    public class CancellablePropertyChangingEventArgs : PropertyChangingEventArgs 
    { 
     public CancellablePropertyChangingEventArgs(String propertyName, Boolean cancel = false) 
      : base(propertyName) 
     { 
      IsCancelled = cancel; 
     } 

     public CancellablePropertyChangingEventArgs(String propertyName, Object oldValue, Object newValue, Boolean cancel = false) 
      : base(propertyName) 
     { 
      OldValue = oldValue; 
      NewValue = newValue; 

      IsCancelled = cancel; 
     } 

     public Object OldValue { get; private set; } 
     public Object NewValue { get; private set; } 

     public Boolean IsCancelled { get; set; } 
    } 
} 


<!-- MainWindow.xaml --> 
<Window x:Class="ObservableDictionaryBinding.MainWindow" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:src="clr-namespace:MyTesting" 
     Title="MainWindow" Height="350" Width="525" Loaded="OnLoaded"> 

    <Grid> 
     <ComboBox x:Name="RankList" Height="23" HorizontalAlignment="Stretch" VerticalAlignment="Top" Margin="12,12,12,0" /> 

     <TextBlock Height="23" Width="40" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="13,100,0,0" Text="Rank:" /> 
     <TextBox x:Name="RankBox" Height="23" HorizontalAlignment="Stretch" VerticalAlignment="Top" Margin="59,97,12,0" /> 
    </Grid> 
</Window> 

//MainWindow.xaml.cs 
using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Text; 
using System.Windows; 
using System.Windows.Controls; 
using System.Windows.Data; 
using System.Windows.Documents; 
using System.Windows.Input; 
using System.Windows.Media; 
using System.Windows.Media.Imaging; 
using System.Windows.Navigation; 
using System.Windows.Shapes; 

namespace MyTesting 
{ 
    public partial class MainWindow : Window 
    { 
     public MainWindow() 
     { 
      MyUser = new User(); 

      InitializeComponent(); 

      MyUser.PropertyChanging += new PropertyChangingEventHandler(MyUser_PropertyChanging); 
     } 

     private User MyUser { get; set; } 

     private Binding RankListBinding { get; set; } 
     private Binding RankBinding { get; set; } 
     private Binding RankListRankBinding { get; set; } 

     private void OnLoaded(object sender, EventArgs e) 
     { 
      DataContext = MyUser; 

      RankListBinding = new Binding("Rankings"); 
      RankListBinding.Source = MyUser; 
      RankList.SetBinding(ComboBox.ItemsSourceProperty, RankListBinding); 
      RankList.SelectedValuePath = "Key"; 
      RankList.DisplayMemberPath = "Value"; 

      RankBinding = new Binding("Rank"); 
      RankBinding.Source = MyUser; 
      RankBox.SetBinding(TextBox.TextProperty, RankBinding); 

      RankListRankBinding = new Binding("Rank"); 
      RankListRankBinding.Source = MyUser; 
      RankList.SetBinding(ComboBox.SelectedValueProperty, RankListRankBinding); 
     } 

     private void MyUser_PropertyChanging(Object sender, PropertyChangingEventArgs e) 
     { 
      CancellablePropertyChangingEventArgs ea = e as CancellablePropertyChangingEventArgs; 

      String text = String.Format("Would you like to change the property '{0}' from '{1}' to '{2}'?", 
        e.PropertyName, 
        (ea.OldValue == null) ? "<null>" : ea.OldValue.ToString(), 
        (ea.NewValue == null) ? "<null>" : ea.NewValue.ToString() 
        ); 

      MessageBoxResult result = MessageBox.Show(this, text, "Property Changed", 
       MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.Yes); 

      if (result == MessageBoxResult.No) 
       ea.IsCancelled = true; 
     } 
    } 
} 

Zaktualizowana metoda: Naprawia powiązanie, ale nie rozwiązuje problemu, który fokus zostaje skradziony przez combobox, gdy użytkownik próbuje zmienić wartość w polu tekstowym, a następnie ją anuluje. Ale przynajmniej interfejs użytkownika jest zgodny pod względem wartości databuntów. Znalazłem to link, które pomogło mi.

protected void SetProperty<T>(String propertyName, ref T property, T value) 
{ 
    if (!Object.Equals(property, value)) 
    { 
     bool cancelled = OnPropertyChanging<T>(propertyName, property, value); 

     if (cancelled) 
     { 
      Application.Current.Dispatcher.BeginInvoke(
       new Action(() => 
       { 
        OnPropertyChanged<T>(propertyName); 
       }), 
       DispatcherPriority.ContextIdle, 
       null 
      ); 

      return; 
     } 

     T originalValue = property; 
     property = value; 
     OnPropertyChanged(propertyName, originalValue, property); 
    } 
} 

Odpowiedz

1

To rozwiązuje UI wyświetlając odpowiednie dane z danymi ... to po prostu nie rozwiązuje problemu kradzieży ostrości:

protected void SetProperty<T>(String propertyName, ref T property, T value) 
{ 
    if (!Object.Equals(property, value)) 
    { 
     bool cancelled = OnPropertyChanging<T>(propertyName, property, value); 

     if (cancelled) 
     { 
      Application.Current.Dispatcher.BeginInvoke(
       new Action(() => 
       { 
        OnPropertyChanged<T>(propertyName); 
       }), 
       DispatcherPriority.ContextIdle, 
       null 
      ); 

      return; 
     } 

     T originalValue = property; 
     property = value; 
     OnPropertyChanged(propertyName, originalValue, property); 
    } 
} 
0

Gdy użytkownik anuluje zmianę własności nadal powinna opublikować INotifyPropertyChanged.PropertyChanged ze starej wartości. Jeśli twoje powiązania są podwójne, wszelkie zmiany, które zostały zmienione przez użytkownika, ulegną zmianie.

+1

Mam próbowali go naprawić poprzez ustawienie właściwości, a następnie ustawiając ją z powrotem do stara wartość, za każdym razem wywołując OnPropertyChanged, która podnosi 2 oddzielne zdarzenia (zgodnie z oczekiwaniami przy 2 różnych połączeniach), ale na końcu Combobox nadal nie aktualizuje się poprawnie po anulowaniu. (Och, i uaktualniłem generyczne także inne metody, nie wpływające na cokolwiek). –