【发布时间】:2021-08-07 21:06:08
【问题描述】:
我有一个 TextBox,其 Text 属性绑定到名为 Name 的 ViewModel 属性,还有一个 Button,其 Enable 属性绑定到名为 IsBusy 的 ViewModel 属性。
TexBox Binding 实现了 BindingComplete,如果属性抛出异常,它会更改 TextBox 的背景颜色。
问题是,当 IsBusy 属性发生变化且 IsBusy 属性绑定未订阅时也会引发 BindingComplete,并且 BindingComplete 事件参数具有“Name”的 BindingField 和 BindingMember,并且此绑定的关联控件是 TextBox。
为什么 IsBusy 属性会引发绑定到 Name 属性的 BindingComplete?
因此,在我强制 TextBox 绑定调用 WriteValue() 以验证出现错误时按预期设置背景颜色的属性,并为 IsBusy 属性分配一个真值以指示它正忙后,它设置背景颜色变回白色,因为 IsBusy 无一例外地引发了 TextBox 的 BindingComplete。
仅供参考,我故意引发 PropertyChanged 事件并引发异常,因为我希望输入不正确的值以供用户查看。
这只是我遇到的问题的一个演示。发生错误时,我有更复杂的业务需求。因此,如果您能解释为什么会发生这种情况以及如何防止或解决它,我们将不胜感激。
public partial class BindingTestForm : Form
{
private TestViewModel viewModel = new TestViewModel();
public BindingTestForm()
{
InitializeComponent();
var textBoxTextBinding = new Binding(nameof(TextBox.Text), viewModel, nameof(TestViewModel.Name), true, DataSourceUpdateMode.OnPropertyChanged);
textBoxTextBinding.BindingComplete += TextBoxTextBinding_BindingComplete;
NameTextBox.DataBindings.Add(textBoxTextBinding);
var enableBinding = new Binding(nameof(Control.Enabled), viewModel, nameof(TestViewModel.IsBusy));
enableBinding.Format += EnableBinding_Format;
SaveButton.DataBindings.Add(enableBinding);
Load += BindingTestForm_Load;
}
private void EnableBinding_Format(object sender, ConvertEventArgs e)
{
e.Value = !(bool)e.Value;
}
private void TextBoxTextBinding_BindingComplete(object sender, BindingCompleteEventArgs e)
{
if (e.Exception != null)
NameTextBox.BackColor = Color.Red;
else
NameTextBox.BackColor = Color.White;
}
private async void BindingTestForm_Load(object sender, EventArgs e)
{
viewModel.IsBusy = true;
await Task.Run(async () =>
{
await Task.Delay(2000);
});
viewModel.IsBusy = false;
}
public class TestViewModel: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _name;
public string Name
{
get => _name;
set
{
_name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(TestViewModel.Name)));
if (string.IsNullOrWhiteSpace(_name))
throw new Exception("Please enter package name.");
if (_name.Length > 5)
throw new Exception("Max length 5.");
}
}
public bool _isBusy = false;
public bool IsBusy
{
get => _isBusy;
set
{
_isBusy = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(TestViewModel.IsBusy)));
}
}
}
private async void SaveButton_Click(object sender, EventArgs e)
{
NameTextBox.DataBindings[nameof(TextBox.Text)].WriteValue();
viewModel.IsBusy = true;
await Task.Run(async () =>
{
await Task.Delay(2000);
});
viewModel.IsBusy = false;
}
}
partial class BindingTestForm
{
/// <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.NameTextBox = new System.Windows.Forms.TextBox();
this.SaveButton = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// NameTextBox
//
this.NameTextBox.Location = new System.Drawing.Point(13, 13);
this.NameTextBox.Name = "NameTextBox";
this.NameTextBox.Size = new System.Drawing.Size(100, 20);
this.NameTextBox.TabIndex = 0;
//
// SaveButton
//
this.SaveButton.Location = new System.Drawing.Point(13, 75);
this.SaveButton.Name = "SaveButton";
this.SaveButton.Size = new System.Drawing.Size(75, 23);
this.SaveButton.TabIndex = 1;
this.SaveButton.Text = "Save";
this.SaveButton.UseVisualStyleBackColor = true;
this.SaveButton.Click += new System.EventHandler(this.SaveButton_Click);
//
// BindingTestForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(800, 450);
this.Controls.Add(this.SaveButton);
this.Controls.Add(this.NameTextBox);
this.Name = "BindingTestForm";
this.Text = "FlowLayoutTestForm";
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.TextBox NameTextBox;
private System.Windows.Forms.Button SaveButton;
}
【问题讨论】:
-
改用文本框的验证事件。您是否考虑过使用 ErrorProvider 组件和 IErrorDataInfo 接口?
-
@Olivier Jacot-Descombes 有很多替代方法可以做到这一点,但我想尽可能多地使用 MVVM。我真的很好奇为什么会这样。
-
恕我直言,您有点过于复杂了。 ViewModel 中可能有一些
IsValid属性,您可以绑定到BackColor(如果您愿意,可以使用bool->Color格式)。顺便说一句,here 是我的一个小型 MVVM 演示(适用于 WinForms 和 WPF),也许这也可以给你一些想法。 -
ErrorProvider和IErrorDataInfo以及@GyörgyKőszeg 的解决方案允许您将逻辑放入视图模型中,而不是处理表单中的事件。这就是 MVVM 的全部意义所在。 -
@György Kőszeg 好吧,这只是为了说明问题,在我的实际项目中,我需要更改无法绑定的文本框边框颜色,并且需要设置工具提示文本。跨度>