如何在一个视图模型中将一个按钮绑定到一个命令,就像在带有MVVM的WPF中一样?
答案 0 :(得分:17)
我想知道是否可以完成同样的事情并且结束编写一个简单的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;
}
}
此外,如果您需要代码,我在博客上发表了here
答案 1 :(得分:6)
您可以创建一个通用命令绑定类,该类允许将命令绑定到从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);
这只是我实施的基本内容,为您提供一个开始,所以自然会有一些注意事项:
ICommand
接口,因此如果您拥有自己的命令模式实现,则可能需要更改。通用约束也可以更改为Control
,它会公开Click
事件和Enabled
属性,这意味着命令几乎可以绑定到任何Control。
答案 2 :(得分:3)
我之前已将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
)
答案 3 :(得分:1)
button1.Click += (s, e) => new MyCommand().Execute();
答案 4 :(得分:1)
如果要使用设计器将命令绑定到控件,请查看此演示应用程序,其中显示了如何在Windows窗体中使用MVVM:
https://bitbucket.org/lbras/mvvmforms
您必须在代码隐藏中编写的唯一代码是创建视图模型实例。
答案 5 :(得分:1)
我的方法基于Gale的答案,但是使用扩展方法并使绑定可用于生命周期管理:
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,它对此具有本机支持:
答案 6 :(得分:0)
我认为你不能直接这样做但是如何使用按钮的点击处理程序来调用命令呢?它不像WPF那么干净,但你仍然可以分离。
答案 7 :(得分:0)
我建议实现INotifyPropertyChanged,你可以在WinForms和WPF中使用它。有关简介,请参阅here;有关其他信息,请参见here。
答案 8 :(得分:0)
您可能会发现 WAF Windows Forms Adapter 很有趣。它展示了如何在Windows窗体应用程序中应用Model-View-ViewModel(MVVM)模式。适配器实现为Windows窗体中缺少的命令支持提供了解决方案。