2010-07-19 8 views
5

J'ai un formulaire WPF avec une zone de liste déroulante et une zone de texte (les deux sont databound à la propriété d'un objet). La modification de l'entrée de la zone de liste déroulante ou de la zone de texte met à jour la propriété de l'objet et la liaison de la base de données entre et met à jour l'interface utilisateur. Le problème est, j'ai mis en place un moyen d'annuler la modification, qui fonctionne, mais vissera la mise à jour de l'interface utilisateur. Si je fais la modification de la liste déroulante et l'annule, la liste déroulante ne ramène pas la valeur sélectionnée à ce qu'elle devrait être (liée par la valeur de l'objet). Si je fais la modification à partir de la zone de texte et l'annule, la zone de texte et la liste déroulante affichent les données correctes, mais le focus est immédiatement donné à la liste déroulante (alors qu'elle aurait dû rester dans la zone de texte). il). Je ne suis pas vraiment sûr de savoir comment régler cela dans un aspect général, car la modification des événements et la vérification du changement n'ont pas été annulées par la suite (car alors quel est le point de liaison des données?) ...WPF DataBinding: modification de propriété annulée - désalignements de zone de liste déroulante

//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; 
     } 
    } 
} 

Méthode mise à jour: Corrige la liaison, mais ne résout pas le problème de vol de la zone de liste déroulante par l'utilisateur lorsque l'utilisateur tente de modifier une valeur dans la zone de texte, puis l'annule. Mais, au moins, l'interface utilisateur correspond en termes de valeurs de databound. J'ai trouvé ce link qui m'a aidé.

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); 
    } 
} 

Répondre

1

Cela résout l'interface utilisateur affiche les données appropriées de databound ... il ne vient résout pas le problème de mise au point volé:

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

Lorsque l'utilisateur annule un changement de propriété, vous devriez toujours afficher le INotifyPropertyChanged.PropertyChanged avec la valeur ancienne. Si vos liaisons sont deux, tout contrôle qui a été modifié par l'utilisateur va revenir en arrière.

+1

J'ai essayé de le réparer en définissant la propriété et la mise en revenir à la ancienne valeur, à chaque appel de OnPropertyChanged, qui déclenche 2 événements distincts (comme prévu avec 2 appels différents), mais à la fin la Combobox ne se met toujours pas à jour correctement lorsqu'elle est annulée. (Oh, et j'ai mis à jour les génériques pour les autres méthodes aussi, pas que cela affecte quoi que ce soit). –