使用INotifyPropertyChanged时,控件未立即更新绑定属性

18

我的控件在失去焦点之前不会更新其绑定对象的属性。 有类似的问题并引用了已接受的答案,其中包括声明DataSourceUpdateMode.OnPropertyChanged,我也这样做了,但是行为仍然存在。 这是一个示例实现。我会尽量详细地说明。 MyConfig类通过我称之为Configuration的Singleton类中的属性进行访问。

[Serializable]
public class MyConfig : INotifyPropertyChanged
{
    public enum MyEnum
    {
        Foo,
        Bar
    }

    public MyConfig()
    {
        MyProperty = MyEnum.Foo;
    }

    private MyEnum _MyProperty;
    public MyEnum MyProperty
    {
        get { return _MyProperty; }
        set { if (value != _MyProperty) { _MyProperty = value; OnPropertyChanged("MyProperty"); } }
    }

    [field: NonSerialized]
    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged(string propertyName)
    {
        if (string.IsNullOrEmpty(propertyName))
            throw new ArgumentNullException(propertyName);
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

public partial class ConfigForm : Form
{
    public ConfigForm()
    {
        InitializeComponent();
        MyComboBox.Items.AddRange(Enum.GetNames(typeof(MyConfig.MyEnum)));
    }

    private void ConfigForm_Load(object sender, EventArgs e)
    {
        MyComboBox.DataSource = Enum.GetValues(typeof(MyConfig.MyEnum));
        MyComboBox.DataBindings.Add("SelectedItem", Configuration.Instance.MyConfig, "MyProperty", false, DataSourceUpdateMode.OnPropertyChanged);
    }
}

根据以下简单的实现,我不确定自己是否忽略了什么以确保立即更改属性。在这种情况下,我可以在ComboBox中从Foo更改为Bar,但除非我将焦点从ComboBox中移开,否则不会发生任何更改。有人有什么想法吗?

2个回答

28

WinForms中的ComboBox在处理OnPropertyChanged时有些问题。这是我从一个旧项目中获取的代码,我用它来使SelectedItem属性的OnPropertyChanged按照我期望的方式工作。这对于我的特定情况有效,但有时我通常很难让这种情况起作用。祝你好运!

/// <summary>
/// A modification of the standard <see cref="ComboBox"/> in which a data binding
/// on the SelectedItem property with the update mode set to DataSourceUpdateMode.OnPropertyChanged
/// actually updates when a selection is made in the combobox.
/// </summary>
public class BindableComboBox : ComboBox
{
    /// <summary>
    /// Raises the <see cref="E:System.Windows.Forms.ComboBox.SelectionChangeCommitted"/> event.
    /// </summary>
    /// <param name="e">An <see cref="T:System.EventArgs"/> that contains the event data.</param>
    protected override void OnSelectionChangeCommitted(EventArgs e)
    {
        base.OnSelectionChangeCommitted(e);

        var bindings = this.DataBindings
            .Cast<Binding>()
            .Where(x => 
                x.PropertyName == "SelectedItem" && 
                x.DataSourceUpdateMode == DataSourceUpdateMode.OnPropertyChanged);
        foreach (var binding in bindings)
        {
            // Force the binding to update from the new SelectedItem
            binding.WriteValue();

            // Force the Textbox to update from the binding
            binding.ReadValue();
        }
    }
}

1
运行得非常好。非常感谢!你还教会了我一些关于自定义现有控件的知识 :) - Josh Clayton
1
这是我找到的唯一真正有效的解决方案。谢谢。 - Wayne Bloss
我使用这个方法来解决当使用鼠标选择下拉框时,绑定的数据没有被更新/改变,但第二次尝试时会更新的问题。在我的情况下,我将其更改为SelectedValue属性。到目前为止,它似乎有效。在线Telerik转换器(https://converter.telerik.com/)也可以成功地将上述代码转换为VB,以防有人需要。感谢Nicholas - 处理组合框是一场噩梦。 - Tim F.

4

感谢@Nicholas Piasecki提供的解决方案,如果你能够基于他的答案找到解决方案,请投票支持他。


我在解决问题时需要做三个主要更改:

  • 我试图访问绑定到ComboBox的SelectedValue属性的对象上的属性。因此,在Linq where子句中必须包括“SelectedValue”属性名称。

  • 如果您使用Visual Studio中的表单设计器设置数据绑定,并且只设置了SelectedItem或SelectedValue所绑定的内容,则默认数据源更新模式为“OnValidation”。 如果您前往ComboBox上的数据绑定的“(高级)”设置,可以看到这一点。 因此,如果您正在使用这种方式,您还必须包括该数据源更新模式。

  • 在我的情况下,我还需要在循环绑定并进行Write / ReadValue调用之后引发OnSelectionChangeCommitted事件。由于我在表单上订阅了ComboBox的SelectionChangeCommitted事件,在循环遍历绑定和强制它们更新之前调用base.OnSelectionChangeCommitted会导致绑定对象的属性仍未设置。

因此,这是我对@Nicholas Piasecki答案的版本(同时转换为VB.NET):

''' <summary>
''' Raises the <see cref="E:System.Windows.Forms.ComboBox.SelectionChangeCommitted"/> event _after_ forcing any data bindings to be updated.
''' </summary>
''' <param name="e">An <see cref="T:System.EventArgs"/> that contains the event data.</param>
Protected Overrides Sub OnSelectionChangeCommitted(e As EventArgs)
    Dim bindings As List(Of Binding) = ( _
        From x In Me.DataBindings.Cast(Of Binding)()
        Where (x.PropertyName = "SelectedItem" OrElse x.PropertyName = "SelectedValue" OrElse x.PropertyName = "SelectedIndex") AndAlso
              (x.DataSourceUpdateMode = DataSourceUpdateMode.OnPropertyChanged OrElse x.DataSourceUpdateMode = DataSourceUpdateMode.OnValidation)
    ).ToList()

    For Each b As Binding In bindings
        ' Force the binding to update from the new SelectedItem
        b.WriteValue()
        ' Force the Textbox to update from the binding
        b.ReadValue()
    Next

    MyBase.OnSelectionChangeCommitted(e)
End Sub

我像上面一样,也将 MyBase.OnSelectionChangeCommitted 放在最后,并添加了 SelectcedValue。+1 - Tim F.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接