【问题标题】:How to force validation to show on UserControl如何强制验证在 UserControl 上显示
【发布时间】:2011-10-27 21:44:06
【问题描述】:

我希望在我的 UserControl 周围显示带有工具提示的库存/标准红色边框验证。请参阅下面的代码。我有主页和 UserControl。

UserControl 有文本框和按钮。 UserControls 绑定到 Id 属性并在 TextBox 中显示此 Id。

主页有 UserControl 和 TextBox。它们绑定到 FirstValue 和 SecondValue。这两个属性都会引发错误。当我在文本框中输入/更改某些内容时 - 我看到了边框和摘要。当我在 UserControl 中更改文本时 - 我在摘要中看到错误但没有边框,当我点击错误时 - 它聚焦按钮,不会转到 TextBox。我该如何解决?我希望整个 UserControl 都在红色边框内。

主页 XAML:

<UserControl x:Class="SilverlightApplication1.MainPage"
    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"
    mc:Ignorable="d"
    d:DesignHeight="275" d:DesignWidth="402" xmlns:my="clr-namespace:SilverlightApplication1" xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk">

    <Grid x:Name="LayoutRoot" Background="White" Width="300" HorizontalAlignment="Left">
        <Grid.RowDefinitions>
            <RowDefinition Height="30" />
            <RowDefinition Height="40" />
            <RowDefinition Height="30" />
            <RowDefinition Height="30" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <TextBox Grid.Row="2" Margin="3" Text="{Binding SecondValue, Mode=TwoWay, NotifyOnValidationError=True}"/>
        <my:TestUserControl Margin="3" Id="{Binding FirstValue, Mode=TwoWay, NotifyOnValidationError=True}"/>
        <sdk:ValidationSummary Grid.Row="4" Name="validationSummary1" />
    </Grid>
</UserControl>

主页 CS

using System.Windows.Controls;


namespace SilverlightApplication1
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq;

    public partial class MainPage : UserControl, INotifyDataErrorInfo, INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
        readonly Dictionary<string, List<string>> _currentErrors;

        private string _firstValue;
        private string _secondValue;

        public MainPage()
        {
            InitializeComponent();
            _currentErrors = new Dictionary<string, List<string>>();
            _firstValue = "test1";
            _secondValue = "test2";
            LayoutRoot.DataContext = this;
        }


        public string FirstValue
        {
            get { return _firstValue; }

            set
            {
                _firstValue = value;
                CheckIfValueIsValid("FirstValue", value);
                this.OnPropertyChanged("FirstValue");
            }
        }

        public string SecondValue
        {
            get { return _secondValue; }
            set
            {
                _secondValue = value;
                CheckIfValueIsValid("SecondValue", value);
                this.OnPropertyChanged("SecondValue");
            }
        }

        public void CheckIfValueIsValid(string propertyName, string value)
        {
            ClearErrorFromProperty(propertyName);

            AddErrorForProperty(propertyName, "Bad value");
        }

        protected void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        public IEnumerable GetErrors(string propertyName)
        {
            if (string.IsNullOrEmpty(propertyName))
            {
                return (_currentErrors.Values);
            }
            MakeOrCreatePropertyErrorList(propertyName);

            return _currentErrors[propertyName];
        }

        public bool HasErrors
        {
            get
            {
                return (_currentErrors.Where(c => c.Value.Count > 0).Count() > 0);
            }
        }

        void FireErrorsChanged(string property)
        {
            if (ErrorsChanged != null)
            {
                ErrorsChanged(this, new DataErrorsChangedEventArgs(property));
            }
        }

        public void ClearErrorFromProperty(string property)
        {
            MakeOrCreatePropertyErrorList(property);

            _currentErrors[property].Clear();
            FireErrorsChanged(property);
        }

        public void AddErrorForProperty(string property, string error)
        {
            MakeOrCreatePropertyErrorList(property);
            _currentErrors[property].Add(error);
            FireErrorsChanged(property);
        }

        void MakeOrCreatePropertyErrorList(string propertyName)
        {
            if (!_currentErrors.ContainsKey(propertyName))
            {
                _currentErrors[propertyName] = new List<string>();
            }
        }


    }
}

用户控件 XAML:

<UserControl x:Class="SilverlightApplication1.TestUserControl"
    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"
    mc:Ignorable="d"
    d:DesignHeight="30" d:DesignWidth="400">

    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>
        <Button Content="..." Grid.Column="1" Padding="10,0" />
        <TextBox Text="{Binding Id, Mode=TwoWay}" />
    </Grid>
</UserControl>

用户控制 CS:

using System.Windows.Controls;

namespace SilverlightApplication1
{
    using System.ComponentModel;
    using System.Windows;

    public partial class TestUserControl : UserControl, INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public TestUserControl()
        {
            InitializeComponent();
            LayoutRoot.DataContext = this;
        }


        public string Id
        {
            get { return (string)base.GetValue(IdProperty); }
            set
            {
                base.SetValue(IdProperty, value);
                this.OnPropertyChanged("Id");
            }
        }

        public static DependencyProperty IdProperty =
            DependencyProperty.Register(
            "Id",
            typeof(string),
            typeof(TestUserControl),
            new PropertyMetadata(OnIdChanged));

        private static void OnIdChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (DesignerProperties.IsInDesignTool) return;

            var lookup = d as TestUserControl;
            lookup.OnPropertyChanged("Id");
        }

        protected void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

    }
}

【问题讨论】:

  • 错误检查发生在对象内部,该对象设置为控件的DataContext 属性。据我所知,您将this 设置为TestUserControl 用户控件的DataContext,但您尚未向该控件添加验证。代码中还有许多其他错误,如果使用 MVVM 模式,可以摆脱它们。无论如何,我会尝试启动你的代码,看看我能做什么。
  • @vorrtex 示例只是一次性代码——我只是为了这个示例快速复制/粘贴了接口实现,以便您可以运行它。在现场项目中,它是 MVVM + PRISM
  • @vorrtex 另外,请参阅我对 Ekk 的回复。为什么我需要在 UserControl 中进行验证?我正在验证 Id 并且我希望 UserControl 被整体验证。因此,作为另一个示例,我可以实现 3 个文本框作为 UserControl 来输入电话号码。然后,我希望 Validation 再次在 Whole UserControl 上工作,并在所有 3 个框(一个矩形)周围显示红色边框。现在我得到了正确的验证,我只是没有得到 UI。同样,我将 UserControl 视为我的 UI 元素,我的主窗体将其视为任何其他控件。

标签: c# silverlight validation inotifydataerrorinfo


【解决方案1】:

要使您的示例正常工作,您应该从TestUserControl.xaml.cs 文件中删除所有代码并修复绑定。像这样:

<Button Content="..." Grid.Column="1" Padding="10,0" />
<TextBox Text="{Binding FirstValue, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnNotifyDataErrors=True}" />

后面是空代码:

public partial class TestUserControl : UserControl
{
    public TestUserControl()
    {
        InitializeComponent();
    }
}

接下来,你的另一个问题

在所有 3 个框(一个矩形)周围显示红色边框

可以通过绑定HasError属性来实现。根据您的代码,它看起来像这样:

<Grid x:Name="LayoutRoot" Background="White">
   <!-- ... -->
   <Border BorderBrush="Red" BorderThickness="1" 
            Grid.ColumnSpan="2" IsHitTestVisible="False"
            Visibility="{Binding HasErrors, Converter={StaticResource BooleanToVisibilityConverter}}" />

我添加了红色的Border 元素,当HasError 属性设置为true 时该元素可见。但是您应该在代码中的某处调用OnPropertyChanged("HasError")

另一种方式:创建自定义控件而不是UserControl。 我已经发布了过多的描述如何在自定义控件中实现验证作为this question about validation 的答案。

我可以给出更具体的答案,但是您应该通过将视图模型与视图分开来修复帖子中的代码,因为现在很难根据当前代码实现某些东西。 查看我关于使用 INotifyDataErrorInfo 实现验证的帖子:WPF and Silverlight Validation

或者你可以下载我的验证类directly

之后代码会简单很多,我可以帮你解决更复杂的问题。

【讨论】:

  • 非常感谢,我会检查你的例子。我确实认为我的解决方案是创建自定义控件我只是不知道如何处理这个问题,因为我是新手,而且现在我还没有想到。我无法删除我的代码的原因是因为我需要依赖属性。以我的电话号码为例。我想使用 5556667777 的“PhoneNumber”属性进行绑定,并将它们显示在 3 个框中。所以,我必须在后面的代码中有依赖属性和逻辑来处理这个值。在我当前的示例中,我只是简化它,显示在文本框中传输到文本的 Id。
  • 我会根据您提供的信息来工作,看看我能想出什么。如果我有更多问题 - 我会在这里发布。现在我看到我真正的解决方案是创建自定义控件。我不知道如何apprach它。对于这个特定的例子,我应该继承 TextBox 还是从头开始?
  • 我尝试了第一个建议更改绑定并准确了解它的作用,但这不是我想要做的,它仅涵盖 TextBox。我无法使边界工作,但我明白了,这仍然不是我需要的 - 因为它只是边界,我需要这个带有 Baloon 弹出窗口的角落等 - 就像常规验证一样
  • @katit 我只在需要扩展现有控件或应用不同样式时才创建自​​定义控件而不是用户控件。实际上,在您的电话号码示例中,您可以使用用户控件以及自定义控件来执行此操作。如果使用 UserControl 方法,创建一个具有 3 个属性的视图模型,这些属性绑定到 3 个文本框,并创建一个带有弹出框的不可见边框,其 Visibility 属性绑定到视图模型的 HasError 属性。此外,这将是更灵活的解决方案,因为您可以在视图模型中验证文本框。
  • @katit 我的回答非常抽象和模糊,因为除了如何修复代码之外,您还有其他问题。而且恐怕这个话题已经太大了,所以最好用确切的问题打开新问题。
【解决方案2】:

改变

<TextBox Text="{Binding Id, Mode=TwoWay}" />

<TextBox Text="{Binding Id, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnDataErrors=True}" />

【讨论】:

  • 这行不通,因为我没有在 UserControl 中实现 INOtifyDataError,这不是我想要的,我不想验证 TextBox,我需要验证整个 UserControl
猜你喜欢
  • 2020-03-10
  • 1970-01-01
  • 2023-03-05
  • 1970-01-01
  • 2013-02-04
  • 1970-01-01
  • 1970-01-01
  • 2010-10-25
  • 1970-01-01
相关资源
最近更新 更多