依赖属性不起作用(产生复合行为)
Dependency Properties not working (making composite behavior)
我正在尝试制作一个由任意简单行为组成的复合行为。我发现行为是制作自定义控件的非常灵活的方式。
目前我为滑块实现了 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 Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=SeekTo; DataItem=null; target element is 'DragCompletedCommandBehavior' (HashCode=52056421); target property is 'Command' (type '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
永远不会触发。 SliderCompositeBehavior
中的主要 OnSourceValueChanged
仍然会触发。但是新的元数据什么也没做。
我该如何解决这些问题?我不明白为什么包含行为的依赖属性不起作用。有人可以解释为什么吗?非常感谢。
我发现了。 after reading this post 我知道元素(在我的例子中是嵌套行为)不是视觉树或逻辑树的一部分。因此无法访问数据上下文。因此绑定不起作用。
但我没有使用 ProxyBinding
which was used here,而是想出了更好的解决方案。
特殊集合 BehaviorCollection
在附加行为时发挥了一些作用。但我使用的是 ObservableCollection
因为行为没有正确附加。
不幸的是 BehaviorCollection
的构造函数是内部的。但是当你有反思的能力时,谁在乎呢? ;)
改用 BehaviorCollection
基本上解决了绑定问题。
如何覆盖元数据问题仍未解决。但我想我会尝试其他方法(比如使用另一个依赖项 属性),而不是覆盖依赖项 属性.
的元数据
这里是对 CompositeBehavior<T>
class 的更正。
[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()
{
}
}
我正在尝试制作一个由任意简单行为组成的复合行为。我发现行为是制作自定义控件的非常灵活的方式。
目前我为滑块实现了 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 Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=SeekTo; DataItem=null; target element is 'DragCompletedCommandBehavior' (HashCode=52056421); target property is 'Command' (type '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
永远不会触发。 SliderCompositeBehavior
中的主要 OnSourceValueChanged
仍然会触发。但是新的元数据什么也没做。
我该如何解决这些问题?我不明白为什么包含行为的依赖属性不起作用。有人可以解释为什么吗?非常感谢。
我发现了。 after reading this post 我知道元素(在我的例子中是嵌套行为)不是视觉树或逻辑树的一部分。因此无法访问数据上下文。因此绑定不起作用。
但我没有使用 ProxyBinding
which was used here,而是想出了更好的解决方案。
特殊集合 BehaviorCollection
在附加行为时发挥了一些作用。但我使用的是 ObservableCollection
因为行为没有正确附加。
不幸的是 BehaviorCollection
的构造函数是内部的。但是当你有反思的能力时,谁在乎呢? ;)
改用 BehaviorCollection
基本上解决了绑定问题。
如何覆盖元数据问题仍未解决。但我想我会尝试其他方法(比如使用另一个依赖项 属性),而不是覆盖依赖项 属性.
的元数据这里是对 CompositeBehavior<T>
class 的更正。
[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()
{
}
}