Почему несвязанные свойства также вызываются несколько раз в привязке данных Winform, и как это исправить? - PullRequest
5 голосов
/ 03 июня 2019

Это мой код 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)
    {

    }
}

А это моя ViewModel (я пытаюсь следовать WPF в Winform)

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));
    }
}

После того, как код скомпилирован и запущен, установите точку останова у метода доступа get в свойстве Name. Теперь попробуйте изменить свойства Surface или Surface1 в пользовательском интерфейсе TextBox, вы обнаружите, что метод доступа Name property get также вызывается, даже несколько раз!

Существует проблема с производительностью при таком вызове.

Понятия не имею, почему при изменении других свойств вызывается несвязанное свойство, почему это так и как его предотвратить?

1 Ответ

3 голосов
/ 06 июня 2019

Понятия не имею, почему при изменении других свойств вызывается несвязанное свойство, почему это так ...

Для класса Form1VM каждое свойство не связано с другими, и вы также внедрили INotifyPropertyChanged для предоставления уведомления об изменениях, поэтому вы ожидаете, что механизм привязки будет достаточно умен, чтобы извлекать только те значения, которые опубликовали изменение уведомление.

К сожалению, это не тот случай, и механизм по умолчанию извлекает все связанные свойства после того, как отправляет изменение в связанный элемент. Механизм по умолчанию контролирует, что событие INotifyPropertyChanged.PropertyChanged отвечает, вытягивая все связанные значения вместо только измененного.

Все это обрабатывается PropertyManager , который поддерживается Свойство BindingContext элемента управления ContainerControl .

Наблюдаемое поведение выглядит как результат PropertyManager.OnCurrentChanged метода , который вызывает BindingManagerBase.PushData , что в конечном итоге приводит к итерации по привязкам и вызову 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 и привязкой.

Ниже показаны изменения в опубликованном коде, необходимые для использования BindingSource.

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 и предоставлении пользовательской реализации CurrencyManager Class , аналогичной тому, как это делает класс BindingSource. Это то, что я никогда не пытался, но я подозреваю, что это будет аналогично задаче создания собственного PropertyManager.

...