提问人:spainchaud 提问时间:2/8/2023 更新时间:2/10/2023 访问量:58
如何在 XAML 数据验证失败时让视图模型知道
How can I let my view model know when XAML data validation has failed
问:
我有一个表格,用户可以在其中设置数值过程的参数。每个参数对象都有一个默认值。
public double DefaultValue
{
get => _defaultValue;
set
{
_defaultValue = value;
OnPropertyChanged("DefaultValue");
}
}
尽管该属性是双精度值,但它可能表示布尔值或整数。对于大多数参数,不需要验证,但我有两个参数,最小值和最大值,它们是有限的。我不能有最小值>最大值或最大值<最小值。我已经在 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 属性?
答:
0赞
Andy
2/8/2023
#1
当您看到带有红色边框的错误出现时,会引发一个路由错误事件,该事件将通过可视化树冒泡。
当您修复错误时,您会收到相同的事件,该事件会引发告诉您错误已修复。
因此,您可以将发生的错误相加并减去已修复的错误。如果你的得分大于零,你就有一些东西需要修复。
这是 Validation.ErrorEvent
然后,您可以将结果传递给 viewmodel,或调用命令来传递事件结果,然后 viewmodel 将执行该添加。
下面是一些标记和代码。
在所有可能出错的控件的父级中:
<i:Interaction.Triggers>
<UIlib:RoutedEventTrigger RoutedEvent="{x:Static Validation.ErrorEvent}">
<e2c:EventToCommand
Command="{Binding ConversionErrorCommand, Mode=OneWay}"
EventArgsConverter="{StaticResource BindingErrorEventArgsConverter}"
PassEventArgsToCommand="True" />
</UIlib:RoutedEventTrigger>
不确定这是否仍然适合复制粘贴,因为它现在有点旧了。
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 fires both when an error is added AND removed
if (e.Action == ValidationErrorEventAction.Added)
{
err.Added = true;
}
else
{
err.Added = false;
}
return err;
}
}
路由事件触发器
// This is necessary in order to grab the bubbling routed source changed and conversion errors
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;
}
}
命令的事件来自 mvvm light。现已弃用,但代码仍然有效。
你可能会喜欢
我认为这解释了棱镜方法:
但我认为 mvvmlight 是开源的,这是 eventtocommand 的代码:
// ****************************************************************************
// <copyright file="EventToCommand.cs" company="GalaSoft Laurent Bugnion">
// Copyright © GalaSoft Laurent Bugnion 2009-2016
// </copyright>
// ****************************************************************************
// <author>Laurent Bugnion</author>
// <email>[email protected]</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<EventArgs>
/// 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 = "[email protected]")]
public class EventToCommand : TriggerAction<DependencyObject>
{
/// <summary>
/// Identifies the <see cref="CommandParameter" /> dependency property
/// </summary>
public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register(
"CommandParameter",
typeof(object),
typeof(EventToCommand),
new PropertyMetadata(
null,
(s, e) =>
{
var sender = s as EventToCommand;
if (sender == null)
{
return;
}
if (sender.AssociatedObject == null)
{
return;
}
sender.EnableDisableElement();
}));
/// <summary>
/// Identifies the <see cref="Command" /> dependency property
/// </summary>
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(
"Command",
typeof(ICommand),
typeof(EventToCommand),
new PropertyMetadata(
null,
(s, e) => OnCommandChanged(s as EventToCommand, e)));
/// <summary>
/// Identifies the <see cref="MustToggleIsEnabled" /> dependency property
/// </summary>
public static readonly DependencyProperty MustToggleIsEnabledProperty = DependencyProperty.Register(
"MustToggleIsEnabled",
typeof(bool),
typeof(EventToCommand),
new PropertyMetadata(
false,
(s, e) =>
{
var sender = s as EventToCommand;
if (sender == null)
{
return;
}
if (sender.AssociatedObject == null)
{
return;
}
sender.EnableDisableElement();
}));
private object _commandParameterValue;
private bool? _mustToggleValue;
/// <summary>
/// Gets or sets the ICommand that this trigger is bound to. This
/// is a DependencyProperty.
/// </summary>
public ICommand Command
{
get
{
return (ICommand) GetValue(CommandProperty);
}
set
{
SetValue(CommandProperty, value);
}
}
/// <summary>
/// Gets or sets an object that will be passed to the <see cref="Command" />
/// attached to this trigger. This is a DependencyProperty.
/// </summary>
public object CommandParameter
{
get
{
return GetValue(CommandParameterProperty);
}
set
{
SetValue(CommandParameterProperty, value);
}
}
/// <summary>
/// Gets or sets an object that will be passed to the <see cref="Command" />
/// attached to this trigger. This property is here for compatibility
/// with the Silverlight version. This is NOT a DependencyProperty.
/// For databinding, use the <see cref="CommandParameter" /> property.
/// </summary>
public object CommandParameterValue
{
get
{
return _commandParameterValue ?? CommandParameter;
}
set
{
_commandParameterValue = value;
EnableDisableElement();
}
}
/// <summary>
/// Gets or sets a value indicating whether the attached element must be
/// disabled when the <see cref="Command" /> property's CanExecuteChanged
/// event fires. If this property is true, and the command's CanExecute
/// method returns false, the element will be disabled. If this property
/// is false, the element will not be disabled when the command's
/// CanExecute method changes. This is a DependencyProperty.
/// </summary>
public bool MustToggleIsEnabled
{
get
{
return (bool) GetValue(MustToggleIsEnabledProperty);
}
set
{
SetValue(MustToggleIsEnabledProperty, value);
}
}
/// <summary>
/// Gets or sets a value indicating whether the attached element must be
/// disabled when the <see cref="Command" /> property's CanExecuteChanged
/// event fires. If this property is true, and the command'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 <see cref="MustToggleIsEnabled" /> property.
/// </summary>
public bool MustToggleIsEnabledValue
{
get
{
return _mustToggleValue == null
? MustToggleIsEnabled
: _mustToggleValue.Value;
}
set
{
_mustToggleValue = value;
EnableDisableElement();
}
}
/// <summary>
/// Called when this trigger is attached to a FrameworkElement.
/// </summary>
protected override void OnAttached()
{
base.OnAttached();
EnableDisableElement();
}
#if SILVERLIGHT
private Control GetAssociatedObject()
{
return AssociatedObject as Control;
}
#else
/// <summary>
/// This method is here for compatibility
/// with the Silverlight version.
/// </summary>
/// <returns>The FrameworkElement to which this trigger
/// is attached.</returns>
private FrameworkElement GetAssociatedObject()
{
return AssociatedObject as FrameworkElement;
}
#endif
/// <summary>
/// This method is here for compatibility
/// with the Silverlight 3 version.
/// </summary>
/// <returns>The command that must be executed when
/// this trigger is invoked.</returns>
private ICommand GetCommand()
{
return Command;
}
/// <summary>
/// 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<MouseButtonEventArgs>).
/// </summary>
public bool PassEventArgsToCommand
{
get;
set;
}
/// <summary>
/// Gets or sets a converter used to convert the EventArgs when using
/// <see cref="PassEventArgsToCommand"/>. If PassEventArgsToCommand is false,
/// this property is never used.
/// </summary>
public IEventArgsConverter EventArgsConverter
{
get;
set;
}
/// <summary>
/// The <see cref="EventArgsConverterParameter" /> dependency property's name.
/// </summary>
public const string EventArgsConverterParameterPropertyName = "EventArgsConverterParameter";
/// <summary>
/// Gets or sets a parameters for the converter used to convert the EventArgs when using
/// <see cref="PassEventArgsToCommand"/>. If PassEventArgsToCommand is false,
/// this property is never used. This is a dependency property.
/// </summary>
public object EventArgsConverterParameter
{
get
{
return GetValue(EventArgsConverterParameterProperty);
}
set
{
SetValue(EventArgsConverterParameterProperty, value);
}
}
/// <summary>
/// Identifies the <see cref="EventArgsConverterParameter" /> dependency property.
/// </summary>
public static readonly DependencyProperty EventArgsConverterParameterProperty = DependencyProperty.Register(
EventArgsConverterParameterPropertyName,
typeof(object),
typeof(EventToCommand),
new PropertyMetadata(null));
/// <summary>
/// The <see cref="AlwaysInvokeCommand" /> dependency property's name.
/// </summary>
public const string AlwaysInvokeCommandPropertyName = "AlwaysInvokeCommand";
/// <summary>
/// Gets or sets a value indicating if the command should be invoked even
/// if the attached control is disabled. This is a dependency property.
/// </summary>
public bool AlwaysInvokeCommand
{
get
{
return (bool)GetValue(AlwaysInvokeCommandProperty);
}
set
{
SetValue(AlwaysInvokeCommandProperty, value);
}
}
/// <summary>
/// Identifies the <see cref="AlwaysInvokeCommand" /> dependency property.
/// </summary>
public static readonly DependencyProperty AlwaysInvokeCommandProperty = DependencyProperty.Register(
AlwaysInvokeCommandPropertyName,
typeof(bool),
typeof(EventToCommand),
new PropertyMetadata(false));
/// <summary>
/// Provides a simple way to invoke this trigger programatically
/// without any EventArgs.
/// </summary>
public void Invoke()
{
Invoke(null);
}
/// <summary>
/// Executes the trigger.
/// <para>To access the EventArgs of the fired event, use a RelayCommand<EventArgs>
/// and leave the CommandParameter and CommandParameterValue empty!</para>
/// </summary>
/// <param name="parameter">The EventArgs of the fired event.</param>
protected override void Invoke(object parameter)
{
if (AssociatedElementIsDisabled()
&& !AlwaysInvokeCommand)
{
return;
}
var command = GetCommand();
var commandParameter = CommandParameterValue;
if (commandParameter == null
&& PassEventArgsToCommand)
{
commandParameter = EventArgsConverter == null
? parameter
: EventArgsConverter.Convert(parameter, EventArgsConverterParameter);
}
if (command != null
&& 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
&& !element.IsEnabled);
}
private void EnableDisableElement()
{
var element = GetAssociatedObject();
if (element == null)
{
return;
}
var command = GetCommand();
if (MustToggleIsEnabledValue
&& command != null)
{
element.IsEnabled = command.CanExecute(CommandParameterValue);
}
}
private void OnCommandCanExecuteChanged(object sender, EventArgs e)
{
EnableDisableElement();
}
}
}
我认为您可能需要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 convert the EventArgs instance.
/// </summary>
/// <param name="value">An instance of EventArgs passed by the
/// event that the EventToCommand instance is handling.</param>
/// <param name="parameter">An optional parameter used for the conversion. Use
/// the <see cref="EventToCommand.EventArgsConverterParameter"/> property
/// to set this value. This may be null.</param>
/// <returns>The converted value.</returns>
object Convert(object value, object parameter);
}
}
评论
0赞
spainchaud
2/10/2023
我已经实现了大部分代码,当发生验证错误时,我可以将对象传递给我的视图模型,但我无法弄清楚如何使用转换器并传递 PropertyError 对象。我没有访问 EventToCommand,我有 InvokeCommandAction。我似乎无法正确设置我的 CommandParameter。
0赞
spainchaud
2/10/2023
我还应该注意,我可以访问 Prism 和 Telerik 库。
0赞
spainchaud
2/11/2023
谢谢。由于功能冻结即将到来,我采取了一种稍微不同的方法,以便向测试人员提供一些东西。我将验证错误字符串作为参数传递给 ConversionErrorCommand。虽然不像传递事件那样灵活或有趣,但它将一直工作到我们应用程序的下一个版本。我将为此添加书签,因为我将来会想使用 EventToCommand。
评论
INotifyDataErrorInfo