基于 TextBox 值 (WPF) 启用按钮
Posted
技术标签:
【中文标题】基于 TextBox 值 (WPF) 启用按钮【英文标题】:Enable button based on TextBox value (WPF) 【发布时间】: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,或者如果您自己实现它,该实现是什么样的,等等。 【参考方案1】:如果 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>
【讨论】:
【参考方案2】:我知道这是一个老问题,但我个人认为将文本框 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>
【讨论】:
【参考方案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");
【讨论】:
请再次检查问题,我添加了代码和一些关于您的答案的 cmets【参考方案4】:现在经过编辑,事情看起来更清晰了,谢谢!这可能是一个愚蠢的问题(我有点厌倦了一整天的工作),但是为什么不直接绑定到命令,而不是通过静态资源呢?
<Button Command="Binding AddObjectCommand">Add</Button>
【讨论】:
这也是我的第一个预感 - 因为静态资源不会被不断地重新评估,即使调用 RaiseCanExecuteChanged 也不会做太多。 是的,似乎他正在将按钮绑定到视图模型的静态实例,该实例与文本框绑定的实例不同,并且很可能前一个实例缺少任何连接到外面的世界开始。 你是对的。问题在于静态资源。解释请参见Edit3。【参考方案5】:在这里呼应安倍,但在这里采取的“正确”路径是使用:
public void RaiseCanExecuteChanged();
在 DelegateCommand 上公开。就依赖关系而言,我认为当命令依赖于 ViewModel 中的属性发生变化时,通过引发这个,你真的不会做任何“坏事”。在这种情况下,耦合或多或少完全包含在 ViewModel 中。
因此,以您上面的示例为例,在“ObjectName”的设置器中,您将在命令“AddObjectCommand”上调用 RaiseCanExecuteChanged。
【讨论】:
Jer,您对 Yanko 回答的评论是正确的。解释请参见Edit3。【参考方案6】:当您的财产发生变化时,请尝试提高 CanExecuteChanged
。命令绑定与属性绑定确实不同,绑定到命令的按钮会通过CanExecuteChanged
事件提醒状态发生变化。
在您的情况下,您可以在对将评估它的绑定属性执行PropertyChanged
并设置命令的内部CanExecute
标志然后引发CanExecuteChanged
时触发检查。更多的是“推动”ICommand
对象而不是“拉动”。
【讨论】:
请再次检查问题,我添加了代码和一些关于您的答案的 cmets以上是关于基于 TextBox 值 (WPF) 启用按钮的主要内容,如果未能解决你的问题,请参考以下文章