情况如下:
<DataTemplate x:Key="ItemTemplate"
DataType="local:RoutedCustomCommand">
<Button Command="{Binding}"
Content="{Binding Text}"
ToolTip="{Binding Description}">
<Button.Visibility>
<MultiBinding Converter="{StaticResource SomeConverter}">
<!-- Converter simply checks flags matching
and returns corresponding Visibility -->
<Binding Path="VisibilityModes" />
<!-- VisibilityModes is a property of local:RoutedCustomCommand -->
<Binding Path="CurrentMode"
RelativeSource="{RelativeSource AncestorType=local:CustomControl}" />
<!-- CurrentMode is a property of local:CustomControl -->
</MultiBinding>
<Button.Visibility>
</Button>
</DataTemplate>
<local:CustomControl>
<!-- ... -->
<ToolBar ...
Width="15"
ItemTemplate={StaticResource ItemTemplate}
... />
<!-- Take a look at Width - it's especially is set to such a value
which forces items placement inside adorner overflow panel -->
<!-- If you change ToolBar to ItemsControl, items won't be wrapped by adorner
panel and everything will be OK -->
<!-- ... -->
</local:CustomControl>
有几个词:当一些元素在装饰者内部时,你不能简单地使用Binding的RelativeSource属性来访问装饰的可视树中的元素。
我已经习惯了使用ToolTip遇到同样的问题,当我需要将其FontSize绑定到工具提示的所有者FontSize时 - 有非常方便的PlacementTarget属性,我不需要在树中查找 - 绑定看起来像这样:<Binding PlacementTarget.FontSize />
这几乎是同样的问题 - 当项目在ToolBarOverflowPanel中时,它似乎在内部装配器中,因此RelativeSource显然无法绑定。
问题是:我该如何解决这个棘手的问题?我真的需要绑定到容器的属性。即使我能够绑定到装饰元素,祖先也有很长的路要走。
UPD:最不幸的副作用是Command无法达到预定目标 - 通过冒泡机制的命令传播在adorner的视觉根处停止:(。
显式目标的规范遇到同样的问题 - 目标必须在local:CustomControl
的可视树内,而相同的RelativeSource绑定无法达到。
UPD2:添加视觉和逻辑树遍历结果:
UPD3:删除旧的遍历结果。添加了更精确的遍历:
UPD4:(希望这是最终版)。遍历逻辑父母的可视树:
VisualTree
System.Windows.Controls.Button
System.Windows.Controls.ContentPresenter
System.Windows.Controls.Primitives.ToolBarOverflowPanel inherits from System.Windows.Controls.Panel
LogicalTree
System.Windows.Controls.Border
Microsoft.Windows.Themes.SystemDropShadowChrome inherits from System.Windows.Controls.Decorator
System.Windows.Controls.Primitives.Popup
System.Windows.Controls.Grid
logical root: System.Windows.Controls.Grid
System.Windows.Controls.Border
LogicalTree
Microsoft.Windows.Themes.SystemDropShadowChrome inherits from System.Windows.Controls.Decorator
System.Windows.Controls.Primitives.Popup
System.Windows.Controls.Grid
logical root: System.Windows.Controls.Grid
Microsoft.Windows.Themes.SystemDropShadowChrome inherits from System.Windows.Controls.Decorator
LogicalTree
System.Windows.Controls.Primitives.Popup
System.Windows.Controls.Grid
logical root: System.Windows.Controls.Grid
System.Windows.Documents.NonLogicalAdornerDecorator inherits from System.Windows.Documents.AdornerDecorator
LogicalTree
logical root: System.Windows.Controls.Decorator
System.Windows.Controls.Decorator
visual root: System.Windows.Controls.Primitives.PopupRoot inherits from System.Windows.FrameworkElement
LogicalTree
System.Windows.Controls.Primitives.Popup
VisualTree
System.Windows.Controls.Grid
System.Windows.Controls.Grid
here it is: System.Windows.Controls.ToolBar
System.Windows.Controls.Grid
logical root: System.Windows.Controls.Grid
提前致谢!
答案 0 :(得分:0)
好的,现在很容易看到这里发生了什么。原始问题中的线索,但在您发布逻辑树之前,我对您所做的事情并不明显。
正如我所怀疑的那样,你的问题是由缺乏逻辑继承引起的:在大多数例子中,你会在网上看到ContentPresenter将呈现一个FrameworkElement,它将是ToolBar的逻辑后代,因此事件路由和FindAncestor会即使可视树被弹出窗口中断也能正常工作。
在您的情况下,没有逻辑树连接,因为ContentPresenter呈现的内容不是FrameworkElement。
换句话说,这将允许绑定和事件路由甚至在装饰器内工作:
<Toolbar Width="15">
<MenuItem .../>
<MenuItem .../>
</Toolbar>
但这不会:
<Toolbar Width="15">
<my:NonFrameworkElementObject />
<my:NonFrameworkElementObject />
</Toolbar>
当然,如果您的项目是FrameworkElement派生的,它们可以是Controls,您可以使用ControlTemplate而不是DataTemplate。或者,他们可以是仅提供数据项的ContentPresenters。
如果您在代码中设置ItemsSource,这是一个简单的更改。替换这个:
MyItems.ItemsSource = ComputeItems();
用这个:
MyItems.ItemsSource = ComputeItems()
.Select(item => new ContentPresenter { Content = item });
如果你在XAML中设置ItemsSource,我通常使用的技术是在我自己的类中创建一个附加属性(例如,“DataItemsSource”)并设置一个PropertyChangedCallback,这样任何时候设置DataItemsSource,它都会.Select()如上所示创建ContentPresenters并设置ItemsSource。这是肉:
public class MyItemsSourceHelper ...
{
... RegisterAttached("DataItemsSource", ..., new FrameworkPropertyMetadata
{
PropertyChangedCallback = (obj, e) =>
{
var dataSource = GetDataItemsSource(obj);
obj.SetValue(ItemsControl.ItemsSource,
dataSource==null ? null :
dataSource.Select(item => new ContentPresenter { Content = item });
}
}
这将允许它工作:
<Toolbar Width="15" DataTemplate="..."
my:MyItemsSourceHelper.DataItemsSource="{Binding myItems}" />
其中myItems是FrameworkElement
适用的非DataTemplate
的集合。 (使用<Toolbar.DataItemsSource><x:Array ...
)
另请注意,这种包装数据项的方法假设您的数据模板是通过样式应用的,而不是通过ItemsControl.ItemTemplate property
。如果您确实希望通过ItemsControl.ItemTemplate应用模板,那么您的ContentPresenters需要在其ContentTemplate
属性中添加一个绑定,该属性使用FindAncestor在ItemsControl
中查找模板。这是在“new ContentPresenter”之后使用“SetBinding”完成的。
希望这有帮助。
答案 1 :(得分:0)
好的,ToolBar
似乎在其溢出面板上有非常奇怪的行为 - 它有测量问题以及随机绑定问题,因此我设计了使用{{1}的简单CommandsHost
控件那里的一切都很棒。
此控件符合我的要求,可随意根据需要进行修改。
这是造型:
Popup
这是逻辑:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:Company.Product">
<SolidColorBrush x:Key="PressedCommandButtonBackgroundBrush" Color="#FFDFB700" />
<SolidColorBrush x:Key="DisabledCommandButtonBackgroundBrush" Color="#FFDDDDDD" />
<SolidColorBrush x:Key="DisabledForegroundBrush" Color="#FF444444" />
<SolidColorBrush x:Key="FocusedBorderBrush" Color="#FFFFD700" />
<ControlTemplate x:Key="PopupButtonTemplate"
TargetType="vm:Button">
<Canvas Margin="{TemplateBinding Padding}"
Width="16"
Height="16">
<Ellipse x:Name="Circle"
Fill="{TemplateBinding Background}"
Canvas.Left="0"
Canvas.Top="0"
Width="16"
Height="16"
Stroke="{TemplateBinding BorderBrush}"
StrokeThickness="2" />
<Path x:Name="Arrow"
Fill="Transparent"
Canvas.Left="1"
Canvas.Top="1"
Width="14"
Height="14"
Stroke="Blue"
StrokeThickness="1.7"
StrokeStartLineCap="Round"
StrokeLineJoin="Miter"
StrokeEndLineCap="Triangle"
Data="M 1.904,1.904 L 11.096,11.096 M 4.335,9.284 L 11.096,11.096 M 9.284,4.335 L 11.096,11.096" />
</Canvas>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Circle"
Property="Fill" Value="{DynamicResource FocusedBorderBrush}" />
</Trigger>
<Trigger Property="IsFocused" Value="True">
<Setter TargetName="Circle"
Property="Fill" Value="{DynamicResource FocusedBorderBrush}" />
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="Circle"
Property="Fill" Value="{StaticResource PressedCommandButtonBackgroundBrush}" />
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="Circle"
Property="Fill" Value="{StaticResource DisabledCommandButtonBackgroundBrush}" />
<Setter TargetName="Arrow"
Property="Stroke" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<Style x:Key="PopupButtonStyle"
TargetType="vm:Button"
BasedOn="{StaticResource {x:Type vm:Button}}">
<Setter Property="Template" Value="{StaticResource PopupButtonTemplate}" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="Black" />
<Setter Property="Padding" Value="0" />
</Style>
<ItemsPanelTemplate x:Key="ItemsPanelTemplate">
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
<DataTemplate x:Key="CommandTemplate"
DataType="vmc:DescriptedCommand">
<vm:LinkButton Content="{Binding Text}"
Command="{Binding}"
ToolTip="{Binding Description}" />
</DataTemplate>
<ControlTemplate x:Key="ControlTemplate"
TargetType="vm:CommandsHost">
<Grid>
<vm:Button x:Name="Button"
Style="{StaticResource PopupButtonStyle}"
Margin="0"
Command="{x:Static vm:CommandsHost.OpenPopupCommand}"
ToolTip="{TemplateBinding ToolTip}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
<Popup x:Name="PART_Popup"
Placement="Right"
PlacementTarget="{Binding ElementName=Button}"
StaysOpen="False"
IsOpen="{Binding IsOpen, Mode=TwoWay,
RelativeSource={x:Static RelativeSource.TemplatedParent}}">
<Border BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}"
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}"
SnapsToDevicePixels="True">
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</Border>
</Popup>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="ToolTip" Value="{x:Null}">
<Setter TargetName="Button"
Property="ToolTip"
Value="{Binding Command.Description, RelativeSource={x:Static RelativeSource.Self}}" />
</Trigger>
<Trigger SourceName="PART_Popup"
Property="IsOpen" Value="True">
<Setter TargetName="Button"
Property="Background"
Value="{StaticResource PressedCommandButtonBackgroundBrush}" />
</Trigger>
<Trigger Property="HasItems" Value="False">
<Setter Property="IsEnabled" Value="False" />
</Trigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding HasItems,
RelativeSource={x:Static RelativeSource.Self}}"
Value="False" />
<Condition Binding="{Binding EmptyVisibility,
RelativeSource={x:Static RelativeSource.Self},
Converter={StaticResource NotEqualsConverter},
ConverterParameter={x:Null}}"
Value="True" />
</MultiDataTrigger.Conditions>
<Setter Property="Visibility"
Value="{Binding EmptyVisibility,
RelativeSource={x:Static RelativeSource.Self}}" />
</MultiDataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<Style TargetType="vm:CommandsHost"
BasedOn="{StaticResource {x:Type ItemsControl}}">
<Setter Property="Template" Value="{StaticResource ControlTemplate}" />
<Setter Property="ItemsPanel" Value="{StaticResource ItemsPanelTemplate}" />
<Setter Property="ItemTemplate" Value="{StaticResource CommandTemplate}" />
<Setter Property="Background" Value="White" />
<Setter Property="BorderBrush" Value="Black" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="Padding" Value="2" />
<Setter Property="FontSize" Value="{DynamicResource ReducedFontSize}" />
</Style>
</ResourceDictionary>
我希望这会对某人有所帮助。