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


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