从UserControl的项目与自定义集合属性绑定

Mat*_*ský 5 data-binding wpf xaml

这个问题是一个"续集"到了这个问题(我已经申请了答案,但它仍然无法工作).

我正在尝试为模块化应用程序创建一个扩展的ToolBar控件,它可以从多个数据源加载其项目(但这不是我现在要解决的问题,现在我希望它在作为常规使用时工作在WPF中找到的ToolBar).

简而言之:我希望ToolBar的项能够绑定到tb:ToolBar的父项.

我有以下XAML代码:

<Window Name="myWindow" DataContext="{Binding ElementName=myWindow}" >
    <DockPanel>
        <tb:ToolBar Name="toolbar" DockPanel.Dock="Top" DataContext="{Binding ElementName=myWindow}>
            <tb:ToolBar.Items>
                <tb:ToolBarControl Priority="-3">
                    <tb:ToolBarControl.Content>
                        <StackPanel Orientation="Horizontal">
                            <TextBlock>Maps:</TextBlock>
                            <ComboBox ItemsSource="{Binding SomeProperty, ElementName=myWindow}">
Run Code Online (Sandbox Code Playgroud)

有关类型的一些信息:

  • tb:ToolBar是一个UserControl具有Items类型的依赖属性FreezableCollection<ToolBarControl>.

  • tb:ToolBarControl是一个UserControlContentControl模板非常相似的模板.

问题是ComboBox失败中的绑定(与通常的"无法找到与引用绑定的源"),因为其DataContext为null.

为什么?

编辑:问题的核心是"为什么DataContext没有继承?",没有它,绑定无法工作.

EDIT2:

这是XAML tb:ToolBar:

<UserControl ... Name="toolBarControl">
    <ToolBarTray>
        <ToolBar ItemsSource="{Binding Items, ElementName=toolBarControl}" Name="toolBar" ToolBarTray.IsLocked="True" VerticalAlignment="Top" Height="26">
Run Code Online (Sandbox Code Playgroud)

编辑3:

我发布了一个有效和无效的例子:http://pastebin.com/Tyt1Xtvg

谢谢你的回答.

Jus*_* XL 3

DataContext我个人不喜欢设置控件的想法。我认为这样做会以某种方式破坏数据上下文继承。请看一下这篇文章。我认为西蒙解释得很好。

至少,尝试删除

DataContext="{Binding ElementName=myWindow}"
Run Code Online (Sandbox Code Playgroud)

<tb:ToolBar Name="toolbar" DockPanel.Dock="Top" DataContext="{Binding ElementName=myWindow}> 
Run Code Online (Sandbox Code Playgroud)

看看进展如何。

更新

实际上,保留所有现有代码(暂时忽略我之前的建议),只需更改

<ComboBox ItemsSource="{Binding SomeProperty, ElementName=myWindow}"> 
Run Code Online (Sandbox Code Playgroud)

<ComboBox ItemsSource="{Binding DataContext.SomeProperty}"> 
Run Code Online (Sandbox Code Playgroud)

看看它是否有效。

我认为由于您构造控件的方式, 与和ComboBox处于同一级别/范围。这意味着它们都共享相同的,因此您实际上不需要任何绑定或绑定来尝试找到其父/祖先。tb:ToolBarControltb:ToolBarDataContextElementNameRelativeSource

DataContext="{Binding ElementName=myWindow}如果从 中删除tb:ToolBar,您甚至可以去掉DataContext绑定中的前缀。这确实是您所需要的。

<ComboBox ItemsSource="{Binding SomeProperty}"> 
Run Code Online (Sandbox Code Playgroud)

更新 2来回答您的编辑 3

这是因为用户Items控件中的集合tb:ToolBar只是一个属性。它不在逻辑和视觉树中,我相信ElementName绑定使用逻辑树。

这就是为什么它不起作用。

添加到逻辑树

我认为要将其添加Items到逻辑树中,您需要做两件事。

首先,您需要覆盖用户控件LogicalChildren中的tb:ToolBar

    protected override System.Collections.IEnumerator LogicalChildren
    {
        get
        {
            if (Items.Count == 0)
            {
                yield break;
            }

            foreach (var item in Items)
            {
                yield return item;
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

然后每当你添加一个新的tb:ToolBarControl你需要调用

AddLogicalChild(item);
Run Code Online (Sandbox Code Playgroud)

试一试。

这有效...

经过一番尝试后,我认为上面向您展示的内容还不够。您还需要将它们添加ToolBarControls到主窗口的名称范围中以启用ElementName绑定。我假设这就是您定义Items依赖属性的方式。

public static DependencyProperty ItemsProperty =
    DependencyProperty.Register("Items",
                                typeof(ToolBarControlCollection),
                                typeof(ToolBar),
                                new FrameworkPropertyMetadata(new ToolBarControlCollection(), Callback));
Run Code Online (Sandbox Code Playgroud)

在回调中,您可以将其添加到名称范围中。

private static void Callback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var toolbar = (ToolBar)d;
    var items = toolbar.Items;

    foreach (var item in items)
    {
        // the panel that contains your ToolBar usercontrol, in the code that you provided it is a DockPanel
        var panel = (Panel)toolbar.Parent;
        // your main window
        var window = panel.Parent;
        // add this ToolBarControl to the main window's name scope
        NameScope.SetNameScope(item, NameScope.GetNameScope(window));

        // ** not needed if you only want ElementName binding **
        // this enables bubbling (navigating up) in the visual tree
        //toolbar.AddLogicalChild(item);
    }
}
Run Code Online (Sandbox Code Playgroud)

另外,如果您想要财产继承,您将需要

// ** not needed if you only want ElementName binding **
// this enables tunneling (navigating down) in the visual tree, e.g. property inheritance
//protected override System.Collections.IEnumerator LogicalChildren
//{
//    get
//    {
//        if (Items.Count == 0)
//        {
//            yield break;
//        }

//        foreach (var item in Items)
//        {
//            yield return item;
//        }
//    }
//}
Run Code Online (Sandbox Code Playgroud)

我已经测试了代码并且运行良好。