WPF Validation Control - wpf

I am new to WPF and trying to implement validation control on submit form.
Can anyone help me. My code doen't show up any error message even if I enter invalid data infect it does nothing.
Here is my code,
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public class UserName : INotifyPropertyChanged, IDataErrorInfo
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
PropertyChanged(this, e);
}
private string username;
public string _UserName
{
get { return username; }
set
{
username = value;
OnPropertyChanged(new PropertyChangedEventArgs("_UserName"));
}
}
public string this[string propertyName]
{
get
{
if (propertyName == "_UserName")
{
bool valid = true;
foreach (char c in _UserName)
{
if (!Char.IsLetterOrDigit(c))
{
valid = false;
break;
}
}
if (!valid)
return "The Username can only contain letters and numbers.";
}
return null;
}
}
public string Error
{
get { return null; }
}
}
}
My XAML code is,
<Grid>
<Label Content="User Name" Height="28" HorizontalAlignment="Left" Margin="27,37,0,0" Name="UserNameLB" VerticalAlignment="Top" Width="96" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="135,37,0,0" Name="UserNameTB" VerticalAlignment="Top" Width="189">
<TextBox.Text>
<Binding Path="_UserName">
<Binding.ValidationRules>
<DataErrorValidationRule></DataErrorValidationRule>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</Grid>

All you need is to put all logic about your validation in a separate class that inherits from class ValidationRule and override method Validate. Then you can set the message you want to see about the reason of validation failure, like:
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
try
{
if (!Char.IsLetterOrDigit(c))
{
return new ValidationResult(false,
"The Username can only contain letters and numbers.");
}
else
{
return new ValidationResult(true, null);
}
}
catch (Exception e)
{
return new ValidationResult(false, "Illegal characters or " + e.Message);
}
}

Try this:
EDIT: (here's a style I define which shows errors for all TextBox controls)
(put it in Window.Resources)
This style will then show the error message in a ToolTip
<Style x:Key="{x:Type TextBox}" 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>
<Grid>
<Label Content="User Name" Height="28" HorizontalAlignment="Left" Margin="27,37,0,0" Name="UserNameLB" VerticalAlignment="Top" Width="96" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="135,37,0,0"
Name="UserNameTB" VerticalAlignment="Top" Width="189"
Text={Binding Path=_UserName, UpdateSourceTrigger=LostFocus,
ValidatesOnDataErrors=true, NotifyOnValidationError=true} />
</Grid>
Source

Related

How to enable a button only when all three textboxes are filled using ICommand in WPF?

I'm new to MVVM. I have three textboxes and a button in my view. I want that button to be enabled when all those three textboxes are filled. My view is as below:
<StackPanel Margin="1,1,1,1" Grid.Row="0">
<!--<Label Margin="2,2,2,2" Content="ID:"/>
<dxe:TextEdit Margin="2,2,2,2" Text="{Binding ElementName=StudentGrid, Path=SelectedItem.Id}"/>-->
<Label Margin="2,2,2,2" Content="Name:"/>
<dxe:TextEdit Margin="2,2,2,2" x:Name="Name" Text="{Binding Path=Name}" />
<Label Margin="2,2,2,2" Content="Last Name:"/>
<dxe:TextEdit Margin="2,2,2,2" x:Name="LastName" Text="{Binding Path=LastName}" />
<Label Margin="2,2,2,2" Content="Age:"/>
<dxe:TextEdit Margin="2,2,2,2" x:Name="Age" Text="{Binding Path=Age}" />
</StackPanel>
<ListView Name="StudentGrid" Grid.Row="1" Margin="1,1,1,1" ItemsSource="{Binding studentList}">
<ListView.View>
<GridView>
<GridViewColumn Header="ID" Width="50" DisplayMemberBinding="{DXBinding Id}"/>
<GridViewColumn Header="Name" Width="80" DisplayMemberBinding="{DXBinding Name}"/>
<GridViewColumn Header="Last Name" Width="80" DisplayMemberBinding="{DXBinding LastName}"/>
<GridViewColumn Header="Age" Width="50" DisplayMemberBinding="{DXBinding Age}"/>
</GridView>
</ListView.View>
</ListView>
<StackPanel Grid.Row="2" Margin="1,2,1,1">
<dx:SimpleButton x:Name="applybtn" Content="Insert" Width="60" HorizontalAlignment="Left" Margin="5,0,0,0" Command="{Binding Path=_myCommand}"/>
</StackPanel>
InserCommand code is:
public class InsertCommand : ICommand
{
public StudentListViewModel _viewModel { get; set; }
public InsertCommand(StudentListViewModel viewModel)
{
_viewModel = viewModel;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_viewModel.InsertStudent();
}
}
StudentListViewModel code is:
public class StudentListViewModel : INotifyPropertyChanged
{
private string _name;
private string _lastName;
private int _age;
public string Name
{
get => _name;
set
{
_name = value;
OnPropertyChanged("Name");
}
}
public string LastName
{ get => _lastName;
set
{
_lastName = value;
OnPropertyChanged("LastName");
}
}
public int Age
{ get => _age;
set
{
_age = value;
OnPropertyChanged("Age");
}
}
public InsertCommand _myCommand { get; set; }
private ObservableCollection<Student> _studentList;
public StudentListViewModel()
{
_myCommand = new InsertCommand(this);
using (MyContext context = new MyContext())
{
_studentList = new ObservableCollection<Student>(context.Students.ToList());
};
}
public void InsertStudent()
{
Student st = new Student()
{
Name = _name,
LastName = _lastName,
Age = _age
};
using (var context = new MyContext())
{
context.Add(st);
context.SaveChanges();
_studentList.Add(st); //For Getting instatnt UI update
}
}
public ObservableCollection<Student> studentList
{
get
{
return _studentList;
}
set
{
_studentList = value;
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
It's not usual to create a new class for each command.
Instead, create a generic command class with action / func parameters to specify each command's logic.
public class RelayCommand : ICommand
{
private readonly Action _execute;
private readonly Func<bool> _canExecute;
public RelayCommand(Action execute) : this(execute, () => true)
{
}
public RelayCommand(Action execute, Func<bool> canExecute)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
public bool CanExecute(object parameter) => _canExecute.Invoke();
public void Execute(object parameter) => _execute.Invoke();
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
Then, create an instance of this class in your ViewModel that you can bind your button to.
public class StudentListViewModel : INotifyPropertyChanged
{
private string _name;
private string _lastName;
private int _age;
public string Name
{
get => _name;
set
{
_name = value;
OnPropertyChanged("Name");
InsertCommand.RaiseCanExecuteChanged();
}
}
public string LastName
{ get => _lastName;
set
{
_lastName = value;
OnPropertyChanged("LastName");
InsertCommand.RaiseCanExecuteChanged();
}
}
public int Age
{ get => _age;
set
{
_age = value;
OnPropertyChanged("Age");
InsertCommand.RaiseCanExecuteChanged();
}
}
public RelayCommand InsertCommand { get; }
public StudentListViewModel()
{
InsertCommand = new RelayCommand(() => InsertStudent(),
() => !string.IsNullOrEmpty(Name) && !string.IsNullOrEmpty(LastName) && Age > 0);
...
}
public void InsertStudent()
{
....
}
}
For a cleaner way to handle command refresh, so that each property doesn't need to care how it is used, check out my blog post.
Usually I create Binding Validations so that I can reuse on other textboxes.
Then on the button I create triggers to enable or disable the button based on the validations defined.
<Button Comman="{Binding InsertCommand}" Content="Insert">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="IsEnabled" Value="False" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=Name, Path=(Validation.HasError)}" Value="False"/>
<Condition Binding="{Binding ElementName=LastName, Path=(Validation.HasError)}" Value="False"/>
<Condition Binding="{Binding ElementName=Age, Path=(Validation.HasError)}" Value="False"/>
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="True" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
For very simple not null validations, Instead of creating validations, I just test against the text property of the textbox
<Button Comman="{Binding InsertCommand}" Content="Insert">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="IsEnabled" Value="True" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=Name, Path=Text}" Value=""/>
<Condition Binding="{Binding ElementName=LastName, Path=Text}" Value=""/>
<Condition Binding="{Binding ElementName=Age, Path=Text}" Value=""/>
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="False" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
button to be enabled when all those three textboxes are filled.
Create a new bool property called IsButtonEnabled.
Whenever text changes (two-way binding and update source triggeer in the binding right? -> How-to bind, my answer) to push the current string(s) to the VM's properties in real time.
Those properties in question, their values will be checked in the IsButtonEnabled getter. Then in each of the setters for the strings, add a OnPropertyChanged for IsButtonEnabled. Also bind IsButtonEnabled to the proper button property to achieve your desired affect.
Example
// Doesn't need its own OnPropertyChanged, because that is set
// for every keystroke.
public bool IsButtonEnabled { get { return !string.IsNullOrEmpty(Name) &&
!string.IsNullOrEmpty(LastName) &&
!string.IsNullOrEmpty(Age); }}
public string Name
{
get => _name;
set
{
_name = value;
OnPropertyChanged("Name");
OnPropertyChanged("IsButtonEnabled");
InsertCommand.RaiseCanExecuteChanged();
}
}
public string LastName
...
set
{
_lastName = value;
OnPropertyChanged("LastName");
OnPropertyChanged("IsButtonEnabled");
...
public string Age
...
set
{
_age= value;
OnPropertyChanged("Age");
OnPropertyChanged("IsButtonEnabled");
....
Xaml
<dx:SimpleButton x:Name="applybtn" IsEnabled="{Binding IsButtonEnabled}" ...
Similar answer given
DataTemplate.DataTrigger to check for greater than or less than?
WPF Multi-Binding / Aggregate Binding to
Collection

Binding Dependency Property of UserControl to MainWindow ViewModel in WPF

Allow me to simplify the problem to its basic blocks.
I have a UserControl1 in my project. It has a TextBox like this:
<TextBox Text="{Binding TextProperty}"/>
In the code-behind, I have a dependency property like this:
public string TextProperty
{
get { return (string)GetValue(TextPropertyProperty); }
set { SetValue(TextPropertyProperty, value); }
}
public static readonly DependencyProperty TextPropertyProperty = DependencyProperty.Register("TextProperty", typeof(string), typeof(UserControl1), new PropertyMetadata(null));
And the constructor of the UserControl is simply
public UserControl1()
{
InitializeComponent();
DataContext = this;
}
In the MainWindow, I have this:
<userctrl:UserControl1 TextProperty="{Binding ABC, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Grid.Row="1" Text="{Binding PQR, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
Now, in the viewModel of the MainWindow, I have:
private string _abc;
public string ABC
{
get { return _abc; }
set
{ _abc = "20";
OnPropertyChanged();
}
}
private string _pqr;
public string PQR
{
get { return _pqr; }
set
{
if(value != _pqr)
{
_pqr = value;
ABC = PQR;
OnPropertyChanged();
}
}
}
Now, even the UserControl is supposed to replicate whatever I write in the textbox, right?
But it doesn't. I have set breakpoints at setter of ABC. It gets updated, but that update does not reach the UserControl. My gut says that the binding is failing silently, and it's because I have set the DataContext to this in the constructor of UserControl.
How am I supposed to solve it?
The Actual Scenario:
Here is the XAML for the UserControl:
<UserControl x:Class="MyDiskTools.UserControls.NodeGrid.NodeGrid"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MyDiskTools.UserControls.NodeGrid"
mc:Ignorable="d">
<Grid>
<Grid.Resources>
<Style TargetType="Button">
<Setter Property="Padding" Value="5"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Command" Value="{Binding InputCommand}"/>
<Setter Property="CommandParameter" Value="{Binding Path=Content, RelativeSource={RelativeSource Self}}"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="BorderThickness" Value="5"/>
<Setter Property="FontSize" Value="20"/>
<Setter Property="FontFamily" Value="Times New Roman"/>
</Trigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<UniformGrid Grid.Row="0" Rows="1">
<Button Content="A" />
<Button Content="B" />
<Button Content="C" />
<Button Content="D" />
<Button Content="E" />
<Button Content="F" />
</UniformGrid>
<UniformGrid Grid.Row="1" Rows="1">
<Button Content="G" />
<Button Content="H" />
<Button Content="I" />
<Button Content="J" />
<Button Content="K" />
<Button Content="L" />
<Button Content="M" />
</UniformGrid>
<UniformGrid Grid.Row="2" Rows="1">
<Button Content="N" />
<Button Content="O" />
<Button Content="P" />
<Button Content="Q" />
<Button Content="R" />
<Button Content="S" />
<Button Content="T" />
</UniformGrid>
<UniformGrid Grid.Row="3" Rows="1">
<Button Content="U" />
<Button Content="V" />
<Button Content="W" />
<Button Content="X" />
<Button Content="Y" />
<Button Content="Z" />
</UniformGrid>
<TextBox Name="InputMessage" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" IsEnabled="False" Background="Beige" Grid.Row="4" Text="{Binding PasswordDisplay, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</UserControl>
Here is the code-behind:
public partial class NodeGrid : UserControl
{
public NodeGrid()
{
InitializeComponent();
InputCommand = new InputCharacterCommand(this);
DataContext = this;
}
public string PasswordDisplay
{
get { return (string)GetValue(PasswordDisplayProperty); }
set { SetValue(PasswordDisplayProperty, value); }
}
public static readonly DependencyProperty PasswordDisplayProperty =
DependencyProperty.Register("PasswordDisplay", typeof(string), typeof(NodeGrid), new PropertyMetadata(""));
private ICommand _inputCommand;
public ICommand InputCommand
{
get
{
return _inputCommand;
}
set
{
_inputCommand = value;
}
}
public void AddCharacter(string input)
{
if (input != null)
{
PasswordDisplay = string.Concat(PasswordDisplay, input);
}
}
public bool InputAllowed()
{
if (PasswordDisplay == null)
{
return true;
}
if (PasswordDisplay.Length < 50)
{
return true;
}
return false;
}
private void OnPropertyChange([CallerMemberName] string property = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
public event PropertyChangedEventHandler PropertyChanged;
}
class InputCharacterCommand : ICommand
{
private NodeGrid _vmodel;
public InputCharacterCommand(NodeGrid vm)
{
_vmodel = vm;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return (_vmodel.InputAllowed());
}
public void Execute(object parameter)
{
_vmodel.AddCharacter(parameter as string);
}
}
And here is how I use it in my MainWindow:
<StackPanel VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<WrapPanel HorizontalAlignment="Center">
<Label Content="Enter PIN"/>
<TextBox CommandManager.PreviewCanExecute="HandleCanExecute" Foreground="Transparent" MinWidth="100" Padding="10,0" Text="{Binding PIN, UpdateSourceTrigger=PropertyChanged}"/>
</WrapPanel>
<customlock:NodeGrid MinHeight="250" MinWidth="500" PasswordDisplay="{Binding NodeGridDisplay}"/>
<Button VerticalAlignment="Center" HorizontalAlignment="Center" Content="Unlock!"/>
</StackPanel>
The Code-behind:
private void HandleCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
if (e.Command == ApplicationCommands.Cut ||
e.Command == ApplicationCommands.Copy ||
e.Command == ApplicationCommands.Paste)
{
e.CanExecute = false;
e.Handled = true;
}
}
And now, the ViewModel:
private string _PIN;
public string PIN
{
get
{
return _PIN;
}
set
{
if(value != _PIN)
{
_PIN = value;
OnPropertyChanged();
NodeGridDisplay = HashIt(_PIN);
}
}
}
private string _nodeGridDisplay;
public string NodeGridDisplay
{
get
{
return _nodeGridDisplay;
}
set
{
if (value != _nodeGridDisplay)
{
_nodeGridDisplay = value;
OnPropertyChanged();
}
}
}
private string HashIt(string input)
{
using (System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create())
{
return System.Text.Encoding.Default.GetString(md5.ComputeHash(System.Text.Encoding.ASCII.GetBytes(input))).GetHashCode().ToString();
}
}
What is the intended function?
Let me illustrate it.
The Lock
One can enter the PIN, the hash of which will be displayed in the NodeGrid. Then the user will click the letters which will be concatenated with the hash. Then the user can click unlock.
You should use ElementName in your UserControl or use RaltiveResource to find it, and you should not use DataContext = this. (see this answer and this link)
Give your userControl a name and write:
<UserControl x:Class= ......
Name="userControl">
<TextBox Text="{Binding Text, ElementName = userControl}"/>
</UserControl>
Please note that you should use Text instead of TextProperty and make sure to change _abc = "20" to _abc = value.

WPF MVVM IDataErrorInfo

i have an app in MVVM pattern ; it contains one textbox and one button;
i add a validation that if textbox is empty , textbox color change to red;
and at this point i want that the button enable be false till the user
enter character in textbox and then button enable change to true;
my XAML Code is:
<Window.Resources>
<ControlTemplate x:Key="ErrorTemplate">
<DockPanel LastChildFill="True">
<Border BorderBrush="Pink" BorderThickness="1">
<AdornedElementPlaceholder />
</Border>
</DockPanel>
</ControlTemplate>
<Style 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}"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBox Margin="10" Text="{Binding ValidateInputText,UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, ValidatesOnDataErrors=True, NotifyOnValidationError=True, ValidatesOnExceptions=True}"
Validation.ErrorTemplate="{StaticResource ErrorTemplate}">
</TextBox>
<Button Margin="10" Grid.Row="2" Command="{Binding ValidateInputCommand}"/>
and my command class is:
public class RelayCommand : ICommand
{
private Action WhatToExcute;
private Func<bool> WhenToExecute;
public RelayCommand(Action what,Func<bool>when )
{
WhatToExcute = what;
WhenToExecute = when;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return WhenToExecute();
}
public void Execute(object parameter)
{
WhatToExcute();
}
}
and my viewmodel is:
public class ViewModel : IDataErrorInfo , INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
Product product = new Product();
public ViewModel()
{
ValidateInputCommand = new RelayCommand(action, valid);
}
public void action()
{
}
public bool valid()
{
if (product.Name == null)
return false;
return true;
}
public string ValidateInputText
{
get { return product.Name; }
set {
product.Name = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(ValidateInputText));
}
}
}
public RelayCommand ValidateInputCommand { get; set; }
public string this[string columnName]
{
get
{
if ("ValidateInputText" == columnName)
{
if (String.IsNullOrEmpty(ValidateInputText))
{
return "Please enter a Name";
}
}
return "";
}
}
public string Error
{
get
{
throw new NotImplementedException();
}
}
}
now when i run my app , the color of text box is red and when i enter a
character it change to normal that is ok but the
button enable is false and does not change
so i could not click button.
what should i do?
The CanExecuteChanged event on your RelayCommand never gets fired. Therefore the button to which the command has been bound will never re-evaluate its IsEnabled status. Your view model could notify the command on changes in its valid status and the command should in turn raise its CanExecuteChanged event.

Manually Triggering Validation in WPF while using Caliburn Micro

I have a view model that implements IDataErrorInfo (and derived from Caliburn Micro's Screen class) like this
public class MyViewModel : Screen, IDataErrorInfo
{
...
public BindableCollection<MyEntity> Entities { get; set; }
public MyEntity SelectedEntity
{
get { return _entity; }
set
{
_entity = value;
OnSelectedEntityChanged();
}
}
private void OnSelectedEntityChanged()
{
// implementation
}
public virtual string Error
{
get { // implementation }
}
public virtual string this[string columnName]
{
get { // implementation }
}
public void Populating(PopulatingEventArgs e)
{
// implementation
}
}
It is bound to the following XAML using Caliburn Micro (only relevant portion shown)
<tk:AutoCompleteBox
x:Name="Entities"
cal:Message.Attach="[Event Populating] = [Populating($eventArgs)]"
SelectedItem="{Binding Path=SelectedEntity, Mode=TwoWay}"
HorizontalAlignment="Stretch"
FilterMode="None"
IsTextCompletionEnabled="True"
Text="{Binding SearchText}"
ValueMemberPath="Name">
<tk:AutoCompleteBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"></TextBlock>
</DataTemplate>
</tk:AutoCompleteBox.ItemTemplate>
</tk:AutoCompleteBox>
The problem I am running into is that when I update the SelectedEntity property programmatically, it does not cause validation to be triggered. I've tried out many different possible solution such as trying to get the binding expression and calling ValidateWithoutUpdate() on it, adding triggers in the XAML that should cause validation to be triggered etc, but none has worked so far.
How can I trigger validation that would eventually call IDataErrorInfo.Error?
Thanks!
/*
Place the IDataErrorInfo implementation in your Model Class ..
not your view model.
The data error is in the Model not in the ViewModel.
The ViewModel references the model,
IDataErrorInfo communicates with the binding via the
xaml ValidatesOnDataErrors=True in the binding
*/
/* model */
public class OldBoy : Screen, IOldBoy, IDataErrorInfo
{
private Guid oldBoyId;
public Guid OldBoyId
{
get
{
return this.oldBoyId;
}
set
{
this.oldBoyId = value;
NotifyOfPropertyChange(() => OldBoyId);
}
}
private string firstName;
public string Firstname
{
get
{
return this.firstName;
}
set
{
this.firstName = value;
NotifyOfPropertyChange(() => Firstname);
}
}
private string surName;
public string Surname
{
get
{
return this.surName;
}
set
{
this.surName = value;
NotifyOfPropertyChange(() => Surname);
}
}
/* include a Property e.g. CanSave in your Model that will be bound
to the IsEnabled DependencyProperty ofIsEnabled on your Submit Button
Viz.
*/
public string this[string name]
{
get
{
string result = null;
if (name == "Firstname")
{
// force the issue
this.CanSave = true;
if (string.IsNullOrEmpty(this.Firstname))
{
result = "Model says that Firstname must be entered";
}
}
if (name == "Surname")
{
// force the issue
this.CanSave = true;
if (string.IsNullOrEmpty(this.Surname))
{
result = "Model says that Surname must be entered";
}
}
return result;
}
}
public string Error
{
get
{
return null;
}
}
private bool canSave;
public bool CanSave
{
get
{
return this.canSave;
}
set
{
if (string.IsNullOrEmpty(this.Firstname) || string.IsNullOrEmpty(this.surName))
{
this.canSave = false;
}
else
{
this.canSave = true;
}
NotifyOfPropertyChange(() => CanSave);
}
}
}
/* in the ViewModel the underlying class for the edit window */
public class DetailsViewModel : Screen, IDetailsViewModel
{
private IOldBoy editingOldBoyItem;
public IOldBoy EditingOldBoyItem
{
get
{
return this.editingOldBoyItem;
}
set
{
this.editingOldBoyItem = value;
NotifyOfPropertyChange(() => EditingOldBoyItem);
}
}
// elided
}
xaml textboxes involved in validation
<Label Grid.Row="00"
Grid.Column="00"
Content="First Name" />
<TextBox Text="{Binding Path=EditingOldBoyItem.Firstname, ValidatesOnDataErrors=True, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource TextBoxCellValidationStyle}"
Grid.Row="00"
Grid.Column="01">
<i:Interaction.Behaviors>
<local:BindableFocusBehavior HasFocus="{Binding SetFocusToFirstName, Mode=TwoWay, UpdateSourceTrigger=Default}" />
</i:Interaction.Behaviors>
</TextBox>
<Label Grid.Row="01"
Grid.Column="00"
Content="Surname" />
<TextBox Text="{Binding Path=EditingOldBoyItem.Surname, ValidatesOnDataErrors=True, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Validation.ErrorTemplate="{StaticResource ValidationTemplate}"
Grid.Row="01"
Grid.Column="01">
<!-- save button that is disabled when CanSave is false -->
<Button Content="Save Edit"
IsEnabled="{Binding Path=EditingOldBoyItem.CanSave}"
x:Name="Save"
Margin="4"
Template="{StaticResource RedGlassButton}" />
app.xaml
<ControlTemplate x:Key="ValidationTemplate">
<DockPanel LastChildFill="True">
<TextBlock DockPanel.Dock="Bottom"
FontSize="12"
FontWeight="Bold"
Foreground="Red">
<TextBlock.ToolTip>
<ToolTip Placement="Center"
Style="{StaticResource ErrorToolTip}">
<TextBlock Foreground="White">
<TextBlock.Text>
<Binding Path="/ErrorContent" />
</TextBlock.Text>
</TextBlock>
</ToolTip>
</TextBlock.ToolTip>
</TextBlock>
<Border BorderBrush="Red"
BorderThickness="1">
<AdornedElementPlaceholder Name="adornerPlaceholder" />
</Border>
</DockPanel>
</ControlTemplate>
<Style x:Key="TextBoxCellValidationStyle"
BasedOn="{StaticResource {x:Type TextBox}}"
TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="Background" Value="LightCyan" />
<Setter Property="Foreground" Value="Red" />
<Setter Property="Validation.ErrorTemplate" Value="{StaticResource ValidationTemplate}" />
</Trigger>
</Style.Triggers>
</Style>

Disable button while processing request

I am new to wpf and xaml (Windows development in general) and my background is asp.net and prior to that classic ASP.
I'm working on an application that needs to have the button disabled/grayed out while the processing occurs and read a post on here to do the following but it doesn't appear to be working. What am I missing?
<Window x:Class="SCGen.Application.LoadForecast.EngineExecution"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:igEditors="http://infragistics.com/Editors"
SizeToContent="WidthAndHeight"
Title="Engine Execution"
ResizeMode="NoResize"
WindowStartupLocation="CenterOwner"
Background="{StaticResource {x:Static SystemColors.ControlBrushKey}}">
<Window.Resources>
<Style TargetType="{x:Type Button}" x:Key="myStyle" BasedOn="{StaticResource ButtonStyle}">
<Setter Property="Command" Value="{Binding ExecuteEngine}" />
<Setter Property="Content" Value="Execute Engine" />
<Style.Triggers>
<Trigger Property="Command" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Border Padding="8">
<StackPanel>
<StackPanel MaxWidth="200" HorizontalAlignment="Left">
<TextBlock Text="Select Forecast Engine" TextAlignment="Center" FontSize="13" />
<igEditors:XamComboEditor ItemsSource="{Binding ForecastEngines}" SelectedItem="{Binding SelectedEngine}" Margin="0,5" />
<Button Style="{StaticResource ResourceKey=myStyle}" />
</StackPanel>
<TextBlock Text="{Binding EngineStatus}" FontSize="15" FontStyle="Italic" Margin="0,14" Width="400" TextWrapping="Wrap" />
</StackPanel>
</Border>
</Window>
I've changed the xaml to the following:
<Button Content="Execute Weather Import" Command="{Binding ExecuteWeather}" Style="{StaticResource ButtonStyle}" IsEnabled="{Binding IsEnabled}"/>
In the ViewModel I have the following:
private bool _isEnabled = true;
public bool IsEnabled
{
get { return _isEnabled; }
set { _isEnabled = value; }
}
and I set the _isEnabled here:
private string LaunchWeatherImport(string strVendor)
{
_isEnabled = false;
string uri = ConfigurationManager.AppSettings["ManualExecutionFacilitatorService"];
ClientConnectionInfo connection = new ClientConnectionInfo(uri) { UseSecurity = true };
connection.SetTimeouts();
Logger.LogInfo("Calling Facilitator service to manually import " + strVendor + " weather data.");
((NetTcpBinding)connection.Binding).Security.Mode = System.ServiceModel.SecurityMode.None;
using (var client = new FacilitatorManualExecutionClient(connection))
{
client.InnerChannel.OperationTimeout = TimeSpan.FromMinutes(int.Parse(ConfigurationManager.AppSettings["OperationTimeOutMinutes"]));
try
{
_isEnabled = true;
return "success";
// uncomment this line before commit
//return client.ExecuteWeather(strVendor);
}
#region catch
catch (Exception ex)
{
Logger.LogError(ex.Message, ex);
return ex.Message;
}
#endregion
}
}
I still can't get it to work properly.
For starters, you're setting the trigger on the Command property but you don't have a binding set on that property for your button:
<Button Style="{StaticResource ResourceKey=myStyle}" />
Should be:
<Button Style="{StaticResource ResourceKey=myStyle}" Command="{Binding MyCommand}" />
[Where MyCommand is the name of your actual command that you're binding to]
I am not so sure that it will work anyway though because your trigger is set to fire when the Command property is null, but if you bind to the command property, following the MVVM pattern then your command property shouldn't be null so the trigger won't fire then either.
UPDATE:
You need to implemented the INotifyPropertyChanged interface on your class that has the property.
public class MyClass : System.ComponentModel.INotifyPropertyChanged
Then add the implementation:
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
Then change your property to be:
private bool _isEnabled = true;
public bool IsEnabled
{
get { return _isEnabled; }
set
{
_isEnabled = value;
NotifyPropertyChanged("IsEnabled");
}
}

Resources