c# - WPF 数据绑定(bind) : Cancelled property change - Combobox misaligns

标签 c# wpf data-binding inotifypropertychanged

我有一个带有组合框和文本框的 WPF 表单(两者都数据绑定(bind)到对象的属性)。更改组合框或文本框输入会更新对象的属性,并且数据绑定(bind)会启动并更新 UI。问题是,我实现了一种取消更改的方法,该方法有效,但搞砸了 UI 更新。如果我从组合框中进行更改并取消它,则组合框不会将所选值恢复为应有的值(受对象值的约束)。如果我从文本框进行更改并取消它,文本框和组合框都会显示正确的数据,但是焦点会立即给予组合框(当它应该留在文本框上时,因为那是我最后的地方它)。我不太确定如何在一般方面解决这个问题,而不是处理更改事件并验证更改之后没有被取消(因为数据绑定(bind)的意义是什么?)...

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

更新方法:这修复了绑定(bind),但没有解决当用户尝试更改文本框中的值然后取消它时组合框窃取焦点的问题。但是,至少 UI 在它的数据绑定(bind)值方面是匹配的。我找到了这个 link这帮助了我。

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

最佳答案

这解决了 UI 显示正确数据绑定(bind)数据的问题......它只是没有解决被盗的焦点问题:

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

关于c# - WPF 数据绑定(bind) : Cancelled property change - Combobox misaligns,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3283074/

相关文章:

c# - 如何在 GridViewHeaderItem 上设置 AutomationProperties.Name?

javascript - 在 if block 内 knockout 不可观察的数组长度

行高度可变的WPF DataGrid

.net - 如何在 VS2008 中禁用 Cider(WPF Gui 编辑器)?

c# - Windows 窗体 (WinForms) 模型 View View 模型模式 (MVVM) 是否进行数据绑定(bind)

C# WPF 数据绑定(bind)到未知数量的复选框

c# - Azure Web 角色启动时 IIS Express 失败

c# - 如何使用反射调用泛型方法?

c# - 使用测试数据库集成测试链式 WCF 服务

c# - .NET 中未显示 Delphi COM 对象