Вы должны следовать шаблону MVVM при работе с WPF.
Прежде всего, необходимо базовый класс, который реализует INotifyPropertyChanged
, который используется WPF для связывания уведомления:
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace WpfApplication1
{
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged([CallerMemberName] string propertyName = "")
{
var handler = PropertyChanged;
handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetProperty<T>(ref T backingField, T newValue, [CallerMemberName] string propertyName = "")
{
return SetProperty<T>(ref backingField, newValue, EqualityComparer<T>.Default, propertyName);
}
protected bool SetProperty<T>(ref T backingField, T newValue, IEqualityComparer<T> comparer, [CallerMemberName] string propertyName = "")
{
if (comparer.Equals(backingField, newValue)) return false;
backingField = newValue;
RaisePropertyChanged(propertyName);
return true;
}
protected bool SetProperty<T>(ref T backingField, T newValue, IComparer<T> comparer, [CallerMemberName] string propertyName = "")
{
if (comparer.Compare(backingField, newValue) == 0) return false;
backingField = newValue;
RaisePropertyChanged(propertyName);
return true;
}
}
}
Для кнопок вам нужен ICommand
поэтому мы строим базовый класс для реализации, что
using System;
using System.Windows.Input;
namespace WpfApplication1
{
public abstract class CommandBase : ICommand
{
public virtual event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public virtual bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
if (CanExecute(parameter))
DoExecute(parameter);
}
protected abstract void DoExecute(object parameter);
}
}
Следующая класс является RelayCommand
унаследовал от CommandBase
using System;
namespace WpfApplication1
{
public class RelayCommand : CommandBase
{
private readonly Func<object, bool> _canexecute;
private readonly Action<object> _execute;
public RelayCommand(Action<object> execute) : this(execute, o => true)
{
}
public RelayCommand(Action<object> execute, Func<object, bool> canexecute)
{
if (execute == null) throw new ArgumentNullException(nameof(execute));
if (canexecute == null) throw new ArgumentNullException(nameof(canexecute));
_execute = execute;
_canexecute = canexecute;
}
public override bool CanExecute(object parameter)
{
return base.CanExecute(parameter) && _canexecute(parameter);
}
protected override void DoExecute(object parameter)
{
_execute(parameter);
}
}
}
Теперь у нас есть небольшая база, мы можем работать. Кнопки должны выполнять что-то и иметь текст, который будет отображаться. Таким образом, мы определяем класс ViewModel, который будет представлять этот
using System.Windows.Input;
namespace WpfApplication1.ViewModel
{
public class CommandViewModel : ObservableObject
{
private ICommand command;
private string displayText;
public ICommand Command
{
get { return command; }
set { SetProperty(ref command, value); }
}
public string DisplayText
{
get { return displayText; }
set { SetProperty(ref displayText, value); }
}
}
}
Далее нам нужен ViewModel, который содержит список и Добавить команда
using System;
using System.Collections.ObjectModel;
namespace WpfApplication1.ViewModel
{
public class MainWindowViewModel : ObservableObject
{
public MainWindowViewModel()
{
AddNewCommand = new CommandViewModel
{
DisplayText = "Add",
Command = new RelayCommand(DoAddNewCommand)
};
Commands = new ObservableCollection<CommandViewModel>();
}
public CommandViewModel AddNewCommand { get; }
public ObservableCollection<CommandViewModel> Commands { get; }
private void DoAddNewCommand(object obj)
{
Commands.Add(new CommandViewModel
{
DisplayText = "Foo",
Command = new RelayCommand(DoFoo),
});
}
private void DoFoo(object obj)
{
throw new NotImplementedException();
}
}
}
Теперь настало время, чтобы связать все это в XAML
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication1"
xmlns:vm="clr-namespace:WpfApplication1.ViewModel"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<vm:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<StackPanel>
<Button
Content="{Binding Path=AddNewCommand.DisplayText}"
Command="{Binding Path=AddNewCommand.Command}"/>
<ItemsControl
ItemsSource="{Binding Path=Commands}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button
Content="{Binding Path=DisplayText}"
Command="{Binding Path=Command}"
Margin="2"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</Window>
Как вы можете видеть, CodeBehind вообще отсутствует. Все делается внутри ViewModels, и представление просто представляет.
Когда вы добавляете элемент в ButtonList, ItemControl создаст для вас кнопку (потому что это кнопка в ItemTemplate).Но ButtonList лучше должен быть 'ObservableCollection <>', а не простой 'List <>', потому что ObservableCollection <> 'уведомляет пользовательский интерфейс, когда элементы были изменены – ASh
, поэтому я бы объявил ButtonList как ObservableCollecition? – raphadko
хорошо, да, это certanly не причинит вреда. Интерфейс ObservableCollecition и INotifyPropertyChanged - это две основные вещи WPF, чтобы уведомить пользовательский интерфейс WPF о любых изменениях в ваших данных – ASh