WPF TextBox setting Text using Trigger during validation - wpf

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.

Related

Setting Validation.ErrorTemplate as Style on a WPF TextBox is causing a binding error exception

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}" />

WPF Error Validation doesn't show ErrorTemplate

I have a window like
<Window.Resources>
<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>
<TextBox x:Name="SupportFolder"
Validation.ErrorTemplate="{StaticResource ValidationTemplate}"
Style="{StaticResource TextBoxInError}"
Padding="2">
<TextBox.Text>
<Binding Path="Preferences.SupportFolder" Mode="TwoWay" UpdateSourceTrigger="LostFocus">
<Binding.ValidationRules>
<local:FolderExistsValidationRule ValidationStep="RawProposedValue"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
So my impression is that when the input for the TextBox is in error that there will be a small red exclamation mark to the left of the TextBox and because of the Style triggers there will be a ToolTip displaying the error. The ToolTip works but I don't see the exclamation point indicating the error. I took this code from the Microsoft Samples but I cannot for the life of me see where I have made a mistake. The sample works (Data Binding -> BindingValidation) but I cannot seem to reproduce the code. Would someone act as a second pair of eyes and see what I am doing wrong?
You should dock the TextBlock in the error template to the left or right:
<TextBlock Foreground="Red" FontSize="20" DockPanel.Dock="Right">!</TextBlock>
Using your current markup, the TextBlock will end up "under" the adorned element.

How to invoke DataTrigger on a Toolbar Button during Validate.HasError

I have implemented the following xaml code to disable a toolbar button when Validation.HasError based on the text entered into the ComboBox based on the UserAccountValidationRule
The core xaml code is shown here:
<telerik:RadComboBox
x:Name="DataProviderComboBox" Width="120" Height="23" IsEditable="True" DataContext="{StaticResource MainWindowViewModel}">
<telerik:RadComboBox.Text>
<Binding Mode="TwoWay" Path="InputString">
<Binding.ValidationRules>
<validationRules:UserAccountValidationRule/>
</Binding.ValidationRules>
</Binding>
</telerik:RadComboBox.Text>
</telerik:RadComboBox>
<telerik:RadButton
Name="ToolbarButton" Width="74" HorizontalAlignment="Left" VerticalAlignment="Top" Content="Button">
<telerik:RadButton.Style>
<Style TargetType="{x:Type telerik:RadButton}">
<Style.Triggers>
<DataTrigger Binding="{Binding (Validation.HasError), ElementName=DataProviderComboBox}" Value="true" >
<Setter Property="IsEnabled" Value="false"/>
</DataTrigger>
</Style.Triggers>
</Style>
</telerik:RadButton.Style>
</telerik:RadButton>
</telerik:RadToolBar>
<Grid>
<telerik:RadButton
Name="OrdinaryButton" Width="74" HorizontalAlignment="Left" VerticalAlignment="Top" Content="Button" Margin="252,26,0,0">
<telerik:RadButton.Style>
<Style TargetType="{x:Type telerik:RadButton}">
<Style.Triggers>
<DataTrigger Binding="{Binding (Validation.HasError), ElementName=DataProviderComboBox}" Value="true" >
<Setter Property="IsEnabled" Value="false"/>
</DataTrigger>
</Style.Triggers>
</Style>
</telerik:RadButton.Style>
</telerik:RadButton>
</Grid>
What I would like is for the DataTrigger in the ToolBarButton to be invoked, but it is not.
I did a check on the validation by creating a second OrdinarryButton, and hooked this up to the validation, and this worked fine. It seems as if the DataTrigger does not work if the button is a toolbar item.
Can anyone explain how to get this to work?
So, as it turns out, if the button is an OrdinaryButton control, then the DataTrigger will be invoked and the button will be disabled on Validation.HasError = true via the xaml code below:
<Grid>
<telerik:RadButton
Name="OrdinaryButton" Width="74" HorizontalAlignment="Left" VerticalAlignment="Top" Content="Button" Margin="252,26,0,0">
<telerik:RadButton.Style>
<Style TargetType="{x:Type telerik:RadButton}">
<Style.Triggers>
<DataTrigger Binding="{Binding (Validation.HasError), ElementName=DataProviderComboBox}" Value="true" >
<Setter Property="IsEnabled" Value="false"/>
</DataTrigger>
</Style.Triggers>
</Style>
</telerik:RadButton.Style>
</telerik:RadButton>
</Grid>
However, if the button control is placed inside a ToolbarButton, the same xaml code will not disable the Toolbar button:
<telerik:RadComboBox
x:Name="DataProviderComboBox" Width="120" Height="23" IsEditable="True" DataContext="{StaticResource MainWindowViewModel}">
<telerik:RadComboBox.Text>
<Binding Mode="TwoWay" Path="InputString">
<Binding.ValidationRules>
<validationRulesParameterPass:UserAccountValidationRule/>
</Binding.ValidationRules>
</Binding>
</telerik:RadComboBox.Text>
</telerik:RadComboBox>
<telerik:RadButton
Name="ToolbarButton" Width="74" HorizontalAlignment="Left" VerticalAlignment="Top" Content="Button">
<telerik:RadButton.Style>
<Style TargetType="{x:Type telerik:RadButton}">
<Style.Triggers>
<DataTrigger Binding="{Binding (Validation.HasError), ElementName=DataProviderComboBox}" Value="true" >
<Setter Property="IsEnabled" Value="false"/>
</DataTrigger>
</Style.Triggers>
</Style>
</telerik:RadButton.Style>
</telerik:RadButton>
</telerik:RadToolBar>
I find this very peculiar, and it might be a previously undiscovered bug? If anyone knows whether this is expected behavior or not, and has a reason for the observed behavior, please feel free to post.
In the meantime, I spent some time developing an acceptable workaround.
In this case I modified the <validationRules:UserAccountValidationRule/> to take a parameter that passes a single boolean IsEnabled property:
<Binding.ValidationRules>
<validationRulesParameterPass:UserAccountValidationRule>
<validationRulesParameterPass:UserAccountValidationRule.AccountValidationParameter>
<validationRulesParameterPass:AccountValidationParameter IsEnabled="{Binding IsButtonEnabled, Source={StaticResource MainWindowViewModel}}" />
</validationRulesParameterPass:UserAccountValidationRule.AccountValidationParameter>
</validationRulesParameterPass:UserAccountValidationRule>
</Binding.ValidationRules>
I also binded the IsButtonEnabled dependency property property to the ToolBar Button item's IsEnabled property.
Then, inside the UserAccountValidationRule, based on whether the value is valid or not, the IsButtonEnabled property will be set to true or false. As this DependencyProperty is connected to the Toolbar Button's, if the validation sets this true, the button's IsEnabled property will also be set to true, and vice versa.
public class UserAccountValidationRule : ValidationRule
{
private AccountValidationParameter _accountValidationParameter;
public UserAccountValidationRule()
{
}
public AccountValidationParameter AccountValidationParameter
{
get { return _accountValidationParameter; }
set { _accountValidationParameter = value; }
}
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
if ( !string.IsNullOrEmpty(value as string) && value as string == "good")
{
AccountValidationParameter.IsEnabled = true;
return new ValidationResult(true, null);
}
else
{
AccountValidationParameter.IsEnabled = false;
return new ValidationResult(false, "bad string.");
}
}
}
Problem solved.

Style of a TextBox: Why is 'AcceptsReturn=true' not applied?

I have a custom control in WPF, which consists of a toggle button, a TextBlock and a TextBox. What I basically want to do is to show the TextBox when the toggle button is checked and the TextBlock otherwise. Furthermore I want allow defining to style properties on the control via dependency properties, which are applied to the TextBlock and the TextBox at runtime. The default template looks like this:
<Style TargetType="{x:Type views:EditableLabel}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type views:EditableLabel}">
<Border Background="{TemplateBinding Background}">
<DockPanel Margin="0">
<telerik:RadToggleButton x:Name="PART_Toggle"
DockPanel.Dock="Right"
IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsInEditMode, Mode=TwoWay}">
<Image Source="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=ToggleImage}" Height="14" />
</telerik:RadToggleButton>
<TextBlock x:Name="PART_TextBlock"
Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Text}" >
</TextBlock>
<TextBox x:Name="PART_TextBox"
Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" >
</TextBox>
</DockPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The custom control has two dependency properties for styles, one for the PART_TextBlock and one for PART_TextBox. The styles are assigned in the OnApplyTemplate method of the custom control and in the property change callbacks of the two dependency properties:
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_textBlock = (TextBlock) GetTemplateChild("PART_TextBlock");
_textBox = (TextBox) GetTemplateChild("PART_TextBox");
_toggleButton = (RadToggleButton) GetTemplateChild("PART_Toggle");
ApplyStyles();
UpdateVisibilities();
}
private void ApplyStyles()
{
if (_textBlock != null) _textBlock.Style = TextBlockStyle;
if (_textBox != null) _textBox.Style = TextBoxStyle;
}
(The callbacks are not shown here, as they are trivial, just calling ApplyStyles().
I use the custom control like this:
<views:EditableLabel Text="{Binding SelectedToolbox.Description, Mode=TwoWay}"
CanEdit="{Binding SelectedToolbox.CanEdit}"
ToggleImage="../Resources/Images/edit-26.png">
<views:EditableLabel.TextBlockStyle>
<Style TargetType="TextBlock">
<Setter Property="TextWrapping" Value="Wrap" />
</Style>
</views:EditableLabel.TextBlockStyle>
<views:EditableLabel.TextBoxStyle>
<Style TargetType="TextBox">
<Setter Property="TextWrapping" Value="Wrap" />
<Setter Property="AcceptsReturn" Value="True" />
<Setter Property="VerticalScrollBarVisibility" Value="Visible" />
</Style>
</views:EditableLabel.TextBoxStyle>
</views:EditableLabel>
Everything works as expected, except from the AcceptsReturn setter is not applied, which I find very strange. I've debugged ApplyStyles(): The style is assigned correctly and both setters are contained within the style.
TextWrapping and VerticalScrollBarVisibility are both set correctly:
while AcceptsReturn is not:
Any ideas, what might be the issue here?
The screenshot you posted suggests AcceptsReturn has a local value, i.e., a value set by explicitly calling the property setter or SetValue. Do you have any code in EditableLabel which explicitly sets the AcceptsReturn property? If so, the local value you set will take precedence over any style setters. You can avoid this by using SetCurrentValue to change the value while leaving the value source unchanged.
Secondly, rather assigning the style in your code behind, it is generally easier and more reliable to simply bind the style within the template, e.g.:
<TextBox x:Name="PART_TextBox" Style="{TemplateBinding TextBoxStyle}" ... />
You might try this first and see if you get better results.

WPF, Validation.ErrorTemplate and Windows Phone

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);
}
}

Resources