C# - 弹出窗口不显示在 MVVM 的第二个选项卡项中
C# - Popup is not displayed in second tab item in MVVM
我在 MVVM 中工作,我在 XAML 中创建了两个 TabItem
。在第一个中,显示弹出窗口,但在第二个中,当我按下对应于一列的按钮时,弹出窗口不显示。
这是我的工作弹出窗口代码:
<Viewbox>
<Grid Height="359" Width="746">
<Popup Name="popupFilter" Placement="MousePoint" IsOpen="{Binding IsFilterOpen, Mode=OneWay}" StaysOpen="True" Width="200">
<Border Background="White" BorderBrush="Gray" BorderThickness="1,1,1,1">
<StackPanel Margin="5,5,5,15">
<ListBox x:Name="listBoxPopupContent"
Height="250"
ItemsSource="{Binding FilterItems}"
BorderThickness="0"
ScrollViewer.VerticalScrollBarVisibility="Auto">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsChecked}"
Content="{Binding Item}"
Command="{Binding DataContext.ApplyFiltersCommand,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ListBox}}}"
CommandParameter="{Binding IsChecked,
RelativeSource={RelativeSource Self},
Mode=OneWay}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Border>
</Popup>
<Grid HorizontalAlignment="Left" Height="261" Margin="0,63,0,0" VerticalAlignment="Top" Width="736">
<TabControl HorizontalAlignment="Left" Height="175" VerticalAlignment="Top" Width="716" Margin="10,0,0,0">
<TabItem Header="Class">
<DataGrid x:Name="ClassViewDataGrid" ItemsSource="{Binding FilteredClassViewItems}"
AutoGenerateColumns="False"
IsReadOnly="True"
CanUserReorderColumns="True"
CanUserResizeColumns="True"
CanUserSortColumns="True">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding ClassName}">
<DataGridTextColumn.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Class" />
<Button Name="buttonClassViewClassFilter" Margin="10,0,0,0"
Command="{Binding DataContext.ShowFilterCommand,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type DataGrid}}}"
CommandParameter="{Binding Name, RelativeSource={RelativeSource Self}}">
<Button.ContentTemplate>
<DataTemplate>
<Image Source="/Images/filter.png" Width="10" Height="10" />
</DataTemplate>
</Button.ContentTemplate>
</Button>
</StackPanel>
</DataGridTextColumn.Header>
以及不显示弹出窗口的 tabItem:
<TabItem Header="Field">
<DataGrid x:Name="FielsdViewDataGrid" ItemsSource="{Binding FilteredFieldViewItems}"
AutoGenerateColumns="False"
IsReadOnly="True"
CanUserReorderColumns="True"
CanUserResizeColumns="True"
CanUserSortColumns="True">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding ClassName}">
<DataGridTextColumn.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Class" />
<Button Name="buttonFieldViewClassFilter" Margin="10,0,0,0"
Command="{Binding DataContext.ShowFilterCommand,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type DataGrid}}}"
CommandParameter="{Binding Name, RelativeSource={RelativeSource Self}}">
<Button.ContentTemplate>
<DataTemplate>
<Image Source="/Images/filter.png" Width="10" Height="10" />
</DataTemplate>
</Button.ContentTemplate>
</Button>
</StackPanel>
</DataGridTextColumn.Header>
...
第二个tabItem
的定义和第一个一样,但是好像没有达到Binding DataContext.ShowFilterCommand
。我试图在那里进行调试,但没有达到。
方法如下:
private void ShowFilterCommandRaised(object obj)
{
IsFilterOpen = !IsFilterOpen;
str = obj;
if (IsFilterOpen)
{
if (str.Equals("buttonClassViewClassFilter"))
{
FilterItems.Clear();
foreach (var classView in classViewItems)
{
FilterItems.Add(new CheckedListItem<string>(classView.ClassName, true));
}
}
if (str.Equals("buttonClassViewExtendsFilter"))
{
FilterItems.Clear();
foreach (var classView in classViewItems)
{
FilterItems.Add(new CheckedListItem<string>(classView.Category, true));
}
}
if (str.Equals("buttonFieldViewClassFilter"))
{
FilterItems.Clear();
foreach (var fieldView in fieldViewItems)
{
FilterItems.Add(new CheckedListItem<string>(fieldView.ClassName, true));
}
}
}
我做错了什么?
如果您调换两个 TabItem 的顺序,或在 TabControl 上设置 SelectedIndex="1"
,您会发现最初可见的始终是唯一有效的。我将 PresentationTraceSources.TraceLevel=High
添加到 Command
绑定中,发现启动时隐藏的 TabItem 中的绑定最初尝试解析其来源 属性,并且当该 TabItem 变为时不再尝试活跃。
问题在于,在第一次尝试解析隐藏选项卡项中的绑定时,该 TabItem 的 UI 的 none 实际上还存在。由于虚拟化,它不会在需要时创建。所创建的只是它的 Header 内容,挂在 space 中。 DataGrid 尚不存在,因此 AncestorType 搜索永远找不到它。最终创建 DataGrid 时,似乎没有引发任何事件来通知 Binding 它需要重复祖先搜索。
解决这个问题很简单:通过 DataTemplate 创建 header 内容。无论如何,这是正确的方法。 DataGrid 显示时将实例化 DataTemplate。
我们将使用一个值转换器来创建一个标识符,该标识符告诉命令用户单击了哪个网格和列。请注意,我们现在为每一列的 Header 属性 提供一个纯字符串,并且模板中的 TextBlock
更改为 <TextBlock Text="{Binding}" />
.
XAML
<TabControl HorizontalAlignment="Left" Height="175" VerticalAlignment="Top" Width="716" Margin="10,0,0,0">
<TabControl.Resources>
<local:GetColumnIdentifier x:Key="GetColumnIdentifier" />
<DataTemplate x:Key="FilterColumnHeaderTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" />
<Button
Margin="10,0,0,0"
Command="{Binding DataContext.ShowFilterCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}"
CommandParameter="{Binding Converter={StaticResource GetColumnIdentifier}, RelativeSource={RelativeSource Self}}"
>
<Button.ContentTemplate>
<DataTemplate>
<Image Source="/Images/filter.png" Width="10" Height="10" />
</DataTemplate>
</Button.ContentTemplate>
</Button>
</StackPanel>
</DataTemplate>
</TabControl.Resources>
<TabItem Header="Class">
<DataGrid
x:Name="ClassViewDataGrid"
ItemsSource="{Binding FilteredClassViewItems}"
AutoGenerateColumns="False"
IsReadOnly="True"
CanUserReorderColumns="True"
CanUserResizeColumns="True"
CanUserSortColumns="True"
>
<DataGrid.Columns>
<DataGridTextColumn
Header="Class"
Binding="{Binding ClassName}"
HeaderTemplate="{StaticResource FilterColumnHeaderTemplate}"
/>
</DataGrid.Columns>
</DataGrid>
</TabItem>
<TabItem Header="Field">
<DataGrid
x:Name="FielsdViewDataGrid"
ItemsSource="{Binding FilteredFieldViewItems}"
AutoGenerateColumns="False"
IsReadOnly="True"
CanUserReorderColumns="True"
CanUserResizeColumns="True"
CanUserSortColumns="True"
>
<DataGrid.Columns>
<DataGridTextColumn
Header="Class"
Binding="{Binding ClassName}"
HeaderTemplate="{StaticResource FilterColumnHeaderTemplate}"
/>
</DataGrid.Columns>
</DataGrid>
</TabItem>
</TabControl>
值转换器:
//using System.Windows.Markup.Primitives;
//using System.Windows.Controls.Primitives;
public class GetColumnIdentifier : IValueConverter
{
private static T GetVisualAncestor<T>(DependencyObject obj)
where T : DependencyObject
{
while (obj != null)
{
if (obj is T)
return obj as T;
else
obj = VisualTreeHelper.GetParent(obj);
}
return null;
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// Not all DataGridColumn subclasses have a Binding property.
var header = GetVisualAncestor<DataGridColumnHeader>((DependencyObject)value);
var datagrid = GetVisualAncestor<DataGrid>(header);
if (header?.Column != null)
{
MarkupObject markupObject = MarkupWriter.GetMarkupObjectFor(header.Column);
var bindingProp = markupObject.Properties.FirstOrDefault(p => p.Name == "Binding");
if (bindingProp?.Value is Binding binding)
{
return $"{datagrid?.Name}.{binding.Path.Path}";
}
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
如果您有多个列绑定到同一个路径,并且您需要区分它们(在这种情况下,您可以对大多数列使用上述解决方案,但为奇怪的特殊情况):
<TabControl HorizontalAlignment="Left" Height="175" VerticalAlignment="Top" Width="716" Margin="10,0,0,0">
<TabItem Header="Class">
<DataGrid
x:Name="ClassViewDataGrid"
ItemsSource="{Binding FilteredClassViewItems}"
AutoGenerateColumns="False"
IsReadOnly="True"
CanUserReorderColumns="True"
CanUserResizeColumns="True"
CanUserSortColumns="True"
>
<DataGrid.Columns>
<DataGridTextColumn
Header="Class"
Binding="{Binding ClassName}"
>
<DataGridTextColumn.HeaderTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" />
<Button
Name="buttonClassViewClassFilter"
Margin="10,0,0,0"
Command="{Binding DataContext.ShowFilterCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}"
CommandParameter="{Binding Name, RelativeSource={RelativeSource Self}}"
>
<Button.ContentTemplate>
<DataTemplate>
<Image Source="/Images/filter.png" Width="10" Height="10" />
</DataTemplate>
</Button.ContentTemplate>
</Button>
</StackPanel>
</DataTemplate>
</DataGridTextColumn.HeaderTemplate>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</TabItem>
<TabItem Header="Field">
<DataGrid
x:Name="FielsdViewDataGrid"
ItemsSource="{Binding FilteredFieldViewItems}"
AutoGenerateColumns="False"
IsReadOnly="True"
CanUserReorderColumns="True"
CanUserResizeColumns="True"
CanUserSortColumns="True"
>
<DataGrid.Columns>
<DataGridTextColumn
Header="Class"
Binding="{Binding ClassName}"
>
<DataGridTextColumn.HeaderTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" />
<Button
Name="buttonFieldViewClassFilter"
Margin="10,0,0,0"
Command="{Binding DataContext.ShowFilterCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}"
CommandParameter="{Binding Name, RelativeSource={RelativeSource Self}}"
>
<Button.ContentTemplate>
<DataTemplate>
<Image Source="/Images/filter.png" Width="10" Height="10" />
</DataTemplate>
</Button.ContentTemplate>
</Button>
</StackPanel>
</DataTemplate>
</DataGridTextColumn.HeaderTemplate>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</TabItem>
</TabControl>
原版
事实证明,OP 每个网格有多个过滤列,因此下面的解决方案不起作用:该命令不仅需要知道哪个网格,还需要知道网格中的哪一列。 DataGridColumn
不在可视化树中,因此我们不能将 x:Name
用作命令参数。我们可以使用 {RelativeSource AncestorType=DataGridColumnHeader}
并编写一个 returns DataGridColumnHeader.Column.Binding.Path.Path
的转换器——但是两个网格都有名为 ClassName
的列。接下来是传递该祖先以及网格本身的多重绑定。由于 OP 的原始解决方案涉及每列不同的内容 header 无论如何,我决定使用多个模板以简单但冗长的方式进行。
我做了一个会影响您的代码的重要更改: 由于两个 DataGrid 列的 header 内容现在由同一模板创建,名称两者的按钮现在相同。因此,CommandParameter
现在绑定到 DataGrid
.
的名称
<TabControl HorizontalAlignment="Left" Height="175" VerticalAlignment="Top" Width="716" Margin="10,0,0,0">
<TabControl.Resources>
<DataTemplate x:Key="FilterColumnHeaderTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" />
<Button
Name="buttonClassFilter"
Margin="10,0,0,0"
Command="{Binding DataContext.ShowFilterCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}"
CommandParameter="{Binding Name, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}"
>
<Button.ContentTemplate>
<DataTemplate>
<Rectangle Fill="DeepSkyBlue" Width="10" Height="10" />
</DataTemplate>
</Button.ContentTemplate>
</Button>
</StackPanel>
</DataTemplate>
</TabControl.Resources>
<TabItem Header="Class">
<DataGrid
x:Name="ClassViewDataGrid"
ItemsSource="{Binding FilteredClassViewItems}"
AutoGenerateColumns="False"
IsReadOnly="True"
CanUserReorderColumns="True"
CanUserResizeColumns="True"
CanUserSortColumns="True"
>
<DataGrid.Columns>
<DataGridTextColumn
Header="Class"
Binding="{Binding ClassName}"
HeaderTemplate="{StaticResource FilterColumnHeaderTemplate}"
/>
</DataGrid.Columns>
</DataGrid>
</TabItem>
<TabItem Header="Field">
<DataGrid
x:Name="FielsdViewDataGrid"
ItemsSource="{Binding FilteredFieldViewItems}"
AutoGenerateColumns="False"
IsReadOnly="True"
CanUserReorderColumns="True"
CanUserResizeColumns="True"
CanUserSortColumns="True"
>
<DataGrid.Columns>
<DataGridTextColumn
Header="Class"
Binding="{Binding ClassName}"
HeaderTemplate="{StaticResource FilterColumnHeaderTemplate}"
/>
</DataGrid.Columns>
</DataGrid>
</TabItem>
</TabControl>
如果您想改变两个不同 DataGrid 中列之间的 header 内容,您可以使用绑定来实现,或者在必要时创建第二个模板。
您也可以在 TabControl.Resources
中创建 done this with a BindingProxy
,但绑定代理是 last-ditch 我们在没有“正确”的方式做某事时使用的拼凑物。在这种情况下,“正确”的方法简单直接,令人满意。
我在 MVVM 中工作,我在 XAML 中创建了两个 TabItem
。在第一个中,显示弹出窗口,但在第二个中,当我按下对应于一列的按钮时,弹出窗口不显示。
这是我的工作弹出窗口代码:
<Viewbox>
<Grid Height="359" Width="746">
<Popup Name="popupFilter" Placement="MousePoint" IsOpen="{Binding IsFilterOpen, Mode=OneWay}" StaysOpen="True" Width="200">
<Border Background="White" BorderBrush="Gray" BorderThickness="1,1,1,1">
<StackPanel Margin="5,5,5,15">
<ListBox x:Name="listBoxPopupContent"
Height="250"
ItemsSource="{Binding FilterItems}"
BorderThickness="0"
ScrollViewer.VerticalScrollBarVisibility="Auto">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsChecked}"
Content="{Binding Item}"
Command="{Binding DataContext.ApplyFiltersCommand,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ListBox}}}"
CommandParameter="{Binding IsChecked,
RelativeSource={RelativeSource Self},
Mode=OneWay}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Border>
</Popup>
<Grid HorizontalAlignment="Left" Height="261" Margin="0,63,0,0" VerticalAlignment="Top" Width="736">
<TabControl HorizontalAlignment="Left" Height="175" VerticalAlignment="Top" Width="716" Margin="10,0,0,0">
<TabItem Header="Class">
<DataGrid x:Name="ClassViewDataGrid" ItemsSource="{Binding FilteredClassViewItems}"
AutoGenerateColumns="False"
IsReadOnly="True"
CanUserReorderColumns="True"
CanUserResizeColumns="True"
CanUserSortColumns="True">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding ClassName}">
<DataGridTextColumn.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Class" />
<Button Name="buttonClassViewClassFilter" Margin="10,0,0,0"
Command="{Binding DataContext.ShowFilterCommand,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type DataGrid}}}"
CommandParameter="{Binding Name, RelativeSource={RelativeSource Self}}">
<Button.ContentTemplate>
<DataTemplate>
<Image Source="/Images/filter.png" Width="10" Height="10" />
</DataTemplate>
</Button.ContentTemplate>
</Button>
</StackPanel>
</DataGridTextColumn.Header>
以及不显示弹出窗口的 tabItem:
<TabItem Header="Field">
<DataGrid x:Name="FielsdViewDataGrid" ItemsSource="{Binding FilteredFieldViewItems}"
AutoGenerateColumns="False"
IsReadOnly="True"
CanUserReorderColumns="True"
CanUserResizeColumns="True"
CanUserSortColumns="True">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding ClassName}">
<DataGridTextColumn.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Class" />
<Button Name="buttonFieldViewClassFilter" Margin="10,0,0,0"
Command="{Binding DataContext.ShowFilterCommand,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type DataGrid}}}"
CommandParameter="{Binding Name, RelativeSource={RelativeSource Self}}">
<Button.ContentTemplate>
<DataTemplate>
<Image Source="/Images/filter.png" Width="10" Height="10" />
</DataTemplate>
</Button.ContentTemplate>
</Button>
</StackPanel>
</DataGridTextColumn.Header>
...
第二个tabItem
的定义和第一个一样,但是好像没有达到Binding DataContext.ShowFilterCommand
。我试图在那里进行调试,但没有达到。
方法如下:
private void ShowFilterCommandRaised(object obj)
{
IsFilterOpen = !IsFilterOpen;
str = obj;
if (IsFilterOpen)
{
if (str.Equals("buttonClassViewClassFilter"))
{
FilterItems.Clear();
foreach (var classView in classViewItems)
{
FilterItems.Add(new CheckedListItem<string>(classView.ClassName, true));
}
}
if (str.Equals("buttonClassViewExtendsFilter"))
{
FilterItems.Clear();
foreach (var classView in classViewItems)
{
FilterItems.Add(new CheckedListItem<string>(classView.Category, true));
}
}
if (str.Equals("buttonFieldViewClassFilter"))
{
FilterItems.Clear();
foreach (var fieldView in fieldViewItems)
{
FilterItems.Add(new CheckedListItem<string>(fieldView.ClassName, true));
}
}
}
我做错了什么?
如果您调换两个 TabItem 的顺序,或在 TabControl 上设置 SelectedIndex="1"
,您会发现最初可见的始终是唯一有效的。我将 PresentationTraceSources.TraceLevel=High
添加到 Command
绑定中,发现启动时隐藏的 TabItem 中的绑定最初尝试解析其来源 属性,并且当该 TabItem 变为时不再尝试活跃。
问题在于,在第一次尝试解析隐藏选项卡项中的绑定时,该 TabItem 的 UI 的 none 实际上还存在。由于虚拟化,它不会在需要时创建。所创建的只是它的 Header 内容,挂在 space 中。 DataGrid 尚不存在,因此 AncestorType 搜索永远找不到它。最终创建 DataGrid 时,似乎没有引发任何事件来通知 Binding 它需要重复祖先搜索。
解决这个问题很简单:通过 DataTemplate 创建 header 内容。无论如何,这是正确的方法。 DataGrid 显示时将实例化 DataTemplate。
我们将使用一个值转换器来创建一个标识符,该标识符告诉命令用户单击了哪个网格和列。请注意,我们现在为每一列的 Header 属性 提供一个纯字符串,并且模板中的 TextBlock
更改为 <TextBlock Text="{Binding}" />
.
XAML
<TabControl HorizontalAlignment="Left" Height="175" VerticalAlignment="Top" Width="716" Margin="10,0,0,0">
<TabControl.Resources>
<local:GetColumnIdentifier x:Key="GetColumnIdentifier" />
<DataTemplate x:Key="FilterColumnHeaderTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" />
<Button
Margin="10,0,0,0"
Command="{Binding DataContext.ShowFilterCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}"
CommandParameter="{Binding Converter={StaticResource GetColumnIdentifier}, RelativeSource={RelativeSource Self}}"
>
<Button.ContentTemplate>
<DataTemplate>
<Image Source="/Images/filter.png" Width="10" Height="10" />
</DataTemplate>
</Button.ContentTemplate>
</Button>
</StackPanel>
</DataTemplate>
</TabControl.Resources>
<TabItem Header="Class">
<DataGrid
x:Name="ClassViewDataGrid"
ItemsSource="{Binding FilteredClassViewItems}"
AutoGenerateColumns="False"
IsReadOnly="True"
CanUserReorderColumns="True"
CanUserResizeColumns="True"
CanUserSortColumns="True"
>
<DataGrid.Columns>
<DataGridTextColumn
Header="Class"
Binding="{Binding ClassName}"
HeaderTemplate="{StaticResource FilterColumnHeaderTemplate}"
/>
</DataGrid.Columns>
</DataGrid>
</TabItem>
<TabItem Header="Field">
<DataGrid
x:Name="FielsdViewDataGrid"
ItemsSource="{Binding FilteredFieldViewItems}"
AutoGenerateColumns="False"
IsReadOnly="True"
CanUserReorderColumns="True"
CanUserResizeColumns="True"
CanUserSortColumns="True"
>
<DataGrid.Columns>
<DataGridTextColumn
Header="Class"
Binding="{Binding ClassName}"
HeaderTemplate="{StaticResource FilterColumnHeaderTemplate}"
/>
</DataGrid.Columns>
</DataGrid>
</TabItem>
</TabControl>
值转换器:
//using System.Windows.Markup.Primitives;
//using System.Windows.Controls.Primitives;
public class GetColumnIdentifier : IValueConverter
{
private static T GetVisualAncestor<T>(DependencyObject obj)
where T : DependencyObject
{
while (obj != null)
{
if (obj is T)
return obj as T;
else
obj = VisualTreeHelper.GetParent(obj);
}
return null;
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// Not all DataGridColumn subclasses have a Binding property.
var header = GetVisualAncestor<DataGridColumnHeader>((DependencyObject)value);
var datagrid = GetVisualAncestor<DataGrid>(header);
if (header?.Column != null)
{
MarkupObject markupObject = MarkupWriter.GetMarkupObjectFor(header.Column);
var bindingProp = markupObject.Properties.FirstOrDefault(p => p.Name == "Binding");
if (bindingProp?.Value is Binding binding)
{
return $"{datagrid?.Name}.{binding.Path.Path}";
}
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
如果您有多个列绑定到同一个路径,并且您需要区分它们(在这种情况下,您可以对大多数列使用上述解决方案,但为奇怪的特殊情况):
<TabControl HorizontalAlignment="Left" Height="175" VerticalAlignment="Top" Width="716" Margin="10,0,0,0">
<TabItem Header="Class">
<DataGrid
x:Name="ClassViewDataGrid"
ItemsSource="{Binding FilteredClassViewItems}"
AutoGenerateColumns="False"
IsReadOnly="True"
CanUserReorderColumns="True"
CanUserResizeColumns="True"
CanUserSortColumns="True"
>
<DataGrid.Columns>
<DataGridTextColumn
Header="Class"
Binding="{Binding ClassName}"
>
<DataGridTextColumn.HeaderTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" />
<Button
Name="buttonClassViewClassFilter"
Margin="10,0,0,0"
Command="{Binding DataContext.ShowFilterCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}"
CommandParameter="{Binding Name, RelativeSource={RelativeSource Self}}"
>
<Button.ContentTemplate>
<DataTemplate>
<Image Source="/Images/filter.png" Width="10" Height="10" />
</DataTemplate>
</Button.ContentTemplate>
</Button>
</StackPanel>
</DataTemplate>
</DataGridTextColumn.HeaderTemplate>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</TabItem>
<TabItem Header="Field">
<DataGrid
x:Name="FielsdViewDataGrid"
ItemsSource="{Binding FilteredFieldViewItems}"
AutoGenerateColumns="False"
IsReadOnly="True"
CanUserReorderColumns="True"
CanUserResizeColumns="True"
CanUserSortColumns="True"
>
<DataGrid.Columns>
<DataGridTextColumn
Header="Class"
Binding="{Binding ClassName}"
>
<DataGridTextColumn.HeaderTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" />
<Button
Name="buttonFieldViewClassFilter"
Margin="10,0,0,0"
Command="{Binding DataContext.ShowFilterCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}"
CommandParameter="{Binding Name, RelativeSource={RelativeSource Self}}"
>
<Button.ContentTemplate>
<DataTemplate>
<Image Source="/Images/filter.png" Width="10" Height="10" />
</DataTemplate>
</Button.ContentTemplate>
</Button>
</StackPanel>
</DataTemplate>
</DataGridTextColumn.HeaderTemplate>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</TabItem>
</TabControl>
原版
事实证明,OP 每个网格有多个过滤列,因此下面的解决方案不起作用:该命令不仅需要知道哪个网格,还需要知道网格中的哪一列。 DataGridColumn
不在可视化树中,因此我们不能将 x:Name
用作命令参数。我们可以使用 {RelativeSource AncestorType=DataGridColumnHeader}
并编写一个 returns DataGridColumnHeader.Column.Binding.Path.Path
的转换器——但是两个网格都有名为 ClassName
的列。接下来是传递该祖先以及网格本身的多重绑定。由于 OP 的原始解决方案涉及每列不同的内容 header 无论如何,我决定使用多个模板以简单但冗长的方式进行。
我做了一个会影响您的代码的重要更改: 由于两个 DataGrid 列的 header 内容现在由同一模板创建,名称两者的按钮现在相同。因此,CommandParameter
现在绑定到 DataGrid
.
<TabControl HorizontalAlignment="Left" Height="175" VerticalAlignment="Top" Width="716" Margin="10,0,0,0">
<TabControl.Resources>
<DataTemplate x:Key="FilterColumnHeaderTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" />
<Button
Name="buttonClassFilter"
Margin="10,0,0,0"
Command="{Binding DataContext.ShowFilterCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}"
CommandParameter="{Binding Name, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}"
>
<Button.ContentTemplate>
<DataTemplate>
<Rectangle Fill="DeepSkyBlue" Width="10" Height="10" />
</DataTemplate>
</Button.ContentTemplate>
</Button>
</StackPanel>
</DataTemplate>
</TabControl.Resources>
<TabItem Header="Class">
<DataGrid
x:Name="ClassViewDataGrid"
ItemsSource="{Binding FilteredClassViewItems}"
AutoGenerateColumns="False"
IsReadOnly="True"
CanUserReorderColumns="True"
CanUserResizeColumns="True"
CanUserSortColumns="True"
>
<DataGrid.Columns>
<DataGridTextColumn
Header="Class"
Binding="{Binding ClassName}"
HeaderTemplate="{StaticResource FilterColumnHeaderTemplate}"
/>
</DataGrid.Columns>
</DataGrid>
</TabItem>
<TabItem Header="Field">
<DataGrid
x:Name="FielsdViewDataGrid"
ItemsSource="{Binding FilteredFieldViewItems}"
AutoGenerateColumns="False"
IsReadOnly="True"
CanUserReorderColumns="True"
CanUserResizeColumns="True"
CanUserSortColumns="True"
>
<DataGrid.Columns>
<DataGridTextColumn
Header="Class"
Binding="{Binding ClassName}"
HeaderTemplate="{StaticResource FilterColumnHeaderTemplate}"
/>
</DataGrid.Columns>
</DataGrid>
</TabItem>
</TabControl>
如果您想改变两个不同 DataGrid 中列之间的 header 内容,您可以使用绑定来实现,或者在必要时创建第二个模板。
您也可以在 TabControl.Resources
中创建 done this with a BindingProxy
,但绑定代理是 last-ditch 我们在没有“正确”的方式做某事时使用的拼凑物。在这种情况下,“正确”的方法简单直接,令人满意。