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