Как я могу передать любой DataGridTextColumn в одну команду, которая будет переключать видимость DataGridTextColumn? - PullRequest
0 голосов
/ 07 февраля 2020

У меня есть DataGrid, и я хочу переключить видимость отдельных DataGridTextColumns с помощью команд, отправленных из ContextMenu. Мне нужен какой-то способ связать конкретный 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 выглядит, как в примере выше, но с большим количеством столбцов и соответствующим ContextIenu 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, чтобы его видимость можно было переключить с помощью обобщенного c метода?

Из того, что я видел, звучит так, будто мне нужно иметь возможность использовать 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. Любая помощь очень ценится.

Ответы [ 2 ]

1 голос
/ 07 февраля 2020

Учитывая ответ Михала выше, я реструктурировал язык моих MenuItems, и вместо того, чтобы предоставить кнопку для переключения видимости каждого DataGridTextColumn, я теперь предлагаю «Показать все» и «Скрыть выбранное». При этом пользователь может Control + выбрать несколько ячеек, чтобы указать, какие столбцы хотели бы скрыть. Чтобы вернуться в базовое состояние, кнопка Показать все устанавливает все Видимость в Видимый. Эта новая настройка также позволяет мне использовать выбор отдельной ячейки для ссылки на любую строку для выполнения действий. В моем случае мне нужно иметь возможность удалять строки, которые являются записями из моей коллекции 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 у меня есть два метода "Click".

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;
    }
}

У меня также есть метод «DeleteShot» во ViewModel, поэтому в мой обновленный DataGrid .xaml добавлено свойство Name и свойство IsAsync = True в ItemsSource.

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

IsAsyn c позволяет мне вызовите мою команду DeleteShot, удалите элемент из моей коллекции ObservableCollection, обновите свойство «shotNumber» каждого элемента в моей коллекции ObservableCollection и обновите DataGrid, чтобы правильно представить столбец «Shot #», без необходимости DataGrid.Items.Refre sh () в .xaml.cs

.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");
}

Я думаю, что все правильно скопировано / вставлено, Я буду проверять, чтобы ответить на любые возникающие вопросы.

0 голосов
/ 07 февраля 2020

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

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;
        }
    }

}

Генерирует DataGrid ContextMenu в методе OnContentRendered. Для каждого DataGridColumn a MenuItem get генерируется командой, которая либо показывает, либо скрывает его.

...