WPF的数据绑定非常强大,可以省去我们在winform下的不少难写代码。本文主要探讨一下WPF中单实例对象数据绑定。
WPF的单实例对象数据绑定的需求主要起源于我写的一个下载工具,我写了一个自动关机的功能,然后想把这个自动关机的状态同时双向绑定到工具栏和菜单中,而工具栏和菜单是分别在两个不同的UserControl中写的,它们之间不共享数据。这样把配置数据绑定到多个不同的控件的需求还有不少。
首先我想到的方式是将配置数据写成静态属性,然后通x:Static过标记直接绑定静态属性到控件上,但是很快就发现了这样的局限性:不能实现双向数据绑定。原因很简单:双向数据绑定的时候,是基于实现了INotifyPropertyChanged的对象通知的。静态属性的变更自然无法通知出去。
既然静态属性的形式不行,就想到了将数据绑定到单实例对象中,便用SINGLETON模式实现了一个,实例代码如下:
<StackPanel>
<CheckBox Content="AutoPowerOff1" IsChecked="{Binding Path=AutoPowerOff, Mode=Default, Source={x:Static src:ConfigData.Instance}}"/>
<CheckBox Content="AutoPowerOff2" IsChecked="{Binding Path=AutoPowerOff, Mode=Default, Source={x:Static src:ConfigData.Instance}}"/>
</StackPanel>
//SINGLETON模式
class
ConfigData : System.ComponentModel.INotifyPropertyChanged
{
public
static
ConfigData Instance = new
ConfigData();
private ConfigData() { }
bool autoPowerOff = false;
public
bool AutoPowerOff
{
get { return autoPowerOff; }
set { autoPowerOff = value; NotifyPropertyChange("AutoPowerOff"); }
}
#region INotifyPropertyChanged 成员
public
event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
void NotifyPropertyChange(string proper)
{
if (PropertyChanged == null)
return;
PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(proper));
}
#endregion
}
这个代码毕竟简单,将两个checkbox的check状态分别绑定到ConfigData的AutoPowerOff属性中,通过静态属性Source={x:Static src:ConfigData.Instance}指定到同一个数据源,所以一个checkbox的状态变更会同步更新到另一个checkbox。实现了我们的需求。
SINGLETON模式非常简单有效,然而有一点让我们不大爽的地方,那就是不支持blend自动绑定,需要手工敲不少代码。Blend的数据绑定中,只能自动生成对象,然后将数据绑定到生产的对象中,类似这种形式。
<StackPanel>
<StackPanel.Resources>
<src:ConfigData x:Key="ConfigDataDataSource" d:IsDataSource="True"/>
<src:ConfigData x:Key="ConfigDataDataSource1" d:IsDataSource="True"/>
</StackPanel.Resources>
<CheckBox Content="AutoPowerOff1" IsChecked="{Binding Path=AutoPowerOff, Mode=Default, Source={StaticResource ConfigDataDataSource}}"/>
<CheckBox Content="AutoPowerOff2" IsChecked="{Binding Path=AutoPowerOff, Mode=Default, Source={StaticResource ConfigDataDataSource1}}"/>
</StackPanel
要让我们的单实例对象能在blend生成的绑定代码下工作,可以通过使用MonoState模式来实现。代码如下:
class
ConfigData : System.ComponentModel.INotifyPropertyChanged
{
static
bool autoPowerOff = false;
public
bool AutoPowerOff
{
//把这个属性设置为static也可以双向绑定,但由于blend不会列出对象的静态属性,需要手工输入属性名
get { return autoPowerOff; }
set { autoPowerOff = value; NotifyPropertyChange("AutoPowerOff"); }
}
#region INotifyPropertyChanged 成员
static
List<ConfigData> instanceList = new
List<ConfigData>();
System.ComponentModel.PropertyChangedEventHandler propertyChanged;
public
event System.ComponentModel.PropertyChangedEventHandler PropertyChanged
{
add { propertyChanged += value; instanceList.Add(this); }
remove { propertyChanged -= value; instanceList.Remove(this); }
}
static
void NotifyPropertyChange(string proper)
{
foreach (var obj in instanceList)
{
if (obj.propertyChanged == null)
continue;
obj.propertyChanged(obj, new System.ComponentModel.PropertyChangedEventArgs(proper));
}
}
#endregion
}
MonoState模式比较巧妙的将blend创建的多个对象的数据共享了起来,对于blend来说,创建了多个对象,而用起来却和一个实例对象一样。更具有通用性。
在WPF的数据绑定中使用MonoState模式关键是PropertyChangedEventHandler事件的通知,由于WPF是基于对象通知的,而所有的对象是共享的数据,因此需要把该事件通知到所有实例。这里我通过采用保存实例列表来实现所有通知的,比较简单,没有考虑效率问题(这种对象一共也创建不了几个),但应该有更高效的方式来实现同样的功能。
本文的代码中还存在一个问题,那就是在代码中对AutoPowerOff属性的访问问题,在不创建对象的前提下,一种方式是把它设置为static类型,这样就可以在代码中通过ConfigData.AutoPowerOff直接的访问,但这样blend不能直接列出该属性,需要手工输入一下。另外一种方式是综合SINGLETON模式创建一个静态的实例,在代码中通过ConfigData. Instance.AutoPowerOff访问。