这是我的通知代码:
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)
{
}
}
这是我的观点模型(我尝试在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));
}
}
代码编译并运行后,将断点放在Name
属性的get
访问器上。现在尝试在 UI 上更改Surface
或Surface1
属性TextBox
,您会发现Name
属性get
访问器也被调用,甚至多次!
这种调用存在性能问题。
我不知道为什么当我更改其他属性时会调用不相关的属性,为什么会这样,以及如何防止它?
为什么当我更改其他属性时会调用不相关的属性,为什么会这样......
对于类Form1VM
,每个属性都与其他属性无关,并且您还实现了INotifyPropertyChanged
来提供更改通知,因此您希望绑定机制足够智能,可以仅拉取已发布更改通知的值。
遗憾的是,情况并非如此,默认机制在向绑定项发送更改后拉取所有绑定属性。 默认机制确实监视事件响应INotifyPropertyChanged.PropertyChanged
拉取所有绑定值,而不仅仅是更改的值。
这一切都由一个 PropertyManager 处理,该 PropertyManager 由控件的 ContainerControl 的 BindingContext 属性维护。
观察到的行为似乎是调用 BindingManagerBase.PushData 的 PropertyManager.OnCurrentChanged 方法的结果,该方法最终导致对绑定的迭代并调用 Binding.PushData,其中以下代码执行并检索基础数据源值。
if (IsBinding) {
dataSourceValue = bindToObject.GetValue();
object controlValue = FormatObject(dataSourceValue);
SetPropValue(controlValue);
modified = false;
}
主题代码声明绑定,以便上述序列由TextBox.Validating
事件触发。 当基础数据源 (_vm
) 引发PropertyChanged
事件时,序列将再次以PropertyManager.OnCurrentChanged
方法开始。
如何预防?
可以创建一个派生的PropertyManager
类来重写OnCurrentChanged
方法并编写自己的行为。 若要使用此自定义类,还需要创建自定义 BindingContext 类来安装它。 如果您可以接受对数据绑定机制传播的更改进行绑定属性的单轮询,则我不建议这样做。 此行为可以通过使用 BindingSource 作为_vm
和绑定之间的中介来实现。
下面显示了对使用绑定源所需的已发布代码的更改。
private BindingSource bs = new BindingSource();
public Form1()
{
InitializeComponent();
_vm = new Form1VM();
bs.DataSource = _vm;
}
private void BindControl(Control control, string propertyName)
{
control.DataBindings.Clear();
control.DataBindings.Add(nameof(control.Text), bs, propertyName, true, DataSourceUpdateMode.OnValidation);
}
另一种选择是让Form1VM
实现 ICurrencyManagerProvider 接口并提供类似于 BindingSource 类的自定义 CurrencyManager 类实现。 这是我从未尝试过的事情,但我怀疑这将是一个类似于派生自定义属性管理器的任务。