Search code examples
c#wpfmvvmdatagrid

How to assign an event handler for DataTemplate in code?


I try to dynamically create and assign a DataTemplate to a HeaderTemplate of DataGrid through code. To do this I have a method GetDatatemplate(string fromstring) that defines an XML literal and then uses this to create a DataTemplate. This works fine as long as I don't include the MouseLeftButtonDown event handler in the DataTemplate.

My DataTemplate saved in string variable called StringHeaderTemplate in code-behind of MainWindow.xaml.cs:

private string StringHeaderTemplate =@"<DataTemplate>
    <DataTemplate.Resources>                  
      <ControlTemplate x:Key=""imgNo"" TargetType=""{x:Type Control}"">
         <Image Source = ""pack://application:,,,/Images/upArrow.png"" />
      </ControlTemplate >
      <ControlTemplate x:Key=""imgUp"" TargetType=""{x:Type Control}"">
         <Image Source = ""pack://application:,,,/Images/upArrow.png"" />
      </ControlTemplate >
      <ControlTemplate x:Key=""imgDown"" TargetType=""{x:Type Control}"" >
         <Image Source = ""pack://application:,,,/Images/downArrow.png"" />
      </ControlTemplate >
   </DataTemplate.Resources> 
   <Grid Background=""Transparent"" MouseLeftButtonDown=""Grid_MouseLeftButtonDown"">
      <Grid.RowDefinitions>
          <RowDefinition/>
          <RowDefinition/>
          <RowDefinition/>
      </Grid.RowDefinitions>
      <Button Content=""Hello""/>
      <TextBlock Grid.Row=""1"" HorizontalAlignment= ""Center"" Text = ""TextBlock"" />
      <CheckBox Grid.Row= ""2"" HorizontalAlignment= ""Center"" IsChecked= ""True"" />
  </Grid >    
</DataTemplate>";

And the method which gets DataTemplate:

private DataTemplate GetDatatemplate(string fromstring)
{
    ParserContext context = new ParserContext();
    context.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
    context.XmlnsDictionary.Add("x", "http://schemas.microsoft.com/winfx/2006/xaml");
    return  (DataTemplate)XamlReader.Parse(fromstring, context);
}

Then I just apply this DataTemplate to HeaderTemplate of DataGrid:

private void dg_AutoGeneratingColumn_1(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
    DataTemplate dtCell = null;
    DataTemplate dtHeader = null;
    string dtString = string.Empty;
    string dtHeaderString = string.Empty;
    switch(Type.GetTypeCode(e.PropertyType))
    {
        case TypeCode.String:
        dtString = StringTemplate.Replace("xxColumnxx", e.PropertyName);
        dtHeaderString=StringHeaderTemplate;
        break;
    }
    if(!string.IsNullOrEmpty(dtString))
    {
        dtCell = GetDataTemplateForDataGrid(dtCellString);
        dtHeader = GetDataTemplateForDataGrid(dtHeaderString);
        DataGridTemplateColumn c = new DataGridTemplateColumn()
        {
          CellTemplate = dtCell,
          HeaderTemplate = dtHeader,
        };
        e.Column = c;            
     }
}

The event handler is really simple:

private void Grid_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
   MessageBox.Show(DateTime.Now.ToString());         
}

The exception I get is a XamlParseException that has an InnerException of type ArgumentException that says:

"Cannot bind to the target method because its signature or security transparency is not compatible with that of the delegate type."

Any ideas what to do?

Update:

I've tried to bind through Command, however the CallSortingCommand is not called. Maybe you know what I am doing wrong?

<Grid Background=""Transparent""> 
    <i:Interaction.Triggers>                 
       <i:EventTrigger EventName=""MouseLeftButtonDown"">                                        
          <prism:InvokeCommandAction Command = ""{Binding 
            RelativeSource={RelativeSource AncestorType=Window, 
            Mode=FindAncestor}, Path=DataContext.CallSortingCommand}"" />
       </i:EventTrigger>             
    </i:Interaction.Triggers>             
    <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition/>
        <RowDefinition/>
    </Grid.RowDefinitions>
    <Button Content=""Hello""/>
    <TextBlock Grid.Row=""1"" HorizontalAlignment= ""Center"" Text = ""TextBlock"" />
    CheckBox Grid.Row= ""2"" HorizontalAlignment= ""Center"" IsChecked= ""True"" />
</Grid >

Solution

  • It is an interesting problem, 'cause possible solution could be in finding object you want to subscribe to in visual tree, after data template is applied. But it is a column and it would be hard to access visual tree in that case.

    I'd suggest to go with Interactions, that allow to define event triggers in XAML. Please have a look at the code below:

        private string StringHeaderTemplate = @"<DataTemplate>
       <Grid Background=""Transparent"">
            <i:Interaction.Triggers>
             <i:EventTrigger EventName=""MouseLeftButtonDown"">
               <si:CallMethodAction MethodName = ""Grid_MouseLeftButtonDown"" TargetObject=""{Binding RelativeSource={RelativeSource AncestorType=Window}}""/>
             </i:EventTrigger>
            </i:Interaction.Triggers>
            <Grid.RowDefinitions>
              <RowDefinition/>
              <RowDefinition/>
              <RowDefinition/>
          </Grid.RowDefinitions>
          <Button Content=""Hello""/>
          <TextBlock Grid.Row=""1"" HorizontalAlignment= ""Center"" Text = ""TextBlock"" />
          <CheckBox Grid.Row= ""2"" HorizontalAlignment= ""Center"" IsChecked= ""True"" />
       </Grid >    
    </DataTemplate>";
    
    
        public void Grid_MouseLeftButtonDown(object sender, RoutedEventArgs e)
        {
            MessageBox.Show(DateTime.Now.ToString());
        }
    
        private DataTemplate GetDatatemplate(string fromstring)
        {
            ParserContext context = new ParserContext();
            context.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
            context.XmlnsDictionary.Add("x", "http://schemas.microsoft.com/winfx/2006/xaml");
            context.XmlnsDictionary.Add("i", "clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity");
            context.XmlnsDictionary.Add("si", "clr-namespace:Microsoft.Expression.Interactivity.Core;assembly=Microsoft.Expression.Interactions");
    
            return (DataTemplate)XamlReader.Parse(fromstring, context);
        }
    

    P.S. Please pay attention to public access modifier of Grid_MouseLeftButtonDown event handler, with private it will not work.

    UPDATE

    Full source code:

    XAML

    <Window x:Class="DataGridDataTemplateInCode.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:local="clr-namespace:DataGridDataTemplateInCode"
            mc:Ignorable="d"
            Title="MainWindow" Height="350" Width="525">
        <Grid>
            <DataGrid Name="dg" ItemsSource="{Binding Items}" AutoGeneratingColumn="dg_AutoGeneratingColumn" />
        </Grid>
    </Window>
    

    C#

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
    
            DataContext = new MainWindowViewModel();
        }
    
    
        private string StringHeaderTemplate = @"<DataTemplate>
       <Grid Background=""Transparent"">
            <i:Interaction.Triggers>
             <i:EventTrigger EventName=""PreviewMouseLeftButtonDown"">             
               <si:CallMethodAction MethodName = ""Grid_MouseLeftButtonDown"" TargetObject=""{Binding RelativeSource={RelativeSource AncestorType=Window}}""/>
             </i:EventTrigger>
            </i:Interaction.Triggers>
            <Grid.RowDefinitions>
              <RowDefinition/>
              <RowDefinition/>
              <RowDefinition/>
          </Grid.RowDefinitions>
          <Button Content=""Hello""/>
          <TextBlock Grid.Row=""1"" HorizontalAlignment= ""Center"" Text = ""Grid_MouseLeftButtonDown"" />
          <CheckBox Grid.Row= ""2"" HorizontalAlignment= ""Center"" IsChecked= ""True"" />
       </Grid >    
    </DataTemplate>";
    
    
        private string DateTimeWithCommandHeaderTemplate = @"<DataTemplate>
       <Grid Background=""Transparent"">
            <i:Interaction.Triggers>
             <i:EventTrigger EventName=""MouseLeftButtonDown"">             
               <i:InvokeCommandAction Command = ""{Binding DataContext.CallSortingCommand, RelativeSource={RelativeSource AncestorType=Window}}""/>
             </i:EventTrigger>
            </i:Interaction.Triggers>
            <Grid.RowDefinitions>
              <RowDefinition/>
              <RowDefinition/>
              <RowDefinition/>
          </Grid.RowDefinitions>
          <Button Content=""Hello""/>
          <TextBlock Grid.Row=""1"" HorizontalAlignment= ""Center"" Text = ""CallSortingCommand"" />
          <CheckBox Grid.Row= ""2"" HorizontalAlignment= ""Center"" IsChecked= ""True"" />
       </Grid >    
    </DataTemplate>";
    
        private string TimeCellTemplate = @"<DataTemplate>
          <TextBlock HorizontalAlignment= ""Center"" Text = ""{Binding Time}"" />
    </DataTemplate>";
    
        private string DescCellTemplate = @"<DataTemplate>
          <TextBlock HorizontalAlignment= ""Center"" Text = ""{Binding Desc}"" />
    </DataTemplate>";
    
    
        public void Grid_MouseLeftButtonDown(object sender, RoutedEventArgs e)
        {
            //MessageBox.Show(DateTime.Now.ToString());
    
            var vm = DataContext as MainWindowViewModel;
    
            vm.Items[0].Desc += "+";
        }
    
        private DataTemplate GetDatatemplate(string fromstring)
        {
            ParserContext context = new ParserContext();
            context.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
            context.XmlnsDictionary.Add("x", "http://schemas.microsoft.com/winfx/2006/xaml");
            context.XmlnsDictionary.Add("i", "clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity");
            context.XmlnsDictionary.Add("si", "clr-namespace:Microsoft.Expression.Interactivity.Core;assembly=Microsoft.Expression.Interactions");
    
            return (DataTemplate)XamlReader.Parse(fromstring, context);
        }
    
        private void dg_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
        {
            DataTemplate dtHeader = null;
            string dtString = string.Empty;
            string dtHeaderString = string.Empty;
    
            DataGridTemplateColumn column = null;
    
            switch (Type.GetTypeCode(e.PropertyType))
            {
                case TypeCode.String:
                    {
                        column = new DataGridTemplateColumn()
                        {
                            CellTemplate = GetDatatemplate(DescCellTemplate),
                            HeaderTemplate = GetDatatemplate(StringHeaderTemplate),
                        };
                    }
    
                    break;
    
                case TypeCode.DateTime:
                    {
                        column = new DataGridTemplateColumn()
                        {
                            CellTemplate = GetDatatemplate(TimeCellTemplate),
                            HeaderTemplate = GetDatatemplate(DateTimeWithCommandHeaderTemplate),
                        };
                    }
    
                    break;
            }
    
            if (column != null)
            {
                e.Column = column;
            }
        }
    }
    
    public class MainWindowViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
    
        public MainWindowViewModel()
        {
    
            for (int i = 0; i < 10; i++)
            {
                _collection.Add(new MyObject() { Time = DateTime.Now.AddSeconds(i), Desc = i.ToString() });
            }
    
            CallSortingCommand = new DelegateCommand(OnCallSortingCommand, (o) => true);
        }
    
        private void OnCallSortingCommand(object obj)
        {
            MessageBox.Show("From OnCallSortingCommand");
        }
    
        public ICommand CallSortingCommand { get; set; }
    
    
        private ObservableCollection<MyObject> _collection = new ObservableCollection<MyObject>();
        public ObservableCollection<MyObject> Items
        {
            get
            {
                return _collection;
            }
        }
    
    
        protected void OnPropertyChanged([CallerMemberName] string property = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
        }     
    }
    
    
    public class MyObject : INotifyPropertyChanged
    {
        public DateTime Time { get; set; }
    
    
        private string _desc;
    
        public string Desc
        {
            get { return _desc; }
            set { _desc = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Desc))); }
        }
    
    
        public event PropertyChangedEventHandler PropertyChanged;
    }
    
    
    
    public class DelegateCommand : ICommand
    {
        private readonly Action<object> _execute;
        private readonly Func<object, bool> _canExecute;
    
        public event EventHandler CanExecuteChanged;
    
    
        public DelegateCommand(Action<object> execute, Func<object, bool> canExecute)
        {
            _execute = execute;
            _canExecute = canExecute;
        }
    
        public bool CanExecute(object parameter)
        {
            return _canExecute(parameter);
        }
    
        public void Execute(object parameter)
        {
            _execute(parameter);
        }
    }