WPF EventHandler for XBox中的TextBox.TextChanged或背后的代码?

说明

在WPF中,使用MvvmLight,我有一个带整数属性SelectedIndex的viewModel。更改此属性的值是一项昂贵的操作,因此,我只想在操作员确定可以完成键入的情况下更新该属性。

我有一个TextBox和一个按钮。操作员键入数字,然后按按钮。这应该导致更新属性的命令。

为此的标准WPF MvvmLight解决方案

class MyViewModel
{
    private int selectedIndex;

    public MyViewModel()
    {
        this.CommandSelectIndex = new RelayCommand(ExecuteSelectIndex,CanSelectIndex);
    }

    public public RelayCommand<int> CommandSelectIndex { get; }

    public int SelectedIndex
    {
        get => this.selectedIndex;
        set => base.Set(nameof(SelectedIndex),ref this.selectedIndex,value);
    }

    private bool CanSelectIndex(int proposedIndex)
    {
         return proposedIndex > 0 && proposedIndex < MyData.Count;
    }

    private void ExecuteSelectIndex(int proposedIndex)
    {
        this.SelectedIndex = proposedIndex;
        ProcessSelectedIndex(proposedIndex);  // Expensive!
    }
}

对于那些了解MvvmLight的人来说,这非常简单。

因此,当操作员输入数字时,我只想更新按钮。我不想对中间值做任何事情:

1 --> 12 --> 123 --> (typing error,backspace) --> 124 [press button]

XAML

<StackPanel Name="Test1" Orientation="Horizontal">
    <TextBox Name="ProposedValue1" Text="1234" Width="300" Height="20"/>
    <Button x:Name="ButtonChangeText1" Content="Change"
                    Height="30" Width="74" Padding="5,2"
                    Command="{Binding Path=CommandSelectedIndex}"
                    commandparameter="{Binding ElementName=ProposedValue1,Path=Text}"/>
</StackPanel>

这部分起作用:在启动时调用CanSelectIndex(1234);如果按下按钮,将调用ExecuteSelectedIndex(1234)

问题

但是,如果TextBox的文本发生变化,则不会调用CanSelectIndex。

原因是因为文本框更改时未引发事件ICommand.CanExecuteChanged

解决方案:

添加事件处理程序:

XAML

<TextBox Name="ProposedValue1" Text="1234" Width="300" Height="20"
         TextChanged="textChangedEventHandler"/>

背后的代码:

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

每当我不得不在后面编写代码时,我总是会感到不安。在后面的代码中编写事件处理程序是标准的,还是我只在教程中看到的简化?

有没有可以在XAML中执行此操作的方法?有约束力吗?

TextChanged="TextChanged="{Binding Path=CommandSelectIndex ??? RaiseCanExecuteChanged() }
iCMS 回答:WPF EventHandler for XBox中的TextBox.TextChanged或背后的代码?

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

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

如果在 GalaSoft.MvvmLight.CommandWpf 名称空间中使用,则命令的状态将根据预定的逻辑进行更新。

,

有没有可以在XAML中执行此操作的方法?有约束力吗?

只需将2020-08-04 T13:31:44.342Z的{​​{1}}属性绑定到视图模型的class CreateData < ActiveRecord::Migration[6.0] def change create_table :data do |t| t.float :temperature t.float :windspeed t.references :user,foreign_key: true t.timestamps end end end 源属性,然后从此setter引发命令的Text方法一个。

如果您出于某些原因确实想处理实际事件,则应调查interaction triggers

,

@Harald Coppulse,你绝对正确!

这是我对MvvmLight的测试代码。

ViewModel:

using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.CommandWpf;

namespace InvalidateCommandMvvmLight.ViewModel
{
    public class MyViewModel : ViewModelBase
    {
        private string _text;
        private int _number;

        public string Text { get => _text; private set => Set(ref _text,value); }

        public int Number { get => _number; set => Set(ref _number,value); }

        public RelayCommand<string> CommandTest { get; }
        public RelayCommand<int> CommandNumber { get; }

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

        private bool CanTest(string text)
        {
            // the text must have a minimum length of 4 
            // and be different from the current one
            return text != null && text.Length >= 4 && text != Text;
        }
        private void Test(string text)
        {
            Text = text;

        }

        private bool CanIntTest(int num)
        {
            // The "num" parameter must be positive,less than 100
            // and is not equal to the Number property
            return num > 0 && num <100 && num != Number;
        }
        private void IntTest(int num)
        {
            Number = num;
        }
    }
}

XAML:

<Window x:Class="InvalidateCommandMvvmLight.MainWindow"
        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:InvalidateCommandMvvmLight"
        xmlns:vm="clr-namespace:InvalidateCommandMvvmLight.ViewModel"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <vm:MyViewModel/>
    </Window.DataContext>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <TextBox x:Name="tbText"
                Text="Alle eendjes zwemmen in het water" VerticalAlignment="Center"
                />

        <Button Content="Change Text"
                Grid.Column="1"
                Margin="5"
                Padding="5,2"
                Command="{Binding Path=CommandTest}"
                CommandParameter="{Binding ElementName=tbText,Path=Text}"/>
        <TextBox Text="{Binding Text,Mode=OneWay}" Grid.Column="2" IsReadOnly="True" VerticalAlignment="Center"/>
        <TextBox x:Name="tbNumber"
                Grid.Row="1"
                Text="55" VerticalAlignment="Center"/>

        <Button Content="Change Number"
                Grid.Row="1" Grid.Column="1"
                Margin="5"
                Padding="5,2"
                Command="{Binding Path=CommandNumber}"
                CommandParameter="{Binding ElementName=tbNumber,Path=Text}"/>
        <TextBox Text="{Binding Number,Mode=OneWay}" IsReadOnly="True"
                 Grid.Row="1" Grid.Column="2" VerticalAlignment="Center"/>
    </Grid>
</Window>

不幸的是,MvvmLight中的CommandsWpf.RelayCommand类未正确实现。
它没有考虑到在WPF中使用不同类型的值的特殊性。

要以典型的WPF方式工作,实现应具有以下内容:

using System.ComponentModel;

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(TypeDescriptor.GetConverter(typeof(T)).IsValid(p) ? (T)TypeDescriptor.GetConverter(typeof(T)).ConvertFrom(p) : default),p => (canExecute == null) || (TypeDescriptor.GetConverter(typeof(T)).IsValid(p) && canExecute((T)TypeDescriptor.GetConverter(typeof(T)).ConvertFrom(p)))
        )
        {}

    }
}

除非您有能力更改RelayCommand实现,否则需要以某种方式使用Binding的功能来自动转换值。

一个变体。
在ViewModel中创建所需类型的属性,并将其用作自动转换的代理。
但是,如果输入了非数字值,则该命令将无法对其进行定义。
您还需要检查Validation.HasError。

ViewModel:

using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.CommandWpf;

namespace InvalidateCommandMvvmLight.ViewModel
{
    public class MyViewModel : ViewModelBase
    {
        private string _text;
        private int _number;
        private int _numberView;

        public string Text { get => _text; private set => Set(ref _text,value); }
        public int NumberView { get => _numberView; set => Set(ref _numberView,less than 100
            // and is not equal to the Number property
            return num > 0 && num <100 && num != Number;
        }
        private void IntTest(int num)
        {
            Number = num;
        }
    }
}

XAML:

<Window x:Class="InvalidateCommandMvvmLight.MainWindow"
        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:InvalidateCommandMvvmLight"
        xmlns:vm="clr-namespace:InvalidateCommandMvvmLight.ViewModel"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <vm:MyViewModel NumberView="55"/>
    </Window.DataContext>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <TextBox x:Name="tbText"
                Text="Alle eendjes zwemmen in het water" VerticalAlignment="Center"
                />

        <Button Content="Change Text"
                Grid.Column="1"
                Margin="5"
                Padding="5,Mode=OneWay}" Grid.Column="2" IsReadOnly="True" VerticalAlignment="Center"/>
        <TextBox x:Name="tbNumber"
                Grid.Row="1"
                Text="{Binding NumberView,UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Center"/>

    <Button Content="Change Number"
                Grid.Row="1" Grid.Column="1"
                Margin="5"
                Padding="5,2"
                Command="{Binding Path=CommandNumber}"
                CommandParameter="{Binding NumberView}">
        <Button.Style>
            <Style TargetType="Button">
                <Style.Triggers>
                    <DataTrigger Binding="{Binding Path=(Validation.HasError),ElementName=tbNumber}"
                                 Value="True">
                        <Setter Property="IsEnabled" Value="False"/>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </Button.Style>
    </Button>
        <TextBox Text="{Binding Number,Mode=OneWay}" IsReadOnly="True"
                 Grid.Row="1" Grid.Column="2" VerticalAlignment="Center"/>
    </Grid>
</Window>

第二个变种。
创建一个显式代理转换器。

转换器:

using System;
using System.ComponentModel;
using System.Windows;

namespace InvalidateCommandMvvmLight
{
    public class ProxyBinding : Freezable
    {
        public Type Type
        {
            get { return (Type)GetValue(TypeProperty); }
            set { SetValue(TypeProperty,value); }
        }

        // Using a DependencyProperty as the backing store for Type.  This enables animation,styling,binding,etc...
        public static readonly DependencyProperty TypeProperty =
            DependencyProperty.Register(nameof(Type),typeof(Type),typeof(ProxyBinding),new PropertyMetadata(typeof(object),ChangedValueOrType));

        private static void ChangedValueOrType(DependencyObject d,DependencyPropertyChangedEventArgs e)
        {
            ProxyBinding proxy = (ProxyBinding)d;
            if (proxy.Type == null)
            {
                proxy.Value = null;
                return;
            }
            if (proxy.Source == null)
                return;

            if (proxy.Type == proxy.Source.GetType())
                return;

            if (TypeDescriptor.GetConverter(proxy.Type).IsValid(proxy.Source))
                proxy.Value = TypeDescriptor.GetConverter(proxy.Type).ConvertFrom(proxy.Source);
            else
                proxy.Value = null;
        }

        public object Source
        {
            get { return GetValue(SourceProperty); }
            set { SetValue(SourceProperty,value); }
        }

        // Using a DependencyProperty as the backing store for Value.  This enables animation,etc...
        public static readonly DependencyProperty SourceProperty =
            DependencyProperty.Register(nameof(Source),typeof(object),new PropertyMetadata(null,ChangedValueOrType));

        public object Value
        {
            get { return GetValue(ValueProperty); }
            protected  set { SetValue(ValuePropertyKey,value); }
        }

        // Using a DependencyProperty as the backing store for readonly Value.  This enables animation,etc...
        protected static readonly DependencyPropertyKey ValuePropertyKey =
            DependencyProperty.RegisterReadOnly(nameof(Value),new PropertyMetadata(null));
        public static readonly DependencyProperty ValueProperty = ValuePropertyKey.DependencyProperty;

        protected override Freezable CreateInstanceCore()
        {
            return new ProxyBinding();
        }
    }
}

XAML:

<Window x:Class="InvalidateCommandMvvmLight.MainWindow"
            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:InvalidateCommandMvvmLight"
            xmlns:vm="clr-namespace:InvalidateCommandMvvmLight.ViewModel"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        mc:Ignorable="d"
            Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <vm:MyViewModel/>
    </Window.DataContext>
    <Window.Resources>
        <local:ProxyBinding x:Key="ProxyInt"
                Type="{x:Type sys:Int32}"
                Source="{Binding ElementName=tbNumber,Path=Text,Mode=OneWay,UpdateSourceTrigger=PropertyChanged}"/>
    </Window.Resources>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <TextBox x:Name="tbText"
                    Text="Alle eendjes zwemmen in het water" VerticalAlignment="Center"
                    />

        <Button Content="Change Text"
                    Grid.Column="1"
                    Margin="5"
                    Padding="5,2"
                    Command="{Binding Path=CommandTest}"
                    CommandParameter="{Binding ElementName=tbText,Mode=OneWay}" Grid.Column="2" IsReadOnly="True" VerticalAlignment="Center"/>
        <TextBox x:Name="tbNumber"
                    Grid.Row="1"
                    Text="55" VerticalAlignment="Center"/>

        <Button Content="Change Number"
                    Grid.Row="1" Grid.Column="1"
                    Margin="5"
                    Padding="5,2"
                    Command="{Binding Path=CommandNumber}"
                    CommandParameter="{Binding Value,Source={StaticResource ProxyInt}}">
        </Button>
        <TextBox Text="{Binding Number,Mode=OneWay}" IsReadOnly="True"
                     Grid.Row="1" Grid.Column="2" VerticalAlignment="Center"/>
        <TextBlock Grid.Row="2" Text="{Binding Value,Source={StaticResource proxy}}"/>
    </Grid>
</Window>

另一个变种。
为绑定创建转换器:

using System;
using System.ComponentModel;
using System.Globalization;
using System.Windows.Data;

namespace InvalidateCommandMvvmLight
{
    public class ValueTypeConverter : IValueConverter
    {
        public object Convert(object value,Type targetType,object parameter,CultureInfo culture)
        {
            if (parameter is Type type && TypeDescriptor.GetConverter(type).IsValid(value))
                return TypeDescriptor.GetConverter(type).ConvertFrom(value);
            return null;
        }

        public object ConvertBack(object value,CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

XAML:

<Window x:Class="InvalidateCommandMvvmLight.MainWindow"
            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:InvalidateCommandMvvmLight"
            xmlns:vm="clr-namespace:InvalidateCommandMvvmLight.ViewModel"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        mc:Ignorable="d"
            Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <vm:MyViewModel/>
    </Window.DataContext>
    <Window.Resources>
    <local:ValueTypeConverter x:Key="ValueTypeConverter"/>
</Window.Resources>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <TextBox x:Name="tbText"
                    Text="Alle eendjes zwemmen in het water" VerticalAlignment="Center"
                    />

        <Button Content="Change Text"
                    Grid.Column="1"
                    Margin="5"
                    Padding="5,2"
                    Command="{Binding Path=CommandNumber}"
                    CommandParameter="{Binding Text,Converter={StaticResource ValueTypeConverter},ConverterParameter={x:Type sys:Int32},ElementName=tbNumber}">
        </Button>
        <TextBox Text="{Binding Number,Mode=OneWay}" IsReadOnly="True"
                     Grid.Row="1" Grid.Column="2" VerticalAlignment="Center"/>
    </Grid>
</Window>
本文链接:https://www.f2er.com/1807455.html

大家都在问