依赖属性不起作用(进行复合行为)

时间:2017-01-09 10:02:35

标签: c# wpf dependency-properties

我正在尝试制作一个由任意简单行为组成的复合行为。我发现行为非常灵活地制作自定义控件。

目前我为滑块实现了5个行为。但他们可以相互冲突。

这些行为是为一个控件设计的。我可以设计他们每个人独立工作而不会相互冲突(值得一提的是我做了这个并且它成功了。但我删除了所有这些因为它只是丑陋。)

有很多共享点,我不想为每个行为重写相同的代码。

所以我试图为一个控件制作复合行为。此行为具有一些附加属性,这些属性为其包含的所有行为共享。因此,这些行为不会相互冲突。而且很多代码冗余也消失了。现在包含行为变得更加简单。

以下是XAML示例,供您更好地了解。

<i:Interaction.Behaviors>
    <b:SliderCompositeBehavior SourceValue="{Binding SharedValue}">
        <sb:FreeSlideBehavior/>
        <sb:LockOnDragBehavior/>
        <sb:CancellableDragBehavior/>
        <sb:KeepRatioBehavior/>
        <sb:DragCompletedCommandBehavior Command="{Binding SeekTo}"/>
    </b:SliderCompositeBehavior>
</i:Interaction.Behaviors>

此外,所有这些行为都是为了独立工作而设计的。就像这样工作就好了。

<i:Interaction.Behaviors>
    <sb:FreeSlideBehavior/>
</i:Interaction.Behaviors>

以下是CompositeBehavior<T> : Behavior<T>

[ContentProperty(nameof(BehaviorCollection))]
public abstract class CompositeBehavior<T> : Behavior<T>
    where T : DependencyObject
{
    public static readonly DependencyProperty BehaviorCollectionProperty =
        DependencyProperty.Register(
            $"{nameof(CompositeBehavior<T>)}<{typeof(T).Name}>",
            typeof(ObservableCollection<Behavior<T>>),
            typeof(CompositeBehavior<T>),
            new FrameworkPropertyMetadata(
                null,
                FrameworkPropertyMetadataOptions.NotDataBindable));

    public ObservableCollection<Behavior<T>> BehaviorCollection
    {
        get
        {
            var collection = GetValue(BehaviorCollectionProperty) as ObservableCollection<Behavior<T>>;

            if (collection == null)
            {
                collection = new ObservableCollection<Behavior<T>>();
                collection.CollectionChanged += OnCollectionChanged;
                SetValue(BehaviorCollectionProperty, collection);
            }

            return collection;
        }
    }

    private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
    {
        // some code to throw exception when same behavior is set more than once.
    }

    protected override void OnAttached()
    {
        foreach (var behavior in BehaviorCollection)
        {
            behavior.Attach(AssociatedObject);
        }
    }

    protected override void OnDetaching()
    {
        foreach (var behavior in BehaviorCollection)
        {
            behavior.Detach();
        }
    }
}

这是SliderCompositeBehavior : CompositeBehavior<Slider>(为简单起见,只显示了一个依赖关系)

public sealed class SliderCompositeBehavior : CompositeBehavior<Slider>
{
    private Slider Host => AssociatedObject;

    public static readonly DependencyProperty SourceValueProperty =
        DependencyProperty.Register(
            nameof(SourceValue),
            typeof(double),
            typeof(SliderCompositeBehavior),
            new FrameworkPropertyMetadata(
                0d,
                FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                OnSourceValueChanged));

    // does the binding
    public double SourceValue
    {
        get { return (double)Host.GetValue(SourceValueProperty); }
        set { Host.SetValue(SourceValueProperty, value); }
    }

    // attached property for containing behaviors.
    public static void SetSourceValue(Slider host, double value)
    {
        host.SetValue(SourceValueProperty, value);
    }

    public static double GetSourceValue(Slider host)
    {
        return (double)host.GetValue(SourceValueProperty);
    }

    private static void OnSourceValueChanged(DependencyObject dpo, DependencyPropertyChangedEventArgs args)
    {
        var soruce = (SliderCompositeBehavior)dpo;

        soruce.Host.Value = (double)args.NewValue;
    }
}

现在我可以看到两个问题。

  • 包含行为的内容中的依赖项属性定义根本不起作用。

  • 重写依赖项属性的元数据不适用于包含属性。

内部DragCompletedCommandBehavior : Behavior<Slider>我有

public sealed class DragCompletedCommandBehavior : Behavior<Slider>
{
    public ICommand Command
    {
        get { return (ICommand)GetValue(CommandProperty); }
        set { SetValue(CommandProperty, value); }
    }

    public static readonly DependencyProperty CommandProperty =
        DependencyProperty.Register(
            nameof(Command),
            typeof(ICommand),
            typeof(DragCompletedCommandBehavior));
}

我在输出时遇到此错误。 (这不会抛出异常。它在程序启动后隐藏在输出显示的某处。)

  

System.Windows.Data错误:2:找不到目标元素的管理FrameworkElement或FrameworkContentElement。 BindingExpression:路径= SeekTo;的DataItem = NULL; target元素是'DragCompletedCommandBehavior'(HashCode = 52056421); target属性是'Command'(类型'ICommand')

在另一种行为中我有这个。

public sealed class LockOnDragBehavior : Behavior<Slider>
{
    static LockOnDragBehavior()
    {
        SliderCompositeBehavior.SourceValueProperty.OverrideMetadata(
            typeof(LockOnDragBehavior),
            new FrameworkPropertyMetadata(
                0d,
                FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                OnSourceValueChanged));
    }

    private static void OnSourceValueChanged(DependencyObject dpo, DependencyPropertyChangedEventArgs args)
    {
        // do something
    }
}

OnSourceValueChanged永远不会发射。 OnSourceValueChanged内的主要SliderCompositeBehavior仍在点火。但是新的元数据无所事事。

我该如何解决这些问题?我不明白为什么包含行为的Dependency属性不起作用。有人可以解释一下原因吗?非常感谢你。

1 个答案:

答案 0 :(得分:2)

我发现了。 after reading this post我理解元素(在我的例子中是嵌套行为)不是视觉或逻辑树的一部分。因此无法访问数据上下文。因此绑定不起作用。

但我没有使用ProxyBinding which was used here而是提出了更好的解决方案。

特殊集合BehaviorCollection在附加行为时会产生一些魔力。但是我使用的ObservableCollection行为没有正确附加。

不幸的是BehaviorCollection的构造函数是内部的。但是,当你有反思能力时,谁在乎呢? ;)

使用BehaviorCollection代替解决了绑定问题。

如何覆盖元数据问题仍未解决。但我想我会尝试其他方法(比如使用另一个依赖属性),而不是覆盖依赖属性的元数据。

以下是CompositeBehavior<T>类的修正。

[ContentProperty(nameof(BehaviorCollection))]
public abstract class CompositeBehavior<T> : Behavior<T>
    where T : DependencyObject
{
    #region Behavior Collection

    public static readonly DependencyProperty BehaviorCollectionProperty =
        DependencyProperty.Register(
            $"{nameof(CompositeBehavior<T>)}<{typeof(T).Name}>",
            typeof(BehaviorCollection),
            typeof(CompositeBehavior<T>),
            new FrameworkPropertyMetadata(
                null,
                FrameworkPropertyMetadataOptions.NotDataBindable));

    public BehaviorCollection BehaviorCollection
    {
        get
        {
            var collection = GetValue(BehaviorCollectionProperty) as BehaviorCollection;

            if (collection == null)
            {
                var constructor = typeof(BehaviorCollection)
                    .GetConstructor(
                        BindingFlags.NonPublic | BindingFlags.Instance,
                        null, Type.EmptyTypes, null);

                collection = (BehaviorCollection) constructor.Invoke(null);
                collection.Changed += OnCollectionChanged;
                SetValue(BehaviorCollectionProperty, collection);
            }

            return collection;
        }
    }

    private void OnCollectionChanged(object sender, EventArgs eventArgs)
    {
        var hashset = new HashSet<Type>();
        foreach (var behavior in BehaviorCollection)
        {
            if (behavior is Behavior<T> == false)
            {
                throw new InvalidOperationException($"{behavior.GetType()} does not inherit from {typeof(Behavior<T>)}.");
            }
            if (hashset.Add(behavior.GetType()) == false)
            {
                throw new InvalidOperationException($"{behavior.GetType()} is set more than once.");
            }
        }
    }

    #endregion

    protected sealed override void OnAttached()
    {
        OnSelfAttached();
        foreach (var behavior in BehaviorCollection)
        {
            behavior.Attach(AssociatedObject);
        }
    }

    protected sealed override void OnDetaching()
    {
        OnSelfDetaching();
        foreach (var behavior in BehaviorCollection)
        {
            behavior.Detach();
        }
    }

    protected virtual void OnSelfAttached()
    {
    }

    protected virtual void OnSelfDetaching()
    {
    }
}