c# - WinForms BindingComplete 由另一个属性触发
问题描述
我有一个 TextBox,其 Text 属性绑定到名为 Name 的 ViewModel 属性,还有一个 Button,其 Enable 属性绑定到名为 IsBusy 的 ViewModel 属性。
并且 TexBox Binding 实现了 BindingComplete 如果属性引发异常,它会更改 TextBox 的背景颜色。
问题是当 IsBusy 属性发生更改且 IsBusy 属性绑定未订阅时也会引发 BindingComplete,并且 BindingComplete 事件参数具有“名称”的 BindingField 和 BindingMember,并且此绑定的关联控件是 TextBox。
为什么 IsBusy 属性会引发绑定到 Name 属性的 BindingComplete?
因此,在我强制 TextBox 绑定调用 WriteValue() 以验证出现错误时按预期设置背景颜色的属性,并为 IsBusy 属性分配一个真值以指示它正忙后,它会设置背景颜色回到 White,因为 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;
}
解决方案
我必须承认我不知道为什么会这样。
作为替代解决方案,我建议您创建一个能够显示红色边框和工具提示的文本框控件。
public class TextBoxEx : TextBox
{
[DllImport("user32")]
private static extern IntPtr GetWindowDC(IntPtr hwnd);
private const int WM_NCPAINT = 0x85;
private readonly ToolTip _toolTip = new ToolTip();
private bool _hasError;
private string _toolTipText;
public string ToolTipText
{
get {
return _toolTipText;
}
set {
if (value != _toolTipText) {
_toolTipText = value;
if (String.IsNullOrEmpty(_toolTipText)) {
_toolTip.Hide(this);
_hasError = false;
} else {
_toolTip.Show(_toolTipText, this, 3000);
_hasError = true;
}
// This is required to update the border color immediately.
var m = Message.Create(Handle, WM_NCPAINT, IntPtr.Zero, IntPtr.Zero);
WndProc(ref m);
}
}
}
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (_hasError && m.Msg == WM_NCPAINT) {
var dc = GetWindowDC(Handle);
using (Graphics g = Graphics.FromHdc(dc)) {
g.DrawRectangle(Pens.Red, 0, 0, Width - 1, Height - 1);
}
}
}
}
将工具提示文本设置为不同于 null 或空的内容会显示工具提示并显示红色边框。
然后数据模型(MVVM 上下文中的视图模型)必须提供错误消息并实现INotifyPropertyChanged
. 例子:
public class Model : INotifyPropertyChanged
{
private string _text;
public string Text
{
get { return _text; }
set {
if (value != _text) {
_text = value;
OnPropertyChanged(nameof(Text));
OnPropertyChanged(nameof(TextErrorMessage));
}
}
}
public string TextErrorMessage
{
get {
return _text?.Length == 4
? null
: "Please enter a text of length 4";
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
现在将 的Text
属性绑定TextBoxEx
到模型的属性,并将Text
的属性绑定到模型的属性。设置两个绑定并将模型对象分配给表单上组件的属性。ToolTipText
TextBoxEx
TextErrorMessage
OnPropertyChanged
DataSource
BindingSource
现在您有了一个功能齐全的 MVVM 模式,除了将模型分配给绑定源之外,不需要表单中的任何代码。
推荐阅读
- css - 背景大小对背景颜色没有任何影响
- excel - 保护开启时图表代码不运行
- pact - Pact:如何匹配键与正则表达式匹配的对象?
- angular - 如何使用 ng2-signalr
- django - 如何访问 forms.py 定义中的运行时 request.session 值?
- r - 将 JSON 导出到文本文件
- javascript - 在 ngOnInit() 内部使用 ngOnInit() 外部的函数
- mysql - 如何使用 SELECT LAST_INSERT_ID(); 在mysql例程中插入语句后?
- python - 排序、排名、groupby 和 sum 组合 -> Python pandas
- c# - Sqlite 使用过程/触发器附加数据库,我将能够从 c# 运行它