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