簡體   English   中英

如何將任何 DataGridTextColumn 傳遞給將切換 DataGridTextColumn 的可見性的單個命令?

[英]How can I pass any DataGridTextColumn to a single Command which will toggle the Visibility of the DataGridTextColumn?

我有一個 DataGrid 並且想使用從 ContextMenu 發送的命令來切換單個 DataGridTextColumns 的可見性。 我需要某種方式將特定的 DataGridTextColumn 或其 Visibility 參數與 ContextMenu MenuItem 命令相關聯。 我可以在我的 ViewModel 中設置一個單獨的 Visibility 變量,並使用單獨的命令切換它們,每個 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);
        }
    }

希望這已經足夠了,這個問題已經讓我死了幾個小時,我覺得我錯過了一些基本的東西。 任何幫助深表感謝。

鑒於上面 Michal 的回應,我重新構建了 MenuItems 的語言,而不是提供一個按鈕來切換每個 DataGridTextColumn 的可見性,我現在提供“全部顯示”和“隱藏所選”。 有了這個,用戶可以 Control+Select 多個單元格來指示要隱藏的列。 要返回基本狀態,“全部顯示”按鈕將所有可見性設置為可見。 這個新設置還允許我使用單個單元格的選擇來引用任何要對其執行操作的行。 就我而言,我需要能夠刪除行,這些行是我的 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 選擇“Cell”,讓我可以訪問一個元素,我可以從中派生出關聯的 Column。 然后在后面的代碼中,我只是遍歷這些並將它們的可見性模式切換為折疊。

在我的 .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 #”列,而無需 DataGrid。 .xaml.cs 中的 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");
}

我想我已經正確地復制/粘貼了所有內容,我會繼續檢查以回答出現的任何問題。

您可以在后面的代碼中生成ContextMenu及其功能。 我知道這不是 MVVM 的方式,但老實說,顯示和隱藏列真的與業務邏輯有關嗎? 我認為它只是 UI 的東西,因此,它不必在視圖模型中。 下面是一個例子:

主窗口.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>

主窗口.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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM