这是一个适当的属性实现和适当的数据绑定的组合。
(1) 属性实现:
属性不需要很复杂,它可以像你的 naïve 方法一样简单,但是必要的部分是它应该提供属性更改通知。您可以使用通用的INotifyPropertyChanged
机制或Windows Forms特定的PropertyNameChanged
命名事件模式。在两种情况下,您都不能使用C# 自动属性功能,而必须手动实现(使用显式支持字段)。以下是一个示例实现:
string myStringProperty;
public string MyStringProperty
{
get { return myStringProperty; }
set
{
if (myStringProperty == value) return;
myStringProperty = value;
var handler = MyStringPropertyChanged;
if (handler != null) handler(this, EventArgs.Empty);
}
}
public event EventHandler MyStringPropertyChanged;
(2) 数据绑定:
将单个控件属性绑定到单个对象属性称为简单数据绑定,可以通过Control.DataBindings实现。您可以查看ControlBindingsCollection.Add方法 / Binding构造函数重载和Binding类的属性/方法/事件以获取更多信息。
Binding
所做的基本上是创建源对象属性和目标对象属性之间的链接(单向或双向)。请注意“属性”这个词 - 这正是开箱即用支持的内容。但是,通过以下简单的帮助程序(在我的回答中提供,如何在TextChange时从false设置TextBox.Enabled为true?),您可以轻松创建单向表达式绑定:
public static void Bind(this Control target, string targetProperty, object source, string sourceProperty, Func<object, object> expression)
{
var binding = new Binding(targetProperty, source, sourceProperty, true, DataSourceUpdateMode.Never);
binding.Format += (sender, e) => e.Value = expression(e.Value);
target.DataBindings.Add(binding);
}
这是一个完整的演示示例:
using System;
using System.Windows.Forms;
namespace Samples
{
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var form = new Form();
var splitView = new SplitContainer { Dock = DockStyle.Fill, Parent = form };
var textEntry = new TextEntryControl { Dock = DockStyle.Fill, Parent = splitView.Panel1 };
var textConsumer = new TextConsumingControl { Dock = DockStyle.Fill, Parent = splitView.Panel2 };
textConsumer.DataBindings.Add("MyStringProperty", textEntry, "MyStringProperty", true, DataSourceUpdateMode.Never);
Application.Run(form);
}
}
class TextEntryControl : UserControl
{
TextBox textBox;
public TextEntryControl()
{
textBox = new TextBox { Parent = this, Left = 16, Top = 16 };
textBox.DataBindings.Add("Text", this, "MyStringProperty", true, DataSourceUpdateMode.OnPropertyChanged);
}
string myStringProperty;
public string MyStringProperty
{
get { return myStringProperty; }
set
{
if (myStringProperty == value) return;
myStringProperty = value;
var handler = MyStringPropertyChanged;
if (handler != null) handler(this, EventArgs.Empty);
}
}
public event EventHandler MyStringPropertyChanged;
}
class TextConsumingControl : UserControl
{
Button button;
public TextConsumingControl()
{
button = new Button { Parent = this, Left = 16, Top = 16, Text = "Click Me" };
button.Bind("Enabled", this, "MyStringProperty", value => !string.IsNullOrEmpty(value as string));
}
string myStringProperty;
public string MyStringProperty
{
get { return myStringProperty; }
set
{
if (myStringProperty == value) return;
myStringProperty = value;
var handler = MyStringPropertyChanged;
if (handler != null) handler(this, EventArgs.Empty);
}
}
public event EventHandler MyStringPropertyChanged;
}
public static class BindingUtils
{
public static void Bind(this Control target, string targetProperty, object source, string sourceProperty, Func<object, object> expression)
{
var binding = new Binding(targetProperty, source, sourceProperty, true, DataSourceUpdateMode.Never);
binding.Format += (sender, e) => e.Value = expression(e.Value);
target.DataBindings.Add(binding);
}
}
}
正如您所看到的,通过这一行代码,可以建立textEntry
和textConsumer
之间的请求(单向)绑定:
textConsumer.DataBindings.Add("MyStringProperty", textEntry, "MyStringProperty", true, DataSourceUpdateMode.Never)
在演示中,你可以看到另一个有趣的点是,如果你愿意,你实际上也可以数据绑定内部控件属性。整个内部文本框和属性之间的同步是通过这一行代码实现的:
textBox.DataBindings.Add("Text", this, "MyStringProperty", true, DataSourceUpdateMode.OnPropertyChanged);
并且启用TextConsumingControl
内部按钮:
button.Bind("Enabled", this, "MyStringProperty", value => !string.IsNullOrEmpty(value as string));
当然,最后两件事情是可选的,你可以在属性设置器中完成,但知道这种选择存在还是很酷的。