【问题标题】:C# - Popup is not displayed in second tab item in MVVMC# - MVVM 的第二个选项卡项中不显示弹出窗口
【发布时间】:2018-04-15 00:41:40
【问题描述】:

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

我做错了什么?

【问题讨论】:

  • 我是否正确理解您将 XAML 包含在有效而不是无效的 XAML 中?
  • @EdPlunkett ,我现在都包含了它们
  • 感谢您的更新。

标签: c# wpf mvvm binding tabitem


【解决方案1】:

如果你交换两个 TabItem 的顺序,或者在 TabControl 上设置SelectedIndex="1",你会发现最初可见的总是唯一有效的。我将PresentationTraceSources.TraceLevel=High 添加到Command 绑定中,发现启动时隐藏的TabItem 中的绑定最初尝试解析其源属性,并且当该TabItem 变为活动状态时不再尝试。

问题在于,在第一次尝试解决隐藏选项卡项中的绑定时,该选项卡项的 UI 尚不存在。由于虚拟化,在需要之前不会创建它。创建的只是它的 Header 内容,悬挂在空间中。 DataGrid 还不存在,因此 AncestorType 搜索永远找不到它。最终创建 DataGrid 时,似乎没有引发任何事件来通知 Binding 它需要重复祖先搜索。

解决这个问题很简单:通过 DataTemplate 创建标题内容。无论如何,这是正确的方法。显示 DataGrid 时,将实例化 DataTemplate。

我们将使用一个值转换器来创建一个标识符,该标识符告诉命令用户单击了哪个网格和列。请注意,我们现在为每列的 Header 属性提供一个纯字符串,并且模板中的 TextBlock 更改为 &lt;TextBlock Text="{Binding}" /&gt;

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>

如果您想改变两个不同 DataGrid 中的列之间的标题内容,您可以使用绑定来实现,或者在必要时创建第二个模板。

您也可以在TabControl.Resources 中创建done this with a BindingProxy,但是绑定代理是我们在没有“正确”方式做某事时使用的最后一招。在这种情况下,“正确”的方式是简单直接且完全令人满意的。

【讨论】:

  • 它是否也可以绑定到两个项目的父项,例如TabControl 本身?在评估绑定时不会创建 DataGrid,但应该创建 TabControl。
  • @Rachel 那没用。从标题内容到 TabControl 的可视化树链通过 DataGrid 引导——如果它还不存在,那么你就不走运了。 ElementName=MyTabControl 也不起作用(我没有忘记给 TabControl 起这个名字!),也可能是因为它们不在同一个可视化树中。在 OP 的原始 XAML 中,DataGridTextColumn.Header 中的 StackPanel 是绑定尝试解析时的事件视界。
  • 啊,好的,很高兴知道。我知道我过去在列标题中遇到过数据绑定问题,因为它们似乎不像其他控件那样继承 DataContext。很高兴知道这个解决方案:)
  • @EdPlunkett 好的。感谢您的完整答案。但我希望每个 PopUp 都有不同的内容,这就是为什么我在 C# 代码中使用按钮的名称.. 知道我单击了哪个按钮并相应地填写列表。我现在可以得到这个方面吗?
  • 这就是为什么我需要确定我点击了哪个按钮
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-10
相关资源
最近更新 更多