代码之家  ›  专栏  ›  技术社区  ›  Nathan Ernst

ListView绑定项的验证规则

  •  4
  • Nathan Ernst  · 技术社区  · 14 年前

    我的实际情况是:我有一个 ListView 还有一种习俗 UserControl 在主细节中设置。可以通过菜单项添加最初无效的多个项。

    我想做的是,最终,如果列表中的任何项目无效,就阻止提交。短期内,我试图给无效的项目一个直观的线索。我的想法是,介绍一种风格 列表视图 瞄准 ListViewItem 触发器上 列表视图项 附上 Validation.HasError 属性触发整行背景变为红色。

    为了执行这个操作,我当然添加了样式,并引入了一个简单的验证规则,我在 DisplayMemberBinding GridViewColumn . 我已经用调试器验证了规则正在被调用,并且规则按预期运行,但是我没有看到样式更改。

    我把下面的所有相关部分都包括在一个复制品中。我非常感谢你的帮助。我要注意的是,按钮总是生成一个带有“valid!”的消息框。以及文本,尽管调试程序显示命中了失败的规则。

    我也在使用.NET 3.5 SP1。

    Person.cs:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace ListViewItemValidation
    {
        public class Person
        {
            public string Name { get; set; }
            public int Age { get; set; }
    
            static Person[] _Data;
            public static Person[] Data
            {
                get
                {
                    if (_Data == null)
                    {
                         _Data =new[]{
                            new Person() { Name="John", Age=30},
                            new Person() { Name="Mary", Age=40},
                            new Person() { Name="", Age=20},
                            new Person() { Name="Tim", Age=-1},
                        };
                    }
                    return _Data;
                }
            }
        }
    }
    

    必需的StringValidator.cs:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Windows;
    using System.Windows.Data;
    using System.Windows.Controls;
    
    namespace ListViewItemValidation
    {
        public class RequiredStringValidator : ValidationRule
        {
            public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
            {
                if (string.IsNullOrEmpty(value as string))
                    return new ValidationResult(false, "String cannot be empty.");
    
                return ValidationResult.ValidResult;
            }
        }
    }
    

    Windows 1.xAML:

    <Window
        x:Class="ListViewItemValidation.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:l="clr-namespace:ListViewItemValidation"  
        Title="Window1" Height="300" Width="300">
        <DockPanel>
            <Button Content="Validate"
                    DockPanel.Dock="Bottom"
                    Click="ValidateClicked"
                    />
            <ListView 
                HorizontalAlignment="Stretch"        
                VerticalAlignment="Stretch"
                ItemsSource="{x:Static l:Person.Data}"
                >
                <ListView.Resources>
                    <Style TargetType="ListViewItem">
                        <Style.Triggers>
                            <Trigger Property="Validation.HasError" Value="True">
                                <Setter Property="Background" Value="Red" />
                            </Trigger>
                        </Style.Triggers>
                    </Style>
                </ListView.Resources>
                <ListView.View>
                    <GridView>
                        <GridView.Columns>
                            <GridViewColumn Header="Name">
                                <GridViewColumn.DisplayMemberBinding>
                                    <Binding Path="Name">
                                        <Binding.ValidationRules>
                                            <l:RequiredStringValidator
                                                ValidatesOnTargetUpdated="True"
                                                ValidationStep="RawProposedValue"
                                                />
                                        </Binding.ValidationRules>
                                    </Binding>
                                </GridViewColumn.DisplayMemberBinding>
                            </GridViewColumn>
                            <GridViewColumn 
                                Header="Age"
                                DisplayMemberBinding="{Binding Path=Age}"
                                />
                        </GridView.Columns>
                    </GridView>
                </ListView.View>
            </ListView>
        </DockPanel>
    </Window>
    

    窗口1.xaml.cs:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;
    
    namespace ListViewItemValidation
    {
        /// <summary>
        /// Interaction logic for Window1.xaml
        /// </summary>
        public partial class Window1 : Window
        {
            public Window1()
            {
                InitializeComponent();
            }
    
            private void ValidateClicked(object sender, RoutedEventArgs e)
            {
                if (Validation.GetHasError(this))
                    MessageBox.Show("invalid!");
                else
                    MessageBox.Show("valid!");
            }
        }
    }
    

    更新:最终解决方案 我添加了以下类以提供ListViewItem的附加属性,以检查是否有任何子级包含带有已失败验证规则的绑定属性:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Media;
    
    namespace ListViewItemValidation
    {
        public class ListViewItemExtensions
        {
            #region ChildrenHaveError Property
    
            public bool ChildrenHaveError
            {
                get { return (bool)this.ListViewItem.GetValue(ChildrenHaveErrorProperty); }
                set { this.ListViewItem.SetValue(ChildrenHaveErrorProperty, value); }
            }
    
            public static bool GetChildrenHaveError(ListViewItem obj)
            {
                return EnsureInstance(obj).ChildrenHaveError;
            }
    
            public static void SetChildrenHaveError(ListViewItem obj, bool value)
            {
                EnsureInstance(obj).ChildrenHaveError = value;
            }
    
            public static readonly DependencyProperty ChildrenHaveErrorProperty =
                DependencyProperty.RegisterAttached(
                    "ChildrenHaveError",
                    typeof(bool),
                    typeof(ListViewItemExtensions),
                    new PropertyMetadata(
                        new PropertyChangedCallback((o, a) => { EnsureInstance((ListViewItem)o); })
                    )
            );
            #endregion
    
            #region ValidatesChildren Property
            public bool ValidatesChildren
            {
                get { return (bool)this.ListViewItem.GetValue(ValidatesChildrenProperty); }
                set { this.ListViewItem.SetValue(ValidatesChildrenProperty, value); }
            }
    
            public static bool GetValidatesChildren(ListViewItem obj)
            {
                return EnsureInstance(obj).ValidatesChildren;
            }
    
            public static void SetValidatesChildren(ListViewItem obj, bool value)
            {
                EnsureInstance(obj).ValidatesChildren = value;
            }
    
            public static readonly DependencyProperty ValidatesChildrenProperty =
                DependencyProperty.RegisterAttached(
                    "ValidatesChildren",
                    typeof(bool),
                    typeof(ListViewItemExtensions),
                    new PropertyMetadata(
                        new PropertyChangedCallback((o, a) => { EnsureInstance((ListViewItem)o); })
                    )
               );
            #endregion
    
            #region Instance Property
            public static ListViewItemExtensions GetInstance(ListViewItem obj)
            {
                return (ListViewItemExtensions)obj.GetValue(InstanceProperty);
            }
    
            public static void SetInstance(ListViewItem obj, ListViewItemExtensions value)
            {
                obj.SetValue(InstanceProperty, value);
            }
    
            public static readonly DependencyProperty InstanceProperty =
                DependencyProperty.RegisterAttached("Instance", typeof(ListViewItemExtensions), typeof(ListViewItemExtensions));
            #endregion
    
            #region ListViewItem Property
            public ListViewItem ListViewItem { get; private set; }
            #endregion
    
            static ListViewItemExtensions EnsureInstance(ListViewItem item)
            {
                var i = GetInstance(item);
                if (i == null)
                {
                    i = new ListViewItemExtensions(item);
                    SetInstance(item, i);
                }
                return i;
            }
    
            ListViewItemExtensions(ListViewItem item)
            {
                if (item == null)
                    throw new ArgumentNullException("item");
    
                this.ListViewItem = item;
                item.Loaded += (o, a) =>
                {
                    this.FindBindingExpressions(item);
                    this.ChildrenHaveError = ComputeHasError(item);
                };
            }
    
            static bool ComputeHasError(DependencyObject obj)
            {
                var e = obj.GetLocalValueEnumerator();
    
                while (e.MoveNext())
                {
                    var entry = e.Current;
    
                    if (!BindingOperations.IsDataBound(obj, entry.Property))
                        continue;
    
                    var binding = BindingOperations.GetBinding(obj, entry.Property);
                    foreach (var rule in binding.ValidationRules)
                    {
                        ValidationResult result = rule.Validate(obj.GetValue(entry.Property), null);
                        if (!result.IsValid)
                        {
                            BindingExpression expression = BindingOperations.GetBindingExpression(obj, entry.Property);
                            Validation.MarkInvalid(expression, new ValidationError(rule, expression, result.ErrorContent, null));
                            return true;
                        }
                    }
                }
    
                for (int i = 0, count = VisualTreeHelper.GetChildrenCount(obj); i < count; ++i)
                    if (ComputeHasError(VisualTreeHelper.GetChild(obj, i)))
                        return true;
    
                return false;
            }
    
            void OnDataTransfer(object sender, DataTransferEventArgs args)
            {
                this.ChildrenHaveError = ComputeHasError(this.ListViewItem);
            }
    
            void FindBindingExpressions(DependencyObject obj)
            {
                var e = obj.GetLocalValueEnumerator();
    
                while (e.MoveNext())
                {
                    var entry = e.Current;
                    if (!BindingOperations.IsDataBound(obj, entry.Property))
                        continue;
    
                    Binding binding = BindingOperations.GetBinding(obj, entry.Property);
                    if (binding.ValidationRules.Count > 0)
                    {
                        Binding.AddSourceUpdatedHandler(obj, new EventHandler<DataTransferEventArgs>(this.OnDataTransfer));
                        Binding.AddTargetUpdatedHandler(obj, new EventHandler<DataTransferEventArgs>(this.OnDataTransfer));
                    }
                }
    
                for (int i = 0, count = VisualTreeHelper.GetChildrenCount(obj); i < count; ++i)
                {
                    var child = VisualTreeHelper.GetChild(obj, i);
                    this.FindBindingExpressions(child);
                }
            }
    
        }
    }
    

    然后,我修改了 列表视图项 风格:

            <Style TargetType="ListViewItem">
                <Style.Setters>
                    <Setter Property="l:ListViewItemExtensions.ValidatesChildren" Value="True" />
                </Style.Setters>
                <Style.Triggers>
                    <Trigger Property="l:ListViewItemExtensions.ChildrenHaveError" Value="True">
                        <Setter Property="Background" Value="Red" />
                    </Trigger>
                </Style.Triggers>
            </Style>
    

    多谢@quartermeister帮我解决这个问题。

    1 回复  |  直到 13 年前
        1
  •  3
  •   Quartermeister    14 年前

    Validation.HasError 只在每个单元格的文本块上设置,因为这是应用绑定的地方。这是ListViewItem的子项之一,但不是ListViewItem本身。它也没有在窗口上设置,这就是为什么您的消息框总是显示“有效!”.

    当一个单元格验证失败时,可以使用一种方法来突出显示整行,即设置 ValidationAdornerSite 使单元格成为其行。这将导致 ErrorTemplate 对于要应用的ListViewItem,默认情况下会为其提供一个红色边框。尝试添加如下样式:

    <Style TargetType="TextBlock">
        <Setter
            Property="Validation.ValidationAdornerSite"
            Value="{Binding RelativeSource={RelativeSource AncestorType=ListViewItem}}"/>
    </Style>