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

565 Views Asked by At

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

2

There are 2 best solutions below

2
Clemens On BEST ANSWER

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;
}
2
Ievgen On

You can use a type for a button viewmodel instead of the list of int.

Then bind 'name' inside of the content and button will contains your object in the DataContext.

class ButtonViewModel
{
  public string Name {get;set;} 
  public int Value {get;set;} 
}  

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

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

        for (int j = 0; j < 5; j++)
        {
            var model = new ButtonViewModel();
            model.Value = i * 10 + j;
            model.Name = "Q: "+ model.Value;
            lsts[i].Add(model);
        }
    }

    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.DataContext as ButtonViewModel).Name);
}

WPF:

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

P.s. Instead of the button "сlick" event "command" property can be used. Than command property should be added to the ButtonViewModel. This is a proper MVVM way. Also ButtonViewModel should implement IPropertyChanged interface to notify interface about the value changes.