我想创建一个Type Collection的AttachedProperty,它包含对其他现有元素的引用,如下所示:
<Window x:Class="myNamespace.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:myNamespace"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<ContentPresenter>
<ContentPresenter.Content>
<Button>
<local:DependencyObjectCollectionHost.Objects>
<local:DependencyObjectCollection>
<local:DependencyObjectContainer Object="{Binding ElementName=myButton}"/>
</local:DependencyObjectCollection>
</local:DependencyObjectCollectionHost.Objects>
</Button>
</ContentPresenter.Content>
</ContentPresenter>
<Button x:Name="myButton" Grid.Row="1"/>
</Grid>
</Window>
因此,我创建了一个名为ObjectContainer的泛型类,以获得使用Binding实现此目的的可能性:
public class ObjectContainer<T> : DependencyObject
where T : DependencyObject
{
static ObjectContainer()
{
ObjectProperty = DependencyProperty.Register
(
"Object",
typeof(T),
typeof(ObjectContainer<T>),
new PropertyMetadata(null)
);
}
public static DependencyProperty ObjectProperty;
[Bindable(true)]
public T Object
{
get { return (T)this.GetValue(ObjectProperty); }
set { this.SetValue(ObjectProperty, value); }
}
}
public class DependencyObjectContainer : ObjectContainer<DependencyObject> { }
public class DependencyObjectCollection : Collection<DependencyObjectContainer> { }
public static class DependencyObjectCollectionHost
{
static DependencyObjectCollectionHost()
{
ObjectsProperty = DependencyProperty.RegisterAttached
(
"Objects",
typeof(DependencyObjectCollection),
typeof(DependencyObjectCollectionHost),
new PropertyMetadata(null, OnObjectsChanged)
);
}
public static DependencyObjectCollection GetObjects(DependencyObject dependencyObject)
{
return (DependencyObjectCollection)dependencyObject.GetValue(ObjectsProperty);
}
public static void SetObjects(DependencyObject dependencyObject, DependencyObjectCollection value)
{
dependencyObject.SetValue(ObjectsProperty, value);
}
public static readonly DependencyProperty ObjectsProperty;
private static void OnObjectsChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
var objects = (DependencyObjectCollection)e.NewValue;
if (objects.Count != objects.Count(d => d.Object != null))
throw new ArgumentException();
}
}
我无法在Collection中建立任何绑定。我想我已经知道了,问题是什么。 Collection中的元素没有与Binding相关的DataContext。但是,我不知道我能对付它做什么。
修改 修复了Button缺少的Name属性。 注意:我知道绑定不起作用,因为每个未明确声明Source的Binding将使用它的DataContext作为它的Source。就像我已经提到的那样:我的Collection中没有这样的DataContext,并且没有VisualTree,其中不存在的FrameworkElement可能是其中的一部分;)
过去可能有人遇到类似的问题,并找到了合适的解决方案。
EDIT2与H.B.s帖子有关: 通过对集合中的项目进行以下更改,它现在似乎可以正常工作:
<local:DependencyObjectContainer Object="{x:Reference myButton}"/>
有趣的行为: 当调用OnObjectsChanged事件处理程序时,该集合包含零元素......我认为这是因为元素的创建(在InitializeComponent方法中完成)尚未完成。
顺便说一下。正如你H.B.表示使用x:Reference时不需要使用Container类。使用x时是否有任何缺点:我在第一时间没有看到的参考?
EDIT3 解决方案: 我添加了一个自定义附加事件,以便在集合发生变化时收到通知。
public class DependencyObjectCollection : ObservableCollection<DependencyObject> { }
public static class ObjectHost
{
static KeyboardObjectHost()
{
ObjectsProperty = DependencyProperty.RegisterAttached
(
"Objects",
typeof(DependencyObjectCollection),
typeof(KeyboardObjectHost),
new PropertyMetadata(null, OnObjectsPropertyChanged)
);
ObjectsChangedEvent = EventManager.RegisterRoutedEvent
(
"ObjectsChanged",
RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(KeyboardObjectHost)
);
}
public static DependencyObjectCollection GetObjects(DependencyObject dependencyObject)
{
return (DependencyObjectCollection)dependencyObject.GetValue(ObjectsProperty);
}
public static void SetObjects(DependencyObject dependencyObject, DependencyObjectCollection value)
{
dependencyObject.SetValue(ObjectsProperty, value);
}
public static void AddObjectsChangedHandler(DependencyObject dependencyObject, RoutedEventHandler h)
{
var uiElement = dependencyObject as UIElement;
if (uiElement != null)
uiElement.AddHandler(ObjectsChangedEvent, h);
else
throw new ArgumentException(string.Format("Cannot add handler to object of type: {0}", dependencyObject.GetType()), "dependencyObject");
}
public static void RemoveObjectsChangedHandler(DependencyObject dependencyObject, RoutedEventHandler h)
{
var uiElement = dependencyObject as UIElement;
if (uiElement != null)
uiElement.RemoveHandler(ObjectsChangedEvent, h);
else
throw new ArgumentException(string.Format("Cannot remove handler from object of type: {0}", dependencyObject.GetType()), "dependencyObject");
}
public static bool CanControlledByKeyboard(DependencyObject dependencyObject)
{
var objects = GetObjects(dependencyObject);
return objects != null && objects.Count != 0;
}
public static readonly DependencyProperty ObjectsProperty;
public static readonly RoutedEvent ObjectsChangedEvent;
private static void OnObjectsPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
Observable.FromEvent<NotifyCollectionChangedEventArgs>(e.NewValue, "CollectionChanged")
.DistinctUntilChanged()
.Subscribe(args =>
{
var objects = (DependencyObjectCollection)args.Sender;
if (objects.Count == objects.Count(d => d != null)
OnObjectsChanged(dependencyObject);
else
throw new ArgumentException();
});
}
private static void OnObjectsChanged(DependencyObject dependencyObject)
{
RaiseObjectsChanged(dependencyObject);
}
private static void RaiseObjectsChanged(DependencyObject dependencyObject)
{
var uiElement = dependencyObject as UIElement;
if (uiElement != null)
uiElement.RaiseEvent(new RoutedEventArgs(ObjectsChangedEvent));
}
}
答案 0 :(得分:2)
您可以在.NET 4中使用x:Reference
,它比ElementName
更“智能”,与绑定不同,它不需要目标成为依赖属性。
您甚至可以删除容器类,但您的属性需要具有正确的类型,以便ArrayList
可以直接转换为属性值,而不是将整个列表添加为项目。直接使用x:References
将不工作。
xmlns:col="clr-namespace:System.Collections;assembly=mscorlib"
<local:AttachedProperties.Objects>
<col:ArrayList>
<x:Reference>button1</x:Reference>
<x:Reference>button2</x:Reference>
</col:ArrayList>
</local:AttachedProperties.Objects>
public static readonly DependencyProperty ObjectsProperty =
DependencyProperty.RegisterAttached
(
"Objects",
typeof(IList),
typeof(FrameworkElement),
new UIPropertyMetadata(null)
);
public static IList GetObjects(DependencyObject obj)
{
return (IList)obj.GetValue(ObjectsProperty);
}
public static void SetObjects(DependencyObject obj, IList value)
{
obj.SetValue(ObjectsProperty, value);
}
进一步将x:References
写为
<x:Reference Name="button1"/>
<x:Reference Name="button2"/>
会导致一些更好的错误。
答案 1 :(得分:2)
我认为答案可以在以下两个链接中找到:
Binding.ElementName Property
XAML Namescopes and Name-related APIs
尤其是第二种状态:
FrameworkElement 具有FindName,RegisterName和UnregisterName方法。如果您调用这些方法的对象拥有XAML名称范围,则这些方法将调用相关XAML名称范围的方法。否则,检查父元素以查看它是否拥有XAML名称范围,并且此过程以递归方式继续,直到找到XAML名称范围(由于XAML处理器行为,保证在根处有XAML名称范围)。 FrameworkContentElement具有类似的行为,但没有FrameworkContentElement将拥有XAML名称范围。这些方法存在于FrameworkContentElement上,因此最终可以将调用转发到 FrameworkElement父元素。
因此,您的示例中的问题是由于您的类最多只有DependencyObjects
但其中没有一个是FrameworkElement
。不是FrameworkElement
,它无法提供Parent
属性来解析Binding.ElementName
中指定的名称。
但这不是结束。为了解析Binding.ElementName
您的容器中的名称,不仅应该是FrameworkElement
,还应该FrameworkElement.Parent
。填充附加属性不会设置此属性,您的实例应该是按钮的逻辑子项,因此它将能够解析名称。
因此,我必须对您的代码进行一些更改才能使其正常工作(解析ElementName
),但在此状态下,我认为它不符合您的需求。我正在粘贴下面的代码,以便您可以使用它。
public class ObjectContainer<T> : FrameworkElement
where T : DependencyObject
{
static ObjectContainer()
{
ObjectProperty = DependencyProperty.Register("Object", typeof(T), typeof(ObjectContainer<T>), null);
}
public static DependencyProperty ObjectProperty;
[Bindable(true)]
public T Object
{
get { return (T)this.GetValue(ObjectProperty); }
set { this.SetValue(ObjectProperty, value); }
}
}
public class DependencyObjectContainer : ObjectContainer<DependencyObject> { }
public class DependencyObjectCollection : FrameworkElement
{
private object _child;
public Object Child
{
get { return _child; }
set
{
_child = value;
AddLogicalChild(_child);
}
}
}
public static class DependencyObjectCollectionHost
{
static DependencyObjectCollectionHost()
{
ObjectsProperty = DependencyProperty.RegisterAttached
(
"Objects",
typeof(DependencyObjectCollection),
typeof(DependencyObjectCollectionHost),
new PropertyMetadata(null, OnObjectsChanged)
);
}
public static DependencyObjectCollection GetObjects(DependencyObject dependencyObject)
{
return (DependencyObjectCollection)dependencyObject.GetValue(ObjectsProperty);
}
public static void SetObjects(DependencyObject dependencyObject, DependencyObjectCollection value)
{
dependencyObject.SetValue(ObjectsProperty, value);
}
public static readonly DependencyProperty ObjectsProperty;
private static void OnObjectsChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
((Button) dependencyObject).Content = e.NewValue;
var objects = (DependencyObjectCollection)e.NewValue;
// this check doesn't work anyway. d.Object was populating later than this check was performed
// if (objects.Count != objects.Count(d => d.Object != null))
// throw new ArgumentException();
}
}
可能你仍然可以通过实施INameScope interface及其FindName
方法来实现这一点,但我还没有尝试过这样做。