How to show a tooltip on invalid input into a textbox - c#

I am trying to setup validation for a phone number field in a WPF application using MVVM. I have the textbox text bound but cant seem to figure out the logic involved in rejecting input and popping up a tooltip. Any suggestions would be appreciated.
[Required]
public string PhoneNumber
{
get
{
return EntityPhone.PhoneNumber;
}
set
{
int intValue = 0;
if(!int.TryParse(value, out intValue))
{
// ToolTip tt = new ToolTip();
// tt.Content = "Invalid Character. Please enter a valid 10-digit number";
}
EntityPhone.PhoneNumber = value;
NotifyOfPropertyChange(() => PhoneNumber);
}
}

First you'll want to make your class inherit IDataErrorInfo, which is used by WPF for validation purposes.
public class MyClass : IDataErrorInfo
{
...
#region IDataErrorInfo Members
string IDataErrorInfo.Error
{
get { return null; }
}
string IDataErrorInfo.this[string columnName]
{
get
{
if (columnName == "PhoneNumber")
{
// Validate property and return a string if there is an error
return "Some error";
}
// If there's no error, null gets returned
return null;
}
}
#endregion
}
Next, tell your binding that it should be validating the value when it changes
<TextBox Text="{Binding Path=PhoneNumber, ValidatesOnDataErrors=True}" ... />
And finally, create a validation template. Here's the style/template I usually use
<!-- ValidatingControl Style -->
<Style TargetType="{x:Type FrameworkElement}" x:Key="ValidatingControl">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip" Value="{Binding
Path=(Validation.Errors)[0].ErrorContent,
RelativeSource={x:Static RelativeSource.Self}}" />
</Trigger>
</Style.Triggers>
</Style>

Related

Catch exceptions with IDataErrorInfo

I want to validate the input of a TextBox. I used the IDataErrorInfo interface to do so. When the user input is bad the property ValueIsValid is set to false. However, I use the property InputValue for the TextInput (its an int). When the user types "1234" the IDataErrorInfo interface checks if the input is correct and sets if needed the ValueIsValid to false.
But when the user types "blabla" the input is not being converted to an int and IDataErrorInfo interface is not being called => ValueIsValid is not set to false.
How do I set ValueIsValid to false when the user types "blabla" in the TextBox?
I can't access the validation.hasError property of the TextBox from my viewmodel because I use MVVM.
ViewModel:
public class ViewModel : IDataErrorInfo
{
public bool ValueIsValid { get; set; }
public string StrErrorMessage
{
get { return "Some Error ..."; }
}
public int InputValue
{
get { return m_inputValue; }
set
{
m_inputValue = value;
NotifyPropertyChanged();
ValueIsValid = true;
}
}
protected int m_inputValue;
public string Error
{
get { return null; }
}
public string this[string columnName]
{
get
{
if (columnName == "InputValue")
{
if (InputValue == 10)
{
ValueIsValid = false;
return "Wrong value in TextBox.";
}
}
return string.Empty;
}
}
}
WPF
<TextBox Text="{Binding InputValue, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}">
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip" Value="{Binding StrErrorMessage}"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>

WPF DataGridHyperlinkColumn: hiding or showing hyperlink depending on a condition

I have an WPF MVVM app. In the main WPF window, I have a WPF datagrid which has some columns. One of them is of type DataGridHyperlinkColumn. This column contains a link which does some stuff on user click.
The datagrid is bound to a data model class, let's say MyDataGridModel (I am only showing here the necessary objects to understand my case) :
public class MyDataGridModel : NotifyBase
{
// Properties, members, methods, etc.
public MyLinkData myLinkData { get; set; }
}
The DataGridHyperlinkColumn column is bound to a data model, let's say MyLinkData:
MyLinkData:
public class MyLinkData
{
private string linkText = string.Empty;
private string linkValue = string.Empty;
public string LinkText
{
get
{
return this.linkText;
}
private set
{
if (!this.linkText.Equals(value))
{
this.linkText = value;
}
}
}
public string LinkValue
{
get
{
return this.linkValue;
}
private set
{
if (!this.linkValue.Equals(value))
{
this.linkValue = value;
}
}
}
#region Constructors
public MyLinkData(string linkText, string linkValue)
{
this.LinkText = linkText;
this.LinkValue = linkValue;
}
#endregion
}
View:
<my:DataGridHyperlinkColumn
Binding="{Binding myLinkData.LinkValue}"
ContentBinding="{Binding myLinkData.LinkText}">
<my:DataGridHyperlinkColumn.ElementStyle>
<Style TargetType="TextBlock">
<EventSetter Event="PreviewMouseLeftButtonDown" Handler="OnCellHyperlinkClick" />
</Style>
</my:DataGridHyperlinkColumn.ElementStyle>
<my:DataGridHyperlinkColumn.CellStyle>
<Style TargetType="my:DataGridCell">
<Style.Resources>
<Style TargetType="Hyperlink">
<Style.Triggers>
<DataTrigger Binding="{Binding myLinkData.LinkValue}" Value="">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</Style.Resources>
</Style>
</my:DataGridHyperlinkColumn.CellStyle>
</my:DataGridHyperlinkColumn>
This link is disabled when link value contains an empty string and enabled otherwise.
Now, what I would like to do is to hide the link when link value contains an empty string instead of making it disabled. So how can I do this?
I was thinking of changing link color to make it not visible but then I may take into account when the row is selected (color is blue) and some other things making things more difficult (in my case rows also change color depending on some conditions).

Textbox Event Handling in ViewModel

I have a situation, where I am validating a textbox for enabling the button. If the textbox is empty the button should be disabled and vice verse. I can handle the code and achieve the solution, if I write the logic in the code behind of the XAML but I feel thats not the correct way and the event should be handled from the viewModel instead of the code behind.
Here is what I have done:
XAML
<TextBox Grid.Row="1" Margin="6,192,264,0" Height="60" VerticalAlignment="Top"
x:Name="txtDNCNotes" Text="{Binding Path=DNCNotes, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
TextWrapping="Wrap" VerticalScrollBarVisibility="Auto"
Visibility="{Binding Path=DNCNoteTxtVisibility}" Grid.Column="1"
behaviour:TextBoxFilters.IsBoundOnChange="True"
TextChanged="TextBox_TextChanged" />
ViewModel
public string DNCNotes
{
get { return _dncNotes; }
set {
if (_dncNotes == value) return;
_dncNotes = value;
OnPropertyChanged("DNCNotes");
}
}
Code behind
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
var ctx = LayoutRoot.DataContext as NextLeadWizardViewModel;
BindingExpression binding = txtDNCNotes.GetBindingExpression(TextBox.TextProperty).UpdateSource();
ctx.ShowDoNotContact();
}
I am trying to write following code in the viewModel to achieve the solution but not sure what to write.
public void ShowDoNotContact()
{
Binding myBinding = new Binding("DNCNotes");
//myBinding.Source = DataContext as NextLeadWizardViewModel;
myBinding.Source = txtDNCNotes;
myBinding.Path = new PropertyPath("DNCNotes");
myBinding.Mode = BindingMode.TwoWay;
myBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
BindingOperations.SetBinding(txtDNCNotes, TextBox.TextProperty, myBinding);
if (_dncNotes == null)
OkCommand.IsEnabled = false;
else
OkCommand.IsEnabled = CanEnableOk();
}
If you want to validate a TextBox which would disable the button, i would use a command, something similar to this;
private ICommand showDCNoteCommand;
public ICommand ShowDCNoteCommand
{
get
{
if (this.showDCNoteCommand == null)
{
this.showDCNoteCommand = new RelayCommand(this.DCNoteFormExecute, this.DCNoteFormCanExecute);
}
return this.showDCNoteCommand;
}
}
private bool DCNoteFormCanExecute()
{
return !string.IsNullOrEmpty(DCNotes);
}
private void DCNoteFormExecute()
{
DCNoteMethod(); //This a method that changed the text
}
This would ensure that the user is unable to continue, or save to progress as the TextBox should not accept a null or empty value, shown within the DCNoteFormCanExecute() (the DCNotes is property that you have defined within your Viewmodel).
and in the xaml, bind it to the button like so;
<Button Content="Save" Grid.Column="1" Grid.Row="20" x:Name="btnSave" VerticalAlignment="Bottom" Width="75" Command="{Binding ShowDCNoteCommand}"
For validation, you could do something simple like so, using attribute validation, using this reference using System.ComponentModel.DataAnnotations;
[Required(ErrorMessage = "DCNotes is required")]
[RegularExpression(#"^[a-zA-Z''-'\s]{1,5}$", ErrorMessage = "DCNotes must contain no more then 5 characters")] //You can change the length of the property to meet the DCNotes needs
public string DCNotes
{
get { return _DCNotes; }
set
{
if (_DCNotes == value)
return;
_DCNotes = value;
OnPropertyChanged("DCNotes");
}
}
and within the xaml, you could create a Resource to highlight the box to notify the user of the textbox not been filled out;
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Margin"
Value="4" />
</Style>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Margin"
Value="4" />
<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>
I hope this helps, otherwise, here's the link that might help;
http://www.codeproject.com/Articles/97564/Attributes-based-Validation-in-a-WPF-MVVM-Applicat
OR
http://www.codearsenal.net/2012/06/wpf-textbox-validation-idataerrorinfo.html#.UOv01G_Za0t
The ViewModel is an acceptable place to add supporting properties for your View that do not effect your model. For example, something along the lines of:
public bool DncCanExecute
{
get
{
return "" != _dncNotes;
}
}
public string DNCNotes
{
get { return _dncNotes; }
set {
if (_dncNotes == value) return;
if (("" == _dncNotes && "" != value) || ("" != _dncNotes && "" == value))
{
_dncNotes = value;
OnPropertyChanged("DncCanExecute");
}
else
{
_dncNotes = value;
}
OnPropertyChanged("DNCNotes");
}
}
From there, you can just bind the Button.IsEnabled property to the DncCanExecute property to get the desired functionality.

Where to raise NotifyPropertyChanged?

I just implemented my business logic validation according to Rachel Lim's blog.
Everything was running great until I decided to put a trigger in my view bound to the IsValid property like this:
<ListBox ItemsSource="{Binding EdgedBoards}" SelectedItem="{Binding SelEdgedBoard, Mode=TwoWay}" DisplayMemberPath="Name">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}">
<Setter Property="Focusable" Value="True" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsValid}" Value="False">
<Setter Property="Focusable" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
The problem is that when the bound item has an error (SelEdgedBoard.IsValid == false) the trigger is not notified to reevaluate and the item keeps its focusable property to true.
I've already tried to put a NotifyPropertyChanged("IsValid") before the GetValidationError() returns its value, but this way I get a stackoverflow exception:
#region IsValid Property
public bool IsValid
{
get
{
return string.IsNullOrWhiteSpace(GetValidationError());
}
}
public string GetValidationError()
{
string error = null;
if (ValidatedProperties != null)
{
foreach (string s in ValidatedProperties)
{
error = GetValidationError(s);
if (!string.IsNullOrWhiteSpace(error))
{
break;
}
}
}
NotifyPropertyChanged("IsValid");
return error;
}
#endregion
Of course it causes a stack overflow. When you do:
NotifyPropertyChanged("IsValid")
You force the WPF infrastructure to reevaluate the value of IsValid. It does this by calling the IsValid getter, which, in turn, calls GetValidationError again!
There are several ways you can handle this. I would probably create a private member variable to hold the last value of IsValid and then compare the current value to the old value before calling NotifyPropertyChanged.
Another way might be to only call NotifyPropertyChanged("IsValid") when one of your validated properties changes (so in the setter of each of those properties) as this is what might cause a change in IsValid.
private bool _isValid;
public bool IsValid
{
get
{
string.IsNullOrWhiteSpace(GetValidationError())
return _isValid;
}
set
{
_isValid = value;
NotifyPropertyChanged("IsValid");
}
}
public string GetValidationError()
{
string error = null;
if (ValidatedProperties != null)
{
foreach (string s in ValidatedProperties)
{
error = GetValidationError(s);
if (!string.IsNullOrWhiteSpace(error))
{
break;
}
}
}
IsValid=string.IsNullOrWhiteSpace(error);
return error;
}
I hope this will help.

WPF/C# IDataErrorInfo Not Firing

I have a combobox and button on my form. The combobox has categories in them. I want to allow/disallow pending on if they are a 'system category' based on a boolean.
Here is my xaml:
<Window.Resources>
<Style TargetType="{x:Type ComboBox}">
<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>
</Window.Resources>
This is the stack panel with the two controls in them:
<StackPanel Grid.Column="1" Grid.Row="1">
<Label Content="Delete Category" Height="28"/>
<ComboBox x:Name="comboBox_DeleteCategory"
Grid.Row="1"
Height="29"
ItemsSource="{Binding Path=CategorySelected.Items, ValidatesOnDataErrors=true, NotifyOnValidationError=true}"
SelectedItem="{Binding Path=CategorySelected.SelectedItem ,ValidatesOnDataErrors=True, NotifyOnValidationError=true}"
DisplayMemberPath="Name"/>
<Button Content="Delete" Height="25" Margin="0,5,0,0" HorizontalAlignment="Right" Width="103.307" Command="{Binding DeleteCommand}"/>
</StackPanel>
I am trying to get the combobox to show a tooltip if it is determined that it is a system category.
The DeleteCommand is working fine so I am not having issues with the button being disabled when I get a hit on the system category.
This is my code to show the tooltip:
#region IDataErrorInfo Members
public string Error { get; set; }
public string this[string columnName]
{
get
{
Error = "";
switch (columnName)
{
case "comboBox_DeleteCategory":
if (CategorySelected.SelectedItem != null && CategorySelected.SelectedItem.IsInternal)
{
Error = CategorySelected.SelectedItem.Name + " is an system category and cannot be deleted.";
break;
}
break;
}
return Error;
}
}
#endregion
Any suggestions?
Thanks,
Eroc
The indexer (public string this[string columnName]) is called with the property name that has been changed by the latest binding update. That is, filtering for "comboBox_DeleteCategory" (the control name) won't help here. You have to filter for the property that was updated by the control's binding and determine if it is in the expected state. You can put a breakpoint in the indexer and watch the columnName's value. What is more, the Error property is not used by WPF at all. Thus, it is not necessary to set it. A simple example:
public class Contact : IDataErrorInfo, INotifyPropertyChanged
{
private string firstName;
public string FirstName
{
// ... set/get with prop changed support
}
#region IDataErrorInfo Members
public string Error
{
// NOT USED BY WPF
get { throw new NotImplementedException(); }
}
public string this[string columnName]
{
get
{
// null or string.Empty won't raise a validation error.
string result = null;
if( columnName == "FirstName" )
{
if (String.IsNullOrEmpty(FirstName))
result = "A first name please...";
else if (FirstName.Length < 5)
result = "More than 5 chars please...";
}
return result;
}
}
#endregion
}

Categories

Resources