Where to set Validation error by code? - c#

I've been told that this code by Christian Moser (http://wpftutorial.net/ValidationErrorByCode.html) is working for validation when only the source is changing, but I don't know where to put it. I have tried to put in a
'PreviewLostKeyboardFocus' class, but it's not working. I already have a validation class that is only working when there is direct user input in the textbox, but I also want validation to occur from a calculation from other textboxes.
I also have properties in ViewModel : INotifyPropertyChanged, but I'm new to MVVM. So I'm not familiar with IDataErrorInfo and would rather use INotifyPropertyChanged if possible.
ValidationError validationError =
new ValidationError(regexValidationRule,
textBox.GetBindingExpression(TextBox.TextProperty));
validationError.ErrorContent = "This is not a valid e-mail address";
Validation.MarkInvalid(
textBox.GetBindingExpression(TextBox.TextProperty),
validationError);
My property
private int _FIMSamlet_score;
public int FIMSamlet_score
{
get { return this._FIMSamlet_score; }
set
{
if (Int32.Parse(value.ToString()) < 18 || Int32.Parse(value.ToString()) > 126)
{ throw new ArgumentException("The value must be between 18 and 126"); }
this._FIMSamlet_score = value;
this.OnPropertyChanged("FIMSamlet_score");
}
}

IDataErrorInfo is part and parcel of a validation scheme using INotifyPropertyChanged. When you set ValidateOnDataErrors=true on a Binding, during the get of an INotifyPropertyChanged enabled property, the value is validated and the value is set.
The method you have linked to would require you to put extensive business logic in the code-behind which is against the MVVM principle of segregation of responsibilities (dumb View, smart ViewModel).
I strongly recommend you learn about IDataErrorInfo and more importantly about the ValidationAttributes defined by System.ComponentModel.DataAnnotations as they are actually the easiest way to perform validation in a pure MVVM environment.
Have a look at this code sample from Microsoft which shows you how to integrate those annotations with IDataErrorInfo into a base ViewModel class which you could inherit from and use as a universal base validating ViewModel class: Validation in MVVM using Data Annotations

ViewModel -> VMStudent.cs
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows;
using WpfDesktopCrud.Model;
namespace WpfDesktopCrud.ViewModel
{
class VMStudent : ViewModelBase, IDataErrorInfo
{
public VMStudent()
{
try
{
Students = new ObservableCollection<Student>();
Students.Add(new Student { id = 1, name = "Dhru", mobno = "0000000000", address = "Shahibaug", email = "dhrusoni84#gmail.com" });
Students.Add(new Student { id = 2, name = "Soni", mobno = "9033259059", address = "Vastrapur", email = "dhru_soni#yahoo.com" });
SaveCommand = new RelayCommand(Save);
NewCommand = new RelayCommand(New);
DeleteCommand = new RelayCommand(Delete);
PropertyChanged += VMStudent_PropertyChanged;
}
catch (Exception ex)
{
throw;
}
}
void VMStudent_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "Selected":
if (Selected != null)
{
ID = Selected.id;
Name = Selected.name;
MobileNo = Selected.mobno;
Address = Selected.address;
Email = Selected.email;
}
break;
}
}
#region Properties
private ObservableCollection<Student> _Students;
public ObservableCollection<Student> Students
{
get { return _Students; }
set
{
_Students = value;
RaisePropertyChanged("Students");
}
}
private Student _Selected;
public Student Selected
{
get { return _Selected; }
set
{
_Selected = value;
RaisePropertyChanged("Selected");
}
}
private int _ID;
public int ID
{
get { return _ID; }
set
{
_ID = value;
RaisePropertyChanged("ID");
}
}
private string _Name;
public string Name
{
get { return _Name; }
set
{
_Name = value;
RaisePropertyChanged("Name");
}
}
private string _MobileNo;
public string MobileNo
{
get { return _MobileNo; }
set
{
_MobileNo = value;
RaisePropertyChanged("MobileNo");
}
}
private string _Address;
public string Address
{
get { return _Address; }
set
{
_Address = value;
RaisePropertyChanged("Address");
}
}
private string _Email;
public string Email
{
get { return _Email; }
set
{
_Email = value;
RaisePropertyChanged("Email");
}
}
#endregion
#region Commands
public RelayCommand SaveCommand { get; set; }
public RelayCommand NewCommand { get; set; }
public RelayCommand DeleteCommand { get; set; }
#endregion
#region Methods
public void Save()
{
if (Selected != null)
{
if (Selected.id != 0)
{
Selected.name = this.Name;
Selected.mobno = this.MobileNo;
Selected.address = this.Address;
Selected.email = this.Email;
MessageBox.Show("Record Updated.", "WPF MVVM", MessageBoxButton.OK, MessageBoxImage.Information, MessageBoxResult.OK);
this.ID = 0;
this.Name = string.Empty;
this.Address = string.Empty;
this.MobileNo = string.Empty;
this.Email = string.Empty;
}
}
else
{
try
{
Students.Add(new Student { id = Students.Count + 1, name = this.Name, mobno = this.MobileNo, address = this.Address, email = this.Email });
MessageBox.Show("Record Saved.", "WPF MVVM", MessageBoxButton.OK, MessageBoxImage.Information, MessageBoxResult.OK);
this.ID = 0;
this.Name = string.Empty;
this.MobileNo = string.Empty;
this.Address = string.Empty;
this.Email = string.Empty;
}
catch (Exception ex)
{
throw;
}
}
}
public void New()
{
if (Selected != null)
{
this.ID = 0;
this.Name = string.Empty;
this.MobileNo = string.Empty;
this.Address = string.Empty;
this.Email = string.Empty;
Selected.id = 0;
}
Selected = null;
}
public void Delete()
{
try
{
Students.Remove(Selected);
this.Name = string.Empty;
this.MobileNo = string.Empty;
this.Address = string.Empty;
this.Email = string.Empty;
MessageBox.Show("Record Deleted.", "WPF MVVM", MessageBoxButton.OK, MessageBoxImage.Information, MessageBoxResult.OK);
}
catch (Exception ex)
{
throw;
}
}
#endregion
public string Error
{
get { throw new NotImplementedException(); }
}
public string this[string columnName]
{
get
{
string result = null;
if (columnName == "Name")
{
if (string.IsNullOrEmpty(Name))
{
result = "Name Cannot Be Empty";
return result;
}
string st = #"!|#|#|\$|%|\?|\>|\<|\*";
if (Regex.IsMatch(Name, st))
{
result = "special chatachter are not allowed";
return result;
}
}
if (columnName == "MobileNo")
{
if (string.IsNullOrEmpty(MobileNo))
{
result = "Mobile Can not be empty";
return result;
}
if (MobileNo.Length > 10 || MobileNo.Length < 10)
{
result = "number should be 10 charachters";
return result;
}
string s1 = #"^[0-9]*$";
if (!Regex.IsMatch(MobileNo, s1))
{
result = "Mobile number contain numeric value ";
return result;
}
else
{
return null;
}
}
if (columnName == "Address")
{
if (string.IsNullOrEmpty(Address))
{
result = "fill your address";
return result;
}
}
if (columnName == "Email")
{
string s1 = #"^[a-zA-Z][\w\.-]{2,28}[a-zA-Z0-9]#[a-zA-Z0-9][\w\.-]*[a-zA-Z0-9]\.[a-zA-Z][a-zA-Z\.]*[a-zA-Z]$";
if (string.IsNullOrEmpty(Email))
{
result = "Email can not be empty";
return result;
}
if (Regex.IsMatch(Email, s1)) //|| Regex.IsMatch(Email, s2))
{
return null;
}
else
{
result = "Not valid email(format: dhru#gmail.com)";
return result;
}
}
return null;
}
}
}
}
View - > Student.xaml
<Window x:Class="WpfDesktopCrud.View.Students"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:VM="clr-namespace:WpfDesktopCrud.ViewModel"
Title="Students" Height="400" Width="500">
<Window.DataContext>
<VM:VMStudent/>
</Window.DataContext>
<Grid>
<Grid.Resources>
<ControlTemplate x:Key="dTemplate">
<StackPanel Orientation="Horizontal">
<DockPanel LastChildFill="True">
<TextBlock DockPanel.Dock="Right" Text="{Binding AdornedElement.(Validation.Errors).[0].ErrorContent, ElementName=ErrorAdorner}" Background="#FA5858" Foreground="White" FontWeight="Light" VerticalAlignment="Center"/>
<AdornedElementPlaceholder VerticalAlignment="Top" x:Name="ErrorAdorner">
<Border BorderBrush="Red" BorderThickness="1" />
</AdornedElementPlaceholder>
</DockPanel>
</StackPanel>
</ControlTemplate>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListBox ItemsSource="{Binding Students}" SelectedItem="{Binding Selected}" DisplayMemberPath="name" SelectedValuePath="id" Margin="0,0,0,26">
</ListBox>
<Grid Grid.Row="1" HorizontalAlignment="Center">
<Grid.Resources>
<Style TargetType="TextBlock">
<Setter Property="Margin" Value="5 2 5 0"/>
<Setter Property="HorizontalAlignment" Value="Right"/>
</Style>
<Style TargetType="TextBox">
<Setter Property="Margin" Value="5 5 5 5"/>
</Style>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="200"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Name"/>
<TextBlock Text="Mobile No" Grid.Row="1"/>
<TextBlock Text="Address" Grid.Row="2"/>
<TextBlock Text="Email" Grid.Row="3"/>
<TextBox x:Name="txtName" Text="{Binding Name,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay,ValidatesOnDataErrors=True}" Validation.ErrorTemplate="{StaticResource dTemplate}"
Grid.Column="1"/>
<TextBox x:Name="txtNo" Text="{Binding MobileNo,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay,ValidatesOnDataErrors=True}" Validation.ErrorTemplate="{StaticResource dTemplate}"
Grid.Row="1" Grid.Column="1"/>
<TextBox x:Name="txtAdd" Text ="{Binding Address,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay,ValidatesOnDataErrors=True}" Validation.ErrorTemplate="{StaticResource dTemplate}"
Grid.Row="2" Grid.Column="1" TextWrapping="WrapWithOverflow" AcceptsReturn="True" ScrollViewer.CanContentScroll="True" VerticalScrollBarVisibility="Auto" Height="90"/>
<TextBox x:Name="txtEmail" Text ="{Binding Email,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay,ValidatesOnDataErrors=True}" Validation.ErrorTemplate="{StaticResource dTemplate}"
Grid.Row="3" Grid.Column="1"/>
</Grid>
<WrapPanel Grid.Row="3" HorizontalAlignment="Center" >
<Button Command="{Binding NewCommand}" Content="New" Width="75" Height="31">
<Button.Style>
<Style TargetType="{x:Type Button}">
<EventSetter Event="Click" Handler="MoveFocusOnClick"/>
</Style>
</Button.Style>
</Button>
<Button Command="{Binding SaveCommand}" Content="Save" Width="75" Height="31">
<Button.Style>
<Style TargetType="Button">
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding ="{Binding Path=(Validation.HasError),ElementName=txtName}" Value="false"/>
<Condition Binding ="{Binding Path=(Validation.HasError),ElementName=txtNo}" Value="false"/>
<Condition Binding ="{Binding Path=(Validation.HasError),ElementName=txtAdd}" Value="false"/>
<Condition Binding ="{Binding Path=(Validation.HasError),ElementName=txtEmail}" Value="false"/>
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="True"/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
<Button Command="{Binding DeleteCommand}" Content="Delete" Width="75" Height="31"/>
</WrapPanel>
</Grid>
</Window>
Model -> Student.cs
using GalaSoft.MvvmLight;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WpfDesktopCrud.Model
{
class Student : ViewModelBase
{
private int _id;
public int id
{
get { return _id; }
set
{
_id = value;
RaisePropertyChanged("id");
}
}
private string _name;
public string name
{
get { return _name; }
set
{
_name = value;
RaisePropertyChanged("name");
}
}
private string _mobno;
public string mobno
{
get { return _mobno; }
set
{
_mobno = value;
RaisePropertyChanged("mobno");
}
}
private string _address;
public string address
{
get { return _address; }
set
{
_address = value;
RaisePropertyChanged("address");
}
}
private string _email;
public string email
{
get { return _email; }
set
{
_email = value;
RaisePropertyChanged("email");
}
}
private DateTime _timer;
public DateTime Timer
{
get { return _timer; }
set
{
_timer = value;
RaisePropertyChanged("timer");
}
}
}
}

Related

How to bind to TextBox.Text in local ValidationRule class

This is my XAML file:
<Window x:Class="WpfListView.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfListView"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ListView Margin="10" Name="lvUsers">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="150" />
<ColumnDefinition Width="20" />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Margin="0,0,5,0">
<Binding Path="Mail" Mode="TwoWay">
<Binding.ValidationRules>
<local:NameValidation>
<local:NameValidation.Params>
<local:NameValidationParameters
OriginalTree="{Binding Source={x:Reference lvUsers}}"
OriginalName="{Binding RelativeSource={RelativeSource Self}}"/> <!-- I want the OriginalName to be TextBox.Text-->
</local:NameValidation.Params>
</local:NameValidation>
</Binding.ValidationRules>
</Binding>
</TextBox>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Window>
This is my MainWindow class:
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<User> items = new List<User>();
items.Add(new User() { Name = "John Doe", Age = 42, Mail = "john#doe-family.com" });
items.Add(new User() { Name = "Jane Doe", Age = 39, Mail = "jane#doe-family.com" });
items.Add(new User() { Name = "Sammy Doe", Age = 7, Mail = "sammy.doe#gmail.com" });
lvUsers.ItemsSource = items;
}
}
And this is my ValidationRule class:
public class NameValidationParameters : DependencyObject
{
public ListView OriginalTree
{
get { return (ListView)this.GetValue(OriginalTreeProperty); }
set { this.SetValue(OriginalTreeProperty, value); }
}
public string OriginalName
{
get { return (string)this.GetValue(OriginalNameProperty); }
set { this.SetValue(OriginalNameProperty, value); }
}
public static readonly DependencyProperty OriginalTreeProperty
= DependencyProperty.Register(nameof(OriginalTree), typeof(ListView),
typeof(NameValidationParameters));
public static readonly DependencyProperty OriginalNameProperty
= DependencyProperty.Register(nameof(OriginalName), typeof(string),
typeof(NameValidationParameters));
}
public class NameValidation : ValidationRule
{
public string ErrorMessage
{ get; set; }
public NameValidationParameters Params { get; set; }
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
ValidationResult lResult = null;
return lResult;
}
}
As you can see, when the NameValidation.Validate is called, I want NameValidation.Params to be populated with correct variables:
NameValidation.Params.OriginalTree should be the original ListView. This I can get.
NameValidation.Params.OriginalName should be the current TextBox's Text. In this case, it is bound to Mail, so I would expect to see that it is the email address like john#doe-family.com. However I can't get this out; all I can see is that NameValidation.Params.OriginalName is "WpfListView.NameValidationParameters"
Also, I want to be able to access the current index in the list inside the NameValidation.Validate method. How can I get it?
As mentioned elsewhere,
Validation rules do not hold or inherit a DataContext this will prevent binding from working as expected if you try to declare everything inline. Instead, declare your bindable rule options as a resource and set the property on your custom rule using a StaticBinding.
I've modified your TextBox to add a resource parameter:
<TextBox Grid.Column="0" Margin="0,0,5,0" x:Name="box1">
<TextBox.Resources>
<local:NameValidationParameters OriginalName="{Binding Mail}" OriginalTree="{Binding Source={x:Reference lvUsers}}" x:Key="Parameters"/>
</TextBox.Resources>
<TextBox.Text>
<Binding Path="Mail" Mode="TwoWay">
<Binding.ValidationRules>
<local:NameValidation Params="{StaticResource Parameters}"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
And NameValidationParameters to implement Freezable
public class NameValidationParameters : Freezable
{
public ListView OriginalTree
{
get { return (ListView)this.GetValue(OriginalTreeProperty); }
set { this.SetValue(OriginalTreeProperty, value); }
}
public string OriginalName
{
get { return (string)this.GetValue(OriginalNameProperty); }
set { this.SetValue(OriginalNameProperty, value); }
}
public static readonly DependencyProperty OriginalTreeProperty
= DependencyProperty.Register(nameof(OriginalTree), typeof(ListView),
typeof(NameValidationParameters));
public static readonly DependencyProperty OriginalNameProperty
= DependencyProperty.Register(nameof(OriginalName), typeof(object),
typeof(NameValidationParameters));
protected override Freezable CreateInstanceCore()
{
return new NameValidationParameters();
}
}
However, Here is an example of how i implemented INotifyDataErrorInfo in my project. This is a very clean way to trigger validation errors and xaml allows you to configure the display of errors
Model class:
public class AttributionInput
{
[Required]
public DateTime? StartDate { get; set; }
[Required]
public DateTime? EndDate { get; set; }
}
ModelWrapper Generic:
public class ModelWrapper<T> : NotifyDataErrorInfoBase
{
public T Model { get; protected set; }
public ModelWrapper(T model)
{
Model = model;
}
public ModelWrapper()
{
}
protected virtual TValue GetValue<TValue>([CallerMemberName] string propertyName = null)
{
return (TValue)typeof(T).GetProperty(propertyName)?.GetValue(Model);
}
protected virtual void SetValue<TValue>(TValue value, [CallerMemberName] string propertyName = null)
{
typeof(T).GetProperty(propertyName)?.SetValue(Model, value);
OnPropertyChanged(propertyName);
ValidatePropertyInternal(propertyName, value);
}
private void ValidatePropertyInternal(string propertyName, object currentValue)
{
ClearErrors(propertyName);
ValidateDataAnnotations(propertyName, currentValue);
ValidateCustomErrors(propertyName);
}
protected virtual IEnumerable<string> ValidateProperty(string propertyName)
{
return null;
}
private void ValidateCustomErrors(string propertyName)
{
var errors = ValidateProperty(propertyName);
if (errors != null)
{
foreach (var error in errors)
{
AddError(propertyName, error);
}
}
}
private void ValidateDataAnnotations(string propertyName, object currentValue)
{
var results = new List<ValidationResult>();
var context = new ValidationContext(Model) { MemberName = propertyName };
Validator.TryValidateProperty(currentValue, context, results);
foreach (var result in results)
{
AddError(propertyName, result.ErrorMessage);
}
}
}
Generic Implementation:
public class AttributionInputWrapper : ModelWrapper<AttributionInput>
{
public AttributionInputWrapper(AttributionInput model) : base(model)
{
}
public DateTime? StartDate
{
get => GetValue<DateTime?>();
set
{
SetValue(value);
if (EndDate < StartDate) EndDate = StartDate;
}
}
public DateTime? EndDate
{
get => GetValue<DateTime?>();
set
{
SetValue(value);
if (EndDate < StartDate) StartDate = EndDate;
}
}
protected override IEnumerable<string> ValidateProperty(string propertyName)
{
if (propertyName == nameof(EndDate) || propertyName == nameof(StartDate))
{
//if (StartDate.Value.Date > EndDate.Value.Date) yield return "Start Date must be <= End Date";
if (EndDate != null && (EndDate.Value.DayOfWeek == DayOfWeek.Saturday || EndDate.Value.DayOfWeek == DayOfWeek.Sunday))
yield return "Please select a week day";
if (StartDate != null && (StartDate.Value.DayOfWeek == DayOfWeek.Saturday || StartDate.Value.DayOfWeek == DayOfWeek.Sunday))
yield return "Please select a week day";
}
}
}
ViewModel:
public class QueryViewModel : DetailViewModelBase, ICommonViewModel
{
private AttributionInputWrapper _attributionInput;
public AttributionInputWrapper AttributionInput
{
get => _attributionInput;
set
{
_attributionInput = value;
OnPropertyChanged();
}
}
}
View :
<Style TargetType="DatePicker">
<Setter Property="Margin" Value="{StaticResource MarginString}"/>
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<StackPanel>
<AdornedElementPlaceholder x:Name="Placeholder1"/>
<TextBlock Text="{Binding ElementName=Placeholder1, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}" FontSize="9"/>
</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>
<DatePicker x:Name="StartDateBox" SelectedDate="{Binding AttributionInput.StartDate, UpdateSourceTrigger=PropertyChanged}" DisplayDateStart="10/01/2017" DisplayDateEnd="{x:Static system:DateTime.Now}"/>

textbox not updating its value from viewmodel

I have a command which sends text when the send button is clicked. The binding is set to two way and the updatesource trigger to propertychanged. but the value of the textbox doesnt change to string.empty which is included in the sendCommand, even though the command was able to take the updated textbox value for a new message.
public class BuddyChatViewModel : BaseViewModel
{
private string chat;
public string Chat
{
get { return chat; }
set
{
chat = value;
RaisePropertyChanged();
}
}
public RelayCommand sendChatCommand { get; private set; }
string username = "";
string buddy = "";
UriStrings url = new UriStrings();
BuddiesHomeModel buddiesList = new BuddiesHomeModel();
HttpService http = new HttpService();
StorageService store = new StorageService();
string response = "";
BuddyChatModel buddyChat = new BuddyChatModel();
List<BuddyChat2Datum> buddychatList = new List<BuddyChat2Datum>();
BuddyChat2Datum tempDatum = new BuddyChat2Datum();
private ObservableCollection<BuddyChat2Datum> buddyChatOC = new ObservableCollection<BuddyChat2Datum>();
public ObservableCollection<BuddyChat2Datum> BuddyChatOC
{
get { return buddyChatOC; }
set
{
buddyChatOC = value;
RaisePropertyChanged();
}
}
private async void sendChatExecute()
{
int i = 0;
string s = url.buddychatText(username, buddy, chat);
chat = "";
response = await http.GetAsync(s);
buddyChat = JsonConvert.DeserializeObject<BuddyChatModel>(response);
buddychatList.Clear();
for (i = 0; i < buddyChat.data.Count; i++)
{
tempDatum.conversation = buddyChat.data[i].conversation;
tempDatum.datetime = buddyChat.data[i].datetime;
tempDatum.from = buddyChat.data[i].from;
tempDatum.to = buddyChat.data[i].to;
if (tempDatum.from == username)
tempDatum.isLeft = false;
else
tempDatum.isLeft = true;
buddychatList.Add(tempDatum);
tempDatum = new BuddyChat2Datum();
}
BuddyChatOC.Clear();
for (i = 0; i < buddychatList.Count; i++)
{
BuddyChatOC.Add(buddychatList[i]);
}
Navigate<BuddyChatViewModel>(buddychatList);
}
#region State Management
public override void LoadState(object navParameter, Dictionary<string, object> state)
{
sendChatCommand = new RelayCommand(sendChatExecute);
int i = 0;
base.LoadState(navParameter, state);
BuddyChatOC.Clear();
// load test items again; in production this would retrieve the live item by id or get it from a local data cache
List<BuddyChat2Datum> buddychatList = (List<BuddyChat2Datum>)navParameter;
//var mes = new MessageDialog(buddychatList.Count.ToString());
//await mes.ShowAsync();
for(i=0;i<buddychatList.Count;i++)
{
BuddyChatOC.Add(buddychatList[i]);
}
username = buddychatList[i-1].username;
buddy = buddychatList[i-1].buddy;
}
public override void SaveState(Dictionary<string, object> state)
{
base.SaveState(state);
}
#endregion
}
}
xaml code:
<Grid Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<ListView x:Name="chatList" HorizontalAlignment="Stretch" ItemsSource="{Binding BuddyChatOC}" ItemTemplateSelector="{StaticResource ChatSelector}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
</ListView>
<RelativePanel Grid.Row="1" Margin="5,10,5,10">
<TextBox x:Name="sendtext" Margin="0,0,2,0" Text="{Binding Chat, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" RelativePanel.AlignLeftWithPanel="True" RelativePanel.LeftOf="sendtextbutton"/>
<Button x:Name="sendtextbutton" Content="Send" Command="{Binding sendChatCommand}" RelativePanel.AlignRightWithPanel="True" >
</Button>
</RelativePanel>
</Grid>
Implement INotifyPropertyChanged in BuddyChatViewModel.
public class BuddyChatViewModel : INotifyPropertyChanged, BaseViewModel
{
private string chat;
public string Chat
{
get { return chat; }
set
{
chat = value;
NotifyPropertyChanged("Chat");
}
}
//INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
If you're using MVVMLight (and from how this question is tagged I assume you do), you need to specify changed property name in RaisePropertyChanged call.
That should work in your case:
public string Chat
{
get { return chat; }
set
{
chat = value;
RaisePropertyChanged(() => Chat);
}
}

Modern UI for WPF - ModernDialog: Allow to click Ok only on some conditions

I started a new small project, using ModernUI for WPF.
I've to display a Dialog with only two textbox and two button(ok/cancel).
The two textbox have some validation(one is a name that should be >0, the other should be an email).
Currently I've the following:
var userEditionForm = new UserEditionForm(oneUserConfigurationViewModel);
var dlg = new ModernDialog
{
Title = "User edition",
Content = userEditionForm,
Width = 300
};
dlg.Buttons = new[] {dlg.OkButton, dlg.CancelButton};
dlg.ShowDialog();
return dlg.DialogResult.HasValue && dlg.DialogResult.Value;
The content user control is:
<UserControl x:Class="Test.UserControls.UserEditionForm"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<StackPanel Orientation="Vertical">
<StackPanel.Resources>
<Style TargetType="StackPanel">
<Setter Property="Orientation" Value="Horizontal" />
<Setter Property="Margin" Value="0,0,0,4" />
</Style>
<Style TargetType="Label" BasedOn="{StaticResource {x:Type Label}}">
<Setter Property="Width" Value="100" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
</StackPanel.Resources>
<StackPanel>
<Label Content="Name" Target="{Binding ElementName=TextFirstName}" />
<TextBox x:Name="TextFirstName" Width="150"
Text="{Binding User.Name, RelativeSource={RelativeSource AncestorType=UserControl}, Mode=TwoWay, ValidatesOnDataErrors=True}" />
</StackPanel>
<StackPanel>
<Label Content="Email" Target="{Binding ElementName=TextEmail}" />
<TextBox x:Name="TextEmail" Width="150"
Text="{Binding User.Email, RelativeSource={RelativeSource AncestorType=UserControl}, Mode=TwoWay, ValidatesOnDataErrors=True}" />
</StackPanel>
</StackPanel>
</Grid>
</UserControl>
The validation is made, but I would like to prevent the user to click "OK" until the validation is good. Is it possible? If yes, how?
This was actually pretty tricky but it worked for me:
ViewModel
public class UserInfo : INotifyPropertyChanged, IDataErrorInfo
{
private static Regex emailRegex = new Regex(#"^\w+(?:\.\w+)*?#[a-zA-Z_]+?\.[a-zA-Z]{2,3}$");
private string firstname;
private string email;
public string FirstName
{
get { return firstname; }
set { firstname = value; OnPropertyChanged(); }
}
public string EMail
{
get { return email; }
set { email = value; OnPropertyChanged(); }
}
public string Error
{
get { return null; }
}
public string this[string columnName]
{
get
{
switch (columnName)
{
case "FirstName":
return string.IsNullOrWhiteSpace(FirstName) ? "Firstname is required!" : null;
case "EMail":
return EMail == null || !emailRegex.IsMatch(EMail) ? "The Email Address is not valid!" : null;
default:
return null;
}
}
}
public LoginCommand LoginCommand { get; private set; }
public UserInfo()
{
LoginCommand = new LoginCommand(this);
}
private void OnPropertyChanged([CallerMemberName]string propertyName = "")
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
Command (No clue what you want to call it...)
public class LoginCommand : ICommand
{
private UserInfo info;
private ICommand attachedCommand;
public bool CanExecute(object parameter)
{
return parameter != null && info["FirstName"] == null && info["EMail"] == null;
}
public event EventHandler CanExecuteChanged = delegate { };
public void Execute(object parameter)
{
Debug.WriteLine("This Works!");
//Add execution logic here
if (attachedCommand != null)
{
attachedCommand.Execute(parameter); //should close the window
}
}
private void Info_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
CanExecuteChanged(this, new EventArgs());
}
public LoginCommand(UserInfo info)
{
this.info = info;
info.PropertyChanged += Info_PropertyChanged;
}
public void AttachCommand(ICommand command) //attach the original command here
{
attachedCommand = command;
}
}
Usage with ModernUI
UserInfo info = new UserInfo();
UserInfoForm form = new UserInfoForm(info);
ModernDialog dialog = new ModernDialog
{
Title = "User edition",
Content = form,
Width = 300
};
Button btnOk = dialog.OkButton;
ICommand originalCommand = btnOk.Command; //the original command should close the window so i keep it
btnOk.Command = info.LoginCommand;
info.LoginCommand.AttachCommand(originalCommand); //and attach it to my command
dialog.Buttons = new[] { btnOk, dialog.CancelButton };
dialog.ShowDialog();

ListBox doesn't show any item

I have created a ListBox with custom ListBoxItem Template, had bound everything and it worked. When I was still working on my project. I runned the program and that ListBox weren't showing items anymore. Here is my code:
This is ListBox which doesn't show any item:
<ListBox
x:Name="LB_SongList"
HorizontalAlignment="Left"
Height="498"
Margin="0,30,0,0"
VerticalAlignment="Top"
Width="319"
Background="{x:Null}"
BorderBrush="{x:Null}"
Drop="LB_SongList_Drop"
PreviewMouseMove="LB_SongList_PreviewMouseMove"
PreviewMouseDoubleClick="LB_SongList_PreviewMouseDoubleClick"
SelectionChanged="LB_SongList_SelectionChanged"
AllowDrop="True"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Visible"
SelectionMode="Multiple"
ItemTemplate="{DynamicResource SongTemplate}"/>
Here is my ListBoxItem Template:
<DataTemplate x:Key="SongTemplate">
<Grid Width="302" Height="35">
<Label Content="{Binding SongName}" HorizontalAlignment="Stretch" Margin="4" VerticalAlignment="Stretch" Width="Auto" Height="Auto" Padding="2,0,0,0" VerticalContentAlignment="Center"/>
<Border BorderBrush="#B26A6A6A" BorderThickness="4" HorizontalAlignment="Stretch" Height="35" VerticalAlignment="Top" Width="Auto">
<Border.Style>
<Style>
<Style.Triggers>
<DataTrigger
Binding="{Binding Path=IsSelected, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}}}"
Value="False">
<Setter
Property="Border.Visibility"
Value="Collapsed"
/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
</Grid>
</DataTemplate>
And the code:
public partial class MenuHolder : UserControl
{
private List<string> List_SongList;
public List<string> L_SongList
{
get { return List_SongList; }
set
{
_File.ListToObservableCollection_Song(value, O_SongList);
List_SongList = value;
}
}
public List<string> L_PlayLists
{
get
{
Settings.Default.Save();
return Settings.Default.L_PlayLists;
}
set
{
Settings.Default.L_PlayLists = value;
Settings.Default.Save();
}
}
public ObservableCollection<Song> O_SongList = new ObservableCollection<Song>();
public ObservableCollection<string> O_PlayList = new ObservableCollection<string>();
public MenuHolder()
{
InitializeComponent();
LB_SongList.ItemsSource = O_SongList;
LB_PlayList.ItemsSource = O_PlayList;
List<string> temp = new List<string>();
temp.Add("TestSong");
L_SongList = temp;
}
}
public class Song : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
string _songName;
string _songLengh;
public Song(){}
public Song(String _name){ SongName = _name; }
public Song(String _name, String _lengh) { SongName = _name; SongLengh = _lengh; }
public string SongName
{
get { return _songName; }
set { _songName = value; RaisePropertyChanged("SongName"); }
}
public string SongLengh
{
get { return _songLengh; }
set { _songLengh = value; RaisePropertyChanged("SongLengh"); }
}
private void RaisePropertyChanged(string propertyName)
{
// take a copy to prevent thread issues
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And the ListToObservableCollection_Song
public void ListToObservableCollection_Song(List<string> _list, ObservableCollection<Music_Player.Menus.MainMenuObjects.Song> _collection)
{
_collection.Clear();
foreach (string _path in _list)
{
_collection.Add(new Menus.MainMenuObjects.Song(GetSongNameFromPath(_path)));
}
}
The worst thing is that it was working before and in the old version of my program it is still working. Still thinking why it is doing that.

Implement Validation for WPF TextBoxes

I have 3 TextBoxes (Id1,Name and Salary). Id and Salary should contain integers and Name should only contain characters. I need validations for my TextBox, it should show errors as I enter wrong characters or integers. Also can this be done only in Xaml without codebehind? Please help me with the required code
This is Xaml code:
<TextBox Name="tb1" HorizontalAlignment="Left" Height="20" Margin="60,10,0,0" TextWrapping="NoWrap" Text="{Binding SelectedItem.Id,ElementName=dgsample}" VerticalAlignment="Top" Width="100" />
<TextBox Name="tb2" HorizontalAlignment="Left" Height="20" Margin="60,60,0,0" TextWrapping="NoWrap" Text="{Binding SelectedItem.Name, ElementName=dgsample}" VerticalAlignment="Top" Width="100"/>
<TextBox Name="tb3" HorizontalAlignment="Left" Height="20" Margin="60,110,0,0" TextWrapping="NoWrap" Text="{Binding SelectedItem.Salary, ElementName=dgsample}" VerticalAlignment="Top" Width="100"/>
There a 3 ways to implement validation:
Validation Rule
Implementation of INotifyDataErrorInfo
Implementation of IDataErrorInfo
Validation rule example:
public class NumericValidationRule : ValidationRule
{
public Type ValidationType { get; set; }
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
string strValue = Convert.ToString(value);
if (string.IsNullOrEmpty(strValue))
return new ValidationResult(false, $"Value cannot be coverted to string.");
bool canConvert = false;
switch (ValidationType.Name)
{
case "Boolean":
bool boolVal = false;
canConvert = bool.TryParse(strValue, out boolVal);
return canConvert ? new ValidationResult(true, null) : new ValidationResult(false, $"Input should be type of boolean");
case "Int32":
int intVal = 0;
canConvert = int.TryParse(strValue, out intVal);
return canConvert ? new ValidationResult(true, null) : new ValidationResult(false, $"Input should be type of Int32");
case "Double":
double doubleVal = 0;
canConvert = double.TryParse(strValue, out doubleVal);
return canConvert ? new ValidationResult(true, null) : new ValidationResult(false, $"Input should be type of Double");
case "Int64":
long longVal = 0;
canConvert = long.TryParse(strValue, out longVal);
return canConvert ? new ValidationResult(true, null) : new ValidationResult(false, $"Input should be type of Int64");
default:
throw new InvalidCastException($"{ValidationType.Name} is not supported");
}
}
}
XAML:
Very important: don't forget to set ValidatesOnTargetUpdated="True" it won't work without this definition.
<TextBox x:Name="Int32Holder"
IsReadOnly="{Binding IsChecked,ElementName=CheckBoxEditModeController,Converter={converters:BooleanInvertConverter}}"
Style="{StaticResource ValidationAwareTextBoxStyle}"
VerticalAlignment="Center">
<!--Text="{Binding Converter={cnv:TypeConverter}, ConverterParameter='Int32', Path=ValueToEdit.Value, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"-->
<TextBox.Text>
<Binding Path="Name"
Mode="TwoWay"
UpdateSourceTrigger="PropertyChanged"
Converter="{cnv:TypeConverter}"
ConverterParameter="Int32"
ValidatesOnNotifyDataErrors="True"
ValidatesOnDataErrors="True"
NotifyOnValidationError="True">
<Binding.ValidationRules>
<validationRules:NumericValidationRule ValidationType="{x:Type system:Int32}"
ValidatesOnTargetUpdated="True" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
<!--NumericValidationRule-->
</TextBox>
INotifyDataErrorInfo example:
public abstract class ViewModelBase : INotifyPropertyChanged, INotifyDataErrorInfo
{
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
ValidateAsync();
}
#endregion
public virtual void OnLoaded()
{
}
#region INotifyDataErrorInfo
private ConcurrentDictionary<string, List<string>> _errors = new ConcurrentDictionary<string, List<string>>();
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public void OnErrorsChanged(string propertyName)
{
var handler = ErrorsChanged;
if (handler != null)
handler(this, new DataErrorsChangedEventArgs(propertyName));
}
public IEnumerable GetErrors(string propertyName)
{
List<string> errorsForName;
_errors.TryGetValue(propertyName, out errorsForName);
return errorsForName;
}
public bool HasErrors
{
get { return _errors.Any(kv => kv.Value != null && kv.Value.Count > 0); }
}
public Task ValidateAsync()
{
return Task.Run(() => Validate());
}
private object _lock = new object();
public void Validate()
{
lock (_lock)
{
var validationContext = new ValidationContext(this, null, null);
var validationResults = new List<ValidationResult>();
Validator.TryValidateObject(this, validationContext, validationResults, true);
foreach (var kv in _errors.ToList())
{
if (validationResults.All(r => r.MemberNames.All(m => m != kv.Key)))
{
List<string> outLi;
_errors.TryRemove(kv.Key, out outLi);
OnErrorsChanged(kv.Key);
}
}
var q = from r in validationResults
from m in r.MemberNames
group r by m into g
select g;
foreach (var prop in q)
{
var messages = prop.Select(r => r.ErrorMessage).ToList();
if (_errors.ContainsKey(prop.Key))
{
List<string> outLi;
_errors.TryRemove(prop.Key, out outLi);
}
_errors.TryAdd(prop.Key, messages);
OnErrorsChanged(prop.Key);
}
}
}
#endregion
}
View Model Implementation:
public class MainFeedViewModel : BaseViewModel//, IDataErrorInfo
{
private ObservableCollection<FeedItemViewModel> _feedItems;
[XmlIgnore]
public ObservableCollection<FeedItemViewModel> FeedItems
{
get
{
return _feedItems;
}
set
{
_feedItems = value;
OnPropertyChanged("FeedItems");
}
}
[XmlIgnore]
public ObservableCollection<FeedItemViewModel> FilteredFeedItems
{
get
{
if (SearchText == null) return _feedItems;
return new ObservableCollection<FeedItemViewModel>(_feedItems.Where(x => x.Title.ToUpper().Contains(SearchText.ToUpper())));
}
}
private string _title;
[Required]
[StringLength(20)]
//[CustomNameValidationRegularExpression(5, 20)]
[CustomNameValidationAttribute(3, 20)]
public string Title
{
get { return _title; }
set
{
_title = value;
OnPropertyChanged("Title");
}
}
private string _url;
[Required]
[StringLength(200)]
[Url]
//[CustomValidation(typeof(MainFeedViewModel), "UrlValidation")]
/// <summary>
/// Validation of URL should be with custom method like the one that implemented below, or with
/// </summary>
public string Url
{
get { return _url; }
set
{
_url = value;
OnPropertyChanged("Url");
}
}
public MainFeedViewModel(string url, string title)
{
Title = title;
Url = url;
}
/// <summary>
///
/// </summary>
public MainFeedViewModel()
{
}
public MainFeedViewModel(ObservableCollection<FeedItemViewModel> feeds)
{
_feedItems = feeds;
}
private string _searchText;
[XmlIgnore]
public string SearchText
{
get { return _searchText; }
set
{
_searchText = value;
OnPropertyChanged("SearchText");
OnPropertyChanged("FilteredFeedItems");
}
}
#region Data validation local
/// <summary>
/// Custom URL validation method
/// </summary>
/// <param name="obj"></param>
/// <param name="context"></param>
/// <returns></returns>
public static ValidationResult UrlValidation(object obj, ValidationContext context)
{
var vm = (MainFeedViewModel)context.ObjectInstance;
if (!Uri.IsWellFormedUriString(vm.Url, UriKind.Absolute))
{
return new ValidationResult("URL should be in valid format", new List<string> { "Url" });
}
return ValidationResult.Success;
}
#endregion
}
XAML:
<UserControl x:Class="RssReaderTool.Views.AddNewFeedDialogView"
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"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="300">
<FrameworkElement.Resources>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate x:Name="TextErrorTemplate">
<DockPanel LastChildFill="True">
<AdornedElementPlaceholder>
<Border BorderBrush="Red"
BorderThickness="2" />
</AdornedElementPlaceholder>
<TextBlock FontSize="20"
Foreground="Red">*?*</TextBlock>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError"
Value="True">
<Setter Property="ToolTip"
Value="{Binding RelativeSource=
{x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}"></Setter>
</Trigger>
</Style.Triggers>
</Style>
<!--<Style 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>-->
</FrameworkElement.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="5" />
<RowDefinition Height="Auto" />
<RowDefinition Height="5" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Text="Feed Name"
ToolTip="Display" />
<TextBox Text="{Binding MainFeedViewModel.Title,UpdateSourceTrigger=PropertyChanged,ValidatesOnNotifyDataErrors=True,ValidatesOnDataErrors=True}"
Grid.Column="2" />
<TextBlock Text="Feed Url"
Grid.Row="2" />
<TextBox Text="{Binding MainFeedViewModel.Url,UpdateSourceTrigger=PropertyChanged,ValidatesOnNotifyDataErrors=True,ValidatesOnDataErrors=True}"
Grid.Column="2"
Grid.Row="2" />
</Grid>
</UserControl>
IDataErrorInfo:
View Model:
public class OperationViewModel : ViewModelBase, IDataErrorInfo
{
private const int ConstCodeMinValue = 1;
private readonly IEventAggregator _eventAggregator;
private OperationInfoDefinition _operation;
private readonly IEntityFilterer _contextFilterer;
private OperationDescriptionViewModel _description;
public long Code
{
get { return _operation.Code; }
set
{
if (SetProperty(value, _operation.Code, o => _operation.Code = o))
{
UpdateDescription();
}
}
}
public string Description
{
get { return _operation.Description; }
set
{
if (SetProperty(value, _operation.Description, o => _operation.Description = o))
{
UpdateDescription();
}
}
}
public string FriendlyName
{
get { return _operation.FriendlyName; }
set
{
if (SetProperty(value, _operation.FriendlyName, o => _operation.FriendlyName = o))
{
UpdateDescription();
}
}
}
public int Timeout
{
get { return _operation.Timeout; }
set
{
if (SetProperty(value, _operation.Timeout, o => _operation.Timeout = o))
{
UpdateDescription();
}
}
}
public string Category
{
get { return _operation.Category; }
set
{
if (SetProperty(value, _operation.Category, o => _operation.Category = o))
{
UpdateDescription();
}
}
}
public bool IsManual
{
get { return _operation.IsManual; }
set
{
if (SetProperty(value, _operation.IsManual, o => _operation.IsManual = o))
{
UpdateDescription();
}
}
}
void UpdateDescription()
{
//some code
}
#region Validation
#region IDataErrorInfo
public ValidationResult Validate()
{
return ValidationService.Instance.ValidateNumber(Code, ConstCodeMinValue, long.MaxValue);
}
public string this[string columnName]
{
get
{
var validation = ValidationService.Instance.ValidateNumber(Code, ConstCodeMinValue, long.MaxValue);
return validation.IsValid ? null : validation.ErrorContent.ToString();
}
}
public string Error
{
get
{
var result = Validate();
return result.IsValid ? null : result.ErrorContent.ToString();
}
}
#endregion
#endregion
}
XAML:
<controls:NewDefinitionControl x:Class="DiagnosticsDashboard.EntityData.Operations.Views.NewOperationView"
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:views="clr-namespace:DiagnosticsDashboard.EntityData.Operations.Views"
xmlns:controls="clr-namespace:DiagnosticsDashboard.Core.Controls;assembly=DiagnosticsDashboard.Core"
xmlns:c="clr-namespace:DiagnosticsDashboard.Core.Validation;assembly=DiagnosticsDashboard.Core"
mc:Ignorable="d">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="40" />
<RowDefinition Height="40" />
<RowDefinition Height="40" />
<RowDefinition Height="40" />
<RowDefinition Height="40" />
<RowDefinition Height="40" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Label Grid.Column="0"
Grid.Row="0"
Margin="5">Code:</Label>
<Label Grid.Column="0"
Grid.Row="1"
Margin="5">Description:</Label>
<Label Grid.Column="0"
Grid.Row="2"
Margin="5">Category:</Label>
<Label Grid.Column="0"
Grid.Row="3"
Margin="5">Friendly Name:</Label>
<Label Grid.Column="0"
Grid.Row="4"
Margin="5">Timeout:</Label>
<Label Grid.Column="0"
Grid.Row="5"
Margin="5">Is Manual:</Label>
<TextBox Grid.Column="1"
Text="{Binding Code,UpdateSourceTrigger=PropertyChanged,ValidatesOnDataErrors=True}"
Grid.Row="0"
Margin="5"/>
<TextBox Grid.Column="1"
Grid.Row="1"
Margin="5"
Text="{Binding Description}" />
<TextBox Grid.Column="1"
Grid.Row="2"
Margin="5"
Text="{Binding Category}" />
<TextBox Grid.Column="1"
Grid.Row="3"
Margin="5"
Text="{Binding FriendlyName}" />
<TextBox Grid.Column="1"
Grid.Row="4"
Margin="5"
Text="{Binding Timeout}" />
<CheckBox Grid.Column="1"
Grid.Row="5"
Margin="5"
IsChecked="{Binding IsManual}"
VerticalAlignment="Center" />
</Grid>
</controls:NewDefinitionControl>
You can additionally implement IDataErrorInfo as follows in the view model. If you implement IDataErrorInfo, you can do the validation in that instead of the setter of a particular property, then whenever there is a error, return an error message so that the text box which has the error gets a red box around it, indicating an error.
class ViewModel : INotifyPropertyChanged, IDataErrorInfo
{
private string m_Name = "Type Here";
public ViewModel()
{
}
public string Name
{
get
{
return m_Name;
}
set
{
if (m_Name != value)
{
m_Name = value;
OnPropertyChanged("Name");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public string Error
{
get { return "...."; }
}
/// <summary>
/// Will be called for each and every property when ever its value is changed
/// </summary>
/// <param name="columnName">Name of the property whose value is changed</param>
/// <returns></returns>
public string this[string columnName]
{
get
{
return Validate(columnName);
}
}
private string Validate(string propertyName)
{
// Return error message if there is error on else return empty or null string
string validationMessage = string.Empty;
switch (propertyName)
{
case "Name": // property name
// TODO: Check validiation condition
validationMessage = "Error";
break;
}
return validationMessage;
}
}
And you have to set ValidatesOnDataErrors=True in the XAML in order to invoke the methods of IDataErrorInfo as follows:
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
To get it done only with XAML you need to add Validation Rules for individual properties. But i would recommend you to go with code behind approach.
In your code, define your specifications in properties setters and throw exceptions when ever it doesn't compliance to your specifications.
And use error template to display your errors to user in UI.
Your XAML will look like this
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style x:Key="CustomTextBoxTextStyle" TargetType="TextBox">
<Setter Property="Foreground" Value="Green" />
<Setter Property="MaxLength" Value="40" />
<Setter Property="Width" Value="392" />
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Trigger.Setters>
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self},Path=(Validation.Errors)[0].ErrorContent}"/>
<Setter Property="Background" Value="Red"/>
</Trigger.Setters>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<TextBox Name="tb2" Height="30" Width="400"
Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=True}"
Style="{StaticResource CustomTextBoxTextStyle}"/>
</Grid>
Code Behind:
public partial class MainWindow : Window
{
private ExampleViewModel m_ViewModel;
public MainWindow()
{
InitializeComponent();
m_ViewModel = new ExampleViewModel();
DataContext = m_ViewModel;
}
}
public class ExampleViewModel : INotifyPropertyChanged
{
private string m_Name = "Type Here";
public ExampleViewModel()
{
}
public string Name
{
get
{
return m_Name;
}
set
{
if (String.IsNullOrEmpty(value))
{
throw new Exception("Name can not be empty.");
}
if (value.Length > 12)
{
throw new Exception("name can not be longer than 12 charectors");
}
if (m_Name != value)
{
m_Name = value;
OnPropertyChanged("Name");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I have implemented this validation. But you would be used code behind. It is too much easy and simplest way.
XAML:
For name Validtion only enter character from A-Z and a-z.
<TextBox x:Name="first_name_texbox" PreviewTextInput="first_name_texbox_PreviewTextInput" > </TextBox>
Code Behind.
private void first_name_texbox_PreviewTextInput ( object sender, TextCompositionEventArgs e )
{
Regex regex = new Regex ( "[^a-zA-Z]+" );
if ( regex.IsMatch ( first_name_texbox.Text ) )
{
MessageBox.Show("Invalid Input !");
}
}
For Salary and ID validation, replace regex constructor passed value with [0-9]+. It means you can only enter number from 1 to infinite.
You can also define length with [0-9]{1,4}. It means you can only enter less then or equal to 4 digit number. This baracket means {at least,How many number}. By doing this you can define range of numbers in textbox.
May it help to others.
XAML:
Code Behind.
private void salary_texbox_PreviewTextInput ( object sender, TextCompositionEventArgs e )
{
Regex regex = new Regex ( "[^0-9]+" );
if ( regex.IsMatch ( salary_texbox.Text ) )
{
MessageBox.Show("Invalid Input !");
}
}
When it comes to Muhammad Mehdi's answer, it is better to do:
private void salary_texbox_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
Regex regex = new Regex ( "[^0-9]+" );
if(regex.IsMatch(e.Text))
{
MessageBox.Show("Error");
}
}
Because when comparing with the TextCompositionEventArgs it gets also the last character, while with the textbox.Text it does not. With textbox, the error will show after next inserted character.
When I needed to do this, I followed Microsoft's example using Binding.ValidationRules and it worked first time.
See their article, How to: Implement Binding Validation:
https://learn.microsoft.com/en-us/dotnet/desktop/wpf/data/how-to-implement-binding-validation?view=netframeworkdesktop-4.8
When it comes to DiSaSteR's answer, I noticed a different behavior. textBox.Text shows the text in the TextBox as it was before the user entered a new character, while e.Text shows the single character the user just entered. One challenge is that this character might not get appended to the end, but it will be inserted at the carret position:
private void salary_texbox_PreviewTextInput(object sender, TextCompositionEventArgs e){
Regex regex = new Regex ( "[^0-9]+" );
string text;
if (textBox.CaretIndex==textBox.Text.Length) {
text = textBox.Text + e.Text;
} else {
text = textBox.Text.Substring(0, textBox.CaretIndex) + e.Text + textBox.Text.Substring(textBox.CaretIndex);
}
if(regex.IsMatch(text)){
MessageBox.Show("Error");
}
}

Categories

Resources