【问题标题】:ValidationRules on a ListView's bound itemsListView 绑定项目上的 ValidationRules
【发布时间】:2010-07-22 23:23:00
【问题描述】:

我的实际情况是这样的:我在主详细信息中有一个 ListView 和一个自定义 UserControl 设置。可以通过一个菜单项添加多个最初无效的项。

如果列表中的任何项目无效,我最终想做的是阻止提交。短期而言,我试图为无效的项目提供视觉线索。我的想法是,在ListViewItem 附加的Validation.HasError 属性上为ListView 目标ListViewItem 触发器引入一种样式,以触发整个行的背景变为红色。

为此,我当然添加了样式,并引入了一个简单的验证规则,我在GridViewColumnDisplayMemberBinding 中使用它。我已经用调试器验证了规则正在被调用,并且规则按预期运行,但我没有看到样式发生变化。

我已将以下所有相关部分包含在副本中。我很感激这里的任何帮助。我应该注意,该按钮始终会生成一个带有“有效!”的消息框。文本也一样,尽管调试器显示失败的规则被命中。

我也在使用 .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;
            }
        }
    }
}

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

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

Window1.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);
            }
        }

    }
}

然后,我将ListViewItem 样式修改为:

        <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 帮助我解决这个问题。

【问题讨论】:

    标签: c# wpf validation data-binding listview


    【解决方案1】:

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

    当一个单元格验证失败时,您可以用来突出显示整行的一种方法是将单元格的ValidationAdornerSite 设置为其所在的行。这将导致应用 ListViewItem 的ErrorTemplate,默认情况下会给它一个红色边框。尝试添加这样的样式:

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

    【讨论】:

    • 我一直在玩这个,但我无法让它工作。似乎 TextBlock 根本没有进行验证(尽管我确实看到了正在调用的验证规则)。我一直在使用 VisualTreeHelper 来检查 ListViewItem 的子项,并在找到的每个 TextBlock 上手动调用 Validation.GetHasError(当然是通过 GridViewRowPresenter 向下导航),无论是否验证规则,它总是返回 false失败了。有什么想法吗?
    • @Nathan:如果我完全运行您在问题中发布的代码,Validation.GetHasError 在 TextBlock 上为空名称字符串返回 true。我也在运行 3.5 SP1,所以我不知道有什么区别。
    • @Quartermeister,很好奇......我将不得不更深入地挖掘简化代码。可能是我必须在我的实际应用程序中使用的一些公司范围的样式正在影响触发器(这是我最近一直在测试各种方法的地方)。我会告诉你我发现了什么。
    • 只是在玩我的小应用程序,我无法更改ListViewItem 的背景。我得到了标准的红色边框,但在(Validation.ValidationAdornerSiteFor).(Validation.HasError) 上没有任何样式触发器,相对源“self”触发了任何样式更改。
    • @Nathan:我认为绑定到 ValidationAdornerSiteFor 不起作用的原因是多个 TextBlock 具有与其 ValidationAdornerSite 相同的 ListViewItem。 ValidationAdornerSiteFor 只返回一个(可能是最后一个),并且那个 TextBlock 没有任何错误。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-04-28
    • 2011-06-21
    • 1970-01-01
    • 1970-01-01
    • 2014-07-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多