wpfbuttondatatemplatecommandparameter

Identify which button was clicked in WPF gird based on 2D array


I'm building an app which will display a list of buttons in 2D grid, similar to How to populate a WPF grid based on a 2-dimensional array

The challenge is to tell which button (in terms of i, j) was clicked in the buttonClick event handler.

My initial thought was to make the button name include a string representation of the i,j (e.g. 'button_2_3'), but it is not possible to specify the name using '{binding ...}'.

It is NOT possible to use the button label (button.content), as the application requirement are to display non-unique labels on the buttons (the buttons label represent the piece on the cell, similar to chess, where the 'Q' label indicate the queens position).

The best alternative seems to store a representation of the row/column in the CommandParameter attribute of the button.

My Question: How to populate the CommandParameter of each button in such a way that it will be simple to extract the (i, j) of the clicked button ? Is there a better alternative to attach user defined data to a button ?

From: How to populate a WPF grid based on a 2-dimensional array

<Window.Resources>
<DataTemplate x:Key="DataTemplate_Level2">
        <Button Content="{Binding}" Height="40" Width="50" Margin="4,4,4,4" click="buttonClick"/>
</DataTemplate>

<DataTemplate x:Key="DataTemplate_Level1">
    <ItemsControl ItemsSource="{Binding}" ItemTemplate="{DynamicResource DataTemplate_Level2}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <StackPanel Orientation="Horizontal"/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
    </ItemsControl>
</DataTemplate>

And

public Window1()
{
    List<List<int>> lsts = new List<List<int>>();

    for (int i = 0; i < 5; i++)
    {
        lsts.Add(new List<int>());

        for (int j = 0; j < 5; j++)
        {
            lsts[i].Add(i * 10 + j);
        }
    }

    InitializeComponent();

    lst.ItemsSource = lsts;
}

private void buttonClick(object sender, EventArgs e)
{
  //do something or...
  Button clicked = (Button) sender;
  MessageBox.Show("Button's name is: " + clicked.Name);
}

alt text


Solution

  • You may bind the Button's Tag property

    <Button Tag="{Binding}" .../>
    

    and use it in a Clicked handler like this:

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        int value = (int)((Button)sender).Tag;
        int row = value / 10;
        int column = value % 10;
    
        Debug.WriteLine("Clicked row {0}, column {1}", row, column);
    }
    

    It may also be simpler to use a single ItemsControl with a UniformGrid as ItemsPanel:

    <ItemsControl ItemsSource="{Binding}"
                  HorizontalAlignment="Left" VerticalAlignment="Top">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <UniformGrid Columns="5"/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Button Content="{Binding}" Tag="{Binding}"
                        Height="40" Width="50" Margin="4" Click="Button_Click"/>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
    

    and bind its ItemsSource to a List<int>:

    public MainWindow()
    {
        InitializeComponent();
    
        var list = new List<int>();
    
        for (int i = 0; i < 5; i++)
        {
            list.AddRange(Enumerable.Range(i * 10, 5));
        }
    
        DataContext = list;
    }