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属性的访问器上。现在试着改变或者Surface或Surface1特性在TextBox用户界面,你会发现,Name属性get访问器也被调用,多次连!
这种调用存在性能问题。
我不知道为什么更改其他属性时会调用不相关的属性,为什么会这样,以及如何防止它呢?
我不知道为什么当我更改其他属性时会调用不相关的属性,为什么会这样......
对于类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 是类似的任务。