如何让我的视图模型知道当 XAML 数据验证失败时。

huangapple go评论56阅读模式
英文:

How can I let my view model know when XAML data validation has failed

问题

我有一个表单,用户在其中设置数字处理的参数。每个参数对象都有一个默认值。

public double DefaultValue
{
    get => _defaultValue;
    set
    {
        _defaultValue = value;
        OnPropertyChanged("DefaultValue");
    }
}

尽管属性是一个 double,它可能表示一个布尔值或整数。对于大多数参数,不需要验证,但我有两个参数,Min 和 Max,它们是受限制的。不能有 Min > Max 或 Max < Min。我已经在 XAML 中实现了验证,如果数据无效,它会在视觉上警告用户。Min 参数的数据模板如下。

<DataTemplate x:Key="MinParameterDataTemplateThin">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="120"/>
        </Grid.ColumnDefinitions>
        <TextBlock Text="{Binding DisplayName, StringFormat='{}{0}:'}" Grid.Column="0" Margin="10,5,5,10" VerticalAlignment="Top" TextWrapping="Wrap"
                   Visibility="{Binding Visibility}" ToolTipService.ShowDuration="20000">
            <TextBlock.ToolTip>
                <ToolTip DataContext="{Binding Path=PlacementTarget.DataContext, RelativeSource={x:Static RelativeSource.Self}}">
                    <TextBlock Text="{Binding Description}"/>
                </ToolTip>
            </TextBlock.ToolTip>                                
        </TextBlock>

        <Grid Grid.Column="1">
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <StackPanel Grid.Row="0" Orientation="Horizontal">
                <TextBox Name="MinTextBox" Margin="5" Width="50" VerticalAlignment="Top"
                         Visibility="{Binding Visibility}" IsEnabled="{Binding IsEnabled}">
                    <TextBox.Resources>
                        <validations:BindingProxy x:Key="proxy" Data="{Binding}"/>
                    </TextBox.Resources>
                    <TextBox.Text>
                        <Binding Path="DefaultValue" StringFormat="N2" Mode="TwoWay"
                                 UpdateSourceTrigger="LostFocus"
                                 ValidatesOnExceptions="True"
                                 NotifyOnValidationError="True"
                                 ValidatesOnNotifyDataErrors="True">
                            <Binding.ValidationRules>
                                <validations:MaximumValueValidation ValidatesOnTargetUpdated="True">
                                    <validations:MaximumValueValidation.MaxValueWrapper>
                                        <validations:MaxValueWrapper MaxValue="{Binding Data.MaxValue, Source={StaticResource proxy}}"/>
                                    </validations:MaximumValueValidation.MaxValueWrapper>
                                </validations:MaximumValueValidation>
                            </Binding.ValidationRules>
                        </Binding>
                    </TextBox.Text>
                </TextBox>
                <TextBlock Text="{Binding UnitSymbol}" Margin="5" VerticalAlignment="Top" Visibility="{Binding Visibility}"/>
            </StackPanel>
            <Label Name="ValidationLabel" Content="{Binding ElementName=MinTextBox, Path=(Validation.Errors)[0].ErrorContent}" Foreground="Red" Grid.Row="1" VerticalAlignment="Top"/>
        </Grid>
    </Grid>
</DataTemplate>

Max 参数有类似的模板。除了视觉警告之外,我需要防止用户保存数据。我想在参数对象中有一个名为 IsValid 的布尔属性,以便在用户尝试保存时进行测试。我如何从 XAML 绑定到这个 IsValid 属性?

【注意】请注意,代码中可能包含一些自定义的验证逻辑,这些逻辑不在提供的信息中,您可能需要将其添加到 IsValid 属性中。

英文:

I have a form where users set parameters for a numerical process. Each parameter object has a default value.

    public double DefaultValue
{
get =&gt; _defaultValue;
set
{
_defaultValue = value;
OnPropertyChanged(&quot;DefaultValue&quot;);
}
}

Although the property is a double, it might represent a Boolean, or an integer. For most parameters validation is not required, but I have two parameters, Min and Max, which are limited. I must not have Min > Max or Max < Min. I have implemented validation in XAML, which visually warns the user if the data is not valid. The data template for the Min parameter is as follows.

    &lt;DataTemplate x:Key=&quot;MinParameterDataTemplateThin&quot;&gt;
&lt;Grid&gt;
&lt;Grid.ColumnDefinitions&gt;
&lt;ColumnDefinition Width=&quot;*&quot;/&gt;
&lt;ColumnDefinition Width=&quot;120&quot;/&gt;
&lt;/Grid.ColumnDefinitions&gt;
&lt;TextBlock Text=&quot;{Binding DisplayName, StringFormat=&#39;{}{0}:&#39;}&quot; Grid.Column=&quot;0&quot; Margin=&quot;10,5,5,10&quot; VerticalAlignment=&quot;Top&quot; TextWrapping=&quot;Wrap&quot;
Visibility=&quot;{Binding Visibility}&quot; ToolTipService.ShowDuration=&quot;20000&quot;&gt;
&lt;TextBlock.ToolTip&gt;
&lt;ToolTip DataContext=&quot;{Binding Path=PlacementTarget.DataContext, RelativeSource={x:Static RelativeSource.Self}}&quot;&gt;
&lt;TextBlock Text=&quot;{Binding Description}&quot;/&gt;
&lt;/ToolTip&gt;
&lt;/TextBlock.ToolTip&gt;                                
&lt;/TextBlock&gt;
&lt;Grid Grid.Column=&quot;1&quot;&gt;
&lt;Grid.RowDefinitions&gt;
&lt;RowDefinition/&gt;
&lt;RowDefinition/&gt;
&lt;/Grid.RowDefinitions&gt;
&lt;StackPanel Grid.Row=&quot;0&quot; Orientation=&quot;Horizontal&quot;&gt;
&lt;TextBox Name =&quot;MinTextBox&quot; Margin=&quot;5&quot; Width=&quot;50&quot; VerticalAlignment=&quot;Top&quot;
Visibility=&quot;{Binding Visibility}&quot; IsEnabled=&quot;{Binding IsEnabled}&quot;&gt;
&lt;TextBox.Resources&gt;
&lt;validations:BindingProxy x:Key=&quot;proxy&quot; Data=&quot;{Binding}&quot;/&gt;
&lt;/TextBox.Resources&gt;
&lt;TextBox.Text&gt;
&lt;Binding Path=&quot;DefaultValue&quot; StringFormat=&quot;N2&quot; Mode=&quot;TwoWay&quot;
UpdateSourceTrigger=&quot;LostFocus&quot;
ValidatesOnExceptions=&quot;True&quot;
NotifyOnValidationError=&quot;True&quot;
ValidatesOnNotifyDataErrors=&quot;True&quot;&gt;
&lt;Binding.ValidationRules&gt;
&lt;validations:MaximumValueValidation ValidatesOnTargetUpdated=&quot;True&quot;&gt;
&lt;validations:MaximumValueValidation.MaxValueWrapper&gt;
&lt;validations:MaxValueWrapper MaxValue=&quot;{Binding Data.MaxValue, Source={StaticResource proxy}}&quot;/&gt;
&lt;/validations:MaximumValueValidation.MaxValueWrapper&gt;
&lt;/validations:MaximumValueValidation&gt;
&lt;/Binding.ValidationRules&gt;
&lt;/Binding&gt;
&lt;/TextBox.Text&gt;
&lt;/TextBox&gt;
&lt;TextBlock Text=&quot;{Binding UnitSymbol}&quot; Margin=&quot;5&quot; VerticalAlignment=&quot;Top&quot; Visibility=&quot;{Binding Visibility}&quot;/&gt;
&lt;/StackPanel&gt;
&lt;Label Name=&quot;ValidationLabel&quot; Content=&quot;{Binding ElementName=MinTextBox, Path=(Validation.Errors)[0].ErrorContent}&quot; Foreground=&quot;Red&quot; Grid.Row=&quot;1&quot; VerticalAlignment=&quot;Top&quot;/&gt;
&lt;/Grid&gt;
&lt;/Grid&gt;
&lt;/DataTemplate&gt;

There is a similar template for the Max parameter. In addition to the visual warning, I need to prevent the user from saving the data. I would like to have a Boolean IsValid property in the parameter object to test when the user tries to save. How would I bind from the XAML to this IsValid property?

答案1

得分: 0

当您看到一个带有红色边框的错误时,会触发一个路由错误事件,该事件会通过可视树冒泡。

当您修复一个错误时,会触发相同的事件,告诉您错误已经被修复。

因此,您可以累加出现的错误并减去修复的错误。如果结果大于零,说明有需要修复的问题。

这就是 Validation.ErrorEvent。

https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.validation.error?view=windowsdesktop-7.0

然后,您可以将结果传递给视图模型,或者调用一个命令来传递事件结果,然后您的视图模型执行该加法。

这里是一些标记和代码。

在可能出现错误的所有控件的父控件中:

<i:Interaction.Triggers>
    <UIlib:RoutedEventTrigger RoutedEvent="{x:Static Validation.ErrorEvent}">
        <e2c:EventToCommand
            Command="{Binding ConversionErrorCommand, Mode=OneWay}"
            EventArgsConverter="{StaticResource BindingErrorEventArgsConverter}"
            PassEventArgsToCommand="True" />
    </UIlib:RoutedEventTrigger>
</i:Interaction.Triggers>

不确定这是否仍然适用于复制粘贴,因为它现在有点老了。

public RelayCommand<PropertyError> ConversionErrorCommand
{
    get
    {
        return conversionErrorCommand
            ?? (conversionErrorCommand = new RelayCommand<PropertyError>
                (PropertyError =>
                {
                    if (PropertyError.Added)
                    {
                        AddError(PropertyError.PropertyName, PropertyError.Error, ErrorSource.Conversion);
                    }
                    FlattenErrorList();
                }));
    }
}

转换器:

public class BindingErrorEventArgsConverter : IEventArgsConverter
{
    public object Convert(object value, object parameter)
    {
        ValidationErrorEventArgs e = (ValidationErrorEventArgs)value;
        PropertyError err = new PropertyError();
        err.PropertyName = ((System.Windows.Data.BindingExpression)(e.Error.BindingInError)).ResolvedSourcePropertyName;
        err.Error = e.Error.ErrorContent.ToString();
        // Validation.ErrorEvent 在添加错误和移除错误时都触发
        if (e.Action == ValidationErrorEventAction.Added)
        {
            err.Added = true;
        }
        else
        {
            err.Added = false;
        }
        return err;
    }
}

RoutedEventTrigger:

public class RoutedEventTrigger : EventTriggerBase<DependencyObject>
{
    RoutedEvent routedEvent;
    public RoutedEvent RoutedEvent
    {
        get
        {
            return routedEvent;
        }
        set
        {
            routedEvent = value;
        }
    }

    public RoutedEventTrigger()
    {
    }
    protected override void OnAttached()
    {
        Behavior behavior = base.AssociatedObject as Behavior;
        FrameworkElement associatedElement = base.AssociatedObject as FrameworkElement;
        if (behavior != null)
        {
            associatedElement = ((IAttachedObject)behavior).AssociatedObject as FrameworkElement;
        }
        if (associatedElement == null)
        {
            throw new ArgumentException("This only works with framework elements");
        }
        if (RoutedEvent != null)
        {
            associatedElement.AddHandler(RoutedEvent, new RoutedEventHandler(this.OnRoutedEvent));
        }
    }
    void OnRoutedEvent(object sender, RoutedEventArgs args)
    {
        base.OnEvent(args);
        args.Handled = true;
    }
    protected override string GetEventName()
    {
        return RoutedEvent.Name;
    }
}

Event to Command 是来自 MVVM Light 的。虽然它现在已经被弃用,但代码仍然可以使用。

您可能会喜欢这个链接:

https://stackoverflow.com/questions/6205472/mvvm-passing-eventargs-as-command-parameter

我认为这个链接解释了 Prism 的方法:

https://weblogs.asp.net/alexeyzakharov/silverlight-commands-hacks-passing-eventargs-as-commandparameter-to-delegatecommand-triggered-by-eventtrigger

但是我认为 MVVMLight 是开源的,这里是 eventtocommand 的代码:

// ****************************************************************************
// <copyright file="EventToCommand.cs" company="GalaSoft Laurent Bugnion">
// Copyright © GalaSoft Laurent Bugnion 2009-2016
// </copyright>
// <author>Laurent Bugnion</author>
// <email>laurent@galasoft.ch</email>
// <date>3.11.2009</date>
// <project>GalaSoft.MvvmLight.Extras</project>
// <web>http://www.mvvmlight.net</web>
// <license>
// See license.txt in this solution or http://www.galasoft.ch/license_MIT.txt
// </license>
// ****************************************************************************

using Microsoft.Xaml.Behaviors;
using System;
using System.Windows;
using System.Windows.Input;

////using GalaSoft.Utilities.Attributes;

namespace GalaSoft.MvvmLight.CommandWpf
{
    /// <summary>
    /// This <see cref="T:System.Windows.Interactivity.TriggerAction`1" /> can be
    /// used to bind any event on any FrameworkElement to an <see cref="ICommand" />.
    /// Typically, this element is used in XAML to connect the attached element
    /// to a command located in a ViewModel. This trigger can only be attached
    /// to a FrameworkElement or a class deriving from FrameworkElement.
    /// <para>To access the EventArgs of the fired event, use a RelayCommand&lt;EventArgs&gt;
    /// and leave the CommandParameter and CommandParameterValue empty!</para>
    /// </summary>
    ////[ClassInfo(typeof(EventToCommand),
    ////  VersionString = "5.2.8",
    ////  DateString = "201504252130",
    ////  Description = "A Trigger used to bind any event to an ICommand.",
    ////  UrlContacts = "http://www.galasoft.ch/contact_en.html",
    ////  Email = "laurent@galasoft.ch")]
    public class EventToCommand : TriggerAction<DependencyObject>
    {
        // ... (略去未提供的代码)
    }
}

我认为您可能需要 IEventArgsConverter:

namespace GalaSoft.MvvmLight.CommandWpf
{
    /// <summary>
    /// The definition of the converter used to convert an EventArgs
    /// in the <see cref="EventToCommand"/> class, if the
    /// <see cref="EventToCommand.PassEventArgsToCommand"/> property is true.
    /// Set an instance of this class to the <see cref="EventToCommand.EventArgsConverter"/>
    /// property of the EventToCommand instance.
    /// </summary>
    ////[ClassInfo(typeof(EventToCommand))]
    public interface IEventArgsConverter
    {
        /// <summary>
        /// The method used to

<details>
<summary>英文:</summary>

When you see an error appear with a red border, there is a routed error event raised which will bubble up through the visual tree.

When you fix an error then you get the same event raised tells you that the error is fixed.

You can therefore add up errors occur and subtract errors fixed. If you have more than zero you have something needs fixing.

This is the Validation.ErrorEvent

https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.validation.error?view=windowsdesktop-7.0

You can then pass the result to the viewmodel or call a command to pass the event result and your viewmodel does that addition.

Here&#39;s some markup and code.

In a parent of all the controls which may error:

        &lt;i:Interaction.Triggers&gt;
            &lt;UIlib:RoutedEventTrigger RoutedEvent=&quot;{x:Static Validation.ErrorEvent}&quot;&gt;
                &lt;e2c:EventToCommand
                     Command=&quot;{Binding ConversionErrorCommand, Mode=OneWay}&quot;
                     EventArgsConverter=&quot;{StaticResource BindingErrorEventArgsConverter}&quot;
                     PassEventArgsToCommand=&quot;True&quot; /&gt;
            &lt;/UIlib:RoutedEventTrigger&gt;


Not sure this will still be copy paste friendly since it&#39;s a bit old now.

        public RelayCommand&lt;PropertyError&gt; ConversionErrorCommand
        {
            get
            {
                return conversionErrorCommand
                    ?? (conversionErrorCommand = new RelayCommand&lt;PropertyError&gt;
                        (PropertyError =&gt;
                        {
                            if (PropertyError.Added)
                            {
                                AddError(PropertyError.PropertyName, PropertyError.Error, ErrorSource.Conversion);
                            }
                            FlattenErrorList();
                        }));
            }
        }

Converter

    public class BindingErrorEventArgsConverter : IEventArgsConverter
    {
        public object Convert(object value, object parameter)
        {
            ValidationErrorEventArgs e = (ValidationErrorEventArgs)value;
            PropertyError err = new PropertyError();
            err.PropertyName = ((System.Windows.Data.BindingExpression)(e.Error.BindingInError)).ResolvedSourcePropertyName;
            err.Error = e.Error.ErrorContent.ToString();
            // Validation.ErrorEvent fires both when an error is added AND removed
            if (e.Action == ValidationErrorEventAction.Added)
            {
                err.Added = true;
            }
            else
            {
                err.Added = false;
            }
            return err;
        }
    }

Routedeventtrigger

    // This is necessary in order to grab the bubbling routed source changed and conversion errors
    public class RoutedEventTrigger : EventTriggerBase&lt;DependencyObject&gt;
    {
        RoutedEvent routedEvent;
        public RoutedEvent RoutedEvent
        {
            get
            {
                return routedEvent;
            }
            set
            {
                routedEvent = value;
            }
        }

        public RoutedEventTrigger()
        {
        }
        protected override void OnAttached()
        {
            Behavior behavior = base.AssociatedObject as Behavior;
            FrameworkElement associatedElement = base.AssociatedObject as FrameworkElement;
            if (behavior != null)
            {
                associatedElement = ((IAttachedObject)behavior).AssociatedObject as FrameworkElement;
            }
            if (associatedElement == null)
            {
                throw new ArgumentException(&quot;This only works with framework elements&quot;);
            }
            if (RoutedEvent != null)
            {
                associatedElement.AddHandler(RoutedEvent, new RoutedEventHandler(this.OnRoutedEvent));
            }
        }
        void OnRoutedEvent(object sender, RoutedEventArgs args)
        {
            base.OnEvent(args);
            args.Handled = true;
        }
        protected override string GetEventName()
        {
            return RoutedEvent.Name;
        }
    }


Event to command is from mvvm light. Which is now deprecated but the code still works.

You might like

https://stackoverflow.com/questions/6205472/mvvm-passing-eventargs-as-command-parameter

I think this explains a prism approach:

https://weblogs.asp.net/alexeyzakharov/silverlight-commands-hacks-passing-eventargs-as-commandparameter-to-delegatecommand-triggered-by-eventtrigger

But I think mvvmlight is open source, here&#39;s the code for eventtocommand:

    // ****************************************************************************
    // &lt;copyright file=&quot;EventToCommand.cs&quot; company=&quot;GalaSoft Laurent Bugnion&quot;&gt;
    // Copyright &#169; GalaSoft Laurent Bugnion 2009-2016
    // &lt;/copyright&gt;
    // ****************************************************************************
    // &lt;author&gt;Laurent Bugnion&lt;/author&gt;
    // &lt;email&gt;laurent@galasoft.ch&lt;/email&gt;
    // &lt;date&gt;3.11.2009&lt;/date&gt;
    // &lt;project&gt;GalaSoft.MvvmLight.Extras&lt;/project&gt;
    // &lt;web&gt;http://www.mvvmlight.net&lt;/web&gt;
    // &lt;license&gt;
    // See license.txt in this solution or http://www.galasoft.ch/license_MIT.txt
    // &lt;/license&gt;
    // ****************************************************************************

    using Microsoft.Xaml.Behaviors;
    using System;
    using System.Windows;
    using System.Windows.Input;

    ////using GalaSoft.Utilities.Attributes;

    namespace GalaSoft.MvvmLight.CommandWpf
    {
        /// &lt;summary&gt;
        /// This &lt;see cref=&quot;T:System.Windows.Interactivity.TriggerAction`1&quot; /&gt; can be
        /// used to bind any event on any FrameworkElement to an &lt;see cref=&quot;ICommand&quot; /&gt;.
        /// Typically, this element is used in XAML to connect the attached element
        /// to a command located in a ViewModel. This trigger can only be attached
        /// to a FrameworkElement or a class deriving from FrameworkElement.
        /// &lt;para&gt;To access the EventArgs of the fired event, use a RelayCommand&amp;lt;EventArgs&amp;gt;
        /// and leave the CommandParameter and CommandParameterValue empty!&lt;/para&gt;
        /// &lt;/summary&gt;
        ////[ClassInfo(typeof(EventToCommand),
        ////  VersionString = &quot;5.2.8&quot;,
        ////  DateString = &quot;201504252130&quot;,
        ////  Description = &quot;A Trigger used to bind any event to an ICommand.&quot;,
        ////  UrlContacts = &quot;http://www.galasoft.ch/contact_en.html&quot;,
        ////  Email = &quot;laurent@galasoft.ch&quot;)]
        public class EventToCommand : TriggerAction&lt;DependencyObject&gt;
        {
            /// &lt;summary&gt;
            /// Identifies the &lt;see cref=&quot;CommandParameter&quot; /&gt; dependency property
            /// &lt;/summary&gt;
            public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register(
                &quot;CommandParameter&quot;,
                typeof(object),
                typeof(EventToCommand),
                new PropertyMetadata(
                    null,
                    (s, e) =&gt;
                    {
                        var sender = s as EventToCommand;
                        if (sender == null)
                        {
                            return;
                        }

                        if (sender.AssociatedObject == null)
                        {
                            return;
                        }

                        sender.EnableDisableElement();
                    }));

            /// &lt;summary&gt;
            /// Identifies the &lt;see cref=&quot;Command&quot; /&gt; dependency property
            /// &lt;/summary&gt;
            public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(
                &quot;Command&quot;,
                typeof(ICommand),
                typeof(EventToCommand),
                new PropertyMetadata(
                    null,
                    (s, e) =&gt; OnCommandChanged(s as EventToCommand, e)));

            /// &lt;summary&gt;
            /// Identifies the &lt;see cref=&quot;MustToggleIsEnabled&quot; /&gt; dependency property
            /// &lt;/summary&gt;
            public static readonly DependencyProperty MustToggleIsEnabledProperty = DependencyProperty.Register(
                &quot;MustToggleIsEnabled&quot;,
                typeof(bool),
                typeof(EventToCommand),
                new PropertyMetadata(
                    false,
                    (s, e) =&gt;
                    {
                        var sender = s as EventToCommand;
                        if (sender == null)
                        {
                            return;
                        }

                        if (sender.AssociatedObject == null)
                        {
                            return;
                        }

                        sender.EnableDisableElement();
                    }));

            private object _commandParameterValue;

            private bool? _mustToggleValue;

            /// &lt;summary&gt;
            /// Gets or sets the ICommand that this trigger is bound to. This
            /// is a DependencyProperty.
            /// &lt;/summary&gt;
            public ICommand Command
            {
                get
                {
                    return (ICommand) GetValue(CommandProperty);
                }

                set
                {
                    SetValue(CommandProperty, value);
                }
            }

            /// &lt;summary&gt;
            /// Gets or sets an object that will be passed to the &lt;see cref=&quot;Command&quot; /&gt;
            /// attached to this trigger. This is a DependencyProperty.
            /// &lt;/summary&gt;
            public object CommandParameter
            {
                get
                {
                    return GetValue(CommandParameterProperty);
                }

                set
                {
                    SetValue(CommandParameterProperty, value);
                }
            }

            /// &lt;summary&gt;
            /// Gets or sets an object that will be passed to the &lt;see cref=&quot;Command&quot; /&gt;
            /// attached to this trigger. This property is here for compatibility
            /// with the Silverlight version. This is NOT a DependencyProperty.
            /// For databinding, use the &lt;see cref=&quot;CommandParameter&quot; /&gt; property.
            /// &lt;/summary&gt;
            public object CommandParameterValue
            {
                get
                {
                    return _commandParameterValue ?? CommandParameter;
                }

                set
                {
                    _commandParameterValue = value;
                    EnableDisableElement();
                }
            }

            /// &lt;summary&gt;
            /// Gets or sets a value indicating whether the attached element must be
            /// disabled when the &lt;see cref=&quot;Command&quot; /&gt; property&#39;s CanExecuteChanged
            /// event fires. If this property is true, and the command&#39;s CanExecute 
            /// method returns false, the element will be disabled. If this property
            /// is false, the element will not be disabled when the command&#39;s
            /// CanExecute method changes. This is a DependencyProperty.
            /// &lt;/summary&gt;
            public bool MustToggleIsEnabled
            {
                get
                {
                    return (bool) GetValue(MustToggleIsEnabledProperty);
                }

                set
                {
                    SetValue(MustToggleIsEnabledProperty, value);
                }
            }

            /// &lt;summary&gt;
            /// Gets or sets a value indicating whether the attached element must be
            /// disabled when the &lt;see cref=&quot;Command&quot; /&gt; property&#39;s CanExecuteChanged
            /// event fires. If this property is true, and the command&#39;s CanExecute 
            /// method returns false, the element will be disabled. This property is here for
            /// compatibility with the Silverlight version. This is NOT a DependencyProperty.
            /// For databinding, use the &lt;see cref=&quot;MustToggleIsEnabled&quot; /&gt; property.
            /// &lt;/summary&gt;
            public bool MustToggleIsEnabledValue
            {
                get
                {
                    return _mustToggleValue == null
                               ? MustToggleIsEnabled
                               : _mustToggleValue.Value;
                }

                set
                {
                    _mustToggleValue = value;
                    EnableDisableElement();
                }
            }

            /// &lt;summary&gt;
            /// Called when this trigger is attached to a FrameworkElement.
            /// &lt;/summary&gt;
            protected override void OnAttached()
            {
                base.OnAttached();
                EnableDisableElement();
            }

    #if SILVERLIGHT
            private Control GetAssociatedObject()
            {
                return AssociatedObject as Control;
            }
    #else
            /// &lt;summary&gt;
            /// This method is here for compatibility
            /// with the Silverlight version.
            /// &lt;/summary&gt;
            /// &lt;returns&gt;The FrameworkElement to which this trigger
            /// is attached.&lt;/returns&gt;
            private FrameworkElement GetAssociatedObject()
            {
                return AssociatedObject as FrameworkElement;
            }
    #endif

            /// &lt;summary&gt;
            /// This method is here for compatibility
            /// with the Silverlight 3 version.
            /// &lt;/summary&gt;
            /// &lt;returns&gt;The command that must be executed when
            /// this trigger is invoked.&lt;/returns&gt;
            private ICommand GetCommand()
            {
                return Command;
            }

            /// &lt;summary&gt;
            /// Specifies whether the EventArgs of the event that triggered this
            /// action should be passed to the bound RelayCommand. If this is true,
            /// the command should accept arguments of the corresponding
            /// type (for example RelayCommand&amp;lt;MouseButtonEventArgs&amp;gt;).
            /// &lt;/summary&gt;
            public bool PassEventArgsToCommand
            {
                get;
                set;
            }

            /// &lt;summary&gt;
            /// Gets or sets a converter used to convert the EventArgs when using
            /// &lt;see cref=&quot;PassEventArgsToCommand&quot;/&gt;. If PassEventArgsToCommand is false,
            /// this property is never used.
            /// &lt;/summary&gt;
            public IEventArgsConverter EventArgsConverter
            {
                get;
                set;
            }

            /// &lt;summary&gt;
            /// The &lt;see cref=&quot;EventArgsConverterParameter&quot; /&gt; dependency property&#39;s name.
            /// &lt;/summary&gt;
            public const string EventArgsConverterParameterPropertyName = &quot;EventArgsConverterParameter&quot;;

            /// &lt;summary&gt;
            /// Gets or sets a parameters for the converter used to convert the EventArgs when using
            /// &lt;see cref=&quot;PassEventArgsToCommand&quot;/&gt;. If PassEventArgsToCommand is false,
            /// this property is never used. This is a dependency property.
            /// &lt;/summary&gt;
            public object EventArgsConverterParameter
            {
                get
                {
                    return GetValue(EventArgsConverterParameterProperty);
                }
                set
                {
                    SetValue(EventArgsConverterParameterProperty, value);
                }
            }

            /// &lt;summary&gt;
            /// Identifies the &lt;see cref=&quot;EventArgsConverterParameter&quot; /&gt; dependency property.
            /// &lt;/summary&gt;
            public static readonly DependencyProperty EventArgsConverterParameterProperty = DependencyProperty.Register(
                EventArgsConverterParameterPropertyName,
                typeof(object),
                typeof(EventToCommand),
                new PropertyMetadata(null));

            /// &lt;summary&gt;
            /// The &lt;see cref=&quot;AlwaysInvokeCommand&quot; /&gt; dependency property&#39;s name.
            /// &lt;/summary&gt;
            public const string AlwaysInvokeCommandPropertyName = &quot;AlwaysInvokeCommand&quot;;

            /// &lt;summary&gt;
            /// Gets or sets a value indicating if the command should be invoked even
            /// if the attached control is disabled. This is a dependency property.
            /// &lt;/summary&gt;
            public bool AlwaysInvokeCommand
            {
                get
                {
                    return (bool)GetValue(AlwaysInvokeCommandProperty);
                }
                set
                {
                    SetValue(AlwaysInvokeCommandProperty, value);
                }
            }

            /// &lt;summary&gt;
            /// Identifies the &lt;see cref=&quot;AlwaysInvokeCommand&quot; /&gt; dependency property.
            /// &lt;/summary&gt;
            public static readonly DependencyProperty AlwaysInvokeCommandProperty = DependencyProperty.Register(
                AlwaysInvokeCommandPropertyName,
                typeof(bool),
                typeof(EventToCommand),
                new PropertyMetadata(false));


            /// &lt;summary&gt;
            /// Provides a simple way to invoke this trigger programatically
            /// without any EventArgs.
            /// &lt;/summary&gt;
            public void Invoke()
            {
                Invoke(null);
            }

            /// &lt;summary&gt;
            /// Executes the trigger.
            /// &lt;para&gt;To access the EventArgs of the fired event, use a RelayCommand&amp;lt;EventArgs&amp;gt;
            /// and leave the CommandParameter and CommandParameterValue empty!&lt;/para&gt;
            /// &lt;/summary&gt;
            /// &lt;param name=&quot;parameter&quot;&gt;The EventArgs of the fired event.&lt;/param&gt;
            protected override void Invoke(object parameter)
            {
                if (AssociatedElementIsDisabled() 
                    &amp;&amp; !AlwaysInvokeCommand)
                {
                    return;
                }

                var command = GetCommand();
                var commandParameter = CommandParameterValue;

                if (commandParameter == null
                    &amp;&amp; PassEventArgsToCommand)
                {
                    commandParameter = EventArgsConverter == null
                        ? parameter
                        : EventArgsConverter.Convert(parameter, EventArgsConverterParameter);
                }

                if (command != null
                    &amp;&amp; command.CanExecute(commandParameter))
                {
                    command.Execute(commandParameter);
                }
            }

            private static void OnCommandChanged(
                EventToCommand element,
                DependencyPropertyChangedEventArgs e)
            {
                if (element == null)
                {
                    return;
                }

                if (e.OldValue != null)
                {
                    ((ICommand) e.OldValue).CanExecuteChanged -= element.OnCommandCanExecuteChanged;
                }

                var command = (ICommand) e.NewValue;

                if (command != null)
                {
                    command.CanExecuteChanged += element.OnCommandCanExecuteChanged;
                }

                element.EnableDisableElement();
            }

            private bool AssociatedElementIsDisabled()
            {
                var element = GetAssociatedObject();

                return AssociatedObject == null
                    || (element != null
                       &amp;&amp; !element.IsEnabled);
            }

            private void EnableDisableElement()
            {
                var element = GetAssociatedObject();

                if (element == null)
                {
                    return;
                }

                var command = GetCommand();

                if (MustToggleIsEnabledValue
                    &amp;&amp; command != null)
                {
                    element.IsEnabled = command.CanExecute(CommandParameterValue);
                }
            }

            private void OnCommandCanExecuteChanged(object sender, EventArgs e)
            {
                EnableDisableElement();
            }
        }
    }

I think you may need ieventargsconverter

    namespace GalaSoft.MvvmLight.CommandWpf
    {
        /// &lt;summary&gt;
        /// The definition of the converter used to convert an EventArgs
        /// in the &lt;see cref=&quot;EventToCommand&quot;/&gt; class, if the
        /// &lt;see cref=&quot;EventToCommand.PassEventArgsToCommand&quot;/&gt; property is true.
        /// Set an instance of this class to the &lt;see cref=&quot;EventToCommand.EventArgsConverter&quot;/&gt;
        /// property of the EventToCommand instance.
        /// &lt;/summary&gt;
        ////[ClassInfo(typeof(EventToCommand))]
        public interface IEventArgsConverter
        {
            /// &lt;summary&gt;
            /// The method used to convert the EventArgs instance.
            /// &lt;/summary&gt;
            /// &lt;param name=&quot;value&quot;&gt;An instance of EventArgs passed by the
            /// event that the EventToCommand instance is handling.&lt;/param&gt;
            /// &lt;param name=&quot;parameter&quot;&gt;An optional parameter used for the conversion. Use
            /// the &lt;see cref=&quot;EventToCommand.EventArgsConverterParameter&quot;/&gt; property
            /// to set this value. This may be null.&lt;/param&gt;
            /// &lt;returns&gt;The converted value.&lt;/returns&gt;
            object Convert(object value, object parameter);
        }
    }

</details>



huangapple
  • 本文由 发表于 2023年2月8日 16:10:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/75382878.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定