WPF DataGrid: привязка DataGridColumn к ContextMenu MenuItems IsChecked (MVVM)

Я хочу контролировать видимость столбца DataGrid через ContextMenu, доступную пользователю, щелкнув правой кнопкой мыши заголовок столбца. ContextMenu отображает имена всех доступных столбцов. Я использую шаблон MVVM.

Мой вопрос: Как привязать свойство DataGridColumn Visibility к свойству IsChecked объекта MenuItem, расположенному в ContextMenu.

Некоторые макеты кода:

<UserControl.Resources>         
    <ContextMenu x:Key="ColumnHeaderContextMenu">  
        <MenuItem Header="Menu Item..1" IsCheckable="True" />  
    </ContextMenu>  
    <Style x:Key="ColumnHeaderStyle" 
           TargetType="{x:Type toolkit:DataGridColumnHeader}">  
        <Setter Property="ContextMenu" 
                Value="{StaticResource ColumnHeaderContextMenu}" />  
    </Style>  
    <BooleanToVisibilityConverter x:Key="booleanToVisibilityConverter" />  
</UserControl.Resources>  

... flaf flaf flaf

<toolkit:DataGrid x:Name="MyGrid" AutoGenerateColumns="False" 
    ItemsSource="{Binding MyCollection, Mode=Default}" 
    EnableColumnVirtualization="True" IsReadOnly="True" 
    ColumnHeaderStyle="{StaticResource ColumnHeaderStyle}">  
    <toolkit:DataGrid.Columns>  
        <toolkit:DataGridTextColumn Binding="{Binding Path=MyEntry}" 
            Header="MyEntry" Visibility="{Binding IsChecked, Converter=
                {StaticResource booleanToVisibilityConverter}.... />
    </toolkit:DataGrid.Columns>     
</toolkit:DataGrid>  

Если я неясен, пожалуйста, дайте мне знать, и я попытаюсь уточнить.

Приветствия,

Ответы

Ответ 1

Я только что написал сообщение в блоге по этой теме. Он позволяет отображать или скрывать DataGridColumn через ContextMenu, доступ к которому можно щелкнуть правой кнопкой мыши по любому заголовку столбца. Эта задача выполняется исключительно через прикрепленные свойства, поэтому она совместима с MVVM.

Посмотреть сообщение в блоге

Ответ 2

Я искал общий, XAML (т.е. без кода), автоматический и простой пример контекстного меню выбора столбца, который привязывается к заголовку столбца WPF DataGrid. Я прочитал буквально сотни статей, но ни один из них, похоже, не делает правильных действий, или они не являются достаточно обобщенными. Итак, вот что я считаю лучшим комбинированным решением:

Сначала поместите их в словарь ресурсов. Я оставлю это как упражнение для чтения, чтобы написать Видимость/Логический конвертер, чтобы убедиться, что флажки отмечены, когда столбец виден и наоборот. Обратите внимание, что, определяя x: Shared = "False" для ресурса контекстного меню, он получит конкретное состояние экземпляра, что означает, что вы можете использовать этот единственный шаблон/ресурс для всего вашего datagrid, и все они будут поддерживать свое собственное состояние.

<Converters:VisiblityToInverseBooleanConverter x:Key="VisiblityToInverseBooleanConverter"/>

<ContextMenu x:Key="ColumnChooserMenu" x:Shared="False"
             DataContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource Self}}" 
             ItemsSource="{Binding Columns, RelativeSource={RelativeSource AncestorType={x:Type sdk:DataGrid}}}">
    <ContextMenu.ItemContainerStyle>
        <Style TargetType="MenuItem">
            <Setter Property="Header" Value="{Binding Header}"/>
            <Setter Property="AutomationProperties.Name" Value="{Binding Header}"/>
            <Setter Property="IsCheckable" Value="True" />
            <Setter Property="IsChecked" Value="{Binding Visibility, Mode=TwoWay, Converter={StaticResource VisiblityToInverseBooleanConverter}}" />
        </Style>
    </ContextMenu.ItemContainerStyle>
</ContextMenu>

<Style x:Key="ColumnHeaderStyle" TargetType="{x:Type Primitives:DataGridColumnHeader}">
    <Setter Property="ContextMenu" Value="{StaticResource ColumnChooserMenu}" />
</Style>

<ContextMenu x:Key="GridItemsContextMenu" >
    <MenuItem Header="Launch Do Some other action"/>
</ContextMenu>

Затем определите DataGrid следующим образом (где OrdersQuery - это некоторый источник данных, открытый моделью View):

<sdk:DataGrid ItemsSource="{Binding OrdersQuery}"
              AutoGenerateColumns="True" 
              ColumnHeaderStyle="{StaticResource ColumnHeaderStyle}"
              ContextMenu="{StaticResource GridItemsContextMenu}">

  <!-- rest of datagrid stuff goes here -->

</sdk:DataGrid>

Это даст вам следующее:

  • Контекстное меню, связанное с заголовками столбцов, которое выступает в качестве выбора столбца.
  • Контекстное меню, связанное с элементами в сетке (для выполнения действий над самими элементами - опять же, привязка действий - упражнение для читателя).

Надеюсь, это поможет людям, которые искали такой пример.

Ответ 3

Я знаю, что это немного устарело. Но я смотрел на это, и этот пост намного проще: http://iimaginec.wordpress.com/2011/07/25/binding-wpf-toolkit%E2%80%99s-datagridcolumn-to-a-viewmodel-datacontext-propogation-for-datagrid-columns-the-mvvm-way-to-interact-with-datagridcolumn/

Все, что вам нужно сделать, - установить DataContext в столбцах, а затем привязать видимость к вашей модели ViewModel в соответствии с нормальным! :) Простой и эффективный

Ответ 4

Хорошо, это было довольно упражнение для WPF n00b.

IanR благодарит за предложение, я использовал аналогичный aproach, но он доцент добирается до вас.

Вот что я придумал, если кто-то может найти более последовательный способ сделать это. Я буду благодарен за любые комментарии:

Препятствия:

  • DataGridColumnHeader не поддерживает контекстное меню. Поэтому контекстное меню должно применяться как стиль.

  • В контекстном меню есть свой собственный datacontext, поэтому нам нужно использовать findancestor для привязки его к datacontext ViewModels.

  • ATM. Элемент управления DataGrid не анализирует его datacontext в своих столбцах. Это можно было бы решить в коде, однако мы используем шаблон MVVM, поэтому я решил следовать jamiers

Решение:

Поместите следующие два блока кода в Window.Resources

    <Style x:Key="ColumnHeaderStyle"
           TargetType="{x:Type toolkit:DataGridColumnHeader}">
        <Setter Property="ContextMenu" 
                Value="{StaticResource ColumnHeaderContextMenu}" />
    </Style>  

    <ContextMenu x:Key="ColumnHeaderContextMenu">
        <MenuItem x:Name="MyMenuItem"
                  IsCheckable="True"
                  IsChecked="{Binding DataContext.IsHidden, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type toolkit:DataGrid}}}"/>
    </ContextMenu>

Затем datagrid выглядит примерно так в XAML

        <toolkit:DataGrid x:Name="MyGrid"
                          AutoGenerateColumns="False"
                          ItemsSource="{Binding SampleCollection, Mode=Default}"
                          EnableColumnVirtualization="True"
                          IsReadOnly="True"
                          ColumnHeaderStyle="{StaticResource ColumnHeaderStyle}">
            <toolkit:DataGrid.Columns>
                <toolkit:DataGridTextColumn Binding="{Binding Path=SamplingUser}"
                                            Header="{Binding (FrameworkElement.DataContext).IsHidden, RelativeSource={x:Static RelativeSource.Self}}"
                                            Visibility="{Binding (FrameworkElement.DataContext).IsHidden,
                                                RelativeSource={x:Static RelativeSource.Self},
                                                Converter={StaticResource booleanToVisibilityConverter}}"/>

Таким образом, свойство visibility на DataGridColumn и свойство ischeked являются привязкой к привязке к свойству IsHidden в viewModel.

В ViewModel:

    public bool IsHidden
    {
        get { return isHidden; }
        set 
        { if (value != isHidden)
            {
                isHidden = value;
                OnPropertyChanged("IsHidden");
                OnPropertyChanged("IsVisible");
            }
        }
    }  

Класс Helper, определенный Jaimer:

class DataGridSupport
{
    static DataGridSupport() 
    {

        DependencyProperty dp = FrameworkElement.DataContextProperty.AddOwner(typeof(DataGridColumn)); 
        FrameworkElement.DataContextProperty.OverrideMetadata ( typeof(DataGrid), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits, new PropertyChangedCallback(OnDataContextChanged)));

    }

    public static void OnDataContextChanged ( DependencyObject d, DependencyPropertyChangedEventArgs e)
    { 
        DataGrid grid = d as DataGrid ; 
        if ( grid != null  ) 
        {                 
            foreach ( DataGridColumn col in grid.Columns ) 
            { 
                col.SetValue ( FrameworkElement.DataContextProperty,  e.NewValue ); 
            } 
        } 
    }
}

Определено в viewmodel (просто для того, чтобы показать, сделанное через Unity в реальном проекте)

    private static DataGridSupport dc = new DataGridSupport();

Приветствия,

Ответ 5

Вместо booleanToVisibilityConverter вы можете использовать x: static

<Setter TargetName="UIElement"  Property="UIElement.Visibility" Value="x:Static Visibility.Hidden" />

Статичность в XAML: http://msdn.microsoft.com/en-us/library/ms742135.aspx

Ответ 6

Я попытался привязать его к ContextMenu, используя "ElementName", но в конце концов, получил его работу с помощью "Свойства" в VM, например

bool _isHidden;
public bool IsHidden
{
  get { return _isHidden; }
  set
  {
    if (value != _isHidden)
    {
      _isHidden = value;
      RaisePropertyChanged("IsHidden");
      RaisePropertyChanged("IsVisible");
    }
  }
}

public Visibility IsVisible
{
  get { return IsHidden ? Visibility.Hidden : Visibility.Visible; }
}

и в XAML:

<Window.ContextMenu>
  <ContextMenu>
    <MenuItem Header="Hidden" IsCheckable="True" IsChecked="{Binding IsHidden}" />
  </ContextMenu>
</Window.ContextMenu>

<toolkit:DataGrid x:Name="MyGrid" AutoGenerateColumns="False" ItemsSource="{Binding MyCollection, Mode=Default}" EnableColumnVirtualization="True" IsReadOnly="True" ColumnHeaderStyle="{StaticResource ColumnHeaderStyle}">
  <toolkit:DataGrid.Columns>
    <toolkit:DataGridTextColumn Binding="{Binding Path=MyEntry}" Header="MyEntry" Visibility="{Binding Path=IsVisible, Mode=OneWay}" />
  </toolkit:DataGrid.Columns>
</toolkit:DataGrid>