WP&Win8中的多值綁定

  在項目開發過程中會遇到用多個屬性決定控件狀態的情況,這時候就需要用到多值綁定了。Wp&Win8並不提供這個功能,我模仿Wpf中的功能實現了Wp&Win8下的替代方案。
  首先闡述下實現原理:
  1 通過反射獲取目標控件上指定的屬性;
  2 用一個綁定列表指定數據源上需要綁定的屬性;
  3 用一個轉換器將綁定的數據列表轉換成需要的狀態並賦值給控件上指定的屬性;
 
  然後就是實現代碼了,由於代碼較多,就不一一貼出來了,只貼關鍵代碼,剩下的各位看示例吧。
  下面是通過反射獲取目標屬性:
        void ParseTargetProperty()
        {
            TargetDependencyProperty = null;
            if (string.IsNullOrEmpty(TargetProperty) || AssociatedObject == null)
                return;

            var sourceDependencyPropertyField = AssociatedObject.
                                                GetType().GetField(TargetProperty + "Property");

            if (sourceDependencyPropertyField != null)
            {
                TargetDependencyProperty = sourceDependencyPropertyField.
                                           GetValue(null) as DependencyProperty;//獲取依賴屬性
            }

            TargetPropertyInfo = AssociatedObject.GetType().GetProperty(TargetProperty);//獲取屬性的反射器

            if (TargetPropertyInfo != null)
                TargetPropertyType = TargetPropertyInfo.PropertyType;

        }
  由於控件上的屬性大多是依賴屬性,直接通過依賴屬性賦值會快的多。各別非依賴屬性則只能依靠反射器賦值了。這裏
還獲取了目標屬性的類型,作爲轉換器傳遞的參數使用。
  綁定列表的實現則稍顯麻煩了。下面是綁定器的代碼:
public class BindingSource : DependencyObject
    {
        #region Value
        public static readonly DependencyProperty ValueProperty =
            DependencyProperty.Register("Value", typeof(object), typeof(BindingSource),
                new PropertyMetadata(null, OnValueChanged));

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

        private static void OnValueChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
        {
            BindingSource slave = depObj as BindingSource;
            if (slave != null)
                slave.OnValueChanged();
        }
        #endregion

        #region Binding Property
        public bool BindsDirectlyToSource { get; set; }

        public IValueConverter Converter { get; set; }

        public CultureInfo ConverterCulture { get; set; }

        public object ConverterParameter { get; set; }

        public string ElementName { get; set; }

        public BindingMode Mode { get; set; }

        public bool NotifyOnValidationError { get; set; }

        public string Path { get; set; }

        public RelativeSource RelativeSource { get; set; }

        public object Source { get; set; }

        public UpdateSourceTrigger UpdateSourceTrigger { get; set; }

        public bool ValidatesOnDataErrors { get; set; }

        public bool ValidatesOnExceptions { get; set; }

        public bool ValidatesOnNotifyDataErrors { get; set; }
        #endregion

        Binding _binding;
        public event Action<object, object> ValueChanged;
        protected void OnValueChanged()
        {
            var hander = this.ValueChanged;
            if (hander != null)
            {
                hander(this, Value);
            }
        }

        public void Init()
        {
            _binding = new Binding();
            _binding.BindsDirectlyToSource = this.BindsDirectlyToSource;
            _binding.Converter = this.Converter;
            _binding.ConverterCulture = this.ConverterCulture;
            _binding.ConverterParameter = this.ConverterParameter;
            if (!string.IsNullOrEmpty(this.ElementName))
            {
                _binding.ElementName = this.ElementName;
            }
            _binding.Mode = this.Mode;
            _binding.NotifyOnValidationError = this.NotifyOnValidationError;
            _binding.Path = new PropertyPath(this.Path);
            if (string.IsNullOrEmpty(_binding.ElementName) && this.RelativeSource != null)
            {
                _binding.RelativeSource = this.RelativeSource;
            }
            if (string.IsNullOrEmpty(_binding.ElementName) && this.RelativeSource == null)
            {
                _binding.Source = this.Source;
            }
            _binding.UpdateSourceTrigger = this.UpdateSourceTrigger;
            _binding.ValidatesOnDataErrors = this.ValidatesOnDataErrors;
            _binding.ValidatesOnExceptions = this.ValidatesOnExceptions;
            _binding.ValidatesOnNotifyDataErrors = this.ValidatesOnNotifyDataErrors;

            BindingOperations.SetBinding(this, ValueProperty, _binding);
        }
    }
這裏面直接將Binding的屬性複製了一遍,然後實現一個Binding的實例並綁定到了Value這個中間值上。這麼做的目的是可以當綁定的值出現變化時這邊可以得到通知,然後更新界面。
  綁定集合的代碼:
    public class BindingCollection : DependencyObjectCollection<BindingSource>
    {
        public event Action<object, object> HasValueChanged;

        public BindingCollection()
        {
            this.CollectionChanged += BindingCollection_CollectionChanged;
        }

        void BindingCollection_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            if (e.NewItems != null && e.NewItems.Count > 0)
            {
                foreach (BindingSource binding in e.NewItems)
                {
                    binding.Init();
                    binding.ValueChanged += binding_ValueChanged;
                }
            }

            if (e.OldItems != null && e.OldItems.Count > 0)
            {
                foreach (BindingSource binding in e.OldItems)
                {
                    binding.ValueChanged -= this.binding_ValueChanged;
                }
            }

        }

        void binding_ValueChanged(object sender, object value)
        {
            var hander = this.HasValueChanged;
            if (hander != null)
            {
                hander(sender, value);
            }
        }
    }
  剩下的就是將綁定列表通過轉換器後更新界面了:
 internal void Source_ValueChanged(object sender, object value)
        {
            if (Converter == null || AssociatedObject == null || (TargetDependencyProperty == null && TargetPropertyInfo == null))
                return;

            var targetValue = Converter.Convert(Bindings, TargetPropertyType, ConverterParameter, null);
            

            if (TargetDependencyProperty != null)//若目標是依賴屬性則直接賦值
            {
                AssociatedObject.SetValue(TargetDependencyProperty, targetValue);
            }
            else if (TargetPropertyInfo != null && TargetPropertyInfo.CanWrite)//非依賴屬性通過反射器賦值
            {
                TargetPropertyInfo.SetValue(this.AssociatedObject, targetValue, null);
            }

        }
使用的時候需要自定義一個轉換器:
    public class NameConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo language)
        {
            var values = value as BindingCollection;
            if (values == null || values.Count < 2)
                return "";

            return string.Format("{0}.{1}", values[0].Value, values[1].Value);
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo language)
        {
            return "";
        }
    }
然後xaml中這樣使用就可以了:
            <TextBlock  Margin="10" Height="80" VerticalAlignment="Top">
               <local:MultiBindings.BindinCollection>
                   <local:MultiBinding TargetProperty="Text" Converter="{StaticResource NameConverter}">
                       <local:BindingSource Path="FristName" Source="{StaticResource ViewModel}"/>
                       <local:BindingSource  Path="SecondName" Source="{StaticResource ViewModel}"/>
                   </local:MultiBinding>
               </local:MultiBindings.BindinCollection>
            </TextBlock>

    界面效果

下面的2個按鈕點一下上面對應的值+1


示例在個人資源中。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章