【WP8】MultiBinding

WP中系统没有像WPF中直接支持MultiBinding,可以通过以下代码实现

 

 

五个类

 

    public class BindingCollection : Collection<BindingBase>
    {
        // Fields
        private readonly BindingCollectionChangedCallback _collectionChangedCallback;

        // Methods
        //internal BindingCollection(BindingCollectionChangedCallback callback)
        //{
        //    _collectionChangedCallback = callback;
        //}

        protected override void ClearItems()
        {
            base.ClearItems();
            OnBindingCollectionChanged();
        }

        protected override void InsertItem(int index, BindingBase item)
        {
            if (item == null)
            {
                throw new ArgumentNullException("item");
            }
            ValidateItem(item);
            base.InsertItem(index, item);
            OnBindingCollectionChanged();
        }

        private void OnBindingCollectionChanged()
        {
            if (_collectionChangedCallback != null)
            {
                _collectionChangedCallback();
            }
        }

        protected override void RemoveItem(int index)
        {
            base.RemoveItem(index);
            OnBindingCollectionChanged();
        }

        protected override void SetItem(int index, BindingBase item)
        {
            if (item == null)
            {
                throw new ArgumentNullException("item");
            }
            ValidateItem(item);
            base.SetItem(index, item);
            OnBindingCollectionChanged();
        }

        private static void ValidateItem(BindingBase binding)
        {
            if (!(binding is Binding))
            {
                throw new NotSupportedException("BindingCollectionContainsNonBinding");
            }
        }
    }
BindingCollection

 

    /// <summary>
    /// A simple element with a single Value property, used as a 'slave'
    /// for a Binding.
    /// </summary>
    public class BindingSlave : FrameworkElement, INotifyPropertyChanged
    {
        #region Value

        public static readonly DependencyProperty ValueProperty =
            DependencyProperty.Register("Value", typeof(object), typeof(BindingSlave),
                new PropertyMetadata(null, OnValueChanged));

        public object Value
        {
            get { return GetValue(ValueProperty); }
            set { SetValue(ValueProperty, value); }
        }

        private static void OnValueChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
        {
            BindingSlave slave = depObj as BindingSlave;
            Debug.Assert(slave != null);
            slave.OnPropertyChanged("Value");
        }

        #endregion

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string name)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(name));
            }
        }

        #endregion

    }
BindingSlave
    /// <summary>
    /// Provides a mechanism for attaching a MultiBinding to an element
    /// </summary>
    public class BindingUtil
    {
        #region DataContextPiggyBack attached property

        /// <summary>
        /// DataContextPiggyBack Attached Dependency Property, used as a mechanism for exposing
        /// DataContext changed events
        /// </summary>
        public static readonly DependencyProperty DataContextPiggyBackProperty =
            DependencyProperty.RegisterAttached("DataContextPiggyBack", typeof(object), typeof(BindingUtil),
                new PropertyMetadata(null, new PropertyChangedCallback(OnDataContextPiggyBackChanged)));

        public static object GetDataContextPiggyBack(DependencyObject d)
        {
            return (object)d.GetValue(DataContextPiggyBackProperty);
        }

        public static void SetDataContextPiggyBack(DependencyObject d, object value)
        {
            d.SetValue(DataContextPiggyBackProperty, value);
        }

        /// <summary>
        /// Handles changes to the DataContextPiggyBack property.
        /// </summary>
        private static void OnDataContextPiggyBackChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            FrameworkElement targetElement = d as FrameworkElement;

            // whenever the targeElement DataContext is changed, copy the updated property
            // value to our MultiBinding.
            MultiBindings relay = GetMultiBindings(targetElement);
            relay.SetDataContext(targetElement.DataContext);
        }

        #endregion

        #region MultiBindings attached property

        public static MultiBindings GetMultiBindings(DependencyObject obj)
        {
            return (MultiBindings)obj.GetValue(MultiBindingsProperty);
        }

        public static void SetMultiBindings(DependencyObject obj, MultiBindings value)
        {
            obj.SetValue(MultiBindingsProperty, value);
        }

        public static readonly DependencyProperty MultiBindingsProperty =
            DependencyProperty.RegisterAttached("MultiBindings",
                typeof(MultiBindings), typeof(BindingUtil), new PropertyMetadata(null, OnMultiBindingsChanged));



        /// <summary>
        /// Invoked when the MultiBinding property is set on a framework element
        /// </summary>
        private static void OnMultiBindingsChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
        {
            FrameworkElement targetElement = depObj as FrameworkElement;

            // bind the target elements DataContext, to our DataContextPiggyBack property
            // this allows us to get property changed events when the targetElement
            // DataContext changes
            targetElement.SetBinding(DataContextPiggyBackProperty, new Binding());

            MultiBindings bindings = GetMultiBindings(targetElement);

            bindings.Initialize(targetElement);
        }

        #endregion

    }
BindingUtil
    /// <summary>
    /// see: http://msdn.microsoft.com/en-us/library/system.windows.data.imultivalueconverter.aspx
    /// </summary>
    public interface IMultiValueConverter
    {
        object Convert(object[] values, Type targetType, object parameter, CultureInfo culture);

        object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture);
    }
IMultiValueConverter
    /// <summary>
    /// Allows multiple bindings to a single property.
    /// </summary>
    [ContentProperty("Bindings")]
    public class MultiBinding : Panel, INotifyPropertyChanged
    {
        #region ConvertedValue dependency property

        public static readonly DependencyProperty ConvertedValueProperty =
            DependencyProperty.Register("ConvertedValue", typeof(object), typeof(MultiBinding),
                new PropertyMetadata(null, OnConvertedValue));

        /// <summary>
        /// This dependency property is set to the resulting output of the
        /// associated Converter.
        /// </summary>
        public object ConvertedValue
        {
            get { return GetValue(ConvertedValueProperty); }
            set { SetValue(ConvertedValueProperty, value); }
        }

        private static void OnConvertedValue(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
        {
            MultiBinding relay = depObj as MultiBinding;
            Debug.Assert(relay != null);
            relay.OnPropertyChanged("ConvertedValue");
        }

        #endregion

        #region CLR properties

        /// <summary>
        /// The target property on the element which this MultiBinding is assocaited with.
        /// </summary>
        public string TargetProperty { get; set; }

        /// <summary>
        /// The Converter which is invoked to compute the result of the multiple bindings
        /// </summary>
        public IMultiValueConverter Converter { get; set; }

        /// <summary>
        /// The configuration parameter supplied to the converter
        /// </summary>
        public object ConverterParameter { get; set; }

        /// <summary>
        /// The bindings, the result of which are supplied to the converter.
        /// </summary>
        public BindingCollection Bindings { get; set; }

        #endregion

        public MultiBinding()
        {
            Bindings = new BindingCollection();
        }

        /// <summary>
        /// Invoked when any of the BindingSlave's Value property changes.
        /// </summary>
        private void SlavePropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            UpdateConvertedValue();
        }

        /// <summary>
        /// Uses the Converter to update the ConvertedValue in order to reflect
        /// the current state of the bindings.
        /// </summary>
        private void UpdateConvertedValue()
        {
            List<object> values = new List<object>();
            foreach (BindingSlave slave in Children)
            {
                values.Add(slave.Value);
            }
            ConvertedValue = Converter.Convert(values.ToArray(), typeof(object), ConverterParameter, CultureInfo.CurrentCulture);
        }

        /// <summary>
        /// Creates a BindingSlave for each Binding and binds the Value
        /// accordingly.
        /// </summary>
        internal void Initialise()
        {
            Children.Clear();
            foreach (Binding binding in Bindings)
            {
                BindingSlave slave = new BindingSlave();
                slave.SetBinding(BindingSlave.ValueProperty, binding);
                slave.PropertyChanged += SlavePropertyChanged;
                Children.Add(slave);
            }
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string name)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(name));
            }
        }

        #endregion
    }
MultiBinding
    [ContentProperty("Bindings")]
    public class MultiBindings : FrameworkElement
    {
        private FrameworkElement _targetElement;

        public ObservableCollection<MultiBinding> Bindings { get; set; }

        public MultiBindings()
        {
            Bindings = new ObservableCollection<MultiBinding>();
        }
#if !SILVERLIGHT
        void Loaded(object sender, RoutedEventArgs e)
        {
            _targetElement.Loaded -= Loaded;
            foreach (MultiBinding binding in Bindings)
            {
                FieldInfo field = _targetElement.GetType().GetField(binding.TargetProperty + "Property", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
                if (field == null) continue;

                System.Windows.Data.MultiBinding newBinding = new System.Windows.Data.MultiBinding
                                                                  {
                                                                      Converter = binding.Converter,
                                                                      ConverterParameter = binding.ConverterParameter
                                                                  };
                foreach (BindingBase bindingBase in binding.Bindings)
                {
                    newBinding.Bindings.Add(bindingBase);
                }
                
                DependencyProperty dp = (DependencyProperty)field.GetValue(_targetElement);

                BindingOperations.SetBinding(_targetElement, dp, newBinding);
            }

        }
#endif

        public void SetDataContext(object dataContext)
        {
            foreach (MultiBinding relay in Bindings)
            {
                relay.DataContext = dataContext;
            }
        }

        public void Initialize(FrameworkElement targetElement)
        {
            _targetElement = targetElement;
#if !SILVERLIGHT
            _targetElement.Loaded += Loaded;
#else
            const BindingFlags DpFlags = BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy;

            foreach (MultiBinding relay in Bindings)
            {
                relay.Initialise();

                // find the target dependency property
                Type targetType = null;
                string targetProperty = null;

                // assume it is an attached property if the dot syntax is used.
                if (relay.TargetProperty.Contains("."))
                {
                    // split to find the type and property name
                    string[] parts = relay.TargetProperty.Split('.');
                    targetType = Type.GetType("System.Windows.Controls." + parts[0] +
                                              ", System.Windows, Version=2.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e");
                    targetProperty = parts[1];
                }
                else
                {
                    targetType = targetElement.GetType();
                    targetProperty = relay.TargetProperty;
                }

                FieldInfo[] sourceFields = targetType.GetFields(DpFlags);
                FieldInfo targetDependencyPropertyField =
                    sourceFields.First(i => i.Name == targetProperty + "Property");
                DependencyProperty targetDependencyProperty =
                    targetDependencyPropertyField.GetValue(null) as DependencyProperty;

                // bind the ConvertedValue of our MultiBinding instance to the target property
                // of our targetElement
                Binding binding = new Binding("ConvertedValue") { Source = relay };
                targetElement.SetBinding(targetDependencyProperty, binding);
            }
#endif
        }
    }
MultiBindings

 

1、定义两个Converter

    //用于多个bool转化为Visibility
    public class VisibilityConverter : IMultiValueConverter
    {
        #region IMultiValueConverter Members

        public object Convert(object[] values, Type targetType,
          object parameter, System.Globalization.CultureInfo culture)
        {
            if (values.Any(value => !(bool) value))
            {
                return Visibility.Collapsed;
            }
            return Visibility.Visible;
        }

        public object[] ConvertBack(object value, Type[] targetTypes,
          object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }

        #endregion
    }
    
    //多个文本转换
    public class TextConverter : IMultiValueConverter
    {
        #region IMultiValueConverter Members

        public object Convert(object[] values, Type targetType,
          object parameter, System.Globalization.CultureInfo culture)
        {
            return string.Join(", ", values);
        }

        public object[] ConvertBack(object value, Type[] targetTypes,
          object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }

        #endregion
    }
Converter

 

2、在Xaml绑定

<phone:PhoneApplicationPage
    x:Class="Bomo.Test.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:multiBindingExtend="clr-namespace:Bomo.Test.MultiBindingExtend"
    xmlns:test="clr-namespace:Bomo.Test"
    mc:Ignorable="d"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    shell:SystemTray.IsVisible="True"
    DataContext="{Binding RelativeSource={RelativeSource Self}}">

    <phone:PhoneApplicationPage.Resources>
        <test:VisibilityConverter x:Key="VisibilityConverter"></test:VisibilityConverter>
        <test:TextConverter x:Key="TextConverter"></test:TextConverter>
    </phone:PhoneApplicationPage.Resources>
    
    <!--LayoutRoot 是包含所有页面内容的根网格-->
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock Text="多路绑定" Style="{StaticResource PhoneTextNormalStyle}" Margin="12,0"/>
            <TextBlock Text="MultiBinding" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
        </StackPanel>

        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"></RowDefinition>
                <RowDefinition Height="Auto"></RowDefinition>
            </Grid.RowDefinitions>
            
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"></ColumnDefinition>
                <ColumnDefinition Width="*"></ColumnDefinition>
            </Grid.ColumnDefinitions>

            
            <StackPanel Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2">
                <TextBlock >如果两个开关都打开,方块变红色</TextBlock>
                <Grid>
                    <Rectangle Width="300" Height="30" Fill="#eee" Margin="10"></Rectangle>
                    <Rectangle Width="300" Height="30" Fill="Red" Margin="10" >
                        <multiBindingExtend:BindingUtil.MultiBindings>
                            <multiBindingExtend:MultiBindings>
                                <multiBindingExtend:MultiBinding TargetProperty="Visibility" Converter="{StaticResource VisibilityConverter}">
                                    <multiBindingExtend:MultiBinding.Bindings>
                                        <multiBindingExtend:BindingCollection>
                                            <Binding Path="Toggle1IsChecked"/>
                                            <Binding Path="Toggle2IsChecked"/>
                                        </multiBindingExtend:BindingCollection>
                                    </multiBindingExtend:MultiBinding.Bindings>
                                </multiBindingExtend:MultiBinding>
                            </multiBindingExtend:MultiBindings>
                        </multiBindingExtend:BindingUtil.MultiBindings>
                    </Rectangle>

                </Grid>
            </StackPanel>
            <ToggleButton Grid.Row="2" Grid.Column="0" IsChecked="{Binding Toggle1IsChecked, Mode=TwoWay}">开关1</ToggleButton>
            <ToggleButton Grid.Row="2" Grid.Column="1" IsChecked="{Binding Toggle2IsChecked, Mode=TwoWay}">开关2</ToggleButton>


            
        </Grid>
        
        <Grid Grid.Row="2">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"></RowDefinition>
                <RowDefinition Height="Auto"></RowDefinition>
                <RowDefinition Height="Auto"></RowDefinition>
            </Grid.RowDefinitions>

            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"></ColumnDefinition>
                <ColumnDefinition Width="*"></ColumnDefinition>
            </Grid.ColumnDefinitions>

            
            <TextBlock Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2">下面文本绑定了两个文本框的内容</TextBlock>
            <TextBlock Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Foreground="{StaticResource PhoneAccentBrush}">
                 <multiBindingExtend:BindingUtil.MultiBindings>
                            <multiBindingExtend:MultiBindings>
                                <multiBindingExtend:MultiBinding TargetProperty="Text" Converter="{StaticResource TextConverter}">
                                    <multiBindingExtend:MultiBinding.Bindings>
                                        <multiBindingExtend:BindingCollection>
                                            <Binding Path="Text1"/>
                                            <Binding Path="Text2"/>
                                        </multiBindingExtend:BindingCollection>
                                    </multiBindingExtend:MultiBinding.Bindings>
                                </multiBindingExtend:MultiBinding>
                            </multiBindingExtend:MultiBindings>
                        </multiBindingExtend:BindingUtil.MultiBindings>
            </TextBlock>

            <TextBox Grid.Row="2" Grid.Column="0" Text="{Binding Text1, Mode=TwoWay}"></TextBox>
            <TextBox Grid.Row="2" Grid.Column="1" Text="{Binding Text2, Mode=TwoWay}"></TextBox>
        </Grid>

        
    </Grid>

</phone:PhoneApplicationPage>
MainPage.xaml

 

3、后台绑定

    public partial class MainPage : INotifyPropertyChanged
    {
        // 构造函数
        public MainPage()
        {
            InitializeComponent();
        }

        #region Toggle1IsChecked

        /// <summary>
        /// The <see cref="Toggle1IsChecked" /> property's name.
        /// </summary>
        public const string Toggle1IsCheckedPropertyName = "Toggle1IsChecked";

        private bool _toggle1IsChecked = false;

        /// <summary>
        /// Sets and gets the Toggle1IsChecked property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public bool Toggle1IsChecked
        {
            get
            {
                return _toggle1IsChecked;
            }

            set
            {
                if (_toggle1IsChecked == value)
                {
                    return;
                }

                _toggle1IsChecked = value;
                RaisePropertyChanged(Toggle1IsCheckedPropertyName);
            }
        }

        #endregion

        #region Toggle2IsChecked

        /// <summary>
        /// The <see cref="Toggle2IsChecked" /> property's name.
        /// </summary>
        public const string Toggle2IsCheckedPropertyName = "Toggle2IsChecked";

        private bool _toggle2IsChecked = false;

        /// <summary>
        /// Sets and gets the Toggle2IsChecked property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public bool Toggle2IsChecked
        {
            get
            {
                return _toggle2IsChecked;
            }

            set
            {
                if (_toggle2IsChecked == value)
                {
                    return;
                }

                _toggle2IsChecked = value;
                RaisePropertyChanged(Toggle2IsCheckedPropertyName);
            }
        }

        #endregion

        #region Text1
        /// <summary>
        /// The <see cref="Text1" /> property's name.
        /// </summary>
        public const string Text1PropertyName = "Text1";

        private string _text1 = string.Empty;

        /// <summary>
        /// Sets and gets the Text1 property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public string Text1
        {
            get
            {
                return _text1;
            }

            set
            {
                if (_text1 == value)
                {
                    return;
                }

                _text1 = value;
                RaisePropertyChanged(Text1PropertyName);
            }
        } 

        #endregion

        #region Text2

        /// <summary>
        /// The <see cref="Text2" /> property's name.
        /// </summary>
        public const string Text2PropertyName = "Text2";

        private string _text2 = string.Empty;

        /// <summary>
        /// Sets and gets the Text2 property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public string Text2
        {
            get
            {
                return _text2;
            }

            set
            {
                if (_text2 == value)
                {
                    return;
                }

                _text2 = value;
                RaisePropertyChanged(Text2PropertyName);
            }
        }

        #endregion

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        protected void RaisePropertyChanged(string name)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(name));
            }
        }

        #endregion
    }
MainPage.xaml.cs

 

这里只支持到属性的绑定,不支持到UIElement的绑定

 

本文引用自:http://blog.csdn.net/huangliangjie/article/details/6734099

posted @ 2014-03-21 18:12  bomo  阅读(1269)  评论(0编辑  收藏  举报