Reputation: 181
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 ButtonDef
s.
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"
});
}
}
Upvotes: 1
Views: 4001
Reputation: 181
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 ViewModel
like 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 Path
enables 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
Upvotes: 1
Reputation: 6749
I don't agree with this approach but I'll answer the question.
You'll need to make a Binding
object and then use BindingOperations
to apply the binding. I'm providing an example in code below with comments.
Typically Commands
don't require a notifiable binding yet CommandParameters
often do. So I'm going to provide a quick example I put together that hopefully gives you enough information to add any binding you need, including the Command
if you want to. If the Command
never changes I strongly suggest just setting it; which is the same as setting a Binding
with Mode = OneTime
.
This example is ONLY to show how to do a binding in code; nothing else. If you have a StackPanel
(or any panel) in your MainWindow
named 'root' then you can copy and paste this code behind to play with it. (Add the necessary using statements also)
Here I provide a simple PersonViewModel
, make a list of those persons (People), and then binding to the list adding ONLY a CommandParameterBinding
for this example.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Initialize();
}
public void Initialize()
{
var ids = 0;
var people = new List<PersonViewModel>()
{
new PersonViewModel() { Id = ids++, Name = "Mathew"},
new PersonViewModel() { Id = ids++, Name = "Mark"},
new PersonViewModel() { Id = ids++, Name = "Luke"},
new PersonViewModel() { Id = ids++, Name = "John"}
};
foreach (var person in people)
{
var button = new Button()
{
Content = person.Name,
Command = person.UpdatePersonCommand
};
SetCommandParameterBinding(button, person);
button.Click += (s, e) => MessageBox.Show(button.CommandParameter.ToString());
root.Children.Add(button);
}
}
//This is the method that answers your question
private static BindingExpressionBase SetCommandParameterBinding(ButtonBase button, PersonViewModel person)
{
//This sets a binding that binds the 'Name' property in PersonViewModel
//Leave constructor parameter emtpy to bind to the object itself i.e. new Binding() { Source = Person }; will bind to person
var binding = new Binding(nameof(PersonViewModel.Name)) { Source = person };
//This sets the binding to the button and button CommandParameterProperty
var bindingExpression = BindingOperations.SetBinding(button, ButtonBase.CommandParameterProperty, binding);
return bindingExpression;
}
}
//This isn't a fully written ViewModel obviously. It's just here to make this example work. INotifyPropertyChanged is not completely implemented. It also definitely doesn't belong in this namespace.
public class PersonViewModel : INotifyPropertyChanged
{
public string Name { get; set; }
public int Id { get; set; }
public ICommand UpdatePersonCommand { get; }
public event PropertyChangedEventHandler PropertyChanged;
}
Upvotes: 1