Search code examples
c#wpfmvvmdata-bindingicommand

Binding commands to dynamically created buttons


I'm trying to create a program which would allow me to choose a program to start a different program based on different requirements. Basically I have a JSON document specifying Name, Path, Icon etc and a button is created for each entry.

I have a ButtonDef class that looks like this:

public class ButtonDef
{
    public int Id { get; set; }
    public string Caption { get; set; }
    public string Cmd { get; set; }
    public string Icon { get; set; }
}

I create an ObservableCollection<ButtonDef> called Buttons which is a public property in my ViewModel and populate is with ButtonDefs.

I have a RelayCommand property and corresponding method that will start the program.

Everything works if I explicitly create a RelayCommand for each button and call it in the Command directive in XAML but since I don't know how many buttons there will be, that is not an OK solution.

My XAML looks like this:

<ItemsControl ItemsSource="{Binding Buttons}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
                <Button Content="{Binding Caption}" Height="30" Width="50" Margin="10" Command="{Binding DoSomething}" />
        </DataTemplate>
    </ItemsControl.ItemTemplate>

</ItemsControl>

The buttons are created fine, with the correct caption but the Command doesn't fire.

How can I make this happen?

Edit:

 public class MainViewModel : ViewModelBase
{

    public ObservableCollection<ButtonDef> Buttons { get; set; }

    public List<DataItem> Items { get; set; }

    public RelayCommand DoSomething { get; set; }






    /// <summary>
    /// Initializes a new instance of the MainViewModel class.
    /// </summary>
    public MainViewModel(IDataService dataService)
    {


        Items = new List<DataItem> { new DataItem("Item 1"), new DataItem("Item 2") };

        //Items.Add(new DataItem("Item 1"));

        if (Buttons == null) Buttons = new ObservableCollection<ButtonDef>();
        foreach (var item in Items)
        {
            Buttons.Add(new ButtonDef
            {
                Caption = item.Title,
                Cmd = "Path to exe"
        });
    }


}

Solution

  • While the link provided by @dymanoid gave me som insights it took some more tweaking to make it work.

    First define the RelayCommand in your ViewModellike this:

    public RelayCommand<object> DoSomething {get; set;}
    

    Initialize the RelayCommand property:

    DoSomething = new RelayCommand<object>(parameter => ExecuteSomething(parameter));
    
    void ExecuteSomething(object parameter)
    {
      // Do your work here
    }
    

    XAML

    The button(s) are declared in the following way:

       <ItemsControl ItemsSource="{Binding Buttons}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <WrapPanel/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                    <Button Content="{Binding Caption}" Height="30" Width="50" Margin="10" Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}},Path=DataContext.DoSomething}" CommandParameter="{Binding Cmd}" />
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    
    </ItemsControl>
    

    where the RelativeSource and the "DataContext" part of the Pathenables access to the window's DataContext.

    Two links which led to the solution:

    WPF Command Parameter for button inside ItemsControl

    and

    Pass different commandparameters to same command using RelayCommand WPF