konrad
konrad

Reputation: 3706

EventToCommand for SelectionChanged on ComboBox inside of DataGrid mvvm/wpf

How can I catch the Selection Changed event that fires on a ComboBox that is embedded into a DataGridComboBoxColum? I would like to use MVVM pattern for this so something like a EventToCommmand solution would be nice.

XAML:

<DataGridComboBoxColumn Header="ViewTemplate" Width="180" SelectedItemBinding="{Binding ViewTemplate}" DisplayMemberPath="Name">
    <DataGridComboBoxColumn.ElementStyle>
        <Style TargetType="{x:Type ComboBox}" BasedOn="{StaticResource {x:Type ComboBox}}">
            <Setter Property="ItemsSource" Value="{Binding DataContext.ViewTemplates, RelativeSource={RelativeSource AncestorType=DataGrid}}"/>
            <Setter Property="IsReadOnly" Value="True"/>
        </Style>
    </DataGridComboBoxColumn.ElementStyle>
    <DataGridComboBoxColumn.EditingElementStyle>
        <Style TargetType="{x:Type ComboBox}" BasedOn="{StaticResource {x:Type ComboBox}}">
            <Setter Property="ItemsSource" Value="{Binding DataContext.ViewTemplates, RelativeSource={RelativeSource AncestorType=DataGrid}}"/>
        </Style>
    </DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>

I would like to use something like this but don't know where/how to set it up in this particular case.

<i:Interaction.Triggers>
    <i:EventTrigger EventName="SelectionChanged">
        <cmd:EventToCommand PassEventArgsToCommand="True" Command="{Binding SelectionChangedCommand}"/>
    </i:EventTrigger>
</i:Interaction.Triggers>

As requested this is my full code for XAML:

<UserControl x:Class="GrimshawRibbon.Revit.Views.ViewManager.ViewManagerView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
             xmlns:local="clr-namespace:GrimshawRibbon.Revit.Views.ViewManager"
             xmlns:ex="clr-namespace:GrimshawRibbon.Revit.Wpf.Extensions"
             xmlns:controls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
             xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Platform"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="500">
    <UserControl.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.xaml" />
                <ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Fonts.xaml" />
                <ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Colors.xaml" />
                <ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Accents/Blue.xaml" />
                <ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Accents/BaseLight.xaml" />
                <ResourceDictionary Source="pack://application:,,,/GrimshawRibbon;component/Revit/Wpf/Style/GrimshawTheme.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </UserControl.Resources>
    <Grid>
        <ex:DataGridEx x:Name="dgViews" 
                               Style="{StaticResource AzureDataGrid}" 
                               Margin="10" 
                               AutoGenerateColumns="False" 
                               ItemsSource="{Binding Views, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" 
                               CanUserAddRows="False" 
                               IsReadOnly="False" 
                               SelectionMode="Extended" 
                               SelectionUnit="FullRow" 
                               SelectedItemsList="{Binding SelectedViews, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="CellEditEnding">
                    <cmd:EventToCommand PassEventArgsToCommand="True" Command="{Binding CellEditEndingCommand}"/>
                </i:EventTrigger>
            </i:Interaction.Triggers>
            <DataGrid.Columns>
                <DataGridTextColumn Header="Name" Binding="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="*"/>
                <DataGridTextColumn Header="ViewType" Binding="{Binding ViewType}" Width="100" IsReadOnly="True"/>
                <DataGridTextColumn Header="ViewGroup" Binding="{Binding ViewGroup, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="130" IsReadOnly="False"/>
                <DataGridTextColumn Header="ViewSubGroup" Binding="{Binding ViewSubGroup, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="130" IsReadOnly="False"/>
                <DataGridCheckBoxColumn ElementStyle="{DynamicResource MetroDataGridCheckBox}" 
                                        EditingElementStyle="{DynamicResource MetroDataGridCheckBox}" 
                                        Header="OnSheet" 
                                        Binding="{Binding OnSheet, Mode=TwoWay}" 
                                        IsReadOnly="True" 
                                        Width="80">
                </DataGridCheckBoxColumn>
                <DataGridComboBoxColumn Header="ViewTemplate" Width="180" SelectedItemBinding="{Binding ViewTemplate}" DisplayMemberPath="Name">
                    <i:Interaction.Triggers>
                        <i:EventTrigger EventName="SelectionChanged">
                            <cmd:EventToCommand PassEventArgsToCommand="True" Command="{Binding DataContext.SelectChangeCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
                        </i:EventTrigger>
                    </i:Interaction.Triggers>
                    <DataGridComboBoxColumn.ElementStyle>
                        <Style TargetType="{x:Type ComboBox}" BasedOn="{StaticResource {x:Type ComboBox}}">
                            <Setter Property="ItemsSource" Value="{Binding DataContext.ViewTemplates, RelativeSource={RelativeSource AncestorType=DataGrid}}"/>
                            <Setter Property="IsReadOnly" Value="True"/>
                        </Style>
                    </DataGridComboBoxColumn.ElementStyle>
                    <DataGridComboBoxColumn.EditingElementStyle>
                        <Style TargetType="{x:Type ComboBox}" BasedOn="{StaticResource {x:Type ComboBox}}">
                            <Setter Property="ItemsSource" Value="{Binding DataContext.ViewTemplates, RelativeSource={RelativeSource AncestorType=DataGrid}}"/>
                        </Style>
                    </DataGridComboBoxColumn.EditingElementStyle>
                </DataGridComboBoxColumn>
            </DataGrid.Columns>
        </ex:DataGridEx>
    </Grid>
</UserControl>

and view model:

public class ViewManagerViewModel : ViewModelBaseEx
{
    public ViewManagerModel Model;
    public ObservableCollection<ViewWrapper> Views { get; private set; }
    public ObservableCollection<ViewWrapper> ViewTemplates { get; private set; }
    public IList SelectedViews { get; set; }
    public RelayCommand<DataGridCellEditEndingEventArgs> CellEditEndingCommand { get; set; }
    public RelayCommand<SelectionChangedEventArgs> SelectChangeCommand { get; set; }

    public ViewManagerViewModel(Document doc)
    {
        Model = new ViewManagerModel(doc);
        Views = Model.CollectViews();
        ViewTemplates = Model.CollectViewTemplates();
        CellEditEndingCommand = new RelayCommand<DataGridCellEditEndingEventArgs>(args => OnCellEditEnding(args));
        SelectChangeCommand = new RelayCommand<SelectionChangedEventArgs>(args => OnSelectionChanged(args));
    }

    private void OnSelectionChanged(SelectionChangedEventArgs e)
    {
        // do something
    }

    /// <summary>
    /// Logic for handling cell editing events.
    /// </summary>
    /// <param name="e">DataGrid Row object.</param>
    private void OnCellEditEnding(DataGridCellEditEndingEventArgs e)
    {
        var wrapper = e.Row.Item as ViewWrapper;
        switch (e.Column.SortMemberPath)
        {
            case "Name":
                Model.ChangeName(wrapper);
                break;
            case "ViewGroup":
                Model.SetParameter(wrapper, "View Group", wrapper.ViewGroup);
                break;
            case "ViewSubGroup":
                Model.SetParameter(wrapper, "View Sub Group", wrapper.ViewSubGroup);
                break;
            default:
                break;
        }
    }

    public override void Apply()
    {
        var vw = new List<ViewWrapper>();
        foreach (ViewWrapper v in SelectedViews)
        {
            vw.Add(v);
        }

        Model.Delete(vw);

        // update collection so that rows get deleted in datagrid
        foreach (ViewWrapper i in vw)
        {
            Views.Remove(i);
        }
    }
}

Upvotes: 2

Views: 2860

Answers (3)

mm8
mm8

Reputation: 169190

Using the MVVM pattern you don't handle the SelectionChanged event for the ComboBox element in the view.

Instead you should implement your selection changed logic in the setter of the ViewTemplate property, or in a method that you call from there, that gets set whenever a new item is selected in the ComboBox, .e.g.:

private ViewWrapper _viewTemplate;
public ViewWrapper ViewTemplate
{
    get { return _viewTemplate; }
    set { _viewTemplate = value; SelectionChanged(); }
}

This is one of corner stones of MVVM. Whenever the source property gets set by the view, the view model can perform some logic. The view only sets a property. It doesn't handle any event.

Edit: If this is not an option for some reason you should replace the DataGridComboBoxColumn with a DataGridTemplateColumn because you cannot apply an EventTrigger to a Style:

<DataGridTemplateColumn Header="ViewTemplate" Width="180">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding ViewTemplate.Name}" />
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
    <DataGridTemplateColumn.CellEditingTemplate>
        <DataTemplate>
            <ComboBox ItemsSource="{Binding DataContext.ViewTemplates, RelativeSource={RelativeSource AncestorType=DataGrid}}"
                                      SelectedItem="{Binding ViewTemplate}"
                                      DisplayMemberPath="Name">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="SelectionChanged">
                        <cmd:EventToCommand PassEventArgsToCommand="True"
                                                            Command="{Binding DataContext.SelectionChangedCommand, RelativeSource={RelativeSource AncestorType=DataGrid}}"/>
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </ComboBox>
        </DataTemplate>
    </DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>

Upvotes: 2

user7583356
user7583356

Reputation: 76

what i would do is something like this so inside your viewmodel, you will have something like

Viewmodel.cs

public class ViewModel
{
    public ViewModel()
{
            _selectChangeCommand= new RelayCommand(OnSelectChange, CanSelectChange);

}
    private RelayCommand _selectChangeCommand;
    public ICommand SelectChangeCommand { get { return _selectChangeCommand; } }
    private bool CanSelectChange()
    {
        return true;
    }

    private void OnSelectChange()
    {
        ..//do something in here when you change something in your combo box
    }
}

In your MainWindow.xaml.cs do something like this:

this.DataContext = new ViewModel();

In your ViewModel.xaml file

<ComboBox....>
    <i:Interaction.Triggers>
         <i:EventTrigger EventName="SelectionChanged">
           <cmd:EventToCommand PassEventArgsToCommand="True" Command="{Binding SelectChangeCommand }"/>
         </i:EventTrigger>
    </i:Interaction.Triggers>
</ComboBox>

One important thing is to make sure that your viewmodel.xaml knows where it references from so that it can see where the SelectionChangeCommand is located so it can do the binding. FYI, for the relaycommand, my advice is to use the Galasoft.MVVMlight so you don't have to implement relaycommand yourself.

Upvotes: 0

orhun.begendi
orhun.begendi

Reputation: 937

You can add eventhandler to your combobox. I added to grid's showing event, you can add another event of course.

private void Grid1_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{ 
  ComboBox cb = e.Control as ComboBox;
                if (cb!=null)
                { cb.SelectionChangeCommitted -= new EventHandler(cb_SelectedIndexChanged);

                    // now attach the event handler
                    cb.SelectionChangeCommitted += new EventHandler(cb_SelectedIndexChanged);
                }
}}

Upvotes: 0

Related Questions