C# 为什么TextBlock不是路由事件上的原始源?
我正在显示C# 为什么TextBlock不是路由事件上的原始源?,c#,wpf,xaml,routed-events,routedeventargs,C#,Wpf,Xaml,Routed Events,Routedeventargs,我正在显示列表视图中元素的上下文菜单。上下文菜单附加到列表视图的文本块s,如下所示 <ListView.Resources> <ContextMenu x:Key="ItemContextMenu"> <MenuItem Command="local:MyCommands.Test" /> </ContextMenu> <Style TargetType="{x:Type TextBlock}" > <Setter
列表视图中元素的上下文菜单。上下文菜单附加到列表视图的文本块
s,如下所示
<ListView.Resources>
<ContextMenu x:Key="ItemContextMenu">
<MenuItem Command="local:MyCommands.Test" />
</ContextMenu>
<Style TargetType="{x:Type TextBlock}" >
<Setter Property="ContextMenu" Value="{StaticResource ItemContextMenu}" />
</Style>
</ListView.Resources>
MainWindow.xaml.cs
关于您的问题,或者更确切地说,关于WPF,与您问题中假设的场景相关的一个令人沮丧的事情是,WPF似乎没有为这个特定场景设计好。特别是:
DisplayMemberBinding
和CellTemplate
属性不能一起工作。也就是说,您可以指定其中一个,但不能同时指定两个。如果指定displaymberbinding
,则它具有优先权,并且不提供显示格式的自定义,只为隐式使用的TextBlock
应用样式设置器
DisplayMemberBinding
不参与WPF中其他地方常见的隐式数据模板行为。也就是说,当您使用此属性时,控件显式使用TextBlock
显示数据,将值绑定到TextBlock.Text
属性。所以你最好绑定到一个字符串值;如果您尝试使用其他类型,WPF不会为您查找任何其他数据模板
然而,即使有这些挫折,我还是能够找到两条不同的途径来解决你的问题。一条路径直接关注您的确切请求,而另一条路径则后退一步(我希望)解决您试图解决的更广泛的问题
第二条路径比第一条路径生成的代码更简单,因此IMHO更好,因为它不涉及处理可视化树以及该树的各个元素彼此相对位置的实现细节。因此,我将展示第一条路径(即,在复杂的意义上,这实际上是“第一条”路径,而不是“第二条”:)
首先,您需要一个小助手类:
类GridColumnDisplayData
{
公共对象显示值{get;set;}
公共字符串ColumnProperty{get;set;}
}
然后需要一个转换器为网格单元生成该类的实例:
class GridColumnDisplayDataConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return new GridColumnDisplayData { DisplayValue = value, ColumnProperty = (string)parameter };
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
XAML如下所示:
<Window x:Class="TestSO44549611TextBlockMenu.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:l="clr-namespace:TestSO44549611TextBlockMenu"
xmlns:s="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<ListView>
<ListView.Resources>
<x:Array Type="{x:Type l:Data}" x:Key="Items">
<l:Data Member1="First Item"/>
<l:Data Member1="Second Item"/>
</x:Array>
<ContextMenu x:Key="ItemContextMenu">
<MenuItem Header="Test" Command="l:MainWindow.Test"
CommandParameter="{Binding ColumnProperty}"/>
</ContextMenu>
<DataTemplate DataType="{x:Type l:GridColumnDisplayData}">
<TextBlock Background="Wheat" Text="{Binding DisplayValue}"
ContextMenu="{StaticResource ItemContextMenu}"/>
</DataTemplate>
<l:GridColumnDisplayDataConverter x:Key="columnDisplayConverter"/>
</ListView.Resources>
<ListView.ItemsSource>
<StaticResource ResourceKey="Items" />
</ListView.ItemsSource>
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="Member1">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding Member1,
Converter={StaticResource columnDisplayConverter}, ConverterParameter=Member1}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
</Window>
类似地,一个转换器(这次是,IMultiValueConverter
)为每个单元格创建该类的实例:
class GridCellHelperConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return new GridCellHelper { DisplayValue = values[0], UIElement = (UIElement)values[1] };
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
最后,XAML:
<Window x:Class="TestSO44549611TextBlockMenu.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:l="clr-namespace:TestSO44549611TextBlockMenu"
xmlns:s="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<ListView>
<ListView.Resources>
<x:Array Type="{x:Type l:Data}" x:Key="Items">
<l:Data Member1="First Item"/>
<l:Data Member1="Second Item"/>
</x:Array>
<l:GridCellHelperConverter x:Key="cellHelperConverter"/>
</ListView.Resources>
<ListView.ItemsSource>
<StaticResource ResourceKey="Items" />
</ListView.ItemsSource>
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="Member1">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Background="Wheat" Text="{Binding DisplayValue}">
<TextBlock.DataContext>
<MultiBinding Converter="{StaticResource cellHelperConverter}">
<Binding Path="Member1"/>
<Binding RelativeSource="{x:Static RelativeSource.Self}"/>
</MultiBinding>
</TextBlock.DataContext>
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="Test" Command="l:MainWindow.Test"
CommandParameter="{Binding UIElement}"/>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
</Window>
在此版本中,您可以看到单元格模板用于设置一个DataContext
值,该值包含绑定属性值和对TextBlock
的引用。然后,这些值由模板中的各个元素解压,即TextBlock.Text
属性和MenuItem.CommandParameter
属性
这里明显的缺点是,由于显示成员必须绑定在声明的单元格模板内,因此必须为每一列重复代码。我没有看到重用模板的方法,而是以某种方式将属性名传递给模板。(另一个版本也有类似的问题,但它是一个简单得多的实现,因此复制/粘贴似乎没有那么繁重)
但是它确实可靠地将TextBlock
引用发送到您的命令处理程序,这正是您所要求的。那么,就是这样。:) 是的,所以在这种情况下,ListView项始终具有first hittestvisibility,我不确定,但您可以尝试将ClickMode=“Press”
放在TextBlock
上,但我不完全确定这是否有效,因为它不是从按钮基继承的,因此保留为注释。如前所述,这是非常自然的,因为事件实际上最初来自ListViewItem
。您应该重新思考为什么需要TextBlock
对象。由于缺乏全面的评估,因此无法给出具体的建议。但是您应该能够通过其他方法确定列索引。您不需要深入查看视图结构本身来确定用户单击的内容,因为这些信息应该绑定在XAML中,并与click/命令一起发送。@PeterDuniho您可能是对的,我应该询问如何获取该列。ContextMenu仅在单击TextBlock时显示。如果在其外部单击,但仍在ListViewItem上,则不会显示任何菜单。我假设TextBlock不被认为是OriginalSource,因为它缺少一些东西,使得它成为OriginalSource@PeterDuniho我还添加了完整的示例代码,感谢您提供的好示例。如果您可以更明确地了解如何获取列索引,如果您可以获取TextBlock
对象,那么这将有所帮助。在示例中,我仍然不清楚您的期望是什么。这意味着我必须为每个列创建一个新的上下文菜单。在实际的代码中,我使用一种行为来生成列,并使它们与配置存储保持同步(以便在下一个应用程序启动时,我得到相同大小的相同列)。我应该能够动态地附加和生成菜单,所以您的回答给了我一个在我的情况下如何解决这个问题的提示。非常感谢。我很抱歉。第一个解决方案有效!UIElement绑定被正确地重新计算为UIElement。我可以使用它。结果是,由于新的多重绑定,我无法获取列名,但我可以在TextBlock的.Tag PropertyHmm上设置列名。嗯,MultiBinding
对我来说很好。
public class GridCellHelper
{
public object DisplayValue { get; set; }
public UIElement UIElement { get; set; }
}
class GridCellHelperConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return new GridCellHelper { DisplayValue = values[0], UIElement = (UIElement)values[1] };
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
<Window x:Class="TestSO44549611TextBlockMenu.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:l="clr-namespace:TestSO44549611TextBlockMenu"
xmlns:s="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<ListView>
<ListView.Resources>
<x:Array Type="{x:Type l:Data}" x:Key="Items">
<l:Data Member1="First Item"/>
<l:Data Member1="Second Item"/>
</x:Array>
<l:GridCellHelperConverter x:Key="cellHelperConverter"/>
</ListView.Resources>
<ListView.ItemsSource>
<StaticResource ResourceKey="Items" />
</ListView.ItemsSource>
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="Member1">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Background="Wheat" Text="{Binding DisplayValue}">
<TextBlock.DataContext>
<MultiBinding Converter="{StaticResource cellHelperConverter}">
<Binding Path="Member1"/>
<Binding RelativeSource="{x:Static RelativeSource.Self}"/>
</MultiBinding>
</TextBlock.DataContext>
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="Test" Command="l:MainWindow.Test"
CommandParameter="{Binding UIElement}"/>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
</Window>