这是MVVM应用程序。有一个窗口和相关的视图模型类。
表单上有TextBox
,Button
和ListBox
。按钮绑定到具有DelegateCommand
功能的CanExecute
。想法是用户在文本框中输入一些数据,按下按钮并将数据附加到列表框中。
当用户在TextBox
中输入正确的数据时,我想启用命令(和按钮)。事情现在就像这样:
CanExecute()
方法包含检查绑定到文本框的属性中的数据是否正确的代码。UpdateSourceTrigger
设置为PropertyChanged
,每个关键用户按下后,视图模型中的属性都会更新。 问题是,当用户在文本框中输入数据时,CanExecute()
不会触发。即使文本框失去焦点,它也不会触发。
我怎样才能做到这一点?
修改
Re Yanko的评论:
Delegate命令在MVVM工具包模板中实现,当您创建新的MVVM项目时,解决方案中有Delegate命令。就像我在Prism视频中看到的那样,这应该是同一个类(或者至少非常相似)。
以下是XAML代码段:
...
<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()
属性的setter中激活ObjectName
。
Re Ben和Abe的答案:
CanExecuteChanged()
是事件处理程序和编译器抱怨:
活动 'System.Windows.Input.ICommand.CanExecuteChanged' 只能出现在左侧 + =或 - =
ICommand
只有两名成员:Execute()
和CanExecute()
您是否有一些示例显示如何进行命令调用CanExecute()
。
我在DelegateCommand.cs
找到了命令管理器助手类,我会调查它,也许有一些机制可以帮助。
无论如何,想要为了激活基于用户输入的命令,需要在属性设置器代码中“轻推”命令对象看起来很笨拙。它将引入依赖关系,MVVM的一个重点是减少它们。
编辑2:
我尝试通过从上面的代码调用CanExecute
到addObjectCommand.RaiseCanExecuteChanged()
属性设置器来激活ObjectName
。这也无济于事。初始化表单时,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有时候真的很棘手。
答案 0 :(得分:4)
现在编辑看起来更加清晰,谢谢!这可能是一个愚蠢的问题(我有点厌倦了漫长的一天的工作),但为什么不直接绑定命令,而不是通过静态资源?
<Button Command="{Binding AddObjectCommand}">Add</Button>
答案 1 :(得分:3)
由于您使用的是DelegateCommand,因此可以在文本属性更改时调用它的RaiseCanExecuteChanged方法。我不确定您要使用CommandReference资源完成什么,但通常只需将命令直接绑定到按钮元素的Command属性:
<TextBox Text="{Binding ObjectName, UpdateSourceTrigger=ValueChanged}" />
<Button Command="{Binding AddObjectCommand}" Content="Add" />
这将是您的视图模型的相关部分:
public string ObjectName
{
get { return objectName; }
set
{
if (value == objectName) return;
value = objectName;
AddObjectCommand.RaiseCanExecuteChanged();
OnPropertyChanged("ObjectName");
}
}
答案 2 :(得分:1)
在您的媒体资源发生变化时尝试提升CanExecuteChanged
。命令绑定与属性绑定完全不同,绑定到命令的按钮会被CanExecuteChanged
事件警告状态发生变化。
在您的情况下,您可以在绑定属性上执行PropertyChanged
时检查它,并设置命令的内部CanExecute
标志然后引发CanExecuteChanged
。更多的是“推”到ICommand
对象而不是“拉”。
答案 3 :(得分:1)
在这里回应安倍,但这里采取的“正确”路径是:
public void RaiseCanExecuteChanged();
在DelegateCommand上公开。就依赖关系而言,当命令依赖于ViewModel中的更改的属性时,我不认为你真的做了任何“坏事”。在这种情况下,耦合或多或少完全包含在ViewModel中。
因此,以上面的例子为例,在“ObjectName”的setter中,你可以在命令“AddObjectCommand”上调用RaiseCanExecuteChanged。
答案 4 :(得分:1)
我知道这是一个古老的问题,但是我个人认为将文本框Length
绑定到按钮的IsEnabled
属性比较容易,例如:
<TextBox Name="txtbox" Width="100" Height="30"/>
<Button Content="SomeButton " Width="100" Height="30"
IsEnabled="{Binding ElementName=txtbox, Path=Text.Length, Mode=OneWay}"></Button>
答案 5 :(得分:0)
如果ElementName绑定不起作用,请使用:
<Entry x:Name="Number1" Text="{Binding Number1Text}" Keyboard="Numeric"></Entry>
<Entry x:Name="Number2" Text="{Binding Number2Text}" Keyboard="Numeric"></Entry>
<Button Text="Calculate" x:Name="btnCalculate" Command="{Binding CalculateCommand}" IsEnabled="{Binding Source={x:Reference Number1, Number2}, Path=Text.Length, Mode=OneWay}"></Button>
或使用:
<Entry x:Name="Number1" Text="{Binding Number1Text}" Placeholder="Number 1" Keyboard="Numeric"></Entry>
<Entry x:Name="Number2" Text="{Binding Number2Text}" Placeholder="Number 2" Keyboard="Numeric"></Entry>
<Button VerticalOptions="Center" Text="Calculate" x:Name="btnCalculate" Command="{Binding CalculateCommand}">
<Button.Triggers>
<DataTrigger TargetType="Button"
Binding="{Binding Source={x:Reference Number1, Number2},
Path=Text.Length}"
Value="{x:Null}">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Button.Triggers>