博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

[Silverlight入门系列]多重绑定MultiBinding有必要吗?

Posted on 2012-07-13 10:03  love楠  阅读(822)  评论(0编辑  收藏  举报

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…..

 

总结

遇到问题,多思考,不要打破系统架构和层次设计,不要胡乱添加业务逻辑,这很容易导致系统的“破窗效应”。动手写代码之前写思考,思考的代价远比写代码、测试、重构的代价小!