locked
How to handle event when any cell has been modified in data grid using MVVM pattern. RRS feed

  • Question

  • Hello,

    I have a data grid. I have to handle event when any cell has been modified. I need to do it in MVVM application (so I don't want to use code-behind). How it can be done? Is it possible to use any kind of command (like for button)? Is it possible to pass additional parameters for command (like CommandParameter for button)?


    Dmitry

    Twitter Lightning Tools LogoLightning Tools Check out our SharePoint tools and web parts | Lightning Tools Blog | Мой Блог

    Monday, June 24, 2013 8:08 PM

Answers

  • You could use EventTriggers by adding a reference to System.Windows.Interactivity.dll and invoke a command when the event is fired:

                  <DataGrid  HeadersVisibility="None" ColumnWidth="*"  ItemsSource="{Binding Descriptions}">
                    <i:Interaction.Triggers>
                      <i:EventTrigger EventName="CellEditEnding">
                        <i:InvokeCommandAction Command="{Binding SomeCommand}"/>
                      </i:EventTrigger>
                    </i:Interaction.Triggers>
                  </DataGrid>

    Tuesday, June 25, 2013 10:46 AM
  • I'm agree with you Magnus. This is another approach. 

    Dimitry, at this moment you have at least three approach to solve your problem:

    1. RoutedEvent
    2. Triggers
    3. PropertyChanged on ViewModel

    I think now you should choose the best one for your context.


    Un saludo. Miguel A. González
    Regards, Miguel A. González
    _________________________
    Si la respuesta es correcta, márcala como correcta. También puedes votar como útil si te ha sido de ayuda
    If the answer is correct, mark it as correct. You can also vote as helpful if you have been helpful

    Tuesday, June 25, 2013 4:32 PM

All replies

  • Since you are using MVVM I guess your DataGrid is bound to some collection of objects. If you have a column that is bound to some property of this object you can handle any additional logic in the setter of this since it will be called when a change to the cell is made.

    Tuesday, June 25, 2013 7:41 AM
  • Hi,

    usually if you use MVVM pattern you must set a collection as a ItemsSource of your DataGrid. You will can implement INotifyPropertyChanged interface on the items of your collection and subscribe to PropertyChanged events of each item.

    If you need more help, please explain in more deep what do you want to do exactly when a cell changed and maybe i can help more with an example.


    Un saludo. Miguel A. González
    Regards, Miguel A. González
    _________________________
    Si la respuesta es correcta, márcala como correcta. También puedes votar como útil si te ha sido de ayuda
    If the answer is correct, mark it as correct. You can also vote as helpful if you have been helpful

    Tuesday, June 25, 2013 7:47 AM
  • Hi guys, thank you for your suggestions.

    I have complicated UI in my project but I simplified it in order to explain the idea. If you press the any button you can see it invokes command. I have to implement something like this but upon cell data changing. So you changed any data in any cell I have to invoke command and pass info about the item.

    <Window x:Class="WpfApplication.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" Height="350" Width="525">
    
    
        <ItemsControl ItemsSource="{Binding Items}">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Border BorderThickness="2" BorderBrush="Red">
                        <StackPanel>                    
                            <Label Content="{Binding Id}"></Label>
                            <Button Content="{Binding Text}" 
                                    Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Path=InvokeCommand}"
                                    CommandParameter="{Binding}">                            
                            </Button>
                            <DataGrid  HeadersVisibility="None" ColumnWidth="*"  ItemsSource="{Binding Descriptions}"/>                    
                        </StackPanel>
                    </Border>
                </DataTemplate>
            </ItemsControl.ItemTemplate>        
        </ItemsControl>    
    </Window>
    

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;
    
    namespace WpfApplication
    {
        public class DelegateCommand : ICommand
        {
            private Action<object> _executeDelegate;
    
            public DelegateCommand(Action<object> executeDelegate)
            {
                _executeDelegate = executeDelegate;
            }
            public void Execute(object parameter)
            {
                _executeDelegate(parameter);
            }
            public bool CanExecute(object parameter) { return true; }
    
            public event EventHandler CanExecuteChanged;
        }
    
        public sealed class Description
        {
            public string Text { get; set; }
        }
    
        public sealed class Item
        {
            public string Id { set; get; }
            public string Text { set; get; }
            public IEnumerable<Description> Descriptions { set; get; }
        }
        /// <summary>
        /// Interaction logic for MainWindow.xaml
        /// </summary>
        public partial class MainWindow : Window
        {
            public List<Item> Items { set; get; }
    
            public ICommand InvokeCommand { get; private set; }        
    
            public MainWindow()
            {
                InitializeComponent();
    
                this.DataContext = this;
    
                InvokeCommand = new DelegateCommand(x => MessageBox.Show(string.Format("Id is equal {0}", (x as Item).Id)));
    
                Items = new List<Item>() 
                { 
                    new Item() 
                    { 
                        Id = 1.ToString(), 
                        Text = "Text #1", 
                        Descriptions = new List<Description>()
                        { 
                            new Description()
                            {
                                Text = "Description #1"
                            },
                            new Description()
                            {
                                Text = "Description #2"
                            }
                        }
                    },
                    new Item() 
                    { 
                        Id = 2.ToString(), 
                        Text = "Text #2", 
                        Descriptions = new List<Description>()
                        { 
                            new Description()
                            {
                                Text = "Description #3"
                            }
                        }
                    }
                };
            }
        }
    }
    


    Dmitry

    Twitter Lightning Tools LogoLightning Tools Check out our SharePoint tools and web parts | Lightning Tools Blog | Мой Блог

    Tuesday, June 25, 2013 10:24 AM
  • You could use EventTriggers by adding a reference to System.Windows.Interactivity.dll and invoke a command when the event is fired:

                  <DataGrid  HeadersVisibility="None" ColumnWidth="*"  ItemsSource="{Binding Descriptions}">
                    <i:Interaction.Triggers>
                      <i:EventTrigger EventName="CellEditEnding">
                        <i:InvokeCommandAction Command="{Binding SomeCommand}"/>
                      </i:EventTrigger>
                    </i:Interaction.Triggers>
                  </DataGrid>

    Tuesday, June 25, 2013 10:46 AM
  • Hi Dimitry,

    Have you thinking about using Routed Commands?.

    The RoutedCommand "trip" form source element (your Button) to the root element of the tree (the Window) until an appropiated commmand handler is found.

    There is RoutedCommand already defined on .net framework for different areas, and you can define your own too. For open a MessageBox you can use the Application.Open command (remember the command has no implementation, only define an action and you make meaning to this action in your context). 

    Below there is a example of this.

    The Window.xaml file:

    <Window x:Class="RoutedCommandExample.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" Height="350" Width="525">
    	<Window.CommandBindings>
    		<!--
    		Add command handler for OpenCommand
    		-->
    		<CommandBinding Command="Open"
    						CanExecute="OpenCanExecute"
    						Executed="CommandExecute" />
    	</Window.CommandBindings>
        <Grid>
    		<ItemsControl ItemsSource="{Binding Items}">
    			<ItemsControl.ItemTemplate>
    				<DataTemplate>
    					<Border BorderThickness="2"
    							BorderBrush="Red">
    						<StackPanel>
    							<Label Content="{Binding Id}"></Label>
    							
    							<!-- 
    							When you use a built in RoutedCommand you can 
    							set it as string. WPF has a default converter to convert Open to Application.Open command
    							-->
    							<Button Content="{Binding Text}"
    									Command="Open"
    									CommandParameter="{Binding}">
    							</Button>
    							<DataGrid  HeadersVisibility="None"
    									   ColumnWidth="*"
    									   ItemsSource="{Binding Descriptions}" />
    						</StackPanel>
    					</Border>
    				</DataTemplate>
    			</ItemsControl.ItemTemplate>
    		</ItemsControl>
    	</Grid>
    </Window>
    

    The Windows.xaml.cs file:

    namespace RoutedCommandExample
    {
    	/// <summary>
    	/// Interaction logic for MainWindow.xaml
    	/// </summary>
    	public partial class MainWindow : Window
    	{
    		public MainWindow()
    		{
    			InitializeComponent();
    		}
    
    		/// <summary>
    		/// Handler for CanExecute command
    		/// </summary>
    		/// <param name="sender"></param>
    		/// <param name="e"></param>
    		private void OpenCanExecute(object sender, CanExecuteRoutedEventArgs e)
    		{
    			// The line below prevent to execute the command. Your Button is disabled.
    			e.CanExecute = false;
    			
    		}
    
    		/// <summary>
    		/// Handler for execute command
    		/// </summary>
    		/// <param name="sender"></param>
    		/// <param name="e"></param>
    		private void CommandExecute(object sender, ExecutedRoutedEventArgs e)
    		{
    
    		}
    	}
    }


    Un saludo. Miguel A. González
    Regards, Miguel A. González
    _________________________
    Si la respuesta es correcta, márcala como correcta. También puedes votar como útil si te ha sido de ayuda
    If the answer is correct, mark it as correct. You can also vote as helpful if you have been helpful

    Tuesday, June 25, 2013 10:50 AM
  • Miguel A. González,

    I like this approach but how I can use it for cell editing rather than button command?

    Magnus (MM8),

    This approach almost works as I need but if I change any cell and press "Esc" the command will be invoked anyway. But if I press Esc the data isn't modified in the cell.


    Dmitry

    Twitter Lightning Tools LogoLightning Tools Check out our SharePoint tools and web parts | Lightning Tools Blog | Мой Блог

    Tuesday, June 25, 2013 11:28 AM
  • If this onChange operation has to do with the Business Logic, it clearly belongs to the ViewModel part of MVVM. So as the other said, the setter of the Property that Cell/Column is bound too.

    If it has to do with the Visual Representation, it should go into the View part of MVVM. Often a WPF trigger or Animation can be used for it. But those require that the property that is Changed implements Change Notification.

    First you need to figure out to with of the two it belongs.
    But in any case, implementing ChangeNotification in all properties of the ViewModel is a good idea. And with INotifyPropertyChange not really a big deal. I even implement it in the Property that Exposes Collections (in case I ever replace that collection).
    The only thing then have to keep in mind is that all Code (especially the ViewModel Code) has to use the Properties (not the hidden varriables behind it). And if your hidden varriable is a Instance of a Model class, to never give out or store a reference directly, always clone:

    http://stackoverflow.com/questions/78536/deep-cloning-objects-in-c-sharp


    Let's talk about MVVM: http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/b1a8bf14-4acd-4d77-9df8-bdb95b02dbe2

    Tuesday, June 25, 2013 11:43 AM
  • Magnus (MM8),

    This approach almost works as I need but if I change any cell and press "Esc" the command will be invoked anyway. But if I press Esc the data isn't modified in the cell.

    Then you can handle the Binding.TargetUpdated for each column: http://social.msdn.microsoft.com/Forums/vstudio/en-US/833cdd77-93fb-4c5e-9b41-712c0f13f52e/cell-value-changed-event-in-datagrid
    Tuesday, June 25, 2013 12:01 PM
  • Magnus (MM8),

    But it requires code-behind, correct?


    Dmitry

    Twitter Lightning Tools LogoLightning Tools Check out our SharePoint tools and web parts | Lightning Tools Blog | Мой Блог

    Tuesday, June 25, 2013 12:34 PM
  • I think the approach to throw a command when a cell changed is no the best one. Instead, try to subscribe to PropertyChanged event of the items as i talked you in previous post. In the other hand, here there is a post explain who to show a messagebox form ViewModel without violating the MVVM pattern.

    Un saludo. Miguel A. González
    Regards, Miguel A. González
    _________________________
    Si la respuesta es correcta, márcala como correcta. También puedes votar como útil si te ha sido de ayuda
    If the answer is correct, mark it as correct. You can also vote as helpful if you have been helpful

    Tuesday, June 25, 2013 12:49 PM
  • Not necessarily. You could for example use a DataGridTemplateColumn to define a DataTemplate with a TextBox and tie its LostFocus event to a command:

    <DataGrid  HeadersVisibility="None" ColumnWidth="*"  ItemsSource="{Binding Descriptions}" 
                                           AutoGenerateColumns="False">
                                    <DataGrid.Columns>
                                        <DataGridTemplateColumn>
                                            <DataGridTemplateColumn.CellTemplate>
                                                <DataTemplate>
                                                    <TextBlock Text="{Binding Text}"/>
                                                </DataTemplate>
                                            </DataGridTemplateColumn.CellTemplate>
                                            <DataGridTemplateColumn.CellEditingTemplate>
                                                <DataTemplate>
                                                    <TextBox Text="{Binding Text}">
                                                        <i:Interaction.Triggers>
                                                            <i:EventTrigger EventName="LostFocus">
                                                                <i:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Path=SomeCommand}"/>
                                                            </i:EventTrigger>
                                                        </i:Interaction.Triggers>
                                                    </TextBox>
                                                </DataTemplate>
                                            </DataGridTemplateColumn.CellEditingTemplate>
                                        </DataGridTemplateColumn>
                                    </DataGrid.Columns>
                                </DataGrid>

    Tuesday, June 25, 2013 4:19 PM
  • I'm agree with you Magnus. This is another approach. 

    Dimitry, at this moment you have at least three approach to solve your problem:

    1. RoutedEvent
    2. Triggers
    3. PropertyChanged on ViewModel

    I think now you should choose the best one for your context.


    Un saludo. Miguel A. González
    Regards, Miguel A. González
    _________________________
    Si la respuesta es correcta, márcala como correcta. También puedes votar como útil si te ha sido de ayuda
    If the answer is correct, mark it as correct. You can also vote as helpful if you have been helpful

    Tuesday, June 25, 2013 4:32 PM