当某个绑定发生更改时,.NET WinForms INotifyPropertyChanged 会更新所有绑定。更好的方法吗?

发布于 2024-09-01 11:19:09 字数 2323 浏览 3 评论 0原文

在 Windows 窗体应用程序中,触发 INotifyPropertyChanged 的​​属性更改将导致窗体从我的绑定对象中读取每个属性,而不仅仅是更改的属性。 (参见下面的示例代码)

这似乎非常浪费,因为接口需要更改属性的名称。它导致我的应用程序中出现大量时钟,因为某些属性获取器需要执行计算。

如果没有更好的方法来做到这一点,我可能需要在我的 getters 中实现某种逻辑来丢弃不必要的读取。

我错过了什么吗?有更好的办法吗?请不要说使用不同的演示技术 - 我正在 Windows Mobile 上执行此操作(尽管该行为也发生在整个框架上)。

这是一些演示该问题的玩具代码。单击该按钮将导致两个文本框都被填充,即使一个属性已更改。

using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;

namespace Example
{
public class ExView : Form
{
    private Presenter _presenter = new Presenter();
    public ExView()
    {
        this.MinimizeBox = false;

        TextBox txt1 = new TextBox();
        txt1.Parent = this;
        txt1.Location = new Point(1, 1);
        txt1.Width = this.ClientSize.Width - 10;
        txt1.DataBindings.Add("Text", _presenter, "SomeText1");

        TextBox txt2 = new TextBox();
        txt2.Parent = this;
        txt2.Location = new Point(1, 40);
        txt2.Width = this.ClientSize.Width - 10;
        txt2.DataBindings.Add("Text", _presenter, "SomeText2");

        Button but = new Button();
        but.Parent = this;
        but.Location = new Point(1, 80);
        but.Click +=new EventHandler(but_Click);
    }

    void but_Click(object sender, EventArgs e)
    {
        _presenter.SomeText1 = "some text 1";
    }
}

public class Presenter : INotifyPropertyChanged
{

    public event PropertyChangedEventHandler PropertyChanged;

    private string _SomeText1 = string.Empty;
    public string SomeText1
    {
        get
        {
            return _SomeText1;
        }
        set
        {
            _SomeText1 = value;
            _SomeText2 = value; // <-- To demonstrate that both properties are read
            OnPropertyChanged("SomeText1");
        }
    }

    private string _SomeText2 = string.Empty;
    public string SomeText2
    {
        get
        {
            return _SomeText2;
        }
        set
        {
            _SomeText2 = value;
            OnPropertyChanged("SomeText2");
        }
    }

    private void OnPropertyChanged(string PropertyName)
    {
        PropertyChangedEventHandler temp = PropertyChanged;
        if (temp != null)
        {
            temp(this, new PropertyChangedEventArgs(PropertyName));
        }
    }
}

}

In a windows forms application, a property change that triggers INotifyPropertyChanged, will result in the form reading EVERY property from my bound object, not just the property changed. (See example code below)

This seems absurdly wasteful since the interface requires the name of the changing property. It is causing a lot of clocking in my app because some of the property getters require calculations to be performed.

I'll likely need to implement some sort of logic in my getters to discard the unnecessary reads if there is no better way to do this.

Am I missing something? Is there a better way? Don't say to use a different presentation technology please -- I am doing this on Windows Mobile (although the behavior happens on the full framework as well).

Here's some toy code to demonstrate the problem. Clicking the button will result in BOTH textboxes being populated even though one property has changed.

using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;

namespace Example
{
public class ExView : Form
{
    private Presenter _presenter = new Presenter();
    public ExView()
    {
        this.MinimizeBox = false;

        TextBox txt1 = new TextBox();
        txt1.Parent = this;
        txt1.Location = new Point(1, 1);
        txt1.Width = this.ClientSize.Width - 10;
        txt1.DataBindings.Add("Text", _presenter, "SomeText1");

        TextBox txt2 = new TextBox();
        txt2.Parent = this;
        txt2.Location = new Point(1, 40);
        txt2.Width = this.ClientSize.Width - 10;
        txt2.DataBindings.Add("Text", _presenter, "SomeText2");

        Button but = new Button();
        but.Parent = this;
        but.Location = new Point(1, 80);
        but.Click +=new EventHandler(but_Click);
    }

    void but_Click(object sender, EventArgs e)
    {
        _presenter.SomeText1 = "some text 1";
    }
}

public class Presenter : INotifyPropertyChanged
{

    public event PropertyChangedEventHandler PropertyChanged;

    private string _SomeText1 = string.Empty;
    public string SomeText1
    {
        get
        {
            return _SomeText1;
        }
        set
        {
            _SomeText1 = value;
            _SomeText2 = value; // <-- To demonstrate that both properties are read
            OnPropertyChanged("SomeText1");
        }
    }

    private string _SomeText2 = string.Empty;
    public string SomeText2
    {
        get
        {
            return _SomeText2;
        }
        set
        {
            _SomeText2 = value;
            OnPropertyChanged("SomeText2");
        }
    }

    private void OnPropertyChanged(string PropertyName)
    {
        PropertyChangedEventHandler temp = PropertyChanged;
        if (temp != null)
        {
            temp(this, new PropertyChangedEventArgs(PropertyName));
        }
    }
}

}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

Oo萌小芽oO 2024-09-08 11:19:09

触发事件时读取所有属性的原因在于触发 ProperyChanged 事件时在绑定对象上调用的 PushData 方法。如果查看堆栈跟踪,您会注意到内部对象 BindToObject 的 PropValueChanged 方法被调用,该方法又调用 BindingManager 上的 Oncurrentchanged 事件。绑定机制会跟踪当前项目的更改,但它不会进行更细粒度的区分。 “罪魁祸首”PushData 方法调用属性上的 getter(使用反射器查看代码)。所以没有办法解决这个问题。话虽这么说,根据经验,在 get 和 set 访问器中,不建议进行繁重的处理,为此使用单独的 get 和 set 方法(如果可能)

另请参阅这篇文章,特别是此评论(http://www. codeproject.com/Messages/2514032/How-Binding-watches-control-properties-ie-how-doe.aspx),它准确地解释了 propertychanged 事件如何被触发,尽管它不会解决您的 getter 问题: http://www.codeproject.com/KB/database/databinding_tutorial.aspx ?msg=2514032

一个值得探索的想法是延迟 getter 的调用。您可以通过使用绑定的 ControlUpdateMode 属性来实现此目的。当该值设置为Never时,当发生变化时,相应的控件将不会更新。但是,当您将值切换回 OnPropertyChanged 时,将调用 PushData 方法,因此将访问 getter。因此,考虑到您的示例,此代码将暂时阻止文本框 2 更新:

void but_Click(object sender, EventArgs e)
        {                   
            txt2.DataBindings[0].ControlUpdateMode = ControlUpdateMode.Never;
            _presenter.SomeText1 = "some text 1";
        }

The reason why all properties are being read when the event gets fired rests in the PushData method called on the binding object when the ProperyChanged event is fired. If you look at the stacktrace, you will notice that the PropValueChanged method of the internal object BindToObject is called, that in turn calls the Oncurrentchanged event on the BindingManager. The binding mechanism keeps track of the current item changes, but it doesn't do a more granular distinction. The "culprit" PushData method calls the getter on your properties (take a look at the code using reflector). So there is no way around it. That being said, as a rule of thumb, in the get and set accessors it is not recommended to do heavy processing, use separate get and set methods for that (if possible)

Also take a look at this article, and this comment in particular (http://www.codeproject.com/Messages/2514032/How-Binding-watches-control-properties-i-e-how-doe.aspx), that explains exactly how the propertychanged event gets fired, though it will not address your getter problem: http://www.codeproject.com/KB/database/databinding_tutorial.aspx?msg=2514032

An idea to explore is to delay the getter being called. You can achieve this by playing around with the ControlUpdateMode property of the binding. When this value is set to Never, the corresponding control will not update when there is a change. However, when you switch the value back to OnPropertyChanged, PushData method will be called, so the getters will be accessed. So considering your example this code will temporary prevent the textbox 2 to update:

void but_Click(object sender, EventArgs e)
        {                   
            txt2.DataBindings[0].ControlUpdateMode = ControlUpdateMode.Never;
            _presenter.SomeText1 = "some text 1";
        }
月下凄凉 2024-09-08 11:19:09

我正在测试像这样的子类化绑定并管理 OnPropertyChanged,也许对您有帮助。

public class apBinding : Binding
{

        public apBinding(string propertyName, INotifyPropertyChanged dataSource, string dataMember)
            : base(propertyName, dataSource, dataMember)
        {
            this.ControlUpdateMode = ControlUpdateMode.Never;
            dataSource.PropertyChanged += new PropertyChangedEventHandler(OnPropertyChanged);
        }

        private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
        {

            if (e.PropertyName == this.BindingMemberInfo.BindingField)
            {
                 this.ReadValue();
            }
        }
    }

现在我发现的问题是控件覆盖了链接对象的值,
所以我修改为

public class apBinding : Binding
{

        public apBinding(string propertyName, INotifyPropertyChanged dataSource, string dataMember)
            : base(propertyName, dataSource, dataMember)
        {

            dataSource.PropertyChanged += new PropertyChangedEventHandler(OnPropertyChanged);
        }

        private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            this.ControlUpdateMode = ControlUpdateMode.Never;
            if (e.PropertyName == this.BindingMemberInfo.BindingField)
            {
                 this.ReadValue();
            }
        }
    }

第一次调用 propertychanges 时禁用 controlupdate。并且控件在第一次运行时正确更新。

I'm testing subclassing binding like this and managing OnPropertyChanged, maybe helps you.

public class apBinding : Binding
{

        public apBinding(string propertyName, INotifyPropertyChanged dataSource, string dataMember)
            : base(propertyName, dataSource, dataMember)
        {
            this.ControlUpdateMode = ControlUpdateMode.Never;
            dataSource.PropertyChanged += new PropertyChangedEventHandler(OnPropertyChanged);
        }

        private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
        {

            if (e.PropertyName == this.BindingMemberInfo.BindingField)
            {
                 this.ReadValue();
            }
        }
    }

Now the problem that i find is that the control overwrites the value of the linked object,
so i modified to

public class apBinding : Binding
{

        public apBinding(string propertyName, INotifyPropertyChanged dataSource, string dataMember)
            : base(propertyName, dataSource, dataMember)
        {

            dataSource.PropertyChanged += new PropertyChangedEventHandler(OnPropertyChanged);
        }

        private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            this.ControlUpdateMode = ControlUpdateMode.Never;
            if (e.PropertyName == this.BindingMemberInfo.BindingField)
            {
                 this.ReadValue();
            }
        }
    }

then the first time propertychanges is called i disable controlupdate. and the control is correctly updated at the first run.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文