Mark Bostleman
Mark Bostleman

Reputation: 2205

Binding to commands in WinForms

How can a button be bound to a command in a view model like in WPF with MVVM?

Upvotes: 24

Views: 20556

Answers (10)

Albert Mosiałek
Albert Mosiałek

Reputation: 190

Binding button to ICommand proposed by other answers has been added to .Net7 as preview feature. You should now be able to bind buttons to commands without custom implementation.

See docs: https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.buttonbase.command?view=windowsdesktop-7.0

MS Blog post about command binding in winforms: https://devblogs.microsoft.com/dotnet/winforms-cross-platform-dotnet-maui-command-binding/

Upvotes: 2

MovGP0
MovGP0

Reputation: 7765

My approach is based on Gale's answer, but using an extension method and making the binding disposable for lifeycle management:

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

You can use it like this:

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

However, one might prefer to use ReactiveUI, which has native support for this:

ReactiveUI 6.0 and WinForms binding

Upvotes: 3

lbras
lbras

Reputation: 140

If you want to bind the command to the control using the designer, check this demo app where I show how to use MVVM in Windows Forms:

https://bitbucket.org/lbras/mvvmforms

The only code you have to write in the code-behind is the creation of the view model instance.

Upvotes: 1

Brett Veenstra
Brett Veenstra

Reputation: 48474

I've attached ICommand objects to the Tag property of Button and MenuItem objects before.

Then, I just see if I can cast and run it if I can, example:

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

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

For even an easier life, try subclassing the controls (e.g. Button, MenuItem)

Upvotes: 3

Olivier Jacot-Descombes
Olivier Jacot-Descombes

Reputation: 112352

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

Upvotes: 1

Benjamin Gale
Benjamin Gale

Reputation: 13177

You could create a generic command binding class that allows a command to be bound to any class that inherits from 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); };
    }
}

The command binding can then be set up using the following code:

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

This is only the bare bones of my implementation to give you a start so naturally there are a few caveats:

  • The example assumes the use of the WPF ICommand interface so may have to be altered if you have your own implementation of the command pattern.
  • The parameters that are passed in should be checked for null references.
  • A more concrete implementation should have some method of removing the event handlers to avoid memory leaks.

The generic constraint can also be changed to Control which exposes the Click event and the Enabled property which means commands can be bound to almost any Control.

Upvotes: 6

Sebastian Piu
Sebastian Piu

Reputation: 8008

I was wondering if the same thing could be done and ended writing a simple CommandManager that queries the registered commands (on the Application.Idle event) and uses databinding to change the Enabled state of the control

This is the code I'm using right now:

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

and this is an exmaple of how to use it:

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

Also if you need the code, I blogged about it here

Upvotes: 19

jbe
jbe

Reputation: 7031

You might find the WAF Windows Forms Adapter interesting. It shows how to apply the Model-View-ViewModel (MVVM) Pattern in a Windows Forms application. The Adapter implementation provides a solution for the missing Command support in Windows Forms.

Upvotes: 0

Matt Warren
Matt Warren

Reputation: 10291

I'd recommend implementing INotifyPropertyChanged you can use it in WinForms as well as WPF. See here for an introduction and here for some additional information.

Upvotes: 0

serialhobbyist
serialhobbyist

Reputation: 4815

I don't think you can do it directly but how about using the button's click handler to invoke the command? It's not as clean as WPF but you still get your separation.

Upvotes: 0

Related Questions