Привязка к командам в WinForms

Как кнопка привязана к команде в модели представления, например, в WPF с MVVM?

Ответы

Ответ 1

Я привязал объекты ICommand к свойству Tag объектов Button и MenuItem раньше.

Затем я просто вижу, могу ли я запустить и запустить его, если можно, например:

private void button1_Click(object sender, EventArgs e)
{
    ICommand command = ((Control)(sender)).Tag as ICommand;

    if (command != null)
    {
        command.Execute();
    }
}

Для еще более простой работы попробуйте подклассифицировать элементы управления (например, Button, MenuItem)

Ответ 2

Мне было интересно, можно ли сделать то же самое и закончил писать простой CommandManager, который запрашивает зарегистрированные команды (в событии Application.Idle) и использует привязку данных для изменения состояния Enabled элемента управления

Это код, который я использую прямо сейчас:

public class CommandManager: Component
{
    private IList<ICommand> Commands { get; set; }
    private IList<ICommandBinder> Binders { get; set; }

    public CommandManager()
    {
        Commands = new List<ICommand>();

        Binders = new List<ICommandBinder>
                      {
                          new ControlBinder(),
                          new MenuItemCommandBinder()
                      };

        Application.Idle += UpdateCommandState;
    }

    private void UpdateCommandState(object sender, EventArgs e)
    {
        Commands.Do(c => c.Enabled);
    }

    public CommandManager Bind(ICommand command, IComponent component)
    {
        if (!Commands.Contains(command))
            Commands.Add(command);

        FindBinder(component).Bind(command, component);
        return this;
    }

    protected ICommandBinder FindBinder(IComponent component)
    {
        var binder = GetBinderFor(component);

        if (binder == null)
            throw new Exception(string.Format("No binding found for component of type {0}", component.GetType().Name));

        return binder;
    }

    private ICommandBinder GetBinderFor(IComponent component)
    {
        var type = component.GetType();
        while (type != null)
        {
            var binder = Binders.FirstOrDefault(x => x.SourceType == type);
            if (binder != null)
                return binder;

            type = type.BaseType;
        }

        return null;
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
            Application.Idle -= UpdateCommandState;

        base.Dispose(disposing);
    }
}

public static class Extensions
{
    public static void Do<T>(this IEnumerable<T> @this, Func<T, object> lambda)
    {
        foreach (var item in @this)
            lambda(item);
    }
}
public abstract class CommandBinder<T> : ICommandBinder where T: IComponent
{
    public Type SourceType
    {
        get { return typeof (T); }
    }

    public void Bind(ICommand command, object source)
    {
        Bind(command, (T) source); 
    }

    protected abstract void Bind(ICommand command, T source);
}

public class ControlBinder: CommandBinder<Control>
{
    protected override void Bind(ICommand command, Control source)
    {
        source.DataBindings.Add("Enabled", command, "Enabled");
        source.DataBindings.Add("Text", command, "Name");
        source.Click += (o, e) => command.Execute();
    }
}

public class MenuItemCommandBinder : CommandBinder<ToolStripItem>
{
    protected override void Bind(ICommand command, ToolStripItem source)
    {
        source.Text = command.Name;
        source.Enabled = command.Enabled;
        source.Click += (o, e) => command.Execute();

        command.PropertyChanged += (o, e) => source.Enabled = command.Enabled;
    }
}

и это пример того, как его использовать:

public partial class Form1 : Form
{
    private CommandManager commandManager;

    public ICommand CommandA { get; set; }
    public ICommand CommandB { get; set; }

    public bool condition;

    public Form1()
    {
        InitializeComponent();

        commandManager = new CommandManager();

        CommandA = new DelegateCommand("Command 1", OnTrue, OnExecute);
        CommandB = new DelegateCommand("Command 2", OnFalse, OnExecute);

        commandManager.Bind(CommandA, button1);
        commandManager.Bind(CommandB, button2);

        commandManager.Bind(CommandA, command1ToolStripMenuItem);
        commandManager.Bind(CommandB, command2ToolStripMenuItem);
    }

    private bool OnFalse()
    {
        return !condition;
    }

    private bool OnTrue()
    {
        return condition;
    }

    private void OnExecute()
    {
        condition = !condition;
    }
}

Также, если вам нужен код, я писал об этом здесь

Ответ 3

Вы можете создать общий класс привязки команд, который позволяет команде привязываться к любому классу, который наследует от ButtonBase.

public class CommandBinding<T> where T : ButtonBase
{
    private T _invoker;
    private ICommand _command;

    public CommandBinding(T invoker, ICommand command)
    {
        _invoker = invoker;
        _command = command;

        _invoker.Enabled = _command.CanExecute(null);
        _invoker.Click += delegate { _command.Execute(null); };
        _command.CanExecuteChanged += delegate { _invoker.Enabled = _command.CanExecute(null); };
    }
}

Затем привязка команд может быть настроена с использованием следующего кода:

CommandBinding<Button> cmdBinding = 
    new CommandBinding<Button>(btnCut, CutCommand);

Это только голые кости моей реализации, чтобы дать вам начало, поэтому, естественно, есть несколько предостережений:

  • В примере предполагается использование интерфейса WPF ICommand, поэтому его можно изменить, если у вас есть собственная реализация шаблона команды.
  • Параметры, которые передаются, должны быть проверены для нулевых ссылок.
  • Более конкретная реализация должна иметь некоторый метод удаления обработчиков событий, чтобы избежать утечек памяти.

Общее ограничение также может быть изменено на Control, которое предоставляет событие Click и свойство Enabled, что означает, что команды могут привязываться практически к любому элементу управления.

Ответ 4

button1.Click += (s, e) => new MyCommand().Execute();

Ответ 5

Если вы хотите связать команду с элементом управления с помощью конструктора, проверьте это демонстрационное приложение, где я покажу, как использовать MVVM в Windows Forms:

https://bitbucket.org/lbras/mvvmforms

Единственным кодом, который вы должны записать в коде, является создание экземпляра модели представления.

Ответ 6

Я не думаю, что вы можете сделать это напрямую, но как насчет использования обработчика нажатия кнопки для вызова команды? Это не так чисто, как WPF, но вы все равно получите свое разделение.

Ответ 7

Я бы рекомендовал реализовать INotifyPropertyChanged, который вы можете использовать в WinForms, а также в WPF. См. здесь для введения и здесь для некоторых дополнительных информация.

Ответ 8

Вы можете найти интересный WAF Windows Forms Adapter. В нем показано, как применить шаблон Model-View-ViewModel (MVVM) в приложении Windows Forms. Реализация адаптера обеспечивает решение для отсутствующей поддержки Command в Windows Forms.

Ответ 9

Мой подход основан на ответе Гейла, но я использую метод расширения и делаю привязку одноразовой для управления жизненным циклом:

    public static class ButtonExtensions
    {
        public static IDisposable Bind(this ButtonBase invoker, ICommand command)
        {
            void Click(object sender, EventArgs args) => command.Execute(null);
            void CanExecuteChanged(object sender, EventArgs args) => invoker.Enabled = command.CanExecute(null);

            invoker.Enabled = command.CanExecute(null);

            invoker.Click += Click;
            command.CanExecuteChanged += CanExecuteChanged;

            return Disposable.Create(() =>
            {
                invoker.Enabled = false;
                invoker.Click -= Click;
                command.CanExecuteChanged -= CanExecuteChanged;
            });
        }
    }

Вы можете использовать это так:

private List<IDisposable> Disposables { get; } = new List<IDisposable>();
private ICommand MyCommand { get; }

public MyControl()
{
    MyCommand = DelegateCommand.Create(() => ...);
    Disposables.Add(myButton.Bind(MyCommand));
}

~MyControl()
{
    foreach(var disposable in Disposables)
    {
        disposable?.Dispose();
    }
}

Однако можно предпочесть использовать ReactiveUI, который имеет встроенную поддержку для этого:

ReactiveUI 6.0 и привязка WinForms