【问题标题】:How can I pass any DataGridTextColumn to a single Command which will toggle the Visibility of the DataGridTextColumn?如何将任何 DataGridTextColumn 传递给将切换 DataGridTextColumn 的可见性的单个命令?
【发布时间】:2020-02-07 04:14:36
【问题描述】:

我有一个 DataGrid,并希望使用从 ContextMenu 发送的命令来切换各个 DataGridTextColumns 的可见性。我需要某种方法将特定的 DataGridTextColumn 或其 Visibility 参数与 ContextMenu MenuItem 命令相关联。我可以在我的 ViewModel 中设置一个单独的可见性变量,并使用单独的命令切换它们,每个 DataGridTextColumn 一个,效果很好,但是我有很多 DataGridTextColumns,这似乎是一种非常重复、混乱且可能不正确的解决问题的方法.

示例 .xaml:

 <FrameworkElement x:Name="dummyElement" Visibility="Collapsed"/>

            <DataGrid ItemsSource="{Binding Shots}" SelectedItem="{Binding SelectedShot, Mode=TwoWay}" AutoGenerateColumns="False" HorizontalScrollBarVisibility="Auto" IsReadOnly="True" AreRowDetailsFrozen="True" HeadersVisibility="All" >

                <DataGrid.Columns>
                    <DataGridTextColumn Visibility="{Binding DataContext.ShotNumberColumnVisibility, Source={x:Reference dummyElement}}" Binding="{Binding Path=ShotNumber}" Header="Shot #" />
                </DataGrid.Columns>

                <DataGrid.ContextMenu>
                    <ContextMenu>
                        <MenuItem Header="Toggle Visibility">
                            <MenuItem Header="Shot Count" Command="{Binding ToggleVisibilityCommand}" />
                        </MenuItem>
                    </ContextMenu>
                </DataGrid.ContextMenu>

            </DataGrid >

目前,我的 View .xaml 看起来像上面的示例,但有更多的列和每个对应的 ContextMenu MenuItem。在我的 ViewModel 中,我可以通过更改 ShotNumberVisibility 来控制可见性。


public MyViewModel()
{
    ToggleVisibilityCommand = new RelayCommand(ToggleVisibility);
}


public Visibility ShotNumberColumnVisibility { get; set; } = Visibility.Visible;


public void ToggleVisibility(object obj)
{
    ShotNumberColumnVisibility = ShotNumberColumnVisibility == Visibility.Visible ? Visibility.Collapsed : Visibility.Visible;
    RaisePropertyChanged("ShotNumberColumnVisibility");
}

我不想为每个单独的 DataGridTextColumn 进行设置。将任何 DataGridTextColumn 传递给我的 ViewModel 以便可以使用通用方法切换其可见性的正确方法是什么?

从我所见,听起来我需要能够使用 CommandParameter 将任何 DataGridTextColumn 发送到我的 ToggleVisibility 函数。这是我想不通的部分。我在我的 .xaml 中考虑类似以下内容,但我还没有让它工作。

CommandParameter="{Binding ElementName=InclinationColumn, Path=Visibility}"

如果还不清楚,这里有一些我想要的命令的伪代码以及我想如何使用它。

<DataGridTextColumn Name="demoColumn" Visibility="{Binding demoColumnVisibility}" />
<MenuItem Header="Toggle Demo Column Visibility" CommandParameter="{Binding demoColumn.Visibility}" Command="{Binding ToggleVisibility}" />

public void ToggleVisibility(object obj)
{
    obj.Visibility = !obj.Visibility
    //OR MAYBE
    //Pass in the name "demoColumn" and use that select which bool to flip. In this case demoColumnVisibility


}

这是我的 RelayCommand:ICommand 类的样子...

 public class RelayCommand : ICommand
    {
        readonly Action<object> _execute;
        readonly Predicate<object> _canExecute;

        public RelayCommand(Action<object> execute, Predicate<object> canExecute)
        {
            if(execute == null)
            {
                throw new NullReferenceException("execute");
            }
            _execute = execute;
            _canExecute = canExecute;
        }

        public RelayCommand(Action<object> execute) : this(execute, null)
        {
        }


        public event EventHandler CanExecuteChanged
        { 
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        public bool CanExecute(object parameter)
        {
            return _canExecute == null ? true : _canExecute(parameter);
        }

        public void Execute(object parameter)
        {
            _execute.Invoke(parameter);
        }
    }

希望这已经足够了,这个问题已经困扰了我好几个小时,我觉得我错过了一些基本的东西。非常感谢任何帮助。

【问题讨论】:

    标签: c# wpf mvvm data-binding commandparameter


    【解决方案1】:

    鉴于上述 Michal 的回复,我重新构建了 MenuItems 的语言,而不是提供一个按钮来切换每个 DataGridTextColumn 的可见性,我现在提供“显示全部”和“隐藏所选内容”。有了这个,用户可以控制+选择多个单元格来指示要隐藏哪些列。要返回基本状态,Show All 按钮将所有 Visibility 设置为 Visible。这个新设置还允许我使用单个单元格的选择来引用任何行以对其执行操作。就我而言,我需要能够删除行,这些行是我的 ObservableCollection 中的条目。

    支持此行为的 .xaml 更改是:

    <DataGrid x:Name="RollTestDataGrid" SelectionUnit="Cell" ItemsSource="{Binding Shots, IsAsync=True}" SelectedIndex="{Binding SelectedShot, Mode=TwoWay}"  AutoGenerateColumns="False" HorizontalScrollBarVisibility="Auto" IsReadOnly="True" AreRowDetailsFrozen="True" HeadersVisibility="All" >
    

    还有……

    <DataGrid.ContextMenu>
        <ContextMenu>
            <MenuItem Header="Toggle Visibility">
                <MenuItem Header="Show All" Name="ShowAllToggle" Click="ShowAllToggle_Click" />
                <MenuItem Header="Hide Selected" Name="HideSelectedButton" Click="HideSelectedButton_Click"/>
            </MenuItem>
        </ContextMenu>
    </DataGrid.ContextMenu>
    

    为我的 SelectionUnit 选择“单元格”,让我可以访问一个元素,我可以从中派生相关的列。然后在后面的代码中,我只是遍历这些并将它们的可见性模式切换为折叠。

    在我的 .xaml.cs 中,我有两个“点击”方法。

    private void ShowAllToggle_Click(object sender, RoutedEventArgs e)
    {
        foreach (DataGridTextColumn col in RollTestDataGrid.Columns)
        {
            col.Visibility = Visibility.Visible;
        }
    }
    
    private void HideSelectedButton_Click(object sender, RoutedEventArgs e)
    {
        foreach (DataGridCellInfo cell in RollTestDataGrid.SelectedCells)
        {
            cell.Column.Visibility = Visibility.Collapsed;
        }
    }
    
    

    我在 ViewModel 中还有一个“DeleteShot”方法,这就是为什么我更新的 DataGrid .xaml 在 ItemsSource 中添加了 Name 和 IsAsync=True 属性。

    x:Name="RollTestDataGrid" SelectionUnit="Cell" ItemsSource="{Binding Shots, IsAsync=True}" 
    

    IsAsync 允许我调用我的 DeleteShot 命令,从我的 ObservableCollection 中删除一个项目,更新我的 ObservableCollection 中每个项目的“shotNumber”属性,并让 DataGrid 更新以正确显示“Shot #”列,而无需到 .xaml.cs 中的 DataGrid.Items.Refresh()

    .xaml

    <MenuItem Header="Delete" Command="{Binding DataContext.DeleteShotCommand, Source={x:Reference dummyElement}}"
    

    .ViewModel

    
    public RelayCommand DeleteShotCommand { get; private set; }
    
    DeleteShotCommand = new RelayCommand(DeleteShot);
    
    
    public void DeleteShot(object obj)
    {
        Shots.RemoveAt(SelectedIdx);
        foreach(nsbRollShot shot in shots)
        {
            shot.ShotNumber = shots.IndexOf(shot) + 1;
        }
        NotifyPropertyChanged("Shots");
    }
    

    我认为我正确地复制/粘贴了所有内容,我会继续检查以回答出现的任何问题。

    【讨论】:

      【解决方案2】:

      您可以在后面的代码中生成ContextMenu 及其功能。我知道这不是 MVVM 方式,但老实说,显示和隐藏列真的与业务逻辑有关吗?我认为这只是 UI 的东西,因此,它不必在视图模型中。这是一个例子:

      MainWindow.xaml

      <Window x:Class="GridColumnVisibilityToggle.MainWindow"
              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
              xmlns:s="clr-namespace:System;assembly=System.Runtime"
              mc:Ignorable="d"
              Title="MainWindow" Height="450" Width="800">
          <Grid>
              <DataGrid x:Name="TheDataGrid"
                        AutoGenerateColumns="False" 
                        HorizontalScrollBarVisibility="Auto" 
                        IsReadOnly="True" 
                        AreRowDetailsFrozen="True" 
                        HeadersVisibility="All" >
      
                  <DataGrid.ItemsSource>
                      <x:Array Type="{x:Type s:String}">
                          <s:String>Item 1</s:String>
                          <s:String>Item 2</s:String>
                          <s:String>Item 3</s:String>
                      </x:Array>
                  </DataGrid.ItemsSource>
      
                  <DataGrid.Columns>
                      <DataGridTextColumn Binding="{Binding .}" Header="Header" />
                  </DataGrid.Columns>
      
              </DataGrid >
          </Grid>
      </Window>
      

      MainWindow.xaml.cs

      using System;
      using System.Linq;
      using System.Windows;
      using System.Windows.Controls;
      
      namespace GridColumnVisibilityToggle
      {
          public partial class MainWindow : Window
          {
              public MainWindow()
              {
                  InitializeComponent();
              }
      
              protected override void OnContentRendered(EventArgs e)
              {
                  base.OnContentRendered(e);
      
                  var cm = new ContextMenu();
                  var visibilityItem = new MenuItem { Header = "Toggle Visibility" };
                  var columnItems = TheDataGrid.Columns.Select(a => new MenuItem
                  {
                      Header = a.Header,
                      Command = new RelayCommand<DataGridColumn>(column => column.Visibility = column.Visibility == Visibility.Visible ? Visibility.Hidden : Visibility.Visible),
                      CommandParameter = a
                  });
                  foreach (var item in columnItems)
                  {
                      visibilityItem.Items.Add(item);
                  }
                  cm.Items.Add(visibilityItem);
                  TheDataGrid.ContextMenu = cm;
              }
          }
      }
      

      我使用过的 ICommand 实现

      using System;
      using System.Reflection;
      using System.Windows.Input;
      
      namespace GridColumnVisibilityToggle
      {
          public class RelayCommand : ICommand
          {
              private readonly Func<object, bool> _canExecute;
              private readonly Action<object> _execute;
      
              public RelayCommand(Action<object> execute)
              {
                  if (execute == null)
                  {
                      throw new ArgumentNullException(nameof(execute));
                  }
      
                  _execute = execute;
              }
      
              public RelayCommand(Action execute)
                : this((Action<object>)(o => execute()))
              {
                  if (execute == null)
                  {
                      throw new ArgumentNullException(nameof(execute));
                  }
              }
      
              public RelayCommand(Action<object> execute, Func<object, bool> canExecute)
                : this(execute)
              {
                  if (canExecute == null)
                  {
                      throw new ArgumentNullException(nameof(canExecute));
                  }
      
                  _canExecute = canExecute;
              }
      
              public RelayCommand(Action execute, Func<bool> canExecute)
                : this((Action<object>)(o => execute()), (Func<object, bool>)(o => canExecute()))
              {
                  if (execute == null)
                  {
                      throw new ArgumentNullException(nameof(execute));
                  }
      
                  if (canExecute == null)
                  {
                      throw new ArgumentNullException(nameof(canExecute));
                  }
              }
      
              public bool CanExecute(object parameter)
              {
                  if (_canExecute != null)
                  {
                      return _canExecute(parameter);
                  }
      
                  return true;
              }
      
              public event EventHandler CanExecuteChanged;
      
              public void Execute(object parameter)
              {
                  _execute(parameter);
              }
      
              public void ChangeCanExecute()
              {
                  var canExecuteChanged = CanExecuteChanged;
                  if (canExecuteChanged == null)
                  {
                      return;
                  }
      
                  canExecuteChanged((object)this, EventArgs.Empty);
              }
          }
          public sealed class RelayCommand<T> : RelayCommand
          {
              public RelayCommand(Action<T> execute)
                  : base((Action<object>)(o =>
                  {
                      if (!RelayCommand<T>.IsValidParameter(o))
                      {
                          return;
                      }
      
                      execute((T)o);
                  }))
              {
                  if (execute == null)
                  {
                      throw new ArgumentNullException(nameof(execute));
                  }
              }
      
              public RelayCommand(Action<T> execute, Func<T, bool> canExecute)
                  : base((Action<object>)(o =>
                  {
                      if (!RelayCommand<T>.IsValidParameter(o))
                      {
                          return;
                      }
      
                      execute((T)o);
                  }), (Func<object, bool>)(o =>
                  {
                      if (RelayCommand<T>.IsValidParameter(o))
                      {
                          return canExecute((T)o);
                      }
      
                      return false;
                  }))
              {
                  if (execute == null)
                  {
                      throw new ArgumentNullException(nameof(execute));
                  }
      
                  if (canExecute == null)
                  {
                      throw new ArgumentNullException(nameof(canExecute));
                  }
              }
      
              private static bool IsValidParameter(object o)
              {
                  if (o != null)
                  {
                      return o is T;
                  }
      
                  var type = typeof(T);
                  if (Nullable.GetUnderlyingType(type) != (Type)null)
                  {
                      return true;
                  }
      
                  return !type.GetTypeInfo().IsValueType;
              }
          }
      
      }
      

      它在OnContentRendered 方法中生成DataGridContextMenu。对于每个DataGridColumn,都会生成一个MenuItem get,并使用一个显示或隐藏它的命令。

      【讨论】:

      • 啊,我喜欢它是仅查看属性,因此不需要由模型控制的推理。在这种情况下,解决方案实际上比您发布的解决方案要简单得多。我将在下面的另一条评论中添加我的代码。
      • @nickeggs 很高兴我能帮上忙。
      猜你喜欢
      • 1970-01-01
      • 2011-12-21
      • 2012-02-09
      • 2010-11-02
      • 1970-01-01
      • 2020-10-14
      • 1970-01-01
      • 2014-09-13
      • 1970-01-01
      相关资源
      最近更新 更多