2010-07-19 11 views
5

Tengo un formulario WPF con un cuadro combinado y un cuadro de texto (ambos están vinculados a un objeto). Cambiar el cuadro combinado o la entrada del cuadro de texto actualiza la propiedad del Objeto y el enlace de datos se inicia y actualiza la interfaz de usuario. El problema es que implementé una forma de cancelar el cambio, que funciona, pero estropea la actualización de la IU. Si realizo el cambio desde el cuadro combinado y lo cancelo, el cuadro combinado no revierte el valor seleccionado de nuevo a lo que debería ser (vinculado por el valor del objeto). Si realizo el cambio desde el cuadro de texto y lo cancelo, tanto el cuadro de texto como el cuadro combinado muestran los datos correctos, pero luego el foco se transfiere instantáneamente al cuadro combinado (cuando debería haber permanecido en el cuadro de texto, ya que es el último que tuve eso). No estoy realmente seguro de cómo solucionar esto en un aspecto general como apposed para manejar eventos de cambio y verificar que el cambio no se haya cancelado después (porque ¿cuál es el punto de enlace de datos?) ...WPF DataBinding: cambio de propiedad cancelado: cuadro combinado no coincide

//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étodo actualizado: Esto corrige el enlace, pero no resuelve el problema de que el cuadro combinado roba el foco cuando el usuario intenta cambiar un valor en el cuadro de texto, luego lo cancela. Pero, al menos, la IU coincide en términos de sus valores de datos. Encontré este link que me ayudó.

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

Respuesta

1

Esto resuelve la interfaz de usuario la visualización de datos de enlace de datos adecuados ... simplemente no soluciona el problema de enfoque robada:

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

Cuando el usuario cancela un cambio de propiedad que aún debe publicar el INotifyPropertyChanged.PropertyChanged con el valor antiguo. Si sus enlaces son dos, cualquier control que haya sido cambiado por el usuario volverá a cambiar.

+1

he intentado solucionarlo mediante el establecimiento de la propiedad, y luego poner de nuevo a la valor antiguo, cada vez que se llama a OnPropertyChanged, que genera 2 eventos separados (como se esperaba con 2 llamadas diferentes), pero al final el Combobox todavía no se actualiza correctamente cuando se cancela. (Ah, y actualicé los genéricos para los otros métodos también, no es que afecte a nada). –

Cuestiones relacionadas