I Have looked through some posts regarding Validation Rules but havn't come across the problem I am experiencing.
I am using a Validation Rule for a Textbox in a WPF Window. I have two checks, one for empty Textbox, and one for invalid characters using a RegEx match.
My problem is such:
In my Textbox:
- Type A - Works, Displays nothings
- Hit Backspace for empty string - Works, displays validation error message "Please enter a value in all fields"
- Type ! - Does not work - It should display "Invalid characters were found", but still displays "Please enter a value in all fields."
- Backspace to empty string- Technically works because it still displays first error "Please enter a value in all fields."
- Type A - Works, no error message
- Type ! - Fine, displays message "Invalid characters were found.
Same happens the other way round.
Open Window
- Type ! - Fine, displays "Invalid characters were found.
- Backspace to empty string - Still displays "Invalid characters were found" instead of "Please enter a value in all fields.
My code as follows:
ProviderNew.xaml:
<Label Name="lblProviderName" Content="Provider Name: "
Grid.Row="1" Grid.Column="0"/>
<TextBox Name="txtBoxProviderName" Margin="2"
MaxLength="20" CharacterCasing="Upper"
Grid.Row="1" Grid.Column="1" LostFocus="TxtBoxProviderNameLostFocus" TextChanged="TxtBoxProviderNameTextChanged">
<TextBox.Text>
<Binding ElementName="This" Path="ProviderName" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<Domain:ProviderValidation />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<TextBlock x:Name="tbNameValidation" Foreground="Red" FontWeight="Bold" Margin="10,0,0,0"
Text="{Binding ElementName=txtBoxProviderName, Path=(Validation.Errors), Converter={StaticResource eToMConverter}}"
Grid.Row="1" Grid.Column="2" />
Privder code behind - ProviderNew.xaml.cs
public static readonly DependencyProperty NewProviderNameProperty = DependencyProperty.Register("ProviderName",
typeof (string), typeof(ProviderNew), new UIPropertyMetadata(""));
public string ProviderName
{
get { return (string) GetValue(NewProviderNameProperty); }
set { SetValue(NewProviderNameProperty, value); }
}
Value Converter Class
public class ErrorsToMessageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var sb = new StringBuilder();
var errors = value as ReadOnlyCollection<ValidationError>;
if (errors != null && errors.Count > 0)
{
foreach (var e in errors.Where(e => e.ErrorContent != null))
{
sb.AppendLine(e.ErrorContent.ToString());
}
}
return sb.ToString();
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Provider Class
private string _providerName;
public string ProviderName
{
get { return _providerName; }
set
{
_providerName = value;
RaisePropertyChanged("ProviderName");
}
}
private void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Validation Rule class
public class ProviderValidation : ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
var str = value as string;
if (str != null && !Regex.IsMatch(str, @"^[a-zA-Z0-9]*$"))
{
return new ValidationResult(false, "Invalid characters were found.");
}
if (String.IsNullOrEmpty(str))
{
return new ValidationResult(false, "Please enter a value in all fields.");
}
return new ValidationResult(true, null);
}
}
What I have tried is setting both LostFocus and TextChanged events to force update using:
var expression = txtBoxProviderName.GetBindingExpression(TextBox.TextProperty);
if (expression != null)
expression.UpdateSource();
Setting breakpoints on the Validate method shows that the correct matches are done and the correct ValidationResult is returned, but it does not update the text correctly.
Am I doing something incredibly silly?
Any suggestions will be appreciated.
Edit 1.
Yeah, I have that working, using MultiDataTrigger and Binding to the textboxes.
What doesn't work is when I first show the Window, the button is Enabled, which I don't want it to be because this could allow the user to click save with empty textboxes.
The Validation Rule doesn't work straight from the beginning when the Window opens.
I set the focus on the textbox and if it loses focus or incorrect data is enter, then The validation rule kicks in and disables the button.
Setting the button to be disabled by default, makes it disabled on opening, but then it isn't enabling when there are no Validation errors.
I can make it work by forcing a check of the Validation Rule on say the Load event, using
var expression = txtBoxProviderName.GetBindingExpression(TextBox.TextProperty);
if (expression != null)
expression.UpdateSource();
But then when the Window first opens, it immediately shows the Validation Error message "Please enter a value in all fields", which I don't really like the look of.
Any way round this, or can I not have the best of both worlds.
Here is the Button code
<Button x:Name="btnSave" Height="25" Width="100" Content="Save" HorizontalAlignment="Right" Margin="0,0,120,0"
Grid.Row="2" Click="BtnSaveClick" >
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="IsEnabled" Value="False" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=txtBoxProviderName, Path=(Validation.HasError)}" Value="False" />
<Condition Binding="{Binding ElementName=txtBoxHelpDesk, Path=(Validation.HasError)}" Value="False" />
<Condition Binding="{Binding ElementName=txtBoxRechargeInstructions, Path=(Validation.HasError)}" Value="False" />
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="True" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
Thanks,
Neill
Edit 2
A quick question. I couldn't find the RelayCommand namespace. Searching other code around I found an MVVM example from microsoft that implements a RelayCommand : ICommand class.
Is this correct?
The code is:
public class RelayCommand : ICommand
{
#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion // Fields
#region Constructors
/// <summary>
/// Creates a new command that can always execute.
/// </summary>
/// <param name="execute">The execution logic.</param>
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
/// <summary>
/// Creates a new command.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion // Constructors
#region ICommand Members
[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute(parameter);
}
#endregion // ICommand Members
}
I implemented the following in my ProviderNew.xaml.cs:
private ICommand saveCommand;
public ICommand SaveCommand
{
get
{
if (saveCommand == null)
{
saveCommand = new RelayCommand(p => DoSave(), p => CanSave() );
}
return saveCommand;
}
}
private bool CanSave()
{
if (!string.IsNullOrEmpty(ProviderName))
{
??? What goes here?
}
return true;
}
private bool DoSave()
{
// Save the information to DB
}
To be honest I am unsure of what should be coded in the block in the 'if (!string.IsNullOrEmpty(ProviderName))'
Also you said to add the ICommand code in the DataContext, so not sure if it is in the right place because When I open the Window, Save is enable and clicking it does nothing, even if all the fields have correct data in it.
Here is the ProviderNew xaml code for the button
<Button x:Name="btnSave" Height="25" Width="100" Content="Save" HorizontalAlignment="Right" Margin="0,0,120,0"
Grid.Row="2" Command="{Binding SaveCommand}" >
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="IsEnabled" Value="False" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=txtBoxHelpDesk, Path=(Validation.HasError)}" Value="False" />
<Condition Binding="{Binding ElementName=txtBoxProviderName, Path=(Validation.HasError)}" Value="False" />
<Condition Binding="{Binding ElementName=txtBoxRechargeInstructions, Path=(Validation.HasError)}" Value="False" />
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="True" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
Your help is very much appreciated.
Regards,
Neill