WPF里面是有多重绑定的,Silverlight里面没有,而且Silverlight5也不会有(SL5功能列表),网上有手工实现Silverlight多重绑定(MultiBinding)的,基本上源头是这篇文章。里面实现Silverlight多重绑定(MultiBinding)的基本是这么几个类:
IMultiValueConverter:
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Globalization;
12:
namespace SLMultiBinding
14: {
/// <summary>
/// see: http://msdn.microsoft.com/en-us/library/system.windows.data.imultivalueconverter.aspx
/// </summary>
interface IMultiValueConverter
19: {
object parameter, CultureInfo culture);
21:
object parameter, CultureInfo culture);
23:
24: }
25: }
示例TitleConverter:
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
11:
namespace SLMultiBinding
13: {
class TitleConverter : IMultiValueConverter
15: {
#region IMultiValueConverter Members
17:
object[] values, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
20: {
string;
string;
23:
, surname, forename);
25: }
26:
value, Type[] targetTypes,
object parameter, System.Globalization.CultureInfo culture)
29: {
new NotImplementedException();
31: }
32:
#endregion
34: }
35: }
BindingUtil.cs:
using System;
using System.Net;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Data;
using System.ComponentModel;
using System.Reflection;
15:
namespace SLMultiBinding
17: {
/// <summary>
/// Provides a mechanism for attaching a MultiBinding to an element
/// </summary>
class BindingUtil
22: {
#region DataContextPiggyBack attached property
24:
/// <summary>
/// DataContextPiggyBack Attached Dependency Property, used as a mechanism for exposing
/// DataContext changed events
/// </summary>
readonly DependencyProperty DataContextPiggyBackProperty =
typeof(BindingUtil),
new PropertyChangedCallback(OnDataContextPiggyBackChanged)));
32:
object GetDataContextPiggyBack(DependencyObject d)
34: {
object)d.GetValue(DataContextPiggyBackProperty);
36: }
37:
value)
39: {
value);
41: }
42:
/// <summary>
/// Handles changes to the DataContextPiggyBack property.
/// </summary>
void OnDataContextPiggyBackChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
47: {
as FrameworkElement;
49:
// whenever the targeElement DataContext is changed, copy the updated property
// value to our MultiBinding.
52: MultiBinding relay = GetMultiBinding(targetElement);
53: relay.DataContext = targetElement.DataContext;
54: }
55:
#endregion
57:
#region MultiBinding attached property
59:
static MultiBinding GetMultiBinding(DependencyObject obj)
61: {
return (MultiBinding)obj.GetValue(MultiBindingProperty);
63: }
64:
value)
66: {
value);
68: }
69:
readonly DependencyProperty MultiBindingProperty =
,
null, OnMultiBindingChanged));
73:
readonly BindingFlags dpFlags = BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy;
75:
/// <summary>
/// Invoked when the MultiBinding property is set on a framework element
/// </summary>
void OnMultiBindingChanged(DependencyObject depObj,
80: DependencyPropertyChangedEventArgs e)
81: {
as FrameworkElement;
83:
// bind the target elements DataContext, to our DataContextPiggyBack property
// this allows us to get property changed events when the targetElement
// DataContext changes
new Binding());
88:
89: MultiBinding relay = GetMultiBinding(targetElement);
90: relay.Initialise();
91:
// find the target dependency property
93: FieldInfo[] sourceFields = targetElement.GetType().GetFields(dpFlags);
94: FieldInfo targetDependencyPropertyField =
);
96: DependencyProperty targetDependencyProperty =
as DependencyProperty;
98:
// bind the ConvertedValue of our MultiBinding instance to the target property
// of our targetElement
);
102: binding.Source = relay;
103: targetElement.SetBinding(targetDependencyProperty, binding);
104: }
105:
#endregion
107:
108: }
109: }
MultiBinding.cs
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Data;
using System.Collections.ObjectModel;
using System.Windows.Markup;
using System.ComponentModel;
using System.Collections.Generic;
using System.Globalization;
17:
namespace SLMultiBinding
19: {
/// <summary>
/// Allows multiple bindings to a single property.
/// </summary>
)]
class MultiBinding : Panel, INotifyPropertyChanged
25: {
26:
#region ConvertedValue dependency property
28:
readonly DependencyProperty ConvertedValueProperty =
typeof(MultiBinding),
null, OnConvertedValue));
32:
/// <summary>
/// This dependency property is set to the resulting output of the
/// associated Converter.
/// </summary>
object ConvertedValue
38: {
object)GetValue(ConvertedValueProperty); }
value); }
41: }
42:
void OnConvertedValue(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
44: {
as MultiBinding;
);
47: }
48:
#endregion
50:
#region CLR properties
52:
/// <summary>
/// The target property on the element which this MultiBinding is assocaited with.
/// </summary>
string TargetProperty { get; set; }
57:
/// <summary>
/// The Converter which is invoked to compute the result of the multiple bindings
/// </summary>
public IMultiValueConverter Converter { get; set; }
62:
/// <summary>
/// The configuration parameter supplied to the converter
/// </summary>
object ConverterParameter { get; set; }
67:
/// <summary>
/// The bindings, the result of which are supplied to the converter.
/// </summary>
public ObservableCollection<Binding> Bindings { get; set; }
72:
#endregion
74:
public MultiBinding()
76: {
new ObservableCollection<Binding>();
78: }
79:
/// <summary>
/// Invoked when any of the BindingSlave's Value property changes.
/// </summary>
object sender, PropertyChangedEventArgs e)
84: {
85: UpdateConvertedValue();
86: }
87:
/// <summary>
/// Uses the Converter to update the ConvertedValue in order to reflect
/// the current state of the bindings.
/// </summary>
void UpdateConvertedValue()
93: {
object>();
in Children)
96: {
97: values.Add(slave.Value);
98: }
object), ConverterParameter,
100: CultureInfo.CurrentCulture);
101: }
102:
/// <summary>
/// Creates a BindingSlave for each Binding and binds the Value
/// accordingly.
/// </summary>
void Initialise()
108: {
in Bindings)
110: {
new BindingSlave();
112: slave.SetBinding(BindingSlave.ValueProperty, binding);
new PropertyChangedEventHandler(Slave_PropertyChanged);
114: Children.Add(slave);
115: }
116: }
117:
#region INotifyPropertyChanged Members
119:
event PropertyChangedEventHandler PropertyChanged;
121:
string name)
123: {
null)
125: {
new PropertyChangedEventArgs(name));
127: }
128: }
129:
#endregion
131: }
132:
/// <summary>
/// A simple element with a single Value property, used as a 'slave'
/// for a Binding.
/// </summary>
class BindingSlave : FrameworkElement, INotifyPropertyChanged
138: {
#region Value
140:
readonly DependencyProperty ValueProperty =
typeof(BindingSlave),
null, OnValueChanged));
144:
object Value
146: {
object)GetValue(ValueProperty); }
value); }
149: }
150:
void OnValueChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
152: {
as BindingSlave;
);
155: }
156:
#endregion
158:
#region INotifyPropertyChanged Members
160:
event PropertyChangedEventHandler PropertyChanged;
162:
string name)
164: {
null)
166: {
new PropertyChangedEventArgs(name));
168: }
169: }
170:
#endregion
172:
173: }
174: }
代码自己看,不解释。
最后使用起来就是这样,可以绑定N个域,逻辑在Converter里面实现:
>
>
>
/>
/>
>
>
>
是否有必要用多重绑定?
一般你要用到多重绑定的情况是:这个域需要根据多个域的值来计算(包含一定的业务逻辑),也就是多重绑定吗?就需要把业务逻辑放到ValueConverter里面吗?不一定。ValueConverter也不太合适。实现方法很多。如果我们用MVVM模式来开发Silverlight的话,那M-V-VM里面就有个M:Model,可以把这个需要根据多个域的值来计算(包含一定的业务逻辑)的域放到呈现模型里面,这样就能很方便的绑定到这个新的Field了。如果你的MVVM模式没有M,只有VM,这就是设计的问题了。一般来说,有很多业务逻辑需要放到这个M里面,包括本文说的复合域的情形,还有数据验证(Validation)的情形,都可能需要用到M。这个M设计的好,绑定才实现的完美。
反过来,本文所实现的方法是用多重绑定加ValueConverter,而把业务逻辑放到ValueConverter里面不太合适。Converter应该是薄薄的,也就是数据转换为适合显示的格式,比如bool转换为Visibility…..
总结
遇到问题,多思考,不要打破系统架构和层次设计,不要胡乱添加业务逻辑,这很容易导致系统的“破窗效应”。动手写代码之前写思考,思考的代价远比写代码、测试、重构的代价小!
WPF里面是有多重绑定的,Silverlight里面没有,而且Silverlight5也不会有(SL5功能列表),网上有手工实现Silverlight多重绑定(MultiBinding)的,基本上源头是这篇文章。里面实现Silverlight多重绑定(MultiBinding)的基本是这么几个类:
IMultiValueConverter:
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Globalization;
12:
namespace SLMultiBinding
14: {
/// <summary>
/// see: http://msdn.microsoft.com/en-us/library/system.windows.data.imultivalueconverter.aspx
/// </summary>
interface IMultiValueConverter
19: {
object parameter, CultureInfo culture);
21:
object parameter, CultureInfo culture);
23:
24: }
25: }
示例TitleConverter:
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
11:
namespace SLMultiBinding
13: {
class TitleConverter : IMultiValueConverter
15: {
#region IMultiValueConverter Members
17:
object[] values, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
20: {
string;
string;
23:
, surname, forename);
25: }
26:
value, Type[] targetTypes,
object parameter, System.Globalization.CultureInfo culture)
29: {
new NotImplementedException();
31: }
32:
#endregion
34: }
35: }
BindingUtil.cs:
using System;
using System.Net;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Data;
using System.ComponentModel;
using System.Reflection;
15:
namespace SLMultiBinding
17: {
/// <summary>
/// Provides a mechanism for attaching a MultiBinding to an element
/// </summary>
class BindingUtil
22: {
#region DataContextPiggyBack attached property
24:
/// <summary>
/// DataContextPiggyBack Attached Dependency Property, used as a mechanism for exposing
/// DataContext changed events
/// </summary>
readonly DependencyProperty DataContextPiggyBackProperty =
typeof(BindingUtil),
new PropertyChangedCallback(OnDataContextPiggyBackChanged)));
32:
object GetDataContextPiggyBack(DependencyObject d)
34: {
object)d.GetValue(DataContextPiggyBackProperty);
36: }
37:
value)
39: {
value);
41: }
42:
/// <summary>
/// Handles changes to the DataContextPiggyBack property.
/// </summary>
void OnDataContextPiggyBackChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
47: {
as FrameworkElement;
49:
// whenever the targeElement DataContext is changed, copy the updated property
// value to our MultiBinding.
52: MultiBinding relay = GetMultiBinding(targetElement);
53: relay.DataContext = targetElement.DataContext;
54: }
55:
#endregion
57:
#region MultiBinding attached property
59:
static MultiBinding GetMultiBinding(DependencyObject obj)
61: {
return (MultiBinding)obj.GetValue(MultiBindingProperty);
63: }
64:
value)
66: {
value);
68: }
69:
readonly DependencyProperty MultiBindingProperty =
,
null, OnMultiBindingChanged));
73:
readonly BindingFlags dpFlags = BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy;
75:
/// <summary>
/// Invoked when the MultiBinding property is set on a framework element
/// </summary>
void OnMultiBindingChanged(DependencyObject depObj,
80: DependencyPropertyChangedEventArgs e)
81: {
as FrameworkElement;
83:
// bind the target elements DataContext, to our DataContextPiggyBack property
// this allows us to get property changed events when the targetElement
// DataContext changes
new Binding());
88:
89: MultiBinding relay = GetMultiBinding(targetElement);
90: relay.Initialise();
91:
// find the target dependency property
93: FieldInfo[] sourceFields = targetElement.GetType().GetFields(dpFlags);
94: FieldInfo targetDependencyPropertyField =
);
96: DependencyProperty targetDependencyProperty =
as DependencyProperty;
98:
// bind the ConvertedValue of our MultiBinding instance to the target property
// of our targetElement
);
102: binding.Source = relay;
103: targetElement.SetBinding(targetDependencyProperty, binding);
104: }
105:
#endregion
107:
108: }
109: }
MultiBinding.cs
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Data;
using System.Collections.ObjectModel;
using System.Windows.Markup;
using System.ComponentModel;
using System.Collections.Generic;
using System.Globalization;
17:
namespace SLMultiBinding
19: {
/// <summary>
/// Allows multiple bindings to a single property.
/// </summary>
)]
class MultiBinding : Panel, INotifyPropertyChanged
25: {
26:
#region ConvertedValue dependency property
28:
readonly DependencyProperty ConvertedValueProperty =
typeof(MultiBinding),
null, OnConvertedValue));
32:
/// <summary>
/// This dependency property is set to the resulting output of the
/// associated Converter.
/// </summary>
object ConvertedValue
38: {
object)GetValue(ConvertedValueProperty); }
value); }
41: }
42:
void OnConvertedValue(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
44: {
as MultiBinding;
);
47: }
48:
#endregion
50:
#region CLR properties
52:
/// <summary>
/// The target property on the element which this MultiBinding is assocaited with.
/// </summary>
string TargetProperty { get; set; }
57:
/// <summary>
/// The Converter which is invoked to compute the result of the multiple bindings
/// </summary>
public IMultiValueConverter Converter { get; set; }
62:
/// <summary>
/// The configuration parameter supplied to the converter
/// </summary>
object ConverterParameter { get; set; }
67:
/// <summary>
/// The bindings, the result of which are supplied to the converter.
/// </summary>
public ObservableCollection<Binding> Bindings { get; set; }
72:
#endregion
74:
public MultiBinding()
76: {
new ObservableCollection<Binding>();
78: }
79:
/// <summary>
/// Invoked when any of the BindingSlave's Value property changes.
/// </summary>
object sender, PropertyChangedEventArgs e)
84: {
85: UpdateConvertedValue();
86: }
87:
/// <summary>
/// Uses the Converter to update the ConvertedValue in order to reflect
/// the current state of the bindings.
/// </summary>
void UpdateConvertedValue()
93: {
object>();
in Children)
96: {
97: values.Add(slave.Value);
98: }
object), ConverterParameter,
100: CultureInfo.CurrentCulture);
101: }
102:
/// <summary>
/// Creates a BindingSlave for each Binding and binds the Value
/// accordingly.
/// </summary>
void Initialise()
108: {
in Bindings)
110: {
new BindingSlave();
112: slave.SetBinding(BindingSlave.ValueProperty, binding);
new PropertyChangedEventHandler(Slave_PropertyChanged);
114: Children.Add(slave);
115: }
116: }
117:
#region INotifyPropertyChanged Members
119:
event PropertyChangedEventHandler PropertyChanged;
121:
string name)
123: {
null)
125: {
new PropertyChangedEventArgs(name));
127: }
128: }
129:
#endregion
131: }
132:
/// <summary>
/// A simple element with a single Value property, used as a 'slave'
/// for a Binding.
/// </summary>
class BindingSlave : FrameworkElement, INotifyPropertyChanged
138: {
#region Value
140:
readonly DependencyProperty ValueProperty =
typeof(BindingSlave),
null, OnValueChanged));
144:
object Value
146: {
object)GetValue(ValueProperty); }
value); }
149: }
150:
void OnValueChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
152: {
as BindingSlave;
);
155: }
156:
#endregion
158:
#region INotifyPropertyChanged Members
160:
event PropertyChangedEventHandler PropertyChanged;
162:
string name)
164: {
null)
166: {
new PropertyChangedEventArgs(name));
168: }
169: }
170:
#endregion
172:
173: }
174: }
代码自己看,不解释。
最后使用起来就是这样,可以绑定N个域,逻辑在Converter里面实现:
>
>
>
/>
/>
>
>
>
是否有必要用多重绑定?
一般你要用到多重绑定的情况是:这个域需要根据多个域的值来计算(包含一定的业务逻辑),也就是多重绑定吗?就需要把业务逻辑放到ValueConverter里面吗?不一定。ValueConverter也不太合适。实现方法很多。如果我们用MVVM模式来开发Silverlight的话,那M-V-VM里面就有个M:Model,可以把这个需要根据多个域的值来计算(包含一定的业务逻辑)的域放到呈现模型里面,这样就能很方便的绑定到这个新的Field了。如果你的MVVM模式没有M,只有VM,这就是设计的问题了。一般来说,有很多业务逻辑需要放到这个M里面,包括本文说的复合域的情形,还有数据验证(Validation)的情形,都可能需要用到M。这个M设计的好,绑定才实现的完美。
反过来,本文所实现的方法是用多重绑定加ValueConverter,而把业务逻辑放到ValueConverter里面不太合适。Converter应该是薄薄的,也就是数据转换为适合显示的格式,比如bool转换为Visibility…..
总结
遇到问题,多思考,不要打破系统架构和层次设计,不要胡乱添加业务逻辑,这很容易导致系统的“破窗效应”。动手写代码之前写思考,思考的代价远比写代码、测试、重构的代价小!