您可以将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