繁体   English   中英

如果 UserControl 中文本框的文本输入无效,则禁用提交按钮

[英]Disable Submit button if input in Textbox's Text inside UserControl is invalid

  1. 我有一个接受用户输入的用户控件。 此用户控件通知用户输入值是否在允许范围内。

  2. 在主要的 Window 中,我将使用其中的几个用户控件从用户那里获取不同参数的值。

  3. 最后,会有一个提交按钮。

  4. 现在,如果用户控件中的任何一个输入值超出范围,我想禁用该按钮。 我怎么做? 我被困在这里。

  5. 如果这是错误的方法,请也请纠正我。 我很乐意遵循最佳实践方法。

非常非常感谢。

XAML 用户控件“UserInputFieldUC”的代码:

<UserControl x:Class="TestUserInput.UserInputFieldUC"
             ...
             xmlns:local="clr-namespace:TestUserInput"
             x:Name="parent">
    <StackPanel Orientation="Horizontal" DataContext="{Binding ElementName=parent}" >

        <TextBlock Name="UserLabel" Width="50" Text="{Binding Path=Label}"/>

        <TextBox Name="MetricValue" Width="50" Text="{Binding Path=Value}" TextChanged="MetricValue_TextChanged"/>
        <TextBlock Name="MetricUnits" Width="50" Text="{Binding Path=Units}" VerticalAlignment="Center"/>

        <TextBlock Name="ErrorDisplay" Width="50" VerticalAlignment="Center"/>        
    </StackPanel>
</UserControl>

“UserInputFieldUC”的代码隐藏:

namespace TestUserInput
{
    /// <summary>
    /// Interaction logic for UserInputFieldUC.xaml
    /// </summary>
    public partial class UserInputFieldUC : UserControl
    {
        public UserInputFieldUC()
        {
            InitializeComponent();
        }
        #region Label DP
        /// <summary>
        /// Label dependency property
        /// </summary>
        public static readonly DependencyProperty LPShowFieldUCPercentCheck =
            DependencyProperty.Register("Label",
                typeof(string),
                typeof(UserInputFieldUC),
                new PropertyMetadata(""));
        /// <summary>
        /// Gets or sets the Label which is displayed to the field
        /// </summary>
        public string Label
        {
            get { return GetValue(LPShowFieldUCPercentCheck) as String; }
            set { SetValue(LPShowFieldUCPercentCheck, value); }
        }
        #endregion // Label DP
        #region Value DP
        /// <summary>
        /// Value dependency property.  
        /// </summary>
        public static readonly DependencyProperty ValueProp =
            DependencyProperty.Register("Value",
                typeof(string),
                typeof(UserInputFieldUC),
                new PropertyMetadata(""));
        /// <summary>
        /// Gets or sets the value being displayed
        /// </summary>
        public string Value
        {
            get { return GetValue(ValueProp) as String; }
            set { SetValue(ValueProp, value); }
        }
        #endregion // Value DP
        #region Units DP
        /// <summary>
        /// Units dependency property
        /// </summary>
        public static readonly DependencyProperty UnitsProperty =
            DependencyProperty.Register("Units",
                typeof(string),
                typeof(UserInputFieldUC),
                new PropertyMetadata(""));
        /// <summary>
        /// Gets or sets the Units which is displayed to the field
        /// </summary>
        public string Units
        {
            get { return GetValue(UnitsProperty) as String; }
            set { SetValue(UnitsProperty, value); }
        }
        #endregion // Units DP
        #region Maximum Allowable Input Value DP
        public static readonly DependencyProperty MaxInputProperty =
            DependencyProperty.Register("UpperLimit",
                typeof(string),
                typeof(UserInputFieldUC), new PropertyMetadata(""));
        public string UpperLimit
        {
            get { return GetValue(MaxInputProperty) as String; }
            set { SetValue(MaxInputProperty, value); }
        }
        #endregion // Max Value DP
        #region Minimum Allowable Input DP
        public static readonly DependencyProperty MinInputProperty =
            DependencyProperty.Register("LowerLimit",
                typeof(string),
                typeof(UserInputFieldUC), new PropertyMetadata(""));
        public string LowerLimit
        {
            get { return GetValue(MinInputProperty) as String; }
            set { SetValue(MinInputProperty, value); }
        }
        #endregion // Max Value DP
        #region Display Error DP
        public static readonly DependencyProperty ErrorProperty =
            DependencyProperty.Register("ShowErr",
                typeof(string),
                typeof(UserInputFieldUC),
                new PropertyMetadata(""));
        public string ShowErr
        {
            get { return GetValue(ErrorProperty) as String; }
            set { SetValue(ErrorProperty, value); }
        }
        #endregion // Display Error DP

        /// <summary>
        /// Check user input
        /// </summary>
        private void MetricValue_TextChanged(object sender, TextChangedEventArgs e)
        {
            string inputText = MetricValue.Text;
            if (inputText == "")
                inputText = "0";
            double inputValue = Convert.ToDouble(inputText);

            double maxValue = Convert.ToDouble(UpperLimit);
            double minValue = Convert.ToDouble(LowerLimit);

            ErrorDisplay.Text = "OK";            
            if (inputValue <= minValue)
            {
                ErrorDisplay.Text = "Err";
            }
            if (inputValue >= maxValue)
            {
                ErrorDisplay.Text = "Err";
            }        
        }
    }
}

XAML 显示用户控件和提交按钮的“MainWindow”代码:

<Window x:Class="TestUserInput.MainWindow"
        ...
        xmlns:local="clr-namespace:TestUserInput"
        Title="MainWindow" Height="450" Width="350">
    <Grid>
        <StackPanel Margin="5">
            <local:UserInputFieldUC Margin="5" Label="Param1" Value="{Binding Path=Param1}" Units="m" LowerLimit="5" UpperLimit="10"/>
            <local:UserInputFieldUC Margin="5" Label="Param2" Value="{Binding Path=Param2}" Units="m" LowerLimit="50" UpperLimit="100"/>
            <Button Content="Submit"/>
        </StackPanel>

    </Grid>
</Window>

主要 Window 的视图模型实现了通常的 INotifyPropertyChangedview 接口

    public class MainWindowViewModel : INotifyPropertyChanged
    {
        public double Param1 { get; set; }
        public double Param2 { get; set; }
        ...
        ...
    }

通常的方法是验证与命令的执行相结合。

您会将 ICommand 的实现公开为 window 视图模型上的公共属性。 调用该 SubmitCommand。 通常是中继命令或委托命令,但这可能稍微偏离主题。

icommand 提供的功能之一是 canexecute。 这是一个返回 true 或 false 的方法。 如果它返回 false,绑定了这个命令的按钮将被禁用。

您的 mainwindowviewmodel 还将实现验证接口之一。 InotifyDataErrorinfo 是推荐的一种。

一个完整的完整工作样本将非常复杂。

本质上,你需要两件事。

如果从视图到绑定属性的转换失败,您的视图需要告诉视图模型。 这将是在文本框中键入的文本,该文本框绑定到 double 例如。 否则视图模型不会知道这些失败。

视图模型还需要在属性更改时检查属性值。 看起来在这种特定情况下可以使用范围属性,但您通常还需要一系列功能。

我看看能不能找到一些代码。

在视图中,您要处理会冒泡的绑定传输错误事件。 添加错误以及删除错误时,您都会收到通知。

我发现的样本是 .Net old with mvvmlight,可能不是直接粘贴到 .net 核心。 它仍然说明了原理。

在父面板中(例如 window 的主网格)。 您需要一些事件处理程序。 我的示例使用交互(现在 xaml 行为 nuget)。

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

那些转换器:

    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;
    }

正如我提到的,您会收到一个事件,用于添加错误和删除错误。 因此,如果其他。

另一个转换器告诉视图模型哪个属性发生了变化。 还有其他选项,例如在用于所有属性设置器的属性更改通知的通用基本方法中起作用。

public class BindingSourcePropertyConverter : IEventArgsConverter
{
    public object Convert(object value, object parameter)
    {
        DataTransferEventArgs e = (DataTransferEventArgs)value;
        Type type = e.TargetObject.GetType();
        BindingExpression binding = ((FrameworkElement)e.TargetObject).GetBindingExpression(e.Property);
        return binding.ParentBinding.Path.Path ?? "";
        //return binding.ResolvedSourcePropertyName ?? "";
    }
}

请注意,这直接与视图模型一起使用,只有值类型属性。 您的 viewmodel 的属性是一个复杂的 object 并且绑定了另一层的属性将需要更复杂的处理。

在视图中,您还需要告诉它通知您要验证数据的每个绑定:

            <TextBox Text="{Binding FirstName
                                  , UpdateSourceTrigger=PropertyChanged
                                  , NotifyOnSourceUpdated=True}"

基本视图模型有点复杂。 正如您将看到的,它实现了 INotifyDataErrorInfo。 Validator.Validate 用于将检查其上的数据注释的视图模型。 还有一个谓词列表用于处理不适合注释的复杂验证。

所有这些都用于驱动一个属性 IsValid。 当它为假时,您的 canexecute 在您的 submitcommand 上应该返回 false。

public class BaseValidVM :  BaseNotifyUI, INotifyDataErrorInfo, INotifyPropertyChanged
{
    // From Validation Error Event
    private RelayCommand<PropertyError> conversionErrorCommand;
    public RelayCommand<PropertyError> ConversionErrorCommand
    {
        get
        {
            return conversionErrorCommand
                ?? (conversionErrorCommand = new RelayCommand<PropertyError>
                    (PropertyError =>
                    {
                        if (PropertyError.Added)
                        {
                            AddError(PropertyError.PropertyName, PropertyError.Error, ErrorSource.Conversion);
                        }
                        FlattenErrorList();
                    }));
        }
    }
    // From Binding SourceUpdate Event
    private RelayCommand<string> sourceUpdatedCommand;
    public RelayCommand<string> SourceUpdatedCommand
    {
        get
        {
            return sourceUpdatedCommand
                ?? (sourceUpdatedCommand = new RelayCommand<string>
                    (Property =>
                    {
                        ValidateProperty(Property);
                    }));
        }
    }
    private RelayCommand validateCommand;
    public RelayCommand ValidateCommand
    {
        get
        {
            return validateCommand
               ?? (validateCommand = new RelayCommand
                    (() =>
                    {
                        bool isOk = IsValid;
                        RaisePropertyChanged("IsValid");
                    }));
        }
    }

    private ObservableCollection<PropertyError> errorList = new ObservableCollection<PropertyError>();
    public ObservableCollection<PropertyError> ErrorList
    {
        get
        {
            return errorList;
        }
        set
        {
            errorList = value;
            RaisePropertyChanged();
        }
    }

    protected Dictionary<string, List<AnError>> errors = new Dictionary<string, List<AnError>>();

    protected bool isBusy = false;
    public bool IsBusy
    {
        get { return isBusy; }
        set { isBusy = value;  RaisePropertyChanged("IsBusy"); }
    }
    
    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

     public IEnumerable GetErrors(string property)
    {
        if (string.IsNullOrEmpty(property))
        {
            return null;
        }
        if (errors.ContainsKey(property) && errors[property] != null && errors[property].Count > 0)
        {
            return errors[property].Select(x => x.Text).ToList();
        }
        return null;
    }
    public bool HasErrors
    {
        get { return errors.Count > 0; }
    }
    public void NotifyErrorsChanged(string propertyName)
    {
        if (ErrorsChanged != null)
        {
            ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
        }
    }

    public virtual Dictionary<string, List<PredicateRule>> ValiditionRules { get; set; }
    private List<string> lastListFailures = new List<string>();
    public bool IsValid {
        get
        {
            // Clear only the errors which are from object Validation
            // Conversion errors won't be detected here
            RemoveValidationErrorsOnly();

            var vContext = new ValidationContext(this, null, null);
            List<ValidationResult> vResults = new List<ValidationResult>();
            Validator.TryValidateObject(this, vContext, vResults, true);
            TransformErrors(vResults);

            // Iterating the dictionary allows you to check the rules for each property which has any rules
            if(ValiditionRules != null)
            {
                foreach (KeyValuePair<string, List<PredicateRule>> ppty in ValiditionRules)
                {
                    ppty.Value.Where(x => x.IsOK(this) == false)
                               .ToList()
                               .ForEach(x =>
                                     AddError(ppty.Key, x.Message, ErrorSource.Validation)
                                );
                }
            }

            var propNames = errors.Keys.ToList();
            propNames.Concat(lastListFailures)
                     .Distinct()
                     .ToList()
                     .ForEach(pn => NotifyErrorsChanged(pn));
            lastListFailures = propNames;

            FlattenErrorList();

            //foreach (var item in errors)
            //{
            //    Debug.WriteLine($"Errors on {item.Key}");
            //    foreach (var err in item.Value)
            //    {
            //        Debug.WriteLine(err.Text);
            //    }
            //}

            if (propNames.Count > 0)
            {
                return false;
            }
            return true;
        }
    }
    private void RemoveValidationErrorsOnly()
    {
        foreach (KeyValuePair<string, List<AnError>> pair in errors)
        {
            List<AnError> _list = pair.Value;
            _list.RemoveAll(x => x.Source == ErrorSource.Validation);
        }

        var removeprops = errors.Where(x => x.Value.Count == 0)
            .Select(x => x.Key)
            .ToList();
        foreach (string key in removeprops)
        {
            errors.Remove(key);
        }
    }
    public void ValidateProperty(string propertyName)
    {
        errors.Remove(propertyName);
        lastListFailures.Add(propertyName);

        if(!propertyName.Contains("."))
        {
            var vContext = new ValidationContext(this, null, null);
            vContext.MemberName = propertyName;
            List<ValidationResult> vResults = new List<ValidationResult>();
            Validator.TryValidateProperty(this.GetType().GetProperty(propertyName).GetValue(this, null), vContext, vResults);

            TransformErrors(vResults);
        }

        // Apply Predicates
        // ****************
        if (ValiditionRules !=null && ValiditionRules.ContainsKey(propertyName))
        {
            ValiditionRules[propertyName].Where(x => x.IsOK(this) == false)
                                         .ToList()
                                         .ForEach(x =>
                                          AddError(propertyName, x.Message, ErrorSource.Validation)
                                           );
        }
        FlattenErrorList();
        NotifyErrorsChanged(propertyName);
        RaisePropertyChanged("IsValid");
    }
    private void TransformErrors(List<ValidationResult> results)
    {
        foreach (ValidationResult r in results)
        {
            foreach (string ppty in r.MemberNames)
            {
                AddError(ppty, r.ErrorMessage, ErrorSource.Validation);
            }
        }
    }
    private void AddError(string ppty, string err, ErrorSource source)
    {
        List<AnError> _list;
        if (!errors.TryGetValue(ppty, out _list))
        {
            errors.Add(ppty, _list = new List<AnError>());
        }
        if (!_list.Any(x => x.Text == err))
        {
            _list.Add(new AnError { Text = err, Source = source });
        }
    }
    private void FlattenErrorList()
    {
        ObservableCollection<PropertyError> _errorList = new ObservableCollection<PropertyError>();
        foreach (var prop in errors.Keys)
        {
            List<AnError> _errs = errors[prop];
            foreach (AnError err in _errs)
            {
                _errorList.Add(new PropertyError { PropertyName = prop, Error = err.Text });
            }
        }
        ErrorList = _errorList;
    }
    public void ClearErrors()
    {
        List<string> oldErrorProperties = errors.Select(x => x.Key.ToString()).ToList();
        errors.Clear();
        ErrorList.Clear();
        foreach (var p in oldErrorProperties)
        {
            NotifyErrorsChanged(p);
        }
        NotifyErrorsChanged("");
    }
}

一个示例视图模型:

public class UserControl1ViewModel : BaseValidVM
{
    private string firstName;
    [Required]
    [StringLength(20, MinimumLength = 2, ErrorMessage = "Invalid length for first name")]
    public string FirstName
    {
        get { return firstName; }
        set { firstName = value; RaisePropertyChanged(); }
    }
    private string surName;

    [Required]
    [StringLength(40, MinimumLength = 4, ErrorMessage = "Invalid length for last name")]
    public string SurName
    {
        get { return surName; }
        set { surName = value; RaisePropertyChanged(); }
    }

    private Decimal amount = 1;
    [Required]
    [MaxDecimalPlaces(MantissaDigits = 1)]
    public Decimal Amount
    {
        get { return amount; }
        set { amount = value; RaisePropertyChanged(); }
    }

    private Decimal amount2 = 2;
    [Required]
    [MaxDecimalPlaces(ErrorMessage = "Amount 2 is money, it can have up to 2 numbers after the decimal place.")]
    public Decimal Amount2
    {
        get { return amount2; }
        set { amount2 = value; RaisePropertyChanged(); }
    }

    private DateTime orderDate = DateTime.Now.Date;
    [Required]
    public DateTime OrderDate
    {
        get { return orderDate; }
        set { orderDate = value; RaisePropertyChanged(); }
    }


    private RelayCommand saveCommand;
    // If IsValid is false then a button bound to savecommand will be disabled.
    public RelayCommand SaveCommand
    {
        get
        {
            return saveCommand
               ?? (saveCommand = new RelayCommand
                    (async () =>
                    {
                        if (IsBusy)
                        {
                            return;
                        }
                        IsBusy = true;
                        Debug.WriteLine("Would have saved");
                        await Task.Delay(2000); // Simulates a delay doing something DO NOT USE LIVE
                        IsBusy = false;
                        SaveCommand.RaiseCanExecuteChanged(); // Force UI to requery canexecute
                    },
                     () => IsValid && !IsBusy  // CanExecute when valid and not busy
                    ));
        }
    }

    // Empty string is validation which is not at all property specific
    // Otherwise.
    // Add an entry per property you need validation on with the list of PredicateRule containing the validation(s)
    // to apply.
    public override Dictionary<string, List<PredicateRule>> ValiditionRules { get; set; } 

    public UserControl1ViewModel()
    {
        // Constructor of the inheriting viewmodel adds any rules which do not suit annotations
        // Note
        // Two alternative styles of writing rules:
        ValiditionRules = new Dictionary<string, List<PredicateRule>>
        {
            {"Amount2",
                new List<PredicateRule>
                {
                  new PredicateRule
                  {
                      Message ="Amount2 must be greater than Amount",
                      IsOK = x => Amount2 > Amount
                  }
                }
            },
            {"OrderDate",
                new List<PredicateRule>
                {
                  new PredicateRule
                  {
                      Message ="Amount must be greater than 1 if Order Date is in future",
                      IsOK = x =>
                      {
                          if(OrderDate.Date > DateTime.Now.Date)
                          {
                              if(Amount <= 1)
                              {
                                  return false;
                              }
                          }
                          return true;
                      }
                  },
                  new PredicateRule
                  {
                      Message ="Order Date may only be a maximum of 31 days in the future",
                      IsOK = x => (OrderDate - DateTime.Now.Date).Days < 31
                  }
                }
            }
        };
    }
}

此代码基于我编写的示例:

https://gallery.tec.net.microsoft.com/WPF-Entity-Framework-MVVM-78cdc204

该示例通过视图模型公开 EF model 类型。

我计划在系列中进行第三次,但一直没有时间。

我建议不要直接绑定到 model 属性并将验证属性放在“伙伴”类中,而是从 model class 复制到视图模型,然后再次返回以提交。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM