WPF:如果“文本框”中的文本更改,则“更新”按钮

要学习WPF Commandcommandparameter,我有一个小型WPF应用程序,其中有一个TextBox和一个Button。每当按下按钮时,都应使用文本框的文本作为参数来调用ICommandTest

这很好。下一步是:如果文本太小,则应禁用该按钮。

我使用MVVMLight来实现命令。只要按下按钮,下面的代码就足以调用方法Test。

到目前为止的代码

以下工作原理:启动时,文本框将获得其正确的初始文本。该按钮询问视图模型是否可以将此文本用作测试的参数:

public class MyViewModel
{
    public ICommand CommandTest {get;}

    public MyViewModel()
    {
        this.CommandTest = new RelayCommand<string>(this.Test,this.CanTest); 
    }

    private bool CanTest(string text)
    {
        // text should have a minimum length of 4
        return text != null && text.Length >= 4;
    }
    private void Test(string text)
    {
        //...
    }

    // ...

}

XAML:水平StackPanel中的可编辑文本框和按钮。

<StackPanel Name="Test" Orientation="Horizontal" Background="AliceBlue">
    <TextBox Name="ProposedTestvalue"
             Text="Alle eendjes zwemmen in het water"
             Width="500" Height="20"/>

    <Button x:Name="ButtonTest" Content="Change"
                    Height="auto" Width="74"
                    Padding="5,2"
                    Command="{Binding Path=CommandTest}"
                    commandparameter="{Binding ElementName=ProposedTestvalue,Path=Text}"/>
</StackPanel>

文本更改

如果我更改文本并按下按钮,则会使用更改后的文本来调用命令。因此Commandcommandparameter可以工作。

但是,如果文本少于4个字符,则该按钮不会禁用。。每当按钮的绑定commandparameter的值更改时,按钮应询问其命令是否可以执行。

该怎么做?

NotifyOnSourceUpdated

Yosef bernal建议添加NotifyOnSourceUpdated:

<Button x:Name="ButtonChangeTestText" Content="Change"
                Height="30" Width="74" Padding="5,2"
                Command="{Binding Path=CommandTest}"
                commandparameter="{Binding ElementName=ProposedTestTextvalue,Path=Text,NotifyOnSourceUpdated=True}"/>

A,这没有任何改变:在启动时,将使用正确的参数调用初始CanTest。更改文本不会引起CanTest。如果按下按钮CanTest,则会调用正确的值。如果文本较小,则CanTest返回false,因此命令不执行。但是,即使CanExecute返回false,该按钮仍保持启用状态。

我应该告诉Button如果无法执行该怎么办?还是禁用按钮是默认行为?

iCMS 回答:WPF:如果“文本框”中的文本更改,则“更新”按钮

您可以将Text的{​​{1}}属性绑定到TextBox上的Text属性。

MyViewModel

<TextBox Name="ProposedTestValue" Text="{Binding Text}" Width="500" Height="20"/> 中创建一个带有后备字段Text的相应MyViewModel属性。

_text

private string _text; public string Text { get => _text; set { if (_text != value) { _text = value; CommandTest.RaiseCanExecuteChanged(); } } } 属性更新时,RaiseCanExecuteChanged方法将强制重新评估CanExecute,具体取决于您的Text。您不再需要UpdateSourceTrigger,因为您可以在视图模型中使用CommandParameter属性。

Text

注意:如果您打算通过视图模型更新public MyViewModel() { this.CommandTest = new RelayCommand(this.Test,this.CanTest); } private bool CanTest() { return Text != null && Text.Length >= 4; } private void Test() { // ...use "Text" here. } 属性,则必须实现Text,否则更改后的值将不会反映在视图中

,

Harald Coppoolse ,您的代码没有错误! 它在您发布的代码之外。 可能是RelayCommand的实现错误。

这是我正在使用的实现示例

using System;
using System.Windows;
using System.Windows.Input;

namespace Common
{
    #region Delegates for WPF Command Methods
    /// <summary>Delegate of the executive team method.</summary>
    /// <param name="parameter">Command parameter.</param>
    public delegate void ExecuteHandler(object parameter);
    /// <summary>Command сan execute method delegate.</summary>
    /// <param name="parameter">Command parameter.</param>
    /// <returns><see langword="true"/> if command execution is allowed.</returns>
    public delegate bool CanExecuteHandler(object parameter);
    #endregion

    #region Class commands - RelayCommand
    /// <summary>A class that implements the ICommand interface for creating WPF commands.</summary>
    public class RelayCommand : ICommand
    {
        private readonly CanExecuteHandler _canExecute;
        private readonly ExecuteHandler _onExecute;
        private readonly EventHandler _requerySuggested;

        public event EventHandler CanExecuteChanged;

        /// <summary>Command constructor.</summary>
        /// <param name="execute">Executable command method.</param>
        /// <param name="canExecute">Method allowing command execution.</param>
        public RelayCommand(ExecuteHandler execute,CanExecuteHandler canExecute = null)
        {
            _onExecute = execute;
            _canExecute = canExecute;

            _requerySuggested = (o,e) => Invalidate();
            CommandManager.RequerySuggested += _requerySuggested;
        }

        public void Invalidate()
            => Application.Current.Dispatcher.BeginInvoke
            (
                new Action(() => CanExecuteChanged?.Invoke(this,EventArgs.Empty)),null
            );

        public bool CanExecute(object parameter) => _canExecute == null ? true : _canExecute.Invoke(parameter);

        public void Execute(object parameter) => _onExecute?.Invoke(parameter);
    }

    #endregion

}

RelayCommand

namespace Common
{
    #region Delegates for WPF Command Methods
    /// <summary>Delegate of the executive team method.</summary>
    /// <param name="parameter">Command parameter.</param>
    public delegate void ExecuteHandler<T>(T parameter);
    /// <summary>Command сan execute method delegate.</summary>
    /// <param name="parameter">Command parameter.</param>
    /// <returns><see langword="true"/> if command execution is allowed.</returns>
    public delegate bool CanExecuteHandler<T>(T parameter);
    #endregion

    /// <summary>Class for typed parameter commands.</summary>
    public class RelayCommand<T> : RelayCommand
    {

        /// <summary>Command constructor.</summary>
        /// <param name="execute">Executable command method.</param>
        /// <param name="canExecute">Method allowing command execution.</param>
        public RelayCommand(ExecuteHandler<T> execute,CanExecuteHandler<T> canExecute = null)
            : base(p => execute(p is T t ? t : default),p => p is T t && (canExecute?.Invoke(t) ?? true)) { }

    }
}

BaseINPC

using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace Common
{
    /// <summary>Base class implementing INotifyPropertyChanged.</summary>
    public abstract class BaseINPC : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>Called AFTER the property value changes.</summary>
        /// <param name="propertyName">The name of the property.
        /// In the property setter,the parameter is not specified. </param>
        public void RaisePropertyChanged([CallerMemberName] string propertyName = "")
            => PropertyChanged?.Invoke(this,new PropertyChangedEventArgs(propertyName));

        /// <summary> A virtual method that defines changes in the value field of a property value. </summary>
        /// <typeparam name = "T"> Type of property value. </typeparam>
        /// <param name = "oldValue"> Reference to the field with the old value. </param>
        /// <param name = "newValue"> New value. </param>
        /// <param name = "propertyName"> The name of the property. If <see cref = "string.IsNullOrWhiteSpace (string)" />,/// then ArgumentNullException. </param> 
        /// <remarks> If the base method is not called in the derived class,/// then the value will not change.</remarks>
        protected virtual void Set<T>(ref T oldValue,T newValue,[CallerMemberName] string propertyName = "")
        {
            if (string.IsNullOrWhiteSpace(propertyName))
                throw new ArgumentNullException(nameof(propertyName));

            if ((oldValue == null && newValue != null) || (oldValue != null && !oldValue.Equals(newValue)))
                OnValueChange(ref oldValue,newValue,propertyName);
        }

        /// <summary> A virtual method that changes the value of a property. </summary>
        /// <typeparam name = "T"> Type of property value. </typeparam>
        /// <param name = "oldValue"> Reference to the property value field. </param>
        /// <param name = "newValue"> New value. </param>
        /// <param name = "propertyName"> The name of the property. </param>
        /// <remarks> If the base method is not called in the derived class,/// then the value will not change.</remarks>
        protected virtual void OnValueChange<T>(ref T oldValue,string propertyName)
        {
            oldValue = newValue;
            RaisePropertyChanged(propertyName);
        }

    }
}

MyViewModel

using Common;

namespace RenderCanCommand
{
    public class MyViewModel : BaseINPC
    {
        private string _text;
        public string Text { get => _text; private set => Set(ref _text,value); }

        public RelayCommand<string> CommandTest { get; }

        public MyViewModel()
        {
            CommandTest = new RelayCommand<string>(Test,CanTest);
        }

        private bool CanTest(string text)
        {
            // text should have a minimum length of 4
            return text != null && text.Length >= 4 && text != Text;
        }
        private void Test(string text)
        {
            Text = text;

        }
    }
}

Window XAML

<Window x:Class="RenderCanCommand.TestWind"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:RenderCanCommand"
        mc:Ignorable="d"
        Title="TуstWind" Height="450" Width="800">
    <Window.DataContext>
        <local:MyViewModel/>
    </Window.DataContext>
    <StackPanel Orientation="Horizontal" Background="AliceBlue">
        <TextBox Name="ProposedTestValue"
             Text="Alle eendjes zwemmen in het water"
             Width="500" Height="20"/>

        <Button Content="Change"
                Height="auto" Width="74"
                Padding="5,2"
                Command="{Binding Path=CommandTest}"
                CommandParameter="{Binding ElementName=ProposedTestValue,Path=Text}"/>
        <TextBox Text="{Binding Text,Mode=OneWay}" IsReadOnly="True" Width="500" Height="20"/>
    </StackPanel>
</Window>

一切正常。 如果文本的长度小于4或文本相同,则按钮将变为无效状态。

,

下面是一个简单的解决方案。

有人建议向ViewModel添加属性ProposedTestValue,并使用该值代替CommandParameter来更新实际接受的值,即按下按钮后的值。

后一种解决方案似乎有点奇怪:我的模型没有提议值的概念,而该提议值最终将成为可接受的按下按钮后的值。除此之外,这意味着我想添加文本框-按钮组合时都必须更改ViewModel。

我已经测试了EldHasp的解决方案,并且可以使用。 感谢EldHasp的广泛回答

但是,我不想太偏离MvvmLight,只是为了解决这个看似罕见的问题。此外:我永远无法说服我的项目负责人做到这一点! :(

完全简单的解决方案

ICommand有一个事件CanExecuteChanged。每当文本框中的文本更改时,事件处理程序都应引发此事件。幸运的是RelayCommand<...>可以做到这一点。

XAML

<TextBox Name="ProposedTestValue" Width="500" Height="20"
         Text="Alle eendjes zwemmen in het water"
         TextChanged="textChangedEventHandler"/>

隐藏代码

 private void textChangedEventHandler(object sender,TextChangedEventArgs args)
 {
     ((MyViewModel)this.DataContext).CommandTest.RaiseCanExecuteChanged();
 }

这几行代码足以确保每当文本更改时都选中CanTest(...)

如果我不得不在后面编写代码,我总是会感到不安,我只会在WPF教程中看到这一点。因此,如果有人在没有大量代码的情况下看到了更好的解决方案,请这样做,如果比此解决方案更干净,我很乐意选择您的解决方案。

,

事实证明, MvvmLight 中的 RelayCommand 类具有两个实现。 在 GalaSoft.MvvmLight.Command 命名空间和 GalaSoft.MvvmLight.CommandWpf 命名空间中。

您可能已经在命名空间 GalaSoft.MvvmLight.Command 中使用过。 而且这种类型实际上不会更新命令的状态。

如果在 GalaSoft.MvvmLight.CommandWpf 名称空间中使用,则其工作方式与我的示例类似。 命令的状态根据预定的逻辑进行更新。

本文链接:https://www.f2er.com/1834174.html

大家都在问