简体   繁体   中英

how to stop a specific column to be generated in DataGrid when the AutoGenerateColumns is set to True?

I have bound a ObservableCollection to a DataGrid and set the AutoGenerateColumns to true in a WPF with MVVM application.

Then how can I stop a specific column to be appeared in the DataGrid?

I have seen a same question here . But I'm looking for more MVVM approach.

MVVM means your UI and your Data layers are completely separate, with the View layer merely being a visual reflection of your Data layer.

So the "MVVM way" of excluding a column depends on where this exclusion should occur:

  • If your Data layer is supposed to exclude a column, remove that column from the data layer.

    This typically means modifying the collection your DataGrid.ItemsSource is binding to so it no longer includes the data that should not be visible.

    Or depending on what your application is for and what counts as "application logic", it may mean maintaining a string or List<string> of columns to exclude, and have the View find some way of binding to those strings and modifying it's display to exclude those columns (a custom dependency property, a converter, re-use of the Tag property + AutoGeneratingColumn event, triggers, etc)

  • If your View layer is supposed to exclude a column, remove that column from the view layer.

    This is usually done by setting AutoGenerateColumns="False" and specifying your <DataGrid.Columns> yourself, however you can also exclude the column from the View layer using the AutoGeneratingColumn event of the DataGrid , and cancelling the generation of the column if it's ColumnName equals to the value you want to exclude, like the question you linked suggested.

     private void DataGrid_AutoGeneratingColumn( object sender, DataGridAutoGeneratingColumnEventArgs e) { if ((string)e.Column.Header == "ID") { e.Cancel = true; } } 

Remember, the whole point of MVVM is to separate your UI and Data layers. There's absolutely nothing wrong with using code-behind the view in MVVM, providing that code is related to UI-specific logic only, and not data/application-specific logic

Or you could do this...

You can usually work around this using attached properties to bind events to Command -s - and then straight to your view-model.

namespace YourNamespace // wrap that into e.g. 'xmlns:local="clr-namespace:YourNamespace"'
public static class Attach
{
    public static ICommand GetAutoGenerateColumnEvent(DataGrid grid) { return (ICommand)grid.GetValue(AutoGenerateColumnEventProperty); }
    public static void SetAutoGenerateColumnEvent(DataGrid grid, ICommand value) { grid.SetValue(AutoGenerateColumnEventProperty, value); }
    public static readonly DependencyProperty AutoGenerateColumnEventProperty =
        DependencyProperty.RegisterAttached("AutoGenerateColumnEvent", typeof(ICommand), typeof(Attach), new UIPropertyMetadata(null, OnAutoGenerateColumnEventChanged));
    static void OnAutoGenerateColumnEventChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
    {
        DataGrid grid = depObj as DataGrid;
        if (grid == null || e.NewValue is ICommand == false)
            return;
        ICommand command = (ICommand)e.NewValue;
        grid.AutoGeneratingColumn += new EventHandler<DataGridAutoGeneratingColumnEventArgs>((s, args) => OnAutoGeneratingColumn(command, s, args));
        // handle unsubscribe if needed
    }
    static void OnAutoGeneratingColumn(ICommand command, object sender, DataGridAutoGeneratingColumnEventArgs e)
    {
        if (command.CanExecute(e)) command.Execute(e);
    }
}

And in your XAML...

<DataGrid 
    local:Attach.AutoGenerateColumnEvent="{Binding AutoGeneratingColumnCommand}" AutoGenerateColumns="True" />  

And in your view-model...

RelayCommand _autoGeneratingColumnCommand;
public RelayCommand AutoGeneratingColumnCommand 
{ 
    get 
    { 
        return _autoGeneratingColumnCommand ?? (_autoGeneratingColumnCommand = new RelayCommand(param => 
        { 
            var e = param as DataGridAutoGeneratingColumnEventArgs;
            if (e != null)
            {
                switch (e.PropertyName)
                {
                    case "ID":
                        e.Cancel = true;
                        break;
                    default:
                        break;
                }
            }
        }, 
        param => true)); 
    } 
}

...where RelayCommand : ICommand implementation you can find on the web (widely used)

It is as MVVM'ish as it can get. I don't see anything wrong with this. I've used it in my MVVM project and it works really well.

Though I don't use AutoGeneratedColumns, but "bindable" columns attached trick.

<DataGrid Name="dataGrid"
          local:DataGridColumnsBehavior.BindableColumns="{Binding ColumnCollection}"
          AutoGenerateColumns="False"
          ...>

and then the actual behavior:

public class DataGridColumnsBehavior
{
    public static readonly DependencyProperty BindableColumnsProperty =
        DependencyProperty.RegisterAttached("BindableColumns",
                                            typeof(ObservableCollection<DataGridColumn>),
                                            typeof(DataGridColumnsBehavior),
                                            new UIPropertyMetadata(null, BindableColumnsPropertyChanged));
    private static void BindableColumnsPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
    {
        DataGrid dataGrid = source as DataGrid;
        ObservableCollection<DataGridColumn> columns = e.NewValue as ObservableCollection<DataGridColumn>;
        dataGrid.Columns.Clear();
        if (columns == null)
        {
            return;
        }
        foreach (DataGridColumn column in columns)
        {
            dataGrid.Columns.Add(column);
        }
        columns.CollectionChanged += (sender, e2) =>
        {
            NotifyCollectionChangedEventArgs ne = e2 as NotifyCollectionChangedEventArgs;
            if (ne.Action == NotifyCollectionChangedAction.Reset)
            {
                dataGrid.Columns.Clear();
                foreach (DataGridColumn column in ne.NewItems)
                {
                    dataGrid.Columns.Add(column);
                }
            }
            else if (ne.Action == NotifyCollectionChangedAction.Add)
            {
                foreach (DataGridColumn column in ne.NewItems)
                {
                    dataGrid.Columns.Add(column);
                }
            }
            else if (ne.Action == NotifyCollectionChangedAction.Move)
            {
                dataGrid.Columns.Move(ne.OldStartingIndex, ne.NewStartingIndex);
            }
            else if (ne.Action == NotifyCollectionChangedAction.Remove)
            {
                foreach (DataGridColumn column in ne.OldItems)
                {
                    dataGrid.Columns.Remove(column);
                }
            }
            else if (ne.Action == NotifyCollectionChangedAction.Replace)
            {
                dataGrid.Columns[ne.NewStartingIndex] = ne.NewItems[0] as DataGridColumn;
            }
        };
    }
    public static void SetBindableColumns(DependencyObject element, ObservableCollection<DataGridColumn> value)
    {
        element.SetValue(BindableColumnsProperty, value);
    }
    public static ObservableCollection<DataGridColumn> GetBindableColumns(DependencyObject element)
    {
        return (ObservableCollection<DataGridColumn>)element.GetValue(BindableColumnsProperty);
    }
}

So, what exactly constitues as "MVVM"ish approach to you? Having everything in XAML is not what "MVVM" pattern is about.

There is also one another trick that you can do. It basically revolves around templating DataGridColumn and writing specific trigger that will collapse column based on some condition. I have no "ready" solution, but it will work.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM