wpf - 在类型集合的 AttachedProperty 内绑定(bind)到其他元素

标签 wpf binding dependency-properties attached-properties

我想创建一个 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();
    }
}

我无法在集合中建立任何绑定(bind)。我想我已经弄清楚问题出在哪里了。 Collection 中的元素没有与 Binding 相关的 DataContext。然而,我不知道我能做些什么来对抗它。

编辑: 修复了按钮缺失的名称属性。 注意:我知道绑定(bind)无法工作,因为每个未显式声明 Source 的 Binding 都会使用它的 DataContext 作为 Source。正如我已经提到的:我们的 Collection 中没有这样的 DataContext,也没有 VisualTree,不存在的 FrameworkElement 可能是其中的一部分;)

也许有人过去遇到过类似的问题并找到了合适的解决方案。

与 H.B. 的帖子相关的 EDIT2: 通过对集合中的项目进行以下更改,它现在似乎可以工作:

<local:DependencyObjectContainer Object="{x:Reference myButton}"/>

有趣的行为: 当调用 OnObjectsChanged 事件处理程序时,集合包含零个元素...我认为这是因为元素的创建(在 InitializeComponent 方法中完成)尚未完成。

顺便说一句。正如你 H.B.表示使用x:Reference时不需要使用Container类。使用 x:Reference 时是否有我一开始没有看到的缺点?

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));
    }
}

最佳答案

您可以使用x:Reference在 .NET 4 中,它比 ElementName “更智能”,并且与绑定(bind)不同,它不需要目标是依赖属性。

您甚至可以摆脱容器类,但您的属性需要具有正确的类型,以便 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"/>

会导致一些更好的错误。

关于wpf - 在类型集合的 AttachedProperty 内绑定(bind)到其他元素,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6162830/

相关文章:

c# - WPF 设置用户控件依赖属性

visual-studio-2008 - 在 XAML 中创建具有代码完成功能的属性或 DependencyProperties

c# - 复杂模型绑定(bind)到列表

c# - 找不到管理 FrameworkElement

c# - Silverlight DependencyProperty问题

wpf - WPF 中的可编辑数据网格

c# - wpf中父子窗口的通信

c# - 获取DataGrid中特定单元格的值

c# - 在 WPF 中动态生成一组具有不同内容的单选按钮

WPF ComboBox SelectionChanged 事件命令未触发