绑定布尔属性值的反值






4.33/5 (4投票s)
是否曾希望能够绑定到“禁用”而不是“启用”?
引言
通常,用户界面代码会因为操作状态的事件处理程序而变得混乱,例如,当点击按钮 A 时,禁用按钮 B 等。
数据绑定是简化用户界面逻辑的好方法。本文假定您熟悉 .NET 数据绑定和 WinForms。(如果您不熟悉这个概念,可以在 CodeProject 或 MSDN 上找到相关信息——这是一种优雅的技术,我将其用于许多目的)。
背景
任何熟悉 WinForms 开发的人都肯定见过管理 UI 元素 Enabled
状态的代码。
传统方法包括仔细的事件处理程序和属性初始化。
public Form1()
{
InitializeComponent();
button1.Enabled = true;
button2.Enabled = false;
panel1.Enabled = false;
}
private void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false;
button2.Enabled = true;
panel1.Enabled = true;
}
private void button2_Click(object sender, EventArgs e)
{
button1.Enabled = true;
button2.Enabled = false;
panel1.Enabled = false;
}
这可以通过绑定来简化,例如:
public Form1()
{
InitializeComponent();
button1.Enabled = true;
button2.Enabled = false;
panel1.DataBindings.Add(new Binding("Enabled", button2, "Enabled"));
}
private void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false;
button2.Enabled = true;
}
private void button2_Click(object sender, EventArgs e)
{
button1.Enabled = true;
button2.Enabled = false;
}
不幸的是,由于按钮 1 和 2 是反向相关的,即当按钮 1 启用时,按钮 2 禁用,因此我们无法添加传统的绑定。这种情况发生的频率比你想象的要高,例如,连接和断开按钮的行为就完全是这样。本文定义的 InvertedBinding
结构为此问题提供了一个无缝的解决方案。
Using the Code
附带的项目是一个针对 .NET 2.0 Framework 的 VS 2008 解决方案,但相关的代码包含在内。只需获取 InvertedBinding
类即可。
使用示例
public Form1()
{
InitializeComponent();
button2.DataBindings.Add(InvertedBinding.Create(button1, "Enabled"));
panel1.DataBindings.Add(new Binding("Enabled", button2, "Enabled"));
}
private void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false;
}
private void button2_Click(object sender, EventArgs e)
{
button1.Enabled = true;
}
现在,整个 UI 启用状态都由 button1.Enabled
驱动。 也不需要在设计器或代码中设置初始值。
实现
using System;
using System.ComponentModel;
using System.Reflection;
using System.Windows.Forms;
namespace InverseBinding
{
public class InvertedBinding : INotifyPropertyChanged, IDisposable
{
#region Private Fields
private readonly object _dataSource;
private readonly EventInfo _changedEvent;
private readonly MethodInfo _getAccessor;
private readonly MethodInfo _setAccessor;
#endregion
#region Constructors
protected InvertedBinding
(object dataSource, string dataSourceBoundPropertyName)
{
if ((dataSource == null) || (dataSourceBoundPropertyName == null))
{
throw new ArgumentNullException();
}
_dataSource = dataSource;
EventInfo propertyChangedEvent =
_dataSource.GetType().GetEvent(string.Format("{0}Changed",
dataSourceBoundPropertyName));
if ((propertyChangedEvent != null) && (typeof(EventHandler).
IsAssignableFrom(propertyChangedEvent.EventHandlerType)))
{
_changedEvent = propertyChangedEvent;
_changedEvent.AddEventHandler(_dataSource,
new EventHandler(OnDataSourcePropertyChanged));
}
PropertyInfo dataSourceBoundProperty =
_dataSource.GetType().GetProperty(dataSourceBoundPropertyName);
if (dataSourceBoundProperty == null)
{
throw new MissingMemberException(string.Format(
"Could not find property '{0}.{1}'",
_dataSource.GetType().FullName,
dataSourceBoundPropertyName));
}
_getAccessor = dataSourceBoundProperty.GetGetMethod();
if (_getAccessor == null)
{
throw new MissingMethodException(string.Format(
"No get accessor for '{0}'", dataSourceBoundProperty.Name));
}
if (!typeof(bool).IsAssignableFrom(_getAccessor.ReturnType))
{
throw new ArgumentException(
string.Format(
"Class only works on boolean properties,
'{0}' is not of type bool",
dataSourceBoundProperty.Name));
}
_setAccessor = dataSourceBoundProperty.GetSetMethod();
}
#endregion
public static Binding Create(object dataSource, string propertyName)
{
return new Binding(propertyName,
new InvertedBinding(dataSource, propertyName), "InvertedProperty");
}
public bool InvertedProperty
{
get
{
return !GetDataBoundValue();
}
set
{
if (_setAccessor == null)
{
// nothing to do since no one will get notified.
return;
}
bool curVal = InvertedProperty;
// a little bit of trickery here, we only want to change
// the value if IS the same
// rather than the conventional if it's different
if (curVal == value)
{
_setAccessor.Invoke(_dataSource, new object[] { !value });
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs("InvertedProperty"));
}
}
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
#region IDisposable Members
public void Dispose()
{
if (_changedEvent != null)
{
_changedEvent.RemoveEventHandler
(_dataSource, new EventHandler(OnDataSourcePropertyChanged));
}
}
#endregion
private void OnDataSourcePropertyChanged(object sender, EventArgs e)
{
// refresh our property (which may trigger our PropertyChanged event)
InvertedProperty = !GetDataBoundValue();
}
private bool GetDataBoundValue()
{
return (bool) _getAccessor.Invoke(_dataSource, null);
}
}
}
关注点
实现 INotifyPropertyChanged
非常重要,以便框架(例如 Button
或 Panel
)在绑定属性更改时更新其状态。
代码展示了许多反射示例,包括动态事件发现和注册。 这种事情对我来说已经不再令人兴奋了,因为这对我来说已经习以为常,但初学者可能会觉得很有趣。:)
可以将此代码调整为针对 .NET 3.0,这将允许使用 lambda 消除魔术字符串。
历史
- 初始修订版
- 小更新(定制异常消息,添加 XML 文档)