MVVM - WPF DataGrid - AutoGeneratingColumn Event

2019-04-14 15:35发布

问题:

I'm currently taking a good look at the excellent toolkit from Laurent and I have the following question.

From Blend 4, I have added an EventTrigger for the Loaded event, in my ViewModel I have the following:

public RelayCommand rcAutoGeneratingColumn { get; private set; }

In the constructor I have:

rcAutoGeneratingColumn = 
   new RelayCommand(o => DataGridAutoGeneratingColumn(o));

Also in the ViewModel, I have the method which I wish to be invoked by the RelayCommand:

    private void DataGridAutoGeneratingColumn(Object o)
    {
        DataGrid grid = (DataGrid)o;

        foreach (DataGridTextColumn col in grid.Columns)
        {
            if (col.Header.ToString().ToLower() == "id")
            {
                col.Visibility = System.Windows.Visibility.Hidden;
            }
        }
    }

My XAML contains the following (for the DataGrid):

<i:Interaction.Triggers>
    <i:EventTrigger EventName="Loaded">
        <GalaSoft_MvvmLight_Command:EventToCommand 
            Command="{Binding rcAutoGeneratingColumn, Mode=OneWay}"
            CommandParameter="{Binding ElementName=dataGrid1, Mode=OneWay}"/>
    </i:EventTrigger>
</i:Interaction.Triggers>

There is NO PROBLEM here the code works just fine, but obviously the event used to hide certain columns should be the AutoGeneratingColumn event and not Loaded. I have used to Loaded event as a getaround.

I was hoping that I could relay any event offered by the control so that, in this case, the following would work instead:

<i:Interaction.Triggers>
    <i:EventTrigger EventName="AutoGeneratingColumn">
        <GalaSoft_MvvmLight_Command:EventToCommand 
            Command="{Binding rcAutoGeneratingColumn, Mode=OneWay}"
            CommandParameter="{Binding ElementName=dataGrid1, Mode=OneWay}"/>
    </i:EventTrigger>
</i:Interaction.Triggers>

I am unable to get the AutoGeneratingColumn event to trigger, and I'm hoping that I've overlooked something and appreciate any advice given!

This behaviour is the same with the GridControl from DevExpress, in that the Loaded event is triggered whereas the ColumnsPopulated event (this being the equivalent of the AutoGeneratingColumn event) is not.

DevExpress offered the following information with regard to my question:

"We have reviewed this question, and come to an interesting conclusion. It looks like the visual tree is not being built at the moment when the Interaction.Triggers are being processed"

If this is true, and there is no other way in which to invoke the events within the ViewModel, then one would have to go ahead and - by using trial and error - note which of the DataGrid events (of which there are over 100) can be invoked in this way and which cannot!

One would like to think that every event which is available in the code-behind, can also be reached when applying the MVVM pattern.

I have searched for an answer but I cannot rule out that I have overlooked something, so if this is to be the case, then please accept my apologies!

回答1:

During the course of developing a project with MVVM you're going to have circumstances where you must handle events in your view's code-behind and EventToCommand just plain doesn't work. You especially find this with Silverlight, but I assume from your question that you're using WPF. It's okay to do some event handling in your view's code-behind, just don't put any business logic there. You can even leave the command in your view model, just call it directly from your event handler.

((YourViewModel)this.DataContext).rcAutoGeneratingColumn.Execute(sender);


回答2:

You don't have to use evil code behind ;-) You can do this using an attached behaviour...

public class AutoGeneratingColumnEventToCommandBehaviour
{
    public static readonly DependencyProperty CommandProperty =
        DependencyProperty.RegisterAttached(
            "Command", 
            typeof(ICommand), 
            typeof(AutoGeneratingColumnEventToCommandBehaviour),
            new PropertyMetadata(
                null,
                CommandPropertyChanged));

    public static void SetCommand(DependencyObject o, ICommand value)
    {
        o.SetValue(CommandProperty, value);
    }

    public static ICommand GetCommand(DependencyObject o)
    {
        return o.GetValue(CommandProperty) as ICommand;
    }

    private static void CommandPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var dataGrid = d as DataGrid;
        if (dataGrid != null)
        {
            if (e.OldValue != null)
            {
                dataGrid.AutoGeneratingColumn -= OnAutoGeneratingColumn;
            }
            if (e.NewValue != null)
            {
                dataGrid.AutoGeneratingColumn += OnAutoGeneratingColumn;
            }
        }
    }

    private static void OnAutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
    {
        var dependencyObject = sender as DependencyObject;
        if (dependencyObject != null)
        {
            var command = dependencyObject.GetValue(CommandProperty) as ICommand;
            if (command != null && command.CanExecute(e))
            {
                command.Execute(e);
            }
        }
    }
}

Then use it in XAML like this...

<DataGrid ItemsSource="{Binding MyGridSource}"
          AttachedCommand:AutoGeneratingColumnEventToCommandBehaviour.Command="{Binding CreateColumnsCommand}">
</DataGrid>


回答3:

Just set EventTrigger.SourceObject property.

<DataGrid
  x:Name="DataGrid"
  AutoGenerateColumns="True"
  IsReadOnly="True"
  ItemsSource="{Binding Data}">
  <i:Interaction.Triggers>
    <i:EventTrigger EventName="AutoGeneratingColumn" SourceObject="{Binding ElementName=DataGrid}">
      <local:EventToCommand
        Command="{Binding ColumnGeneratingCommand}"
        PassEventArgsToCommand="True" />
    </i:EventTrigger>
  </i:Interaction.Triggers>
</DataGrid>