WPF 验证取决于必填/非必填字段

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/33256045/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 13:29:24  来源:igfitidea点击:

WPF Validation depending on Required/Not required field

c#wpfvalidationxaml

提问by Massimo

I'm new to WPF's developing but I was thinking about how to kill 3 birds with one stone. Example: I've a form with 2 TextBox and 2 TextBlocks. The first 'bird' would be to be able to "enrich" some textblock with an asterisk if they refer to required fields:

我是 WPF 开发的新手,但我正在考虑如何用一块石头杀死 3 只鸟。示例:我有一个包含 2 个文本框和 2 个文本块的表单。第一个“鸟”将能够“丰富”一些带有星号的文本块,如果它们指的是必填字段:

<TextBlock Grid.Row="0" Grid.Column="0" Text="Age" customProperty="Required" /> <TextBlock Grid.Row="1" Grid.Column="0" Text="Foot Size/>

<TextBlock Grid.Row="0" Grid.Column="0" Text="Age" customProperty="Required" /> <TextBlock Grid.Row="1" Grid.Column="0" Text="Foot Size/>

Then the TextBlocks would show their text differently, the first will have an asterisk, while the one with no customproperty defined would have not.

然后 TextBlocks 会以不同的方式显示它们的文本,第一个会有一个星号,而没有定义自定义属性的则没有。

The second bird would be to have some kind of validation on the value of the textbox, which If I understood correctly is done by using a CustomValidationRule, for which I implemented a class:

第二只鸟是对文本框的值进行某种验证,如果我理解正确,这是通过使用 CustomValidationRule 来完成的,为此我实现了一个类:

class AgeController: ValidationRule
{
    public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
    {
        if (value == null)
            return new ValidationResult(false, "Null value");

        int temp = 1;
        Boolean noIllegalChars = int.TryParse(value.ToString(), out temp);
        if (temp >= 1)
            return new ValidationResult(true, null);
        else
            return new ValidationResult(false, "Correggi");
    }
}

By adding this to the textBlox XAML code:

通过将其添加到 textBlox XAML 代码:

<TextBox.Text>
     <Binding Path="blabla" UpdateSourceTrigger="PropertyChanged"  ValidatesOnDataErrors="True">
         <Binding.ValidationRules>
              <local:AgeController ValidationStep="RawProposedValue" />
         </Binding.ValidationRules>
     </Binding>
</TextBox.Text>

And this works BUT the validation process should be different for required and not required fields: if it's required a blank input is not valid but if it's optional a blank field is OK. How do I achieve this without specifying two different ValidationRule while referencing the textblock linked to the textbox?

这是有效的,但是对于必填字段和非必填字段,验证过程应该不同:如果需要,则空白输入无效,但如果它是可选的,则空白字段是可以的。在引用链接到文本框的文本块时,如何在不指定两个不同的 ValidationRule 的情况下实现这一点?

/tldr: I'm trying to find a way to enrich a textblock with an attribute that adds a style to its text (asterisk or whatever the client wants, I modify how the enrichment modifies the text in just one place), the textbox's validation referring to the enriched textblock would then behave differently based on the value of the enrichment.

/tldr:我试图找到一种方法来丰富文本块的属性,为文本添加样式(星号或客户想要的任何东西,我只在一个地方修改了丰富内容修改文本的方式),文本框的验证引用丰富的文本块然后会根据丰富的值表现出不同的行为。

I hope I didn't mess up the explanation.

我希望我没有搞砸解释。

采纳答案by Emmanuel DURIN

1. TextBlock has no ControlTemplate property. So the (*) required can't be added to TextBlock

1. TextBlock 没有 ControlTemplate 属性。因此不能将所需的 (*) 添加到 TextBlock

Label has a controltemplate and can give focus to an input field. Let's use it.

Label 有一个控制模板,可以将焦点放在输入字段上。让我们使用它。

Usage of Targetproperty for passing focus to TextBox when Alt+F is pressed:

使用Target属性在按下 Alt+F 时将焦点传递给 TextBox:

<!-- Prefixing Firstname with _ allows the user to give focus
     to the textbox (Target) by pressing Alt + F-->

    <local:LabelWithRequiredInfo  Content="_Firstname" 
                                  IsRequired="false" 
                                  Target="{Binding ElementName=textboxFirstname,
                                  Mode=OneWay}" ... />

Creation of a sub class of Label : LabelWithRequiredInfo, so a IsRequired property can be added.
(Use VS Add New Item/WPF Custom Control).

创建 Label 的子类:LabelWithRequiredInfo,因此可以添加 IsRequired 属性。
(使用 VS 添加新项目/WPF 自定义控件)。

2. Creation of IsRequired dependency property to the control so binding will work - we need it !

2. 为控件创建 IsRequired 依赖属性,以便绑定可以工作——我们需要它!

public class LabelWithRequiredInfo : Label
{
    public bool IsRequired
    {
        get { return (bool)GetValue(IsRequiredProperty); }
        set { SetValue(IsRequiredProperty, value); }
    }

    // Using a DependencyProperty as the backing store for IsRequired.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty IsRequiredProperty =
        DependencyProperty.Register("IsRequired", typeof(bool), typeof(LabelWithRequiredInfo), new PropertyMetadata(false));
    static LabelWithRequiredInfo()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(LabelWithRequiredInfo), new FrameworkPropertyMetadata(typeof(LabelWithRequiredInfo)));
    }
}

3. Let's fill the LabelWithRequiredInfo template in Themes\Generic.xaml

3.让我们填写Themes\Generic.xaml中的LabelWithRequiredInfo模板

(But the template is first designed in MainWindow.xaml rigth clicking on a label/Edit template/Copy - so it can visualized - then the template content is copied in Generic.xaml)

(但模板首先在 MainWindow.xaml 中设计,单击标签/编辑模板/复制 - 因此它可以可视化 - 然后将模板内容复制到 Generic.xaml 中)

<Style TargetType="{x:Type local:LabelWithRequiredInfo}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:LabelWithRequiredInfo}">
                <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true">
                    <!-- A grid has been added to the template content to have multiple content.  -->
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="*"/>
                            <ColumnDefinition Width="30"/>
                        </Grid.ColumnDefinitions>
                        <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                        <!-- The Visibility  property has to be converted because it's not a bool but has a Visibility type
                             The converter (pretty classical) can be found in the attached solution, and is declared in the resource section
                             The binding is made on a property of the component : IsRequired
                        -->
                        <TextBlock  Text="(*)" 
                                    Visibility="{TemplateBinding IsRequired,Converter={StaticResource booleanToVisibilityConverter}}"
                                    Foreground="Red"
                                    Grid.Column="1"
                                    Margin="5 0"/>
                    </Grid>
                </Border>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsEnabled" Value="false">
                        <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                    </Trigger>
                </ControlTemplate.Triggers>

            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

4. Declaration of the converter in Generic.xaml :

4. Generic.xaml 中的转换器声明:

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:TextboxRequiredMandatoryInput">
    <local:BooleanToVisibilityConverter  x:Key="booleanToVisibilityConverter"/>

5. Declaration of a ValidationRule taking into account IsRequired behavior :

5. 考虑到 IsRequired 行为的 ValidationRule 声明:

class RequiredValidationRule : ValidationRule
{
    public bool IsRequired { get; set; }
    public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
    {
        var content = value as String;
        if (content != null)
        {
            if (IsRequired && String.IsNullOrWhiteSpace(content))
                return new ValidationResult(false, "Required content");
        }
        return ValidationResult.ValidResult;
    }
}

6. And use it in your binding as you found :

6.并在您发现的绑定中使用它:

<TextBox x:Name="textboxFirstname" HorizontalAlignment="Left" Height="23" Margin="236,94,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120">
    <TextBox.Text>
        <Binding Path="Firstname" UpdateSourceTrigger="PropertyChanged"  ValidatesOnDataErrors="True">
            <Binding.ValidationRules>
                <local:RequiredValidationRule IsRequired="true" ValidationStep="RawProposedValue" />
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
</TextBox>

You will find the complete solution here :

您将在此处找到完整的解决方案:

http://1drv.ms/1igpsyb

http://1drv.ms/1igpsyb

回答by tgpdyk

For re-usability and the way you described the requirement, an aggregate control may be needed. I think a UserControl + some DependencyProperty are a perfect one for this.

为了可重用性和您描述需求的方式,可能需要聚合控制。我认为 UserControl + some DependencyProperty 是一个完美的选择。

For my UserControl, I would have like this..

对于我的 UserControl,我会喜欢这个..

<UserControl x:Class="WpfApplication1.InputFieldControl"
         x:Name="InputUserCtrl"
         ...             >
<StackPanel x:Name="MainPanel">
    <TextBlock x:Name="Label">
        <TextBlock.Text>
            <MultiBinding StringFormat="{}{0}{1}:">
                <Binding Path="InputLabel" ElementName="InputUserCtrl"/>
                <Binding Path="RequiredStringSymbol" ElementName="InputUserCtrl"/>
            </MultiBinding>
        </TextBlock.Text>
    </TextBlock>
    <TextBox x:Name="Value" Text="{Binding DataContext, ElementName=InputUserCtrl}"/>
</StackPanel>

Then on its partial class (I have added number of properties, refer to the usage part):

然后在它的部分类(我添加了一些属性,参考使用部分):

public partial class InputFieldControl : UserControl
{
    // Required property
    public static readonly DependencyProperty RequiredProperty =
                   DependencyProperty.Register("Required", 
                   typeof(bool), typeof(InputFieldControl), 
                   new PropertyMetadata(true, OnRequiredChanged));
    private static void OnRequiredChanged(DependencyObject d, DependencyPropertyChangedEventArgs e){
        InputFieldControl ctrl = d as InputFieldControl;
        // symbol is voided
        if ((bool)e.NewValue == false)
            ctrl.RequiredStringSymbol = string.Empty;
    }
    public bool Required {
        get { return (bool)GetValue(RequiredProperty); }
        set { SetValue(RequiredProperty, value); }
    }
    // Required string symbol
    public static readonly DependencyProperty RequiredStringSymbolProperty =
                  DependencyProperty.Register("RequiredStringSymbol",
                  typeof(string), typeof(InputFieldControl),
                  new PropertyMetadata("*"));
    public string RequiredStringSymbol{
        get { return (string)GetValue(RequiredStringSymbolProperty); }
        set { SetValue(RequiredStringSymbolProperty, value); }
    }
    // Input Label
    public static readonly DependencyProperty InputLabelProperty =
                  DependencyProperty.Register("InputLabel",
                  typeof(string), typeof(InputFieldControl),
                  new PropertyMetadata(string.Empty));
    public string InputLabel{
        get { return (string)GetValue(InputLabelProperty); }
        set { SetValue(InputLabelProperty, value); }
    }

And I can use the control like this:

我可以像这样使用控件:

<StackPanel>
    <customCtrl:InputFieldControl Required="True"
                                  RequiredStringSymbol="+" 
                                  InputLabel="RequiredField"/>
    <customCtrl:InputFieldControl Required="False"
                                  InputLabel="NormalField"/>
    <customCtrl:InputFieldControl Required="True"
                                  RequiredStringSymbol="*" 
                                  InputLabel="AnotherRequiredField">
    </customCtrl:InputFieldControl>
</StackPanel>

As to the validation part, I would rather use IDataErrorInfo. This can go hand in hand with your ViewModel since we can now bind the Required property.

至于验证部分,我宁愿使用 IDataErrorInfo。这可以与您的 ViewModel 齐头并进,因为我们现在可以绑定 Required 属性。

回答by Andy

First point: you can define a custom template for your controls, in which you would add the visual elements you want ( the asterisk, etc. ) . You can control their visibility using triggers. ( you can ask a separate question for more details on this)

第一点:您可以为您的控件定义一个自定义模板,您可以在其中添加所需的视觉元素(星号等)。您可以使用触发器控制它们的可见性。(您可以提出一个单独的问题以获取更多详细信息)

Second/Third: You can define a boolean property IsRequiredon AgeController, and you can set it to TRUE/FALSE when defining the validation:

二/三:您可以定义一个布尔值属性IsRequiredAgeController,你可以定义验证时,将其设置为TRUE / FALSE:

<TextBox.Text>
     <Binding Path="blabla" UpdateSourceTrigger="PropertyChanged"  ValidatesOnDataErrors="True">
         <Binding.ValidationRules>
              <local:AgeController ValidationStep="RawProposedValue" 
                                                   IsRequired="**True**" />
                                               OR: IsRequired="**False**" />

         </Binding.ValidationRules>
     </Binding>
</TextBox.Text>

Then, this value would be available to you when implementing the validation:

然后,在实施验证时,您将可以使用此值:

public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
    {
        if (IsRequired)
        {
           ...
        }
        else
        {
           ...
        }
    }

回答by Emmanuel DURIN

Here is a second answer that is not exactly Massimo's question.

这是第二个答案,它不完全是 Massimo 的问题。

I did it, thinking it could be easier to use for the XAML designer

我做到了,认为XAML 设计器可能更容易使用

The goal is to have a Label (subclass actually to have the required red symbol (*)) easier to use.

目标是让标签(实际上是子类具有所需的红色符号 (*))更易于使用。

It gives focusto a TexBlock thanks to the usual Target property

由于通常的Target 属性,它可以将焦点放在TexBlock 上

<local:LabelWithRequiredInfo  Content="_Firstname" 
                              Target="{Binding ElementName=textboxFirstname, Mode=OneWay}" 
                              ... />  

And because there is a target, the LabelWithRequiredInfocan check for the presence of the RequiredValidationRulein the TextBox.TextProperty.

而且因为有一个目标,在LabelWithRequiredInfo可以检查存在RequiredValidationRuleTextBox.TextProperty

So most of the time no need for a IsRequired property.

所以大多数时候不需要 IsRequired 属性。

public LabelWithRequiredInfo()
{
    var dpd = DependencyPropertyDescriptor.FromProperty(Label.TargetProperty, typeof(Label));
    dpd.AddValueChanged(this, SearchForRequiredValidationRule);
}
private void SearchForRequiredValidationRule(object sender, EventArgs e)
{
    var textbox = Target as TextBox;
    if (textbox != null)
    {
        Binding binding = BindingOperations.GetBinding(textbox, TextBox.TextProperty);
        var requiredValidationRule = binding.ValidationRules
                                            .OfType<RequiredValidationRule>()
                                            .FirstOrDefault();
        if (requiredValidationRule != null)
        {
            // makes the required legend (red (*) for instance) to appear
            IsRequired = true;
        }
    }
}

And if a required legend must be provided on a checkbox or a combobox or whatever there is still a IsRequiredproperty on the LabelWithRequiredInfo

如果必须提供一个复选框或组合框或任何仍然有一个需要传奇IsRequired对房地产LabelWithRequiredInfo

<local:LabelWithRequiredInfo  Content="_I agree with the terms of contract" 
                              Target="{Binding ElementName=checkboxIAgree}"
                              IsRequired='"true"                                  
                              ... />

And it is still possible to add other validation rules on the textbox (or any control) to check for a number, regex, ...

并且仍然可以在文本框(或任何控件)上添加其他验证规则来检查数字、正则表达式、...

And last bonus, set the RequiredLegendas a dependency property in the LabelWithRequiredInfo:

最后一个奖励是,将RequiredLegend设置为LabelWithRequiredInfo 中的依赖属性:

public Object RequiredLegend
{
    get { return (Object)GetValue(RequiredLegendProperty); }
    set { SetValue(RequiredLegendProperty, value); }
}

// Using a DependencyProperty as the backing store for RequiredLegend.  This enables animation, styling, binding, etc...
public static readonly DependencyProperty RequiredLegendProperty =
    DependencyProperty.Register("RequiredLegend", typeof(Object), typeof(LabelWithRequiredInfo), new PropertyMetadata(null));

So that the template of LabelWithRequiredInfocan use it to display some text :

这样LabelWithRequiredInfo的模板可以使用它来显示一些文本:

<local:LabelWithRequiredInfo RequiredLegend="(*)" ... />

Or something more XAML-ish :

或者更像 XAML 的东西:

<local:LabelWithRequiredInfo ... >
    <local:LabelWithRequiredInfo.RequiredLegend>
        <TextBlock Text="(*)" Foreground="Red" />
    </local:LabelWithRequiredInfo.RequiredLegend>

Just need to change the template of control in themes\Generic.xaml.

只需要更改themes\Generic.xaml 中的控件模板即可。

It now uses a ContentControl to display text or a control :

它现在使用 ContentControl 来显示文本或控件:

<Style TargetType="{x:Type local:LabelWithRequiredInfo}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:LabelWithRequiredInfo}">
                <Border ...>
                    <Grid>
                        <Grid.ColumnDefinitions ... />
                        <ContentPresenter ... />
                        **<ContentControl Content="{TemplateBinding RequiredLegend}" Visibility="{TemplateBinding IsRequired,Converter={StaticResource booleanToVisibilityConverter}}" Grid.Column="1" /> **
                    </Grid>

Here is link to full working solution : http://1drv.ms/1MxltVZ

这是完整工作解决方案的链接:http: //1drv.ms/1MxltVZ

Best coding

最佳编码