This is fairly readily handled in XAML with a validation rule and a converter. We put a validation rule on the textbox and bind the color of the label to the text of the textbox using a converter. The XAML looks like this.
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication13"
Title="MainWindow" Height="350" Width="525"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Window.Resources>
<local:RequiredValidator x:Key="RequiredValidator"/>
<local:RequiredColorConverter x:Key="RequiredColorConverter"/>
</Window.Resources>
<StackPanel Orientation="Horizontal" Height="30">
<Label Name="DescriptionLabel" Content="Description:">
<Label.Style>
<Style TargetType="{x:Type Label}">
<Setter Property="Foreground" Value="{Binding ElementName=DescriptionTextBox, Path=(Validation.HasError), Converter={StaticResource RequiredColorConverter}}"/>
</Style>
</Label.Style>
</Label>
<TextBox Name="DescriptionTextBox" Width="200">
<TextBox.Text>
<Binding Path="Description" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay">
<Binding.ValidationRules>
<StaticResource ResourceKey="RequiredValidator"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</StackPanel>
</Window>
--------------------------------------------------------
Class MainWindow
Public Property Description As String
Public Sub New()
InitializeComponent()
End Sub
End Class
As you can see will still need to define our validation rule and converter, both of which are fairly trivial classes.
Public Class RequiredValidator
Inherits ValidationRule
Public Overloads Overrides Function Validate(value As Object, cultureInfo As Globalization.CultureInfo) As ValidationResult
If String.IsNullOrWhiteSpace(value) Then
Return New ValidationResult(False, "Error")
Else
Return New ValidationResult(True, Nothing)
End If
End Function
End Class
Public Class RequiredColorConverter
Implements IValueConverter
Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As Globalization.CultureInfo) As Object Implements IValueConverter.Convert
If CBool(value) Then
Return New SolidColorBrush(System.Windows.Media.Colors.Red)
Else
Return New SolidColorBrush(System.Windows.Media.Colors.Black)
End If
End Function
Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As Globalization.CultureInfo) As Object Implements IValueConverter.ConvertBack
Throw New NotImplementedException("RequiredColorConverter")
End Function
End Class
As you can see when you run the project, both the textbox and the label turn red when the textbox is empty. As a side note, I tried to bind the label to the (Validation.Errors) collection but when I did that Visual Studio kept crashing (bug?) I know you can use this in a trigger without a converter so I think for some reason you can't pass the Validation.Errors to a converter.
Well this all works fine but it's very repetitive if you have several mandatory fields in an application. Even if I created a Style I would need two styles, one for the label and one for the textbox. So I wondered how difficult it would be to do this in code instead. The idea would be to write a utility method that took a textbox and an optional label and add the validator and style in code.
MakeTextboxRequired(EditDescriptionTextBox, EditDescriptionLabel)
It turns out that creating validation rules and styles in code is quite difficult and verbose.
Change the XAML to...
<Label Name="DescriptionLabel" Content="Description:"></Label>
<TextBox Name="DescriptionTextBox" Width="200" Text="{Binding Description}"></TextBox>
This is what the code looks like...
Public Sub MakeTextboxRequired(oTextBox As TextBox, oLabel As Label)
' Attach a mandatory validation rule to the control
' Attach a binding to the color of the label
Dim sElementName As String = oTextBox.Name
Dim ValidationBinding As New Binding
Dim RequiredValidator As ValidationRule = Me.FindResource("RequiredValidator")
Try
ValidationBinding.Path = New PropertyPath("Description")
ValidationBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
ValidationBinding.ValidatesOnDataErrors = True
ValidationBinding.NotifyOnValidationError = True
ValidationBinding.ValidationRules.Add(RequiredValidator)
oTextBox.SetBinding(TextBox.TextProperty, ValidationBinding)
If oLabel IsNot Nothing Then
Dim Style As New Style(GetType(Label))
Dim ColorConverter As IValueConverter = Me.FindResource("RequiredColorConverter")
Dim ColorBinding As New Binding
ColorBinding.ElementName = sElementName
ColorBinding.Path = New PropertyPath("(Validation.HasError)")
ColorBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
ColorBinding.ValidatesOnDataErrors = True
ColorBinding.NotifyOnValidationError = True
ColorBinding.Converter = ColorConverter
Style.Setters.Add(New Setter(Label.ForegroundProperty, ColorBinding))
oLabel.Style = Style
End If
Catch ex As Exception
Throw New Exception("MakeTextboxRequired: " & ex.Message)
End Try
End Sub
Now we can get rid of the styles in XAML and let the code do all the hard work.
I'm not saying this is a better approach, but it was an interesting exercise.
No comments:
Post a Comment