为什么在Winform数据绑定中还多次调用了不相关的属性,以及如何解决它?

Gra*_*ton 5 c# data-binding properties inotifypropertychanged winforms

这是我的winform代码:

partial class Form1
{
    /// <summary>
    /// Required designer variable.
    /// </summary>
    private System.ComponentModel.IContainer components = null;

    /// <summary>
    /// Clean up any resources being used.
    /// </summary>
    /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
    protected override void Dispose(bool disposing)
    {
        if (disposing && (components != null))
        {
            components.Dispose();
        }
        base.Dispose(disposing);
    }

    #region Windows Form Designer generated code

    /// <summary>
    /// Required method for Designer support - do not modify
    /// the contents of this method with the code editor.
    /// </summary>
    private void InitializeComponent()
    {
        this.textBox1 = new System.Windows.Forms.TextBox();
        this.textBox2 = new System.Windows.Forms.TextBox();
        this.textBox3 = new System.Windows.Forms.TextBox();
        this.SuspendLayout();
        // 
        // textBox1
        // 
        this.textBox1.Location = new System.Drawing.Point(28, 129);
        this.textBox1.Name = "textBox1";
        this.textBox1.Size = new System.Drawing.Size(100, 20);
        this.textBox1.TabIndex = 0;
        this.textBox1.Leave += new System.EventHandler(this.textBox1_Leave);
        // 
        // textBox2
        // 
        this.textBox2.Location = new System.Drawing.Point(28, 227);
        this.textBox2.Name = "textBox2";
        this.textBox2.Size = new System.Drawing.Size(100, 20);
        this.textBox2.TabIndex = 1;
        // 
        // textBox3
        // 
        this.textBox3.Location = new System.Drawing.Point(28, 283);
        this.textBox3.Name = "textBox3";
        this.textBox3.Size = new System.Drawing.Size(100, 20);
        this.textBox3.TabIndex = 2;
        // 
        // Form1
        // 
        this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
        this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
        this.ClientSize = new System.Drawing.Size(579, 412);
        this.Controls.Add(this.textBox1);
        this.Controls.Add(this.textBox3);
        this.Controls.Add(this.textBox2);
        this.Name = "Form1";
        this.Text = "Form1";
        this.Load += new System.EventHandler(this.Form1_Load);
        this.ResumeLayout(false);
        this.PerformLayout();

    }

    #endregion

    private System.Windows.Forms.TextBox textBox1;
    private System.Windows.Forms.TextBox textBox2;
    private System.Windows.Forms.TextBox textBox3;
}


public partial class Form1 : Form
{

    private readonly Form1VM _vm;
    public Form1()
    {
        InitializeComponent();
        _vm = new Form1VM();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        BindControlsToVM();
    }


    private void BindControl(Control control, string propertyName)
    {
        control.DataBindings.Clear();
        control.DataBindings.Add(nameof(control.Text), _vm, propertyName);
    }
    private void BindControlsToVM()
    {
        BindControl(textBox1, nameof(_vm.Name));
        BindControl(textBox2, nameof(_vm.Surface));
        BindControl(textBox3, nameof(_vm.Surface1));
    }

    private void textBox1_Leave(object sender, EventArgs e)
    {

    }

    private void button1_Click(object sender, EventArgs e)
    {

    }
}
Run Code Online (Sandbox Code Playgroud)

这是我的ViewModel(我尝试在Winform中遵循WPF)

public class Form1VM : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private string _name;
    public string Name
    {
        get { return _name; }
        set
        {
            _name = value;
            OnPropertyChanged(nameof(Name));
            //OnPropertyChanged(nameof(Surface));
        }
    }

    private string _surface;
    public string Surface
    {
        get { return _surface; }
        set
        {
            _surface = value;
            OnPropertyChanged(nameof(Surface));
        }
    }

    private string _surface1;
    public string Surface1
    {
        get { return _surface1; }
        set
        {
            _surface1 = value;
            OnPropertyChanged(nameof(Surface1));
        }
    }

    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}
Run Code Online (Sandbox Code Playgroud)

代码编译并运行后,将断点get放在Name属性的访问器上。现在试着改变或者SurfaceSurface1特性在TextBox用户界面,你会发现,Name属性get访问器也被调用,多次连!

这种调用存在性能问题。

我不知道为什么更改其他属性时会调用不相关的属性,为什么会这样,以及如何防止它呢?

TnT*_*nMn 3

我不知道为什么当我更改其他属性时会调用不相关的属性,为什么会这样......

对于类Form1VM,每个属性都与其他属性无关,并且您还实现了INotifyPropertyChanged提供更改通知,因此您希望绑定机制足够智能,仅提取已发布更改通知的值。

不幸的是,情况并非如此,默认机制在向绑定项发送更改后会拉取所有绑定属性。默认机制确实监视INotifyPropertyChanged.PropertyChanged事件响应拉动所有绑定值,而不仅仅是更改的值。

这一切都由PropertyManager 处理,该 PropertyManager由控件的ContainerControl的BindingContext 属性维护。

观察到的行为似乎是PropertyManager.OnCurrentChanged 方法的结果,该方法调用BindingManagerBase.PushData,最终导致对绑定进行迭代并调用Binding.PushData,其中以下代码执行并检索基础数据源值。

if (IsBinding) {
   dataSourceValue = bindToObject.GetValue();
   object controlValue = FormatObject(dataSourceValue);
   SetPropValue(controlValue);
   modified = false;
}
Run Code Online (Sandbox Code Playgroud)

主题代码声明绑定,以便事件触发上述序列TextBox.Validating。当底层数据源 ( _vm) 引发PropertyChanged事件时,该序列再次从该PropertyManager.OnCurrentChanged方法开始。

如何预防?

您可以创建一个PropertyManager重写方法的派生类OnCurrentChanged并编写您自己的行为。要使用此自定义类,您还需要创建一个自定义 BindingContext 类来安装它。如果您可以接受对数据绑定机制传播的更改的绑定属性的单次轮询,那么我不建议这样做。此行为可以通过使用 BindingSource 作为_vm绑定之间的中介来完成。

下面显示了使用 BindingSource 所需的已发布代码的更改。

private BindingSource bs = new BindingSource();
public Form1()
{
    InitializeComponent();
    _vm = new Form1VM();
    bs.DataSource = _vm;
}
Run Code Online (Sandbox Code Playgroud)
private void BindControl(Control control, string propertyName)
{
    control.DataBindings.Clear();
    control.DataBindings.Add(nameof(control.Text), bs, propertyName, true, DataSourceUpdateMode.OnValidation);
}
Run Code Online (Sandbox Code Playgroud)

另一种替代方法是Form1VM实现ICurrencyManagerProvider 接口并提供自定义的CurrencyManager 类实现,类似于 BindingSource 类的方式。这是我从未尝试过的事情,但我怀疑这与派生自定义 PropertyManager 是类似的任务。