这个问题是this question的“续集”(我已经应用了答案,但它仍然不起作用)。
我正在尝试为模块化应用程序创建一个扩展的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}">
有关类型的一些信息:
tb:ToolBar
是UserControl
,其依赖项属性Items
的类型为FreezableCollection<ToolBarControl>
。
tb:ToolBarControl
是UserControl
,其模板与ContentControl's template几乎完全相同。
问题是ComboBox
中的绑定失败(使用通常的“无法找到与引用绑定的源”),因为其DataContext为null。
为什么吗
编辑:问题的核心是“为什么DataContext没有被继承?”,没有它,绑定就无法工作。
EDIT2:
以下是tb:ToolBar
:
<UserControl ... Name="toolBarControl">
<ToolBarTray>
<ToolBar ItemsSource="{Binding Items, ElementName=toolBarControl}" Name="toolBar" ToolBarTray.IsLocked="True" VerticalAlignment="Top" Height="26">
编辑3:
我发布了一个有效和无效的示例:http://pastebin.com/Tyt1Xtvg
感谢您的回答。
答案 0 :(得分:3)
我个人不喜欢在控件中设置DataContext
的想法。我认为这样做会以某种方式破坏数据上下文继承。请看this post。我认为西蒙解释得很好。
至少,尝试删除
DataContext="{Binding ElementName=myWindow}"
这
<tb:ToolBar Name="toolbar" DockPanel.Dock="Top" DataContext="{Binding ElementName=myWindow}>
看看它是怎么回事。
<强>更新强>
实际上,请保留所有现有代码(暂时忽略我之前的建议),只需更改
即可<ComboBox ItemsSource="{Binding SomeProperty, ElementName=myWindow}">
到
<ComboBox ItemsSource="{Binding DataContext.SomeProperty}">
看看它是否有效。
我认为,由于您构建控件的方式,ComboBox
与tb:ToolBarControl
和tb:ToolBar
处于同一级别/范围。这意味着它们共享相同的DataContext
,因此您不需要任何ElementName
绑定或RelativeSource
绑定来尝试查找其父/祖先。
如果从DataContext="{Binding ElementName=myWindow}
中删除tb:ToolBar
,您甚至可以删除绑定中的前缀DataContext
。这就是你真正需要的。
<ComboBox ItemsSource="{Binding SomeProperty}">
更新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;
}
}
}
然后,无论何时添加新的tb:ToolBarControl
,您都需要致电
AddLogicalChild(item);
试一试。
这个工作......
稍微玩了一下之后,我想我上面给你看的是不够的。您还需要将这些ToolBarControls
添加到主窗口的名称范围,以启用ElementName
绑定。我假设您定义了Items
依赖项属性。
public static DependencyProperty ItemsProperty =
DependencyProperty.Register("Items",
typeof(ToolBarControlCollection),
typeof(ToolBar),
new FrameworkPropertyMetadata(new ToolBarControlCollection(), Callback));
在回调中,您可以将其添加到名称范围。
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);
}
}
此外,如果您想要属性继承,则需要
// ** 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;
// }
// }
//}
我已经测试了代码并且工作正常。
答案 1 :(得分:2)
我把你发布的Xaml片段试图重现你的问题。
DataContext
似乎从我能说的内容中继承得很好。但是,ElementName
绑定失败,我认为这与以下事实有关:即使您在ComboBox
中添加Window
,它也会在不同的范围内结束。 (首先将其添加到自定义Items
的{{1}}属性中,然后使用绑定填充到框架ToolBar
ToolBar
绑定而不是RelativeSource
绑定似乎工作正常。
但是如果你真的想在Binding中使用控件的名称,那么你可以查看Dr.WPF's excellent ObjectReference
implementation
它看起来像这样
ElementName
我上传了一个小型示例项目,其中<Window ...
tb:ObjectReference.Declaration="{tb:ObjectReference myWindow}">
<!--...-->
<ComboBox ItemsSource="{Binding Path=SomeProperty,
Source={tb:ObjectReference myWindow}}"
和RelativeSource
在此处成功使用:https://www.dropbox.com/s/tx5vdqlm8mywgzw/ToolBarTest.zip?dl=0
我近似的自定义ObjectReference
部分在ToolBar
中看起来像这样
Window
绑定失败,但ElementName
和RelativeSource
绑定工作
ObjectReference
答案 2 :(得分:1)
通常,如果没有DataContext
,那么ElementName
也将无效。如果情况允许,您可以尝试使用x:Reference
。
为此,您需要将绑定控件移动到引用控件的资源中,更改绑定并在其所在位置使用StaticResource
,例如。
<Window Name="myWindow" DataContext="{Binding ElementName=myWindow}" >
<Window.Resources>
<ComboBox x:Key="cb"
ItemsSource="{Binding SomeProperty,
Source={x:Reference myWindow}}"/>
</Window.Resources>
<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>
<StaticResource ResourceKey="cb"/>
答案 3 :(得分:0)
正确的答案可能是将所有内容添加到逻辑树中,如前面的答案中所述,但以下代码已经证明对我来说很方便。我不能发布我的所有代码,但是......
编写自己的Binding MarkupExtension,它可以让您回到XAML文件的根元素。 此代码未编译,因为我修改了我的真实代码以发布此内容。
[MarkupExtensionReturnType(typeof(object))]
public class RootBindingExtension : MarkupExtension
{
public string Path { get; set; }
public RootElementBinding(string path)
{
Path = path;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
IRootObjectProvider rootObjectProvider =
(IRootObjectProvider)serviceProvider.GetService(typeof(IRootObjectProvider));
Binding binding = new Binding(this.Path);
binding.Source = rootObjectProvider.RootObject;
// Return raw binding if we are in a non-DP object, like a Style
if (service.TargetObject is DependencyObject == false)
return binding;
// Otherwise, return what a normal binding would
object providedValue = binding.ProvideValue(serviceProvider);
return providedValue;
}
}
用法:
<ComboBox ItemsSource={myBindings:RootBinding DataContext.SomeProperty} />