Behavior,Trigger,TriggerAction中使用ElementName

Behavior,Trigger,TriggerAction屬於元素的附加屬性,並不在可視化樹中,當我們在綁定的時候需要使用ElementName指定數據源的時候,ElementName是不起作用的。因爲使用ElementName屬性本身的元素需要在可視化樹中。
  在FrameworkElement中有一個FindName函數,通過這個函數我們可以找到當前 XAML 命名空間中指定的對象。而Binding中的ElementName屬性實現也是間接的調用到這個函數。  Behavior,Trigger,TriggerAction中都保存有被附加的對象,通過這個對象和FindName再加上反射,我們就可以實現ElementName。
   Behavior,Trigger,TriggerAction中並沒有ElementName函數,但可以通過調用附加對象的ElementName來實現這個功能。我選擇使用接口來擴展這樣的功能。
  首先定義通用接口:
 interface IElementBinding
    {
        /// <summary>
        /// 附加對象改變時觸發的事件
        /// </summary>
        event EventHandler AssociatedObjectChanged;//附加對象改變時觸發的事件

        /// <summary>
        /// 附加對象是否在可視化樹中
        /// </summary>
        /// <returns></returns>
        bool IsLoaded();

        /// <summary>
        /// 查找元素
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        object FindName(string name);

        /// <summary>
        /// 獲取繼承接口的對象
        /// </summary>
        /// <returns></returns>
        DependencyObject GetDependencyObject();
    }
  我說明一下IsLoaded函數,其他的函數看註釋。FindName是有使用條件的,對象本身必須處於可是化樹中才可通過這個函數找到指定的對象,IsLoaded就是用來判斷這個的,具體如何判斷看我後面的代碼。
  定義完接口然後需要一個結構來指定邦定中需要用到的屬性:
 public class BindingProperties
    {
        /// <summary>
        /// 被附加源中需要綁定的屬性名稱
        /// </summary>
        public string SourceProperty { get; set; } 
        /// <summary>
        /// 需要綁定控件的名稱
        /// </summary>
        public string ElementName { get; set; }
        /// <summary>
        /// 需要綁定控件中的目標屬性
        /// </summary>
        public string TargetProperty { get; set; }
        /// <summary>
        /// 轉換器
        /// </summary>
        public IValueConverter Converter { get; set; }
    }
  下面是施行邦定的關鍵代碼:
public class ElementBinding
    {
        public static BindingProperties GetBinding(DependencyObject obj)
        {
            if (obj == null)
                return null;
            return obj.GetValue(BindingProperty) as BindingProperties;
        }

        public static void SetBinding(DependencyObject obj, BindingProperties value)
        {
            obj.SetValue(BindingProperty, value);
        }

        public static readonly DependencyProperty BindingProperty =
            DependencyProperty.RegisterAttached("Binding", typeof(BindingProperties), typeof(ElementBinding),
            new PropertyMetadata(null, new PropertyChangedCallback(ElementBinding.OnBinding)));



        private static void OnBinding(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
        {
            var Interactivity = depObj as IElementBinding;
            if (Interactivity == null)
                return;

            if (Interactivity.IsLoaded())
            { CreateRelayBinding(Interactivity, GetBinding(Interactivity.GetDependencyObject())); }
            else
            {
                Interactivity.AssociatedObjectChanged += Interactivity_AssociatedObjectChanged;
            }
        }

        static void Interactivity_AssociatedObjectChanged(object sender, EventArgs e)
        {
            IElementBinding sourceElement = sender as IElementBinding;

            BindingProperties bindingProperties = GetBinding(sourceElement.GetDependencyObject());

            CreateRelayBinding(sourceElement, bindingProperties);
        }

        private static void CreateRelayBinding(IElementBinding sourceElement, BindingProperties bindingProperties)
        {
            if (sourceElement == null || bindingProperties == null)
                return;

            //獲取被綁定源
           var source=sourceElement.GetDependencyObject();
           if (source == null)
               return;
           //查找目標對象
            DependencyObject targetElement = sourceElement.FindName(bindingProperties.ElementName) as DependencyObject;

            if (targetElement == null)
                return;

            //通過反射找到被綁定源中需要被綁定的附加屬性
            var sourceDependencyPropertyField = source.GetType().GetField(bindingProperties.SourceProperty + "Property", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);

            if (sourceDependencyPropertyField == null)
                return;

            DependencyProperty sourceDependencyProperty = sourceDependencyPropertyField.GetValue(null) as DependencyProperty;

            Binding binding = new Binding();
            binding.Source = targetElement;
            binding.Path = new PropertyPath(bindingProperties.TargetProperty);
            binding.Converter = bindingProperties.Converter;
            //綁定
            BindingOperations.SetBinding(source, sourceDependencyProperty, binding);
        }

        public static bool IsElementLoaded(FrameworkElement element)
        {
            if (element == null)
                return false;
            UIElement rootVisual = Application.Current.RootVisual;
            DependencyObject parent = element.Parent;
            if (parent == null)
            {
                parent = System.Windows.Media.VisualTreeHelper.GetParent(element);
            }
            return parent != null || (rootVisual != null && element == rootVisual);
        }

    }
  邦定的過程在CreateRelayBinding函數中。
  邦定流程好了以後可以開始對Behavior,Trigger,TriggerAction進行改造了。邦定中TriggerAction用的比較多,我就
以這個爲例子,Behavior和Trigger自行擴展。
  首先自己定義一個TriggerAction基類,所有自定義的TriggerAction都從這個基類繼承,然後再裏面實現IElementBinding的所有接口。注意:不實現IElementBinding接口是無法使用ElementBinding類的。
 public abstract class CustomTriggerActionBase : TriggerAction<FrameworkElement>, IElementBinding
    {
        public event EventHandler AssociatedObjectChanged;

        protected override void OnAttached()
        {
            base.OnAttached();

            if (ElementBinding.IsElementLoaded(this.AssociatedObject) && AssociatedObjectChanged != null)
                AssociatedObjectChanged(this, new EventArgs());
            else if (this.AssociatedObject != null)
                this.AssociatedObject.Loaded += AssociatedObject_Loaded;

        }

        protected override void OnDetaching()
        {
            base.OnDetaching();
            this.AssociatedObject.Loaded -= AssociatedObject_Loaded;
        }

        void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
        {
            if (AssociatedObjectChanged != null)
                AssociatedObjectChanged(this, new EventArgs());
        }

        public bool IsLoaded()
        {
            return ElementBinding.IsElementLoaded(this.AssociatedObject);
        }

        public object FindName(string name)
        {
            return this.AssociatedObject == null ? null : this.AssociatedObject.FindName(name);
        }

        public DependencyObject GetDependencyObject()
        {
            return this;
        }
    }
  使用起來很簡單,比如我們有一個NavigateAction,用來導航,裏面有一個Parameter,用來向下一個頁面傳遞參數,
使用時這樣寫:
            <Button Content="To Page1" Width="200" Height="80" >
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="Click">
                        <local:NavigateAction NavigateName="/Page1.xaml">
                            <local:ElementBinding.Binding>
                                <local:BindingProperties SourceProperty="Parameter" ElementName="ParameterText" TargetProperty="Text"/>
                            </local:ElementBinding.Binding>
                        </local:NavigateAction>
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </Button>
  導航前界面:
  
  導航後的界面:

  示例代碼:

ElementBindingText.zip


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