2

in my WPF project I create a custom ListView in Code Behind. In this ListView is a column that contains a button, defined by a datatemplate in my resource dictionary.

<DataTemplate x:Key="DataTemplate_EditButton">
  <Button Style="{DynamicResource Button_Image}" Width="25" ... />
</DataTemplate>

When I initialize the ListView, I create the column with the following code:

GridViewColumn buttonColumn = new GridViewColumn();
DataTemplate dt = Application.Current.TryFindResource("DataTemplate_EditButton") as DataTemplate;
buttonColumn.CellTemplate = dt;
...

gridView.Columns.Add(buttonColumn);

Now I want to bind an event handler to the click event of the button. I cannot do it in the template, because I would need to create a code behind class for the Dictionary and I need the event handler in the ListView-UserControl anyway. When I create the column with the data template there is of course no way to access the button that is created for each row.

What would be the best way to deal with the click event of the buttons created in the described way?

Thanks in advance,
Frank

Aaginor
  • 4,516
  • 11
  • 51
  • 75
  • 4
    Is there a reason why doing it in code behind and not using a proper way? – Mighty Badaboom Jun 26 '17 at 13:20
  • 4
    *"I want to bind an event handler to the click event of the button"* - don't. Use commands. – Sinatr Jun 26 '17 at 13:20
  • 1
    Please have a look at this [SO answer](https://stackoverflow.com/a/19380935/2029607), and also shame on you for doing this in code behind. – XAMlMAX Jun 26 '17 at 13:33
  • Possible duplicate of [how to access a control within Data Template from code behind?](https://stackoverflow.com/questions/19379946/how-to-access-a-control-within-data-template-from-code-behind) – Sinatr Jun 26 '17 at 14:12

2 Answers2

5

Since your template is shared between many controls - good way might be to use routed commands. First declare a command (or use one of existing ones, for example from ApplicationCommands class):

public static class Commands {
    public static RoutedCommand EditRow = new RoutedCommand("Edit", typeof(Commands));
}

Use this command in your template:

<DataTemplate x:Key="DataTemplate_EditButton">
    <Button x:Name="button" Command="{x:Static my:Commands.EditRow}" />
</DataTemplate>

Then bind to that command in your control (in constructor):

this.CommandBindings.Add(new CommandBinding(Commands.EditRow, EditButtonClicked));

private void EditButtonClicked(object sender, ExecutedRoutedEventArgs args) 
{
    var button = args.OriginalSource;
    // do what you need here
}
Evk
  • 98,527
  • 8
  • 141
  • 191
  • I already red about Commands but the examples were not as clear as yours. Thanks for the explanation! :) – Aaginor Jun 26 '17 at 14:59
  • Note that those are routed commands. Other (more common) use of commands is just bind to the view model (``) but in your case it seems not applicable (just so that you know there are different types of commands in wpf). – Evk Jun 26 '17 at 15:11
  • Thanks for this. So little code, keeping our Custom Controls nice and clean. Great example of usage. – TravisWhidden Aug 15 '17 at 15:58
2

What would be the best way to deal with the click event of the buttons created in the described way?

You need to wait until the Button elements have actually been created before you can attach the event handler. You could do this by handling the StatusChanged event for the ListView's ItemContainerGenerator.

Please refer to the following sample code. It should give you the idea.

private void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
{
    ItemContainerGenerator icg = sender as ItemContainerGenerator;
    if (icg.Status == System.Windows.Controls.Primitives.GeneratorStatus.ContainersGenerated)
    {
        foreach (var item in icg.Items)
        {
            var container = icg.ContainerFromItem(item) as ListViewItem;
            Button button = FindVisualChild<Button>(container);
            if (button != null)
            {
                button.Click -= Button_Click;
                button.Click += Button_Click;
            }
        }
    }
}

private void Button_Click(object sender, RoutedEventArgs e)
{
    MessageBox.Show("clicked");
}

private static T FindVisualChild<T>(DependencyObject parent) where T : DependencyObject
{
    for (int childCount = 0; childCount < VisualTreeHelper.GetChildrenCount(parent); childCount++)
    {
        DependencyObject child = VisualTreeHelper.GetChild(parent, childCount);
        if (child != null && child is T)
            return (T)child;
        else
        {
            T childOfChild = FindVisualChild<T>(child);
            if (childOfChild != null)
                return childOfChild;
        }
    }
    return null;
}
mm8
  • 163,881
  • 10
  • 57
  • 88
  • I was looking for the best event to hook on after the buttons were created. Thanks a lot for pointing out the correct one. Now I need to find a way to mark both answers as correct ones :D – Aaginor Jun 26 '17 at 15:01