【发布时间】:2011-01-26 12:48:16
【问题描述】:
这是 MVVM 应用程序。有一个窗口和相关的视图模型类。
表格上有TextBox、Button 和ListBox。按钮绑定到具有CanExecute 功能的DelegateCommand。想法是用户在文本框中输入一些数据,按下按钮并将数据附加到列表框。
当用户在TextBox 中输入正确数据时,我想启用命令(和按钮)。事情现在是这样的:
-
CanExecute()方法包含检查绑定到文本框的属性中的数据是否正确的代码。 - 文本框绑定到视图模型中的属性
-
UpdateSourceTrigger设置为PropertyChanged并且视图模型中的属性在用户按下每个键后更新。
问题是当用户在文本框中输入数据时CanExecute() 不会触发。即使文本框失去焦点也不会触发。
我怎样才能做到这一点?
编辑:
Re Yanko 的评论:
Delegate 命令在 MVVM 工具包模板中实现,当您创建新的 MVVM 项目时,解决方案中有 Delegate 命令。正如我在 Prism 视频中看到的那样,这应该是同一类(或至少非常相似)。
这里是 XAML sn-p:
...
<UserControl.Resources>
<views:CommandReference x:Key="AddObjectCommandReference"
Command="{Binding AddObjectCommand}" />
</UserControl.Resources>
...
<TextBox Text="{Binding ObjectName, UpdateSourceTrigger=PropertyChanged}"> </TextBox>
<Button Command="{StaticResource AddObjectCommandReference}">Add</Button>
...
查看模型:
// Property bound to textbox
public string ObjectName
{
get { return objectName; }
set {
objectName = value;
OnPropertyChanged("ObjectName");
}
}
// Command bound to button
public ICommand AddObjectCommand
{
get
{
if (addObjectCommand == null)
{
addObjectCommand = new DelegateCommand(AddObject, CanAddObject);
}
return addObjectCommand;
}
}
private void AddObject()
{
if (ObjectName == null || ObjectName.Length == 0)
return;
objectNames.AddSourceFile(ObjectName);
OnPropertyChanged("ObjectNames"); // refresh listbox
}
private bool CanAddObject()
{
return ObjectName != null && ObjectName.Length > 0;
}
正如我在问题的第一部分中所写,以下事情有效:
-
ObjectName的属性设置器在文本框中的每次按键时触发 - 如果我将
return true;放入CanAddObject(),则命令处于活动状态(按钮)
在我看来绑定是正确的。
我不知道的是如何从上面的代码中使CanExecute() 在ObjectName 属性的设置器中触发。
Re Ben 和 Abe 的回答:
-
CanExecuteChanged()是事件处理程序,编译器报错:事件 'System.Windows.Input.ICommand.CanExecuteChanged' 只能出现在左侧 += 或 -=
ICommand中只有两个成员:Execute()和CanExecute()
您是否有一些示例说明如何进行命令调用CanExecute()。
我在DelegateCommand.cs 中找到了命令管理器助手类,我会研究一下,也许有一些机制可以提供帮助。
无论如何,为了根据用户输入激活命令,需要在属性设置器代码中“轻推”命令对象的想法看起来很笨拙。它将引入依赖关系,而 MVVM 的一大要点就是减少它们。
编辑 2:
我尝试通过从上面的代码调用addObjectCommand.RaiseCanExecuteChanged() 到ObjectName 属性设置器来激活CanExecute。这也无济于事。 CanExecute() 在表单初始化时被触发了几次,但之后它就再也不会被执行了。这是代码:
// Property bound to textbox
public string ObjectName
{
get { return objectName; }
set {
objectName = value;
addObjectCommand.RaiseCanExecuteChanged();
OnPropertyChanged("ObjectName");
}
}
编辑 3:解决方案
正如Yanko Yankov 和JerKimball 所写,问题是静态资源。当我像 Yanko 建议的那样更改按钮绑定时:
<Button Command="{Binding AddObjectCommand}">Add</Button>
事情立即开始运作。我什至不需要RaiseCanExecuteChanged()。现在CanExecute 会自动触发。
我为什么首先使用静态资源?
原始代码来自WPF MVVM toolkit 手册。该手册中的示例将命令定义为静态资源,然后将其绑定到菜单项。不同之处在于,在我的示例中,MVVM 手册不是使用字符串属性,而是使用ObservableCollection。
编辑 4:最终解释
我终于明白了。我需要做的就是阅读CommandReference 类中的评论。它说:
/// <summary>
/// This class facilitates associating a key binding in XAML markup to a command
/// defined in a View Model by exposing a Command dependency property.
/// The class derives from Freezable to work around a limitation in WPF when
/// databinding from XAML.
/// </summary>
所以,CommandReference 用于KeyBinding,它不适用于视觉元素中的绑定。在上面的代码中,资源中定义的命令引用适用于 KeyBinding,我在此用户控件上没有。
当然,WPF MVVM 工具包附带的示例代码是正确的,但我看错了,在可视元素绑定中使用了CommandReference。
这个 WPF MVVM 有时真的很棘手。
【问题讨论】:
-
理论上这应该可行。既然您提到了 DelegateCommand,我们应该假设您使用的是 Prism/CAL,还是您自己实现 ICommand?一些源代码将有助于查看。例如,按钮的绑定是什么样的(在代码中)。您如何连接 DelegateCommand,或者如果您自己实现它,该实现是什么样的,等等。