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

时间:2020-07-29 07:33:56

标签: c# wpf xaml

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

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

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

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

  5. 如果这样做是错误的方法,请也纠正我。我希望遵循最佳实践方法。

非常感谢。

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

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

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

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

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

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

1 个答案:

答案 0 :(得分:2)

通常的方法是将验证与可以执行的命令结合在一起。

您将在窗口viewmodel上将ICommand的实现公开为公共属性。调用该SubmitCommand。通常是中继命令或委托命令,但这可能与主题略有出入。

icommand提供的功能之一就是可以执行。这是一个返回true或false的方法。如果返回false,将禁用带有此命令绑定的按钮。

您的mainwindowview模型还将实现验证接口之一。推荐使用InotifyDataErrorinfo。

一个完整的完整工作示例将很复杂。

本质上,您需要两件事。

您的视图需要告诉视图模型是否存在从视图到绑定属性的转换失败。这将在绑定到双精度字符(例如)的文本框中键入文本。 否则,视图模型将不知道这些失败。

视图模型还需要在属性值更改时检查其值。在这种特定情况下,似乎可以使用range属性,但通常还需要一系列函子。

我看看是否可以找到一些代码。

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

我发现的示例是使用mvvmlight的.Net旧版本,并且可能不是直接粘贴到.net核心上。仍然说明了原理。

在父面板中(例如,窗口的主网格)。您需要一些事件处理程序。我的示例使用了交互(现在是xaml behaviors 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;
    }

正如我所提到的,您将获得一个事件,该事件添加了一个错误,而另一个则被删除。因此,如果还有。

另一个转换器正在告诉viewmodel哪个属性已更改。还有其他选项,例如以一种通用的基本方法行事,用于从所有属性设置器发出属性更改通知。

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框。您的viewmodel属性是一个复杂的对象,并且具有绑定属性,而另一层向下则需要更多的复杂性。

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

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

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

所有这些都用于驱动属性IsValid。 如果那是假的,那么您可以在submitcommand上执行的操作应该返回假。

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.technet.microsoft.com/WPF-Entity-Framework-MVVM-78cdc204

该示例通过viewmodels公开EF模型类型。

我计划了系列中的三分之一,但没有时间。

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

相关问题