In the following code, I am using ValidationRule to validate user input on a TextBox. It works fine. If user enters invalid data (any non-alphabet character) it displays a small red ! sign on the left of the textbox, and also if you mouseover the textbox it would display an invalid data message (as shown in the image below).
Question: How can we modify the following code so - instead of tooltip, the invalid data message is displayed just below the textbox?
Remarks: I tried replacing ToolTip with TexBlock in the following line <Setter Property="ToolTip" of the XAML below, and put a textblock below TexBox as follows: <TextBlock Text="{Binding (Validation.Errors)[0].ErrorContent, ElementName=txtTest}"/>. But (as expected) it did not work since TextBlock, of course, is not a property of TextBox
MyDataSource.cs:
public class MyDataSource
{
public MyDataSource()
{
}
public string FirstName { get; set; }
}
AlphabetsOnlyValidationRule:
public class AlphabetsOnlyValidationRule : 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.ToList().All(t => char.IsLetter(t)))
{
return new ValidationResult(false, "First Name should contains alphabets only");
}
return new ValidationResult(true, null);
}
}
MainWindow.xaml:
<Window.Resources>
<local:MyDataSource x:Key="Ods"/>
<local:AlphabetsOnlyValidationRule x:Key="AlphabetsOnlyKey"/>
<ControlTemplate x:Key="ValidationTemplate">
<DockPanel>
<TextBlock Foreground="Red" FontSize="20">!</TextBlock>
<AdornedElementPlaceholder/>
</DockPanel>
</ControlTemplate>
<Style x:Key="TextBoxInError" TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<TextBox x:Name="txtTest" Margin="48,0,572,388" Style="{StaticResource TextBoxInError}" Validation.ErrorTemplate="{StaticResource ValidationTemplate}">
<TextBox.Text>
<Binding Path="FirstName" Source="{StaticResource Ods}" UpdateSourceTrigger="PropertyChanged" >
<Binding.ValidationRules>
<local:AlphabetsOnlyValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</Grid>
Screenshot of Validation Message [When user enters non-alphabet character]
Add a TextBlock that binds to the ErrorContent property to the Validation.ErrorTemplate:
<ControlTemplate x:Key="ValidationTemplate">
<DockPanel>
<TextBlock Foreground="Red" FontSize="20" DockPanel.Dock="Left">!</TextBlock>
<TextBlock Text="{Binding [0].ErrorContent}" Foreground="Red" DockPanel.Dock="Bottom"/>
<AdornedElementPlaceholder/>
</DockPanel>
</ControlTemplate>
Related
In my WPF app I have a below resource:
<ResourceDictionary>
<Style x:Key="OnErrorTextBoxStyle" TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="BorderBrush" Value="Red"/>
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<StackPanel>
<AdornedElementPlaceholder x:Name="placeholder" />
<TextBlock FontSize="11" FontStyle="Italic" Foreground="Red"
Text="{Binding ElementName=placeholder, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}" />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
My TextBox:
<TextBox Grid.Column="0"
Grid.Row="1"
Style="{StaticResource OnErrorTextBoxStyle}"
Height="30"
Margin="5,8,8,15">
<TextBox.Text>
<Binding Path="MyPath"
UpdateSourceTrigger="PropertyChanged"
>
<Binding.ValidationRules>
<Rules:PathValidationRule ValidatesOnTargetUpdated="True" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
In the view model my myPath property has below aspect (I only show imporant things here):
public string MyPath
{
get => myObject.currentPath.LocalPath; // currentPath is an Uri Object
set
{
if (!string.IsNullOrWhiteSpace(value))
{
// creates an Uri object using value and Uri.TryCreate
if (Uri.TryCreate(value, UriKind.Absolute, out Uri newUri))
{
myObject.currentPath = newUri;
OnPropertyChanged();
}
}
}
}
When I try to set MyPath property from the view model I get below error:
Cannot get 'Item[]' value (type 'ValidationError') from
'(Validation.Errors)' (type 'ReadOnlyObservableCollection`1').
BindingExpression:Path=AdornedElement.(0)[0].ErrorContent;
DataItem='AdornedElementPlaceholder' (Name='placeholder'); target
element is 'TextBlock' (Name=''); target property is 'Text' (type
'String')
ArgumentOutOfRangeException:'System.ArgumentOutOfRangeException:
Specified argument was out of the range of valid values. Parameter
name: index'
If I remove the static resource Style="{StaticResource OnErrorTextBoxStyle}" from the TextBox, then all works perfectly. So I guess I am doing something wrong in the static resource but I do not know what.
My TextBox has a validation rule which validates what user is typing in. I am not using any other validation mechanism such as INotifyDataErrorInfo and IDataErrorInfo.
Try this binding in the ControlTemplate:
<TextBlock FontSize="11" FontStyle="Italic" Foreground="Red"
Text="{Binding [0].ErrorContent}" />
This is my first attempt at using Binding.ValidationRules. They are working as expected with the exception of:
When I enter a TextBox value, for example: "abc" it's fine. When I backspace over letters "c", "b", then "a" the rule fires as it should and warns me the TextBox is empty. The strange behavior is that the string property, to which CompanyName is bound, still retains the "a". Therefore the string is not empty and I can't use the string.Empty comparison in my business logic.
It seems that the ValidationRule is preventing that last edit/character deletion to the string property.
I hope that makes sense, but I have included some images. The green "abc" Label in the image is bound to the same property as the TextBox.
What I want to do is control the visibility of a "Save" Button based on a valid TextBox. Pretty standard I would imagine, so I must be approaching this wrong or I a missing something simple.
<TextBox x:Name="CompanyNameTextBox" Grid.Row="1" TabIndex="1"
Style="{StaticResource TextBoxStyleWithError}"
Validation.ErrorTemplate="{StaticResource validationErrorTemplate}">
<TextBox.Text>
<Binding Path="objCompanyClass.CompanyName" UpdateSourceTrigger="PropertyChanged" >
<Binding.ValidationRules>
<HelperClass:CompanyNameValidator Max="50"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<Style x:Key="TextBoxStyleWithError" TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
<Setter Property="Background" Value="DarkRed"/>
<Setter Property="Margin" Value="3 15 3 3"/>
</Trigger>
</Style.Triggers>
</Style>
<ControlTemplate x:Key="validationErrorTemplate">
<DockPanel>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Top" VerticalAlignment="Center" Margin="0 0 0 3">
<TextBlock Foreground="Red" FontSize="12" Margin="2,0,0,0"
Text="{Binding ElementName=ErrorAdorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">
</TextBlock>
</StackPanel>
<AdornedElementPlaceholder x:Name="ErrorAdorner" ></AdornedElementPlaceholder>
</DockPanel>
</ControlTemplate>
C#
public class CompanyNameValidator : ValidationRule
{
public static bool TextEntryValid = false;
private int _max;
public int Max
{
get { return _max; }
set { _max = value; }
}
public override ValidationResult Validate (object value, System.Globalization.CultureInfo cultureInfo)
{
if (string.IsNullOrEmpty(value.ToString()))
{
TextEntryValid = false;
return new ValidationResult(false, "Company Name cannot be empty.");
}
else
{
if (value.ToString().Length > Max)
{
TextEntryValid = false;
return new ValidationResult(false, $"Cannot be more than {Max} characters long.");
}
}
TextEntryValid = true;
return ValidationResult.ValidResult;
}
}
I'm develepoing a Windows Phone application, and I need to validate some user inputs in text boxs. Here the XAML of one of these textbox:
<TextBox
Name="times"
Grid.Row="1"
Height="80"
Text="{Binding UpdateSourceTrigger=Explicit,
Mode=TwoWay,
Path=orari,
ValidatesOnDataErrors=True,
ValidatesOnExceptions=True,
NotifyOnValidationError=true}"
TextChanged="TextBoxChangedHandler"
/>
Using BreakPoints I'm sure the IDataError finds the error, but the TextBox appereance doesn't change. I've read I should use Validate.ErrorTemplate in the XAML, but I don't find this option, pheraps it doesn't exist in Windows Phone? How can I do to change the style of the textbox if the input is not valid?
Thanks
Hard to tell from what you have posted, but here's an example of some of my code that does something very similar perhaps it can help you locate your error.
The style to be used by textboxes I want to be validated, gets a red box when an error has occurred.
<Style x:Key="ValidationTextBox" TargetType="TextBox" BasedOn="{StaticResource {x:Type TextBox}}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<Border BorderBrush="Red" BorderThickness="1">
<AdornedElementPlaceholder />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
The text box itself
<TextBox Style="{StaticResource ValidationTextBox}">
<TextBox.Text>
<Binding Path="Description" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<rules:MandatoryInputRule ValidatesOnTargetUpdated="True" />
<rules:IllegalCharsRule ValidatesOnTargetUpdated="True" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
An example validation rule
class IllegalCharsRule : ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
if (value != null)
{
string input = value as string;
if (input.Contains(",") || input.Contains("/") || input.Contains(#"\") || input.Contains(".") || input.Contains("\"") || input.Contains("'"))
return new ValidationResult(false, "Validation error. Illegal characters.");
}
return new ValidationResult(true, null);
}
}
I get no binding errors and this code works at another place. I haven't found out yet what I do now differently to the code where it works and it's not that much code.
In UserControl.Resource:
<Style TargetType="TextBox">
<Setter Property="BorderBrush" Value="DarkBlue"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Margin" Value="0,1,0,1"/>
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<StackPanel Orientation="Horizontal">
<AdornedElementPlaceholder/>
<Grid Margin="2,0,0,0">
<Ellipse Width="20" Height="20" Fill="Red"/>
<TextBlock Foreground="White" Text="X" FontWeight="Bold"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
Below in Xaml too:
<TextBlock Height="23" HorizontalAlignment="Left" Margin="22,90,0,0"
Text="Keywords" VerticalAlignment="Top"/>
<TextBox Height="23" HorizontalAlignment="Left" Margin="22,108,0,0"
VerticalAlignment="Top" Width="244">
<Binding Path="Tags" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<DataErrorValidationRule ValidatesOnTargetUpdated="False"/>
</Binding.ValidationRules>
</Binding>
</TextBox>
The button SAVE in my ViewModel is only activated when the Model.Tags property is longer 10 chars input from the user. The button activation/disable works fine when I enter 10,11 and then back 8 chars. All the property changes are fired.
Model:
namespace TBM.Model
{
public class Document : EntityBase , IDataErrorInfo
{
public int Id { get; set; }
public string DocumentName { get; set; }
public string Tags { get; set; }
public byte[] DocumentData { get; set; }
public int PeriodId { get; set; }
string IDataErrorInfo.Error { get { return null; } }
string IDataErrorInfo.this[string propertyName]
{
get { return this.GetValidationError(propertyName); }
}
public bool IsValid
{
get
{
foreach (string property in ValidatedProperties)
if (GetValidationError(property) != null)
return false;
return true;
}
}
static readonly string[] ValidatedProperties = { "Tags", };
private string GetValidationError(string propertyName)
{
if (Array.IndexOf(ValidatedProperties, propertyName) < 0)
return null;
string error = null;
switch (propertyName)
{
case "Tags": error = this.IsTagsEmpty(Tags); break;
default:
Debug.Fail("Unexpected property being validated on Document: " + propertyName);
break;
}
return error;
}
private string IsTagsEmpty(string value)
{
if (value != null && value.Trim().Length >= 10)
return null;
else
return "The keywords must have at least 10 chars!";
}
}
}
ViewModel:
public RelayCommand SaveDocumentCommand
{
get { return _saveDocumentCommand ?? (_saveDocumentCommand =
new RelayCommand(() => SaveDocument(),() => CanSaveDocument())); }
}
private bool CanSaveDocument()
{
return _document.IsValid;
}
//...
What does not work is the ErrorTemplate with the red Ellipse is not showing at all?
UPDATE: Exactly the below code works in a TEST project. But in my productive project It does not find the Resource??? Why the heck this?
<TextBlock Height="23" HorizontalAlignment="Left" Margin="22,89,0,0"
Text="Keywords" VerticalAlignment="Top"/>
<TextBox Style="{StaticResource bla}" Height="23" HorizontalAlignment="Left"
Margin="22,109,0,0" VerticalAlignment="Top" Width="244">
<Binding Path="Tags" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<DataErrorValidationRule ValidatesOnTargetUpdated="False"
ValidationStep="UpdatedValue"/>
</Binding.ValidationRules>
</Binding>
</TextBox>
<UserControl.Resources>
<Style x:Name="bla" TargetType="TextBox">
<Setter Property="BorderBrush" Value="DarkBlue"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Margin" Value="0,1,0,1"/>
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<StackPanel Orientation="Horizontal">
<AdornedElementPlaceholder/>
<Grid Margin="2,0,0,0">
<Ellipse Width="20" Height="20" Fill="Red"/>
<TextBlock Foreground="White" Text="X" FontWeight="Bold"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
I had similar problem. Fight it for hours just to realize that something was wrong with adorner layer.
What I did is put my with controls inside . And that was it. For some reason this decorator layer sometimes is gone. This is certainly true for TabControl (but in my case it was some other reason).
So it should look like this
<AdornerDecorator>
<Grid>
<TextBox .../>
</Grid>
</AdornerDecorator>
Hope this helps!
I have had a similar problem to this, it turned out not to be the template but the validation was not returning what I expected.
The ViewModel have to implement IDataErrorInfo, not the Model. The ViewModel binds to your View as the DataContext, not the Model, so implement the interface in ViewModel and bind to corresponding properties in XAML.
I am having a requirement where in I have to revert the values of a TextBox to old value when the user enters a wrong input. I am using MVVM framework so I dont want to write any codebehind. The Text and Tag of TextBox is databound from ViewModel variable. So my Tag field of TextBox will always have old value. I want to use the Tag field value to revert my Text value.
<Style TargetType="{x:Type TextBox}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel LastChildFill="True">
<TextBlock DockPanel.Dock="Right"
Foreground="Orange"
FontSize="12pt">
</TextBlock>
<Border BorderBrush="Red" BorderThickness="1">
<AdornedElementPlaceholder />
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true" >
<Setter Property="ToolTip"
Value="{Binding Path=Tag,RelativeSource={RelativeSource Self}}">
</Setter>
<Setter Property="Text"
Value="{Binding Path=Tag,RelativeSource={RelativeSource Self}}">
</Setter>
</Trigger>
</Style.Triggers>
</Style>
<TextBox Width="68" Tag="{Binding SampleText}" Height="23" HorizontalAlignment="Left" Margin="39,37,0,0" VerticalAlignment="Top" >
<TextBox.Text>
<Binding Path="SampleText" NotifyOnValidationError="True" ValidatesOnDataErrors="True" ValidatesOnExceptions="True">
<Binding.ValidationRules>
<val:SampleTextValidator></val:SampleTextValidator>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Now When an error happens, the TextBox is highlighted red.I have written a Trigger to revert the value back to original value (value stored in Tag field). Tt is not working. But Tooltip part is working. I am confused fully. Please help where am I doing wrong!!!. Correct me with a sample code if possible!!!!
My first guess is that when you made your text input invalid (eg. delete all values), you cause the tag to bind to the same value, hence, it will reflect an empty string.
What you need is a separate property for your original value to bind your tag to.
private string _oldValue;
public string OldValue
{
get {...}
set {... NotifyPropertyChanged()...}
}
private string _sampleText;
public string SampleText
{
get { return _sampleText; }
set {
OldValue = _sampleText;
_sampleText = value;
NotifyPropertyChanged(...);
}
}
<TextBox Width="68" Tag="{Binding OldValue}" ... >
Don't forget to implement INotifyPropertyChanged.