基于TextBox值(WPF)启用按钮

时间:2010-03-11 18:53:20

标签: wpf mvvm command

这是MVVM应用程序。有一个窗口和相关的视图模型类。

表单上有TextBoxButtonListBox。按钮绑定到具有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:

我尝试通过从上面的代码调用CanExecuteaddObjectCommand.RaiseCanExecuteChanged()属性设置器来激活ObjectName。这也无济于事。初始化表单时,CanExecute()会被触发几次,但之后它永远不会被再次执行。这是代码:

   // Property bound to textbox
   public string ObjectName
    {
        get { return objectName; }
        set { 
            objectName = value;
            addObjectCommand.RaiseCanExecuteChanged();              
            OnPropertyChanged("ObjectName");
        }
    }

编辑3:解决方案

正如Yanko YankovJerKimball所写,问题是静态资源。当我改变像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有时候真的很棘手。

6 个答案:

答案 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>