从具有自定义集合属性的UserControl项目绑定 [英] Binding from items of an UserControl with custom collection property
问题描述
这个问题是这个续集问题(我已经应用了答案,但仍然无法使用)。
我正在为模块化应用程序创建一个扩展的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 =Horizontal>
< 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中的
usercontrol只是一个属性,它不在逻辑和可视化树中,我相信项目
集合:ToolBar 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 anUserControl
with dependency propertyItems
of typeFreezableCollection<ToolBarControl>
.tb:ToolBarControl
is anUserControl
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屋!