Reputation: 23749
In the following XAML, I am using MultiDataTrigger and ValidationRule in trying to keep Submit button disabled
until the two textboxes have valid values (alphanumeric) entered in them. This works working fine, in that when value is invalid (say, some@test
) button gets disabled and when you make that value valid (say, sometest
) it enables the button.
Issue: But when you start the application it displays the button as Enabled - that defeats the whole purpose since you can submit the button even if you have not yet start entering the textbox values.
Question: What I may be missing here and how can we fix it?
Remarks: If you look (and I tested) the MultiDataTrigger
example in the first link above, you would notice (as the two images show there) that the initial value (as expected) is shown as Unverified
with red
color, and when you check the two checkboxes the text value changes to Verified
with green color. So, it seems I'm clearly missing something in XAML above. Please note that I have set the initial state of the button to <Setter Property="IsEnabled" Value="False"/>
.
<Window x:Class="WPF_DataValidation.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:WPF_DataValidation"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<local:MyDataSource x:Key="myds"/>
<ControlTemplate x:Key="ValidationTemplate">
<DockPanel>
<TextBlock Text="{Binding [0].ErrorContent}" Foreground="Red" DockPanel.Dock="Bottom" Grid.Row="1"/>
<AdornedElementPlaceholder/>
</DockPanel>
</ControlTemplate>
<Style TargetType="Label">
<Setter Property="Margin" Value="5" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
<Style x:Key="DefaultTextBoxStyle" TargetType="TextBox">
<Setter Property="Margin" Value="5" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="Labels" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Name="lblFirstName">First Name:</Label>
<TextBox Name="txtFirstName" Grid.Column="1" Style="{StaticResource DefaultTextBoxStyle}" Validation.ErrorTemplate="{StaticResource ValidationTemplate}">
<TextBox.Text>
<Binding Path="FirstName" Source="{StaticResource myds}" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:AlphanumericOnlyValidationRule/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<Label Name="lblLastName" Grid.Row="1">Last Name:</Label>
<TextBox Name="txtLastName" Grid.Row="1" Grid.Column="1" Style="{StaticResource DefaultTextBoxStyle}" Validation.ErrorTemplate="{StaticResource ValidationTemplate}">
<TextBox.Text>
<Binding Path="LastName" Source="{StaticResource myds}" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:AlphanumericOnlyValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<Button x:Name="btnTest" Content="Test" Grid.Row="2" HorizontalAlignment="Right">
<Button.Style>
<Style TargetType="Button">
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=(Validation.HasError), ElementName=txtFirstName}" Value="False"/>
<Condition Binding="{Binding Path=(Validation.HasError), ElementName=txtLastName}" Value="False"/>
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="True"/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</Grid>
</Window>
MyDataSource.cs:
public class MyDataSource
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
AlphanumericOnlyValidationRule.cs
using System.Windows.Controls;
using System.Linq;
using System.Globalization;
namespace WPF_DataValidation
{
public class AlphanumericOnlyValidationRule: ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
string sVal = value as string;
if (string.IsNullOrEmpty(sVal))
{
return new ValidationResult(false, "Please enter some text");
}
if (!sVal.All(char.IsLetterOrDigit))
{
//Note: this will also return false if text has a blank space
return new ValidationResult(false, "This field must contain alphanumeric characcters only");
}
return new ValidationResult(true, null);
}
}
}
OBSERVATION
The issue seems to be that when app loads first time it probably first does disable the submit button. But the value Validation.HasError
in the MultiDataTrigger.Conditions
block of the XAML assumes there is no validation error (since the app has just been loaded and user has not yet taken any action), and hence it enables the submission button again. This is just my observation based on the following XAML that I used by modifying the MultiDataTrigger example as shown below. Here I just changed the TextBlock
to Button
along with corresponding values and noticed that initially the button is disabled (since the checkboxes by default are unchecked), and when you check both the checkboxes, button gets enabled (as expected). So, we probably need to see how (in the above XAML) we can disable the button when app initially loades.
Modified XAML from the online MiltiDataTrigger Example:
<Button.Style>
<Style TargetType="Button">
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=(Validation.HasError), ElementName=txtFirstName}" Value="False"/>
<Condition Binding="{Binding Path=(Validation.HasError), ElementName=txtLastName}" Value="False"/>
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="True"/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
Upvotes: 1
Views: 732
Reputation: 406
It is expected behavior since the validation occurs
(...)when the value of a target is transferred to the binding source property.
In your case to force the validation before even updating the value of the controls you can add ValidatesOnTargetUpdated="True"
property to the ValidationRule
and it will trigger validation when the target of the Binding is updated.
<TextBox
Name="txtLastName"
Grid.Row="1"
Grid.Column="1"
Style="{StaticResource DefaultTextBoxStyle}"
Validation.ErrorTemplate="{StaticResource ValidationTemplate}"
>
<TextBox.Text>
<Binding
Path="LastName"
Source="{StaticResource myds}"
UpdateSourceTrigger="PropertyChanged"
>
<Binding.ValidationRules>
<local:AlphanumericOnlyValidationRule ValidatesOnTargetUpdated="True" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
ValidationRule.ValidatesOnTargetUpdated - MS DOC
Upvotes: 1