从具有自定义集合属性的UserControl项目绑定 [英] Binding from items of an UserControl with custom collection property

查看:101
本文介绍了从具有自定义集合属性的UserControl项目绑定的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这个问题是这个续集问题(我已经应用了答案,但仍然无法使用)。



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


简单来说:我希望ToolBar的项目能够绑定到tb:ToolBar的父母。



我有以下XAML代码:

 < Window Name =myWindowDataContext ={Binding ElementName = myWindow}> 
& DockPanel>
< tb:ToolBar Name =toolbarDockPanel.Dock =TopDataContext ={Binding ElementName = myWindow}>
< tb:ToolBar.Items>
< tb:ToolBarControl Priority = - 3>
< tbBaseControl.Content>
< StackPanel Orientation =Horizo​​ntal>
< TextBlock>地图: TextBlock>
< ComboBox ItemsSource ={Binding SomeProperty,ElementName = myWindow}>

有关类型的一些信息:




  • tb:ToolBar 一个 UserControl 与依赖属性项目类型 FreezableCollection< ToolBarControl>


  • tb:ToolBarControl 是一个 UserControl 与模板几乎完全相同的 ContentControl的模板




问题是在 ComboBox 失败(通常是找不到与引用绑定的源),因为它的DataContext为空。



为什么?



编辑:问题的核心是为什么不继承DataContext?,没有它,绑定不能



EDIT2:



这里是 tb的XAML:ToolBar

 < UserControl ... Name =toolBarControl> 
< ToolBarTray>
< ToolBar ItemsSource ={Binding Items,ElementName = toolBarControl}Name =toolBarToolBarTray.IsLocked =TrueVerticalAlignment =TopHeight =26>

编辑3:



一些有用的工具以及什么不起作用: http://pastebin.com/Tyt1Xtvg



感谢您的答案。

解决方案

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



至少,尝试删除

  DataContext ={Binding ElementName = myWindow}

 < tb:ToolBar Name =toolbarDockPanel.Dock =TopDataContext ={Binding ElementName = myWindow}> 

,看看如何进行。



更新



其实,保留所有现有的代码(忽略我以前的建议片刻),只需更改

 < ComboBox ItemsSource ={Binding SomeProperty,ElementName = myWindow}> 

 < ComboBox ItemsSource ={Binding DataContext.SomeProperty}> 

,看看是否有效。



结构化控件的方式, ComboBox tb位于同一级别/范围:ToolBarControl 和< code > tb:ToolBar 。这意味着它们都共享相同的 DataContext ,所以你真的不需要任何 ElementName 绑定或 RelativeSource 绑定以尝试查找其父/祖。



如果您删除 DataContext ={Binding ElementName = myWindow} tb:ToolBar ,你甚至可以摆脱前缀 DataContext 在绑定中,这真的是你需要的。

 < ComboBox ItemsSource ={Binding SomeProperty}> 

更新2 回答您的编辑3



这是因为您的 tb中的项目集合:ToolBar usercontrol只是一个属性,它不在逻辑和可视化树中,我相信 ElementName 绑定使用逻辑树。



这是为什么它不起作用。



添加到逻辑树



我想添加项目进入逻辑树,你需要做两件事。



首先你需要覆盖 LogicalChildren 在您的 tb:ToolBar usercontrol。

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

foreach(项目中的项目)
{
yield return item;
}
}
}

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

  AddLogicalChild(item); 

给它一个镜头。



这个工作...



在玩了一下之后,我想我上面显示的还不够。您还需要将这些 ToolBarControls 添加到您的主窗口的名称范围以启用 ElementName 绑定。我假设这是你如何定义你的项目依赖属性。

  public static DependencyProperty ItemsProperty = 
DependencyProperty.Register(Items,
typeof(ToolBarControlCollection)
typeof(ToolBar),
new FrameworkPropertyMetadata(new ToolBarControlCollection(),Callback));

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

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

foreach(项目中的项目)
{
//包含您的ToolBar用户控件的面板,在您提供的代码中,它是一个DockPanel
var面板=(面板)toolbar.Parent;
//你的主窗口
var window = panel.Parent;
//将此ToolBarControl添加到主窗口的名称范围
NameScope.SetNameScope(item,NameScope.GetNameScope(window));

// **如果您只想要ElementName绑定,则不需要
//这可以在视觉树中启动冒泡(向上导航)
//toolbar.AddLogicalChild(item );
}
}

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

  // **如果您只想要ElementName绑定**不需要** 
//这样可以启用隧道(向下导航)在视觉树中,例如属性继承
// protected override System.Collections.IEnumerator LogicalChildren
// {
// get
// {
// if(Items.Count == 0)
// {
// yield break;
//}

// foreach(项目中的var项目)
// {
// yield return item;
//}
//}
//}

我已经测试了代码,并且工作正常。


This question is a "sequel" to this question (I have applied the answer, but it still won't work).

I'm trying to create an extended ToolBar control for a modular application, which can load its items from multiple data sources (but that is not the issue I'm trying to solve right now, now I want it to work when used as regular ToolBar found in WPF).

In short: I want the ToolBar's items to be able to bind to the tb:ToolBar's parents.

I have following XAML code:

<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}">

Some info about the types:

  • tb:ToolBar is an UserControl with dependency property Items of type FreezableCollection<ToolBarControl>.

  • tb:ToolBarControl is an UserControl with template pretty much identical to ContentControl's template.

The problem is that the binding in the ComboBox fails (with the usual "Cannot find source for binding with reference"), because its DataContext is null.

Why?

EDIT: The core of the question is "Why is the DataContext not inherited?", without it, the bindings can't work.

EDIT2:

Here is XAML for the tb:ToolBar:

<UserControl ... Name="toolBarControl">
    <ToolBarTray>
        <ToolBar ItemsSource="{Binding Items, ElementName=toolBarControl}" Name="toolBar" ToolBarTray.IsLocked="True" VerticalAlignment="Top" Height="26">

EDIT 3:

I posted an example of what works and what doesn't: http://pastebin.com/Tyt1Xtvg

Thanks for your answers.

解决方案

I personally don't like the idea of setting DataContext in controls. I think doing this will somehow break the data context inheritance. Please take a look at this post. I think Simon explained it pretty well.

At least, try removing

DataContext="{Binding ElementName=myWindow}"

from

<tb:ToolBar Name="toolbar" DockPanel.Dock="Top" DataContext="{Binding ElementName=myWindow}> 

and see how it goes.

UPDATE

Actually, keep all your existing code (ignore my previous suggestion for a moment), just change

<ComboBox ItemsSource="{Binding SomeProperty, ElementName=myWindow}"> 

to

<ComboBox ItemsSource="{Binding DataContext.SomeProperty}"> 

and see if it works.

I think because of the way you structure your controls, the ComboBox is at the same level/scope as the tb:ToolBarControl and the tb:ToolBar. That means they all share the same DataContext, so you don't really need any ElementName binding or RelativeSource binding to try to find its parent/ancestor.

If you remove DataContext="{Binding ElementName=myWindow} from the tb:ToolBar, you can even get rid of the prefix DataContext in the binding. And this is really all you need.

<ComboBox ItemsSource="{Binding SomeProperty}"> 

UPDATE 2 to answer your Edit 3

This is because your Items collection in your tb:ToolBar usercontrol is just a property. It's not in the logical and visual tree, and I believe ElementName binding uses logical tree.

That's why it is not working.

Add to logical tree

I think to add the Items into the logical tree you need to do two things.

First you need to override the LogicalChildren in your tb:ToolBar usercontrol.

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

            foreach (var item in Items)
            {
                yield return item;
            }
        }
    }

Then whenever you added a new tb:ToolBarControl you need to call

AddLogicalChild(item);

Give it a shot.

This WORKS...

After playing around with it a little bit, I think what I showed you above isn't enough. You will also need to add these ToolBarControls to your main window's name scope to enable ElementName binding. I assume this is how you defined your Items dependency property.

public static DependencyProperty ItemsProperty =
    DependencyProperty.Register("Items",
                                typeof(ToolBarControlCollection),
                                typeof(ToolBar),
                                new FrameworkPropertyMetadata(new ToolBarControlCollection(), Callback));

In the callback, it is where you add it to the name scope.

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

Also if you want property inheritance, you will need

// ** 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;
//        }
//    }
//}

I have tested the code and it works fine.

这篇关于从具有自定义集合属性的UserControl项目绑定的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆