我在MVVM工作,我在XAML中创建了两个TabItem
。在第一个中,显示弹出窗口,但在第二个窗口中,当我按下与列对应的按钮时,不显示弹出窗口。
以下是我使用Popup的代码:
<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));
}
}
}
我做错了什么?
答案 0 :(得分:2)
如果您交换两个TabItem的顺序,或者在TabControl上设置SelectedIndex="1"
,您会发现最初可见的那个是唯一有效的。我将PresentationTraceSources.TraceLevel=High
添加到Command
绑定中,发现启动时隐藏的TabItem中的绑定尝试最初解析其源属性,并且当该TabItem变为活动状态时不再尝试。
问题在于,在第一次尝试解析隐藏选项卡项中的绑定时,该TabItem的UI实际上还没有存在。由于虚拟化,在需要之前不会创建它。所有创建的都是它的Header内容,悬挂在空间中。 DataGrid尚不存在,因此AncestorType搜索永远不会找到它。最终创建DataGrid时,似乎没有引发事件通知Binding它需要重复祖先搜索。
对此的修复很简单:通过DataTemplate创建标题内容。无论如何,这是正确的方法。 DataTemplate将在显示DataGrid时实例化。
我们将使用值转换器创建一个标识符,告诉命令用户单击了哪个网格和列。请注意,我们现在为每个列提供其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}
并编写一个返回DataGridColumnHeader.Column.Binding.Path.Path
的转换器 - 但这两个网格都有名为ClassName
的列。接下来是一个多绑定,它传递的是祖先,也是网格本身。由于OP的原始解决方案无论如何都涉及每列标题的不同内容,因此我决定使用多个模板以简单而冗长的方式进行。
我做了一个会影响代码的重要更改:由于DataGrid列的标题内容现在都是由同一个模板创建的,因此两个按钮的名称现在都相同。因此,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>
如果要更改两个不同DataGrids中列之间的标题内容,可以使用绑定执行此操作,或者根据需要创建第二个模板。
您可以在TabControl.Resources
中创建done this with a BindingProxy
,但绑定代理是我们在没有“正确”方式执行某些操作时使用的最后沟渠。在这种情况下,“正确”的方式既简单又直接,完全令人满意。