【问题标题】:Enable button based on TextBox value (WPF)基于 TextBox 值 (WPF) 启用按钮
【发布时间】:2011-01-26 12:48:16
【问题描述】:

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

表格上有TextBoxButtonListBox。按钮绑定到具有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 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 有时真的很棘手。

【问题讨论】:

  • 理论上这应该可行。既然您提到了 DelegateCommand,我们应该假设您使用的是 Prism/CAL,还是您自己实现 ICommand?一些源代码将有助于查看。例如,按钮的绑定是什么样的(在代码中)。您如何连接 DelegateCommand,或者如果您自己实现它,该实现是什么样的,等等。

标签: wpf mvvm command


【解决方案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
      猜你喜欢
      • 2013-07-03
      • 2011-09-22
      • 2020-03-08
      • 2013-07-16
      • 1970-01-01
      • 2011-09-15
      • 2011-06-07
      • 2013-10-14
      • 1970-01-01
      相关资源
      最近更新 更多