将简单绑定到“文本”属性的解决方案扩展到多个控件以处理与任何类型的绑定?
我的问题是:如何超越编写用于数据绑定多个控件(没有内置 DataSource 属性的控件)的技术的自定义实现,针对每种可能的数据类型,简单的属性...如下面的代码中所描述和演示...实现更强大的解决方案,该解决方案将独立于绑定是字符串、int还是其他类型。
我的猜测是:这将涉及反思;但是,我被困在这一点上。我正在寻找有关下一步移动“方向”的战略建议、提示、线索,而不是完整的代码答案,但当然我感谢所有回复,如果您在回复中发布代码,我一定会研究代码! Marc Clifton 2005 年关于 CodeProject 简单数据绑定 的文章:似乎演示了基于反射的方法:但是,老实说,我并没有真正理解他的代码,而且,就 .NET 而言,2005 年已经是很久以前的事了。
背景:部分是为了回应各种SO问题和答案,例如:以三种形式更新用户控件:我开发了一种成功的技术,可以将各种控件的文本属性同时绑定到公共类中定义的一个源;还能够使用定义一个扩展方法和两个公共方法的静态类来“抽象”绑定过程的一些细节。
我已经验证了“MainForm”中控件上的 TextBox、MainForm 上 UserControl 上的 TextBox 以及第二个窗体上的 TextBox“独立”打开(即,form2.Parent == null)都正确更新(即,两个-way 绑定有效)来自“DataSource 等效”公共类。改变一项:改变全部。
代码:此类的实例将为数据绑定提供目标属性 (theText):
public class TextDataBinder
{
public event PropertyChangedEventHandler PropertyChanged;
private string _theText;
public string theText
{
get { return _theText; }
// note : if 'setter is declared 'internal : blocks
// auto-updating when run-time user modifies consumers
// but will still allow update via code
set
{
_theText = value;
OnPropertyChanged(new PropertyChangedEventArgs("theText"));
}
}
protected void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, e);
}
}
}
代码:此静态类可以隐藏一些绑定过程的复杂性,并允许轻松绑定到多个控件:
public static class TextBindingExtender
{
public static TextDataBinder CurrentDataSource;
public static void SetCurrentDataSource(TextDataBinder newCurrentDataSource)
{
CurrentDataSource = newCurrentDataSource;
}
// extension method for Control
public static void AddTextBinding(this Control theControl, string controlPropertyName, string targetPropertyName)
{
theControl.DataBindings.Add(controlPropertyName, CurrentDataSource, targetPropertyName, false, DataSourceUpdateMode.OnPropertyChanged);
}
// bind to all Controls in a List<Control>
public static void AddTextBindings(List<Control> theControls, string controlPropertyName, string targetPropertyName)
{
foreach (Control theControl in theControls)
{
theControl.AddTextBinding(controlPropertyName, targetPropertyName);
}
}
}
如何使用上述类(在 Form 中)加载事件):
// create a new TextDataBinder
TextBindingExtender.CurrentDataSource = new TextDataBinder();
// bind to multiple textboxes, label, on a UserControl, on another Form, etc.
TextBindingExtender.AddTextBindings(new List<Control> { textBox1, textBox2, userControl11.tb, label1, instanceOfForm2.tb }, "Text", "theText");
// test assigning some initial text to the bound property
TextBindingExtender.CurrentDataSource.theText = "some initial text";
My question is : how to move beyond writing a custom implementation of a technique for databinding multiple controls (controls without built-in DataSource properties), for each possible type of data, to simple properties ... as described and demonstrated in code that follows ... to achieve a more poweful solution that will be independent of whether the binding is to a string, or an int, or other types.
My guess is: this will involve reflection; but, I'm stuck at that point. I'm looking for strategic advice on which "direction" to move next, hints, clues, not a complete code answer, but of course I appreciate all responses, and I'll sure study code if you post code in reply ! Marc Clifton's 2005 article on CodeProject Simple Databinding: appears to demonstrate a reflection based approach: but, honestly, I do not really grok his code, and, in terms of .NET, 2005 is a long time ago.
Background: Partly in response to various SO questions and answers, like: Update Usercontrol on Three Forms: I've evolved a successful technique for databinding text properties of various controls simultaneously to one source defined in a Public class; also been able to "abstract" some of the details of the binding process using a static class that defines one extension method, and two public methods.
I've verifed that TextBoxes on Controls in a "MainForm," TextBoxes on a UserControl on the MainForm, and a TextBox on a second Form opened "independently" (i.e., form2.Parent == null) all update properly (i.e., two-way binding is in effect) from the "DataSource equivalent" public class. Change one: change all.
Code: an instance of this class will supply the target property (theText) for databinding:
public class TextDataBinder
{
public event PropertyChangedEventHandler PropertyChanged;
private string _theText;
public string theText
{
get { return _theText; }
// note : if 'setter is declared 'internal : blocks
// auto-updating when run-time user modifies consumers
// but will still allow update via code
set
{
_theText = value;
OnPropertyChanged(new PropertyChangedEventArgs("theText"));
}
}
protected void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, e);
}
}
}
Code: this static class enables hiding some of the binding process complexity, and allows easy binding to multiple controls:
public static class TextBindingExtender
{
public static TextDataBinder CurrentDataSource;
public static void SetCurrentDataSource(TextDataBinder newCurrentDataSource)
{
CurrentDataSource = newCurrentDataSource;
}
// extension method for Control
public static void AddTextBinding(this Control theControl, string controlPropertyName, string targetPropertyName)
{
theControl.DataBindings.Add(controlPropertyName, CurrentDataSource, targetPropertyName, false, DataSourceUpdateMode.OnPropertyChanged);
}
// bind to all Controls in a List<Control>
public static void AddTextBindings(List<Control> theControls, string controlPropertyName, string targetPropertyName)
{
foreach (Control theControl in theControls)
{
theControl.AddTextBinding(controlPropertyName, targetPropertyName);
}
}
}
How the above classes are used (in a Form Load event) :
// create a new TextDataBinder
TextBindingExtender.CurrentDataSource = new TextDataBinder();
// bind to multiple textboxes, label, on a UserControl, on another Form, etc.
TextBindingExtender.AddTextBindings(new List<Control> { textBox1, textBox2, userControl11.tb, label1, instanceOfForm2.tb }, "Text", "theText");
// test assigning some initial text to the bound property
TextBindingExtender.CurrentDataSource.theText = "some initial text";
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
这实际上取决于你想做什么;但最终常见的数据绑定(对于简单的属性,手动完成)包括:
TypeDescriptor.GetProperties(obj)[propName]
,为您提供一个抽象 (PropertyDescriptor
),.IsReadOnly
) code>).GetValue()
、.SetValue()
),.Converter
、.ConvertFromString()
、.ConvertToString()
) 这是一个关键位,意味着您不必担心什么数据类型要求.DisplayName
,或.Name
,如果为空/null),.SupportsChangeEvents
).AddValueChanged()
、.RemoveValueChanged()
),INotifyPropertyChanged
)如果您可能绑定到列表而不是单个对象:
- 列表可能被抽象在
IListSource
后面- 该列表可能具有自定义属性,因此请检查
ITypedList
- 否则,识别项目的
Type
并使用TypeDescriptor.GetProperties(type)
- 您需要考虑一个“货币管理器”(即绑定到同一列表的所有内容都应该始终指向列表中的同一记录)
还有诸如
ICustomTypeDescriptor
和之类的东西>TypeDescriptionProvider
需要考虑,但大多数时候TypeDescriptor
会自动为您处理此问题。正如您所看到的 - 有很多事情需要考虑!很多工作......你不必做的一件事就是反思;这是在
PropertyDescriptor
后面抽象出来的。原因是并非所有数据都是静态类型的;考虑一下 DataTable - 列(映射到可绑定数据属性)在编译时并未固定,因此反射是不合适的。同样,其他一些类型也有自定义的“属性包”实现。PropertyDescriptor
让您的代码能够以相同的方式处理动态(不是 4.0 意义上的)属性和反射属性。它也可以很好地与“HyperDescriptor”(另一种属性定制)等内容配合使用。It really depends what you want to do; but ultimately common data-binding (for simple properties, done manually) consists of:
TypeDescriptor.GetProperties(obj)[propName]
, giving you an abstraction (PropertyDescriptor
).IsReadOnly
).GetValue()
,.SetValue()
).Converter
,.ConvertFromString()
,.ConvertToString()
) THIS is a key bit that means you don't have to worry about what the data type is.DisplayName
, or.Name
if that it empty/null).SupportsChangeEvents
).AddValueChanged()
,.RemoveValueChanged()
)INotifyPropertyChanged
)If you might be binding to a list rather than a single object:
- the list might be abstracted behind
IListSource
- the list might have custom properties, so check for
ITypedList
- otherwise, identify the
Type
of the items and useTypeDescriptor.GetProperties(type)
- you need to consider a "currency manager" (i.e. should all the things bound to the same list be pointing to the same record in the list all the time)
There are also things like
ICustomTypeDescriptor
andTypeDescriptionProvider
to consider, but most of the timeTypeDescriptor
handles this for you automatically.As you can see - lots of things to think about! Lots of work... the one thing that you don't have to do is reflection; this is abstracted behind
PropertyDescriptor
. The reason for this is that not all data is static-typed; think aboutDataTable
- the columns (which map to bindable data properties) are not fixed at compile-time, so reflection isn't appropriate. Likewise, some other types have custom "property bag" implementations.PropertyDescriptor
lets your code handle either dynamic (not in the 4.0 sense) and reflective properties identically. It also works nicely with things like "HyperDescriptor", another property customisation.