Связывание команд с динамически создаваемыми кнопками - PullRequest
0 голосов
/ 07 мая 2018

Я пытаюсь создать программу, которая позволила бы мне выбрать программу для запуска другой программы в зависимости от требований. По сути, у меня есть документ JSON с указанием имени, пути, значка и т. Д., И для каждой записи создается кнопка.

У меня есть ButtonDef класс, который выглядит так:

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

Я создаю ObservableCollection<ButtonDef> с именем Buttons, который является публичной собственностью в моем ViewModel, и заполняется ButtonDef s.

У меня есть свойство RelayCommand и соответствующий метод, который запустит программу.

Все работает, если я явно создаю RelayCommand для каждой кнопки и вызываю ее в директиве Command в XAML, но, поскольку я не знаю, сколько будет кнопок, это не является хорошим решением.

Мой XAML выглядит так:

<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>

Кнопки созданы отлично, с правильным заголовком, но команда не запускается.

Как я могу это сделать?

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"
        });
    }


}

Ответы [ 2 ]

0 голосов
/ 08 мая 2018

Несмотря на то, что ссылка, предоставленная @dymanoid, дала мне некоторое понимание, потребовалось еще несколько настроек, чтобы заставить ее работать.

Сначала определите RelayCommand в вашем ViewModel следующим образом:

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

Инициализировать свойство RelayCommand:

DoSomething = new RelayCommand<object>(parameter => ExecuteSomething(parameter));

void ExecuteSomething(object parameter)
{
  // Do your work here
}

XAML

Кнопки объявлены следующим образом:

   <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>

, где RelativeSource и часть "DataContext" Path разрешают доступ к DataContext окна.

Две ссылки, которые привели к решению:

Параметр команды WPF для кнопки внутри ItemsControl

и

Передача разных параметров команды в одну и ту же команду с помощью RelayCommand WPF

0 голосов
/ 07 мая 2018

Я не согласен с таким подходом, но я отвечу на вопрос.

Вам потребуется создать объект Binding, а затем использовать BindingOperations, чтобы применить привязку. Я привожу пример в коде ниже с комментариями.

Обычно Commands не требует обязательной привязки, а CommandParameters часто это делают. Итак, я приведу краткий пример, который я собрал, который, надеюсь, даст вам достаточно информации для добавления любой необходимой привязки, включая Command, если вы хотите. Если Command никогда не меняется, я настоятельно рекомендую просто установить его; что аналогично установке Binding с Mode = OneTime.

Этот пример предназначен ТОЛЬКО для того, чтобы показать, как выполнить привязку в коде; ничего больше. Если у вас есть StackPanel (или любая панель) в вашем MainWindow с именем root, вы можете скопировать и вставить этот код, чтобы поиграть с ним. (Добавьте необходимые операторы также)

Здесь я предоставляю простой PersonViewModel, создаю список этих людей (людей), а затем привязываюсь к списку, добавляя ТОЛЬКО * CommandParameterBinding для этого примера.

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;
}
...