【问题标题】:How to bind to TextBox.Text in local ValidationRule class如何在本地 ValidationRule 类中绑定到 TextBox.Text
【发布时间】:2023-03-26 20:17:02
【问题描述】:

这是我的 XAML 文件:

<Window x:Class="WpfListView.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfListView"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">

    <Grid>
        <ListView Margin="10" Name="lvUsers">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="*" />
                            <ColumnDefinition Width="150" />
                            <ColumnDefinition Width="20" />
                        </Grid.ColumnDefinitions>
                        <TextBox Grid.Column="0" Margin="0,0,5,0">
                            <Binding Path="Mail" Mode="TwoWay">
                                <Binding.ValidationRules>
                                    <local:NameValidation>
                                        <local:NameValidation.Params>
                                            <local:NameValidationParameters 
                                                    OriginalTree="{Binding Source={x:Reference lvUsers}}"
                                                    OriginalName="{Binding RelativeSource={RelativeSource Self}}"/>   <!-- I want the OriginalName to be TextBox.Text-->
                                        </local:NameValidation.Params>
                                    </local:NameValidation>
                                </Binding.ValidationRules>
                            </Binding>
                        </TextBox>
                    </Grid>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </Grid>


</Window>

这是我的MainWindow 班级:

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        List<User> items = new List<User>();
        items.Add(new User() { Name = "John Doe", Age = 42, Mail = "john@doe-family.com" });
        items.Add(new User() { Name = "Jane Doe", Age = 39, Mail = "jane@doe-family.com" });
        items.Add(new User() { Name = "Sammy Doe", Age = 7, Mail = "sammy.doe@gmail.com" });
        lvUsers.ItemsSource = items;
    }
}

这是我的ValidationRule 课程:

   public class NameValidationParameters : DependencyObject
    {
        public ListView OriginalTree
        {
            get { return (ListView)this.GetValue(OriginalTreeProperty); }
            set { this.SetValue(OriginalTreeProperty, value); }


        }

        public string OriginalName
        {
            get { return (string)this.GetValue(OriginalNameProperty); }
            set { this.SetValue(OriginalNameProperty, value); }


        }

        public static readonly DependencyProperty OriginalTreeProperty
        = DependencyProperty.Register(nameof(OriginalTree), typeof(ListView),
            typeof(NameValidationParameters));

        public static readonly DependencyProperty OriginalNameProperty
= DependencyProperty.Register(nameof(OriginalName), typeof(string),
    typeof(NameValidationParameters));


    }

    public class NameValidation : ValidationRule
    {
        public string ErrorMessage
        { get; set; }

        public NameValidationParameters Params { get; set; }



        public override ValidationResult Validate(object value, CultureInfo cultureInfo)
        {

            ValidationResult lResult = null;

            return lResult;
        }
    }

如您所见,当调用 NameValidation.Validate 时,我希望 NameValidation.Params 填充正确的变量:

  1. NameValidation.Params.OriginalTree 应该是原来的ListView。这个我可以得到。
  2. NameValidation.Params.OriginalName 应该是当前文本框的文本。在这种情况下,它绑定到Mail,所以我希望看到它是像john@doe-family.com 这样的电子邮件地址。但是我无法解决这个问题;我只能看到NameValidation.Params.OriginalName 是“WpfListView.NameValidationParameters”

另外,我希望能够访问NameValidation.Validate 方法内列表中的当前索引。我怎样才能得到它?

【问题讨论】:

  • 对于 originalName,可以绑定为 {Binding Path=Text,RelativeSource={RelativeSource AncestorType={x:Type TextBox}}}
  • 但是,我建议您使用 INotifyDataErrorInfo 来实现模型类的包装并将其绑定到 UI。这将是显示验证错误的一种更简洁的方式,并且可以异步工作。
  • @Skyuppercut,感谢您的评论和建议。你想扩展你的答案吗?
  • @Skyuppercut,您将其绑定为OriginalName="{Binding Path=Text,RelativeSource={RelativeSource AncestorType={x:Type TextBox}}}"/&gt; 的建议无效; OriginalName 仍然是 NULL

标签: c# wpf xaml data-binding


【解决方案1】:

正如在别处提到的, 验证规则不持有或继承 DataContext,如果您尝试内联声明所有内容,这将阻止绑定按预期工作。相反,将可绑定规则选项声明为资源,并使用 StaticBinding 在自定义规则上设置属性。

我已修改您的 TextBox 以添加资源参数:

  <TextBox Grid.Column="0" Margin="0,0,5,0" x:Name="box1">
        <TextBox.Resources>
             <local:NameValidationParameters OriginalName="{Binding Mail}" OriginalTree="{Binding Source={x:Reference lvUsers}}" x:Key="Parameters"/>
        </TextBox.Resources>
        <TextBox.Text>
           <Binding Path="Mail" Mode="TwoWay">
               <Binding.ValidationRules>
                  <local:NameValidation Params="{StaticResource Parameters}"/>     
               </Binding.ValidationRules>
            </Binding>
         </TextBox.Text>
  </TextBox>

和NameValidationParameters来实现Freezable

 public class NameValidationParameters : Freezable
{
    public ListView OriginalTree
    {
        get { return (ListView)this.GetValue(OriginalTreeProperty); }
        set { this.SetValue(OriginalTreeProperty, value); }


    }

    public string OriginalName
    {
        get { return (string)this.GetValue(OriginalNameProperty); }
        set { this.SetValue(OriginalNameProperty, value); }


    }

    public static readonly DependencyProperty OriginalTreeProperty
         = DependencyProperty.Register(nameof(OriginalTree), typeof(ListView),
             typeof(NameValidationParameters));

    public static readonly DependencyProperty OriginalNameProperty
        = DependencyProperty.Register(nameof(OriginalName), typeof(object),
            typeof(NameValidationParameters));

    protected override Freezable CreateInstanceCore()
    {
        return new NameValidationParameters();
    }
}

但是,这是我如何在项目中实现 INotifyDataErrorInfo 的示例。这是一种触发验证错误的非常干净的方法,xaml 允许您配置错误的显示

模型类:

 public class AttributionInput
{
    [Required]
    public DateTime? StartDate { get; set; }

    [Required]
    public DateTime? EndDate { get; set; }
}

ModelWrapper 通用:

public class ModelWrapper<T> : NotifyDataErrorInfoBase
{
    public T Model { get; protected set; }

    public ModelWrapper(T model)
    {
        Model = model;
    }

    public ModelWrapper()
    {

    }
    protected virtual TValue GetValue<TValue>([CallerMemberName] string propertyName = null)
    {
        return (TValue)typeof(T).GetProperty(propertyName)?.GetValue(Model);
    }

    protected virtual void SetValue<TValue>(TValue value, [CallerMemberName] string propertyName = null)
    {
        typeof(T).GetProperty(propertyName)?.SetValue(Model, value);
        OnPropertyChanged(propertyName);
        ValidatePropertyInternal(propertyName, value);
    }

    private void ValidatePropertyInternal(string propertyName, object currentValue)
    {
        ClearErrors(propertyName);
        ValidateDataAnnotations(propertyName, currentValue);
        ValidateCustomErrors(propertyName);
    }

    protected virtual IEnumerable<string> ValidateProperty(string propertyName)
    {
        return null;
    }

    private void ValidateCustomErrors(string propertyName)
    {
        var errors = ValidateProperty(propertyName);
        if (errors != null)
        {
            foreach (var error in errors)
            {
                AddError(propertyName, error);
            }
        }
    }

    private void ValidateDataAnnotations(string propertyName, object currentValue)
    {
        var results = new List<ValidationResult>();
        var context = new ValidationContext(Model) { MemberName = propertyName };
        Validator.TryValidateProperty(currentValue, context, results);

        foreach (var result in results)
        {
            AddError(propertyName, result.ErrorMessage);
        }
    }
}

通用实现:

    public class AttributionInputWrapper : ModelWrapper<AttributionInput>
{
    public AttributionInputWrapper(AttributionInput model) : base(model)
    {
    }

    public DateTime? StartDate
    {
        get => GetValue<DateTime?>();
        set
        {
            SetValue(value);
            if (EndDate < StartDate) EndDate = StartDate;
        }
    }

    public DateTime? EndDate
    {
        get => GetValue<DateTime?>();
        set
        {
            SetValue(value);
            if (EndDate < StartDate) StartDate = EndDate;
        }
    }

    protected override IEnumerable<string> ValidateProperty(string propertyName)
    {
        if (propertyName == nameof(EndDate) || propertyName == nameof(StartDate))
        {
            //if (StartDate.Value.Date > EndDate.Value.Date) yield return "Start Date must be <= End Date";
            if (EndDate != null && (EndDate.Value.DayOfWeek == DayOfWeek.Saturday || EndDate.Value.DayOfWeek == DayOfWeek.Sunday))
                yield return "Please select a week day";
            if (StartDate != null && (StartDate.Value.DayOfWeek == DayOfWeek.Saturday || StartDate.Value.DayOfWeek == DayOfWeek.Sunday))
                yield return "Please select a week day";
        }
    }
}

视图模型:

public class QueryViewModel : DetailViewModelBase, ICommonViewModel
{
  private AttributionInputWrapper _attributionInput;
  public AttributionInputWrapper AttributionInput
    {
        get => _attributionInput;
        set
        {
            _attributionInput = value;
            OnPropertyChanged();
        }
    }
 }

查看:

<Style TargetType="DatePicker">
        <Setter Property="Margin" Value="{StaticResource MarginString}"/>
        <Setter Property="Validation.ErrorTemplate">
            <Setter.Value>
                <ControlTemplate>
                    <StackPanel>
                        <AdornedElementPlaceholder x:Name="Placeholder1"/>
                        <TextBlock Text="{Binding ElementName=Placeholder1, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}" FontSize="9"/>
                    </StackPanel>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Style.Triggers>
            <Trigger Property="Validation.HasError" Value="True">
                <Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
            </Trigger>
        </Style.Triggers>
    </Style>
<DatePicker x:Name="StartDateBox" SelectedDate="{Binding AttributionInput.StartDate, UpdateSourceTrigger=PropertyChanged}" DisplayDateStart="10/01/2017" DisplayDateEnd="{x:Static system:DateTime.Now}"/>

【讨论】:

  • 我不确定你的回答与我的问题有什么关系,在这种情况下......
  • @Graviton 你让我扩展我的答案。因此,我编写了 INotifyDataErrorInfo 的实现。
  • 我想我更感兴趣的是让我现有的代码工作,即如何使OriginalName 假设一个正确的值(你的评论解决方案不起作用),以及如何捕获验证文本框的索引。
猜你喜欢
  • 2011-05-19
  • 2010-10-02
  • 2019-01-22
  • 1970-01-01
  • 2011-09-07
  • 2015-11-25
  • 2012-07-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多