C# - 弹出窗口未显示在MVVM的第二个标签项中

时间:2017-11-02 13:08:09

标签: c# wpf mvvm binding tabitem

我在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));
            }
        }
    }

我做错了什么?

1 个答案:

答案 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,但绑定代理是我们在没有“正确”方式执行某些操作时使用的最后沟渠。在这种情况下,“正确”的方式既简单又直接,完全令人满意。