Textbox Event Handling in ViewModel - c#

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.

Related

wpf fire PropertyChanged event in binding source that binding gets updated

I'm trying to make some CustomControl Textboxes with validation features.
(for example a number only textbox or a zip-code textbox etc.)
It should realized in a .dll library file.
My project contains a CustomControl for the textbox, a class wich handles the validations,
and a ErrMsgGui CustomControl that should show a error message in a TextBlock
(exmp.: Only numbers allowed...)
My problem is that I don't get the TextBlock Text updated when a method in the validation class is called
Is there a way to trigger the PropertyChangeEvent which updates the Textblock text within the validaiton class?
(Im quite new to wpf)
Generic.xaml:
<Style TargetType="{x:Type local:NumTb}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:NumTb}">
<TextBox Background="{TemplateBinding Background}" Text="{Binding Source={StaticResource NumTbVm}, Path=NumTbText, UpdateSourceTrigger=PropertyChanged}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type local:ErrMsgGui}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ErrMsgGui}">
<TextBlock Text="{ Binding Source={StaticResource val}, Path=ErrMsgGuiText, UpdateSourceTrigger=PropertyChanged}" Background="{TemplateBinding Background}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Validations.cs:
private const string ONLY_NUMBERS_REGEX = #"^[0-9]+$"; //Nur Zahlen
private string _ErrMsgGuiText;
public string ErrMsgGuiText
{
get { return _ErrMsgGuiText; }
set
{
_ErrMsgGuiText = value;
Debug.Print("QueryText: " + value);
OnPropertyChanged("ErrMsgGuiText");
}
}
public object[] onlyNumbers(string s2c, bool output)
{
object[] objRes = new object[2];
bool result = true;
string errMsg = "";
Regex regex = new Regex(ONLY_NUMBERS_REGEX);
if (s2c != null && s2c != "" && !regex.IsMatch(s2c))
{
result = false;
errMsg = "Nur Zahlen sind zulässig";
}
objRes[0] = result;
objRes[1] = errMsg;
if (output == true)
{
ErrMsgGuiText = errMsg;
}
return objRes;
}
public void onlyNumbers(string s2c)
{
onlyNumbers(s2c, true);
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
protected void OnPropertyChanged(string prop)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
NumTbViewModel.cs:
Validations val = null;
public NumTbViewModel()
{
val = new Validations();
}
private string _NumTbText;
public string NumTbText
{
get { return _NumTbText; }
set
{
_NumTbText = value;
this.OnPropertyChanged("NumTbText");
val.onlyNumbers(_NumTbText);
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
protected void OnPropertyChanged(string prop)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
It looks like the TextBlock source is looking at a static resource for the Validations class and the Validations called in your NumTbViewModel is NOT the same as the static resource. A solution could be to add a property to NumTbViewModel.cs and point your binding to that property so the Validations class instances will be the same. In NumTbViewModel.cs add:
Validations _val;
public Validations Val
{
get { return _val; }
set
{
_val = value;
this.OnPropertyChanged("Val");
}
}
Change your source and path in xaml binding on the TextBlock:
<TextBlock Text="{ Binding Source={StaticResource NumTbVm}, Path=Val.ErrMsgGuiText, UpdateSourceTrigger=PropertyChanged}" Background="{TemplateBinding Background}"/>
Another way:
You could also set the Val property of your NumTbViewModel when you define your static resource like so:
<local:Validations x:Key="val" />
<local:NumTbViewModel x:Key="NumTbVm" Val="{StaticResource val}" />
Doing this you can keep the bindings like you originally had.

Utilizing Override in UserControl to Change Property Breaks Trigger

I have created an instance of a TextBox that implements ICommandSource, I would like to control the IsEnabled property via the DataContext. This portion of my code works, on top of this I would like to control the Text property via this same method or by extension the IsEnabled property.
Basically when the TextBox transitions from IsEnabled="False" to IsEnabled="True" I would like to reset the Text field to an empty string or preferably null.
I have attempted to do this in a handful of ways without success.
Attempt 1
<ctrl:CommandTextBox x:Name="txtSerialNumber"
Command="{Binding VMFactory.CreateViewModelCommand, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
CommandParameter="{Binding Text, RelativeSource={RelativeSource Self}}" DecoderPrefix="S">
<ctrl:CommandTextBox.Style>
<Style TargetType="{x:Type ctrl:CommandTextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding}" Value="{x:Null}">
<Setter Property="IsEnabled" Value="True" />
<Setter Property="Text" Value="{x:Null}" />
</DataTrigger>
</Style.Triggers>
<Setter Property="IsEnabled" Value="False" />
<Setter Property="Text" Value="{Binding SerialNumber, Mode=OneWay}" />
</Style>
</ctrl:CommandTextBox.Style>
</ctrl:CommandTextBox>
This does work but only when the CommandParameter does not need to be "Decoded". It seems as though when my text property is changed via the override it breaks the trigger until the application is restarted.
CommandTextBox.cs
public class CommandTextBox : DecoderTextBox, ICommandSource
{
// Additional Fields, Properties, and Methods removed for the sake of brevity.
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (e.Key == Key.Enter && Command != null)
{
RoutedCommand command = Command as RoutedCommand;
if (command != null)
command.Execute(CommandParameter, CommandTarget);
else
Command.Execute(CommandParameter);
if (CommandResetsText)
this.Text = String.Empty;
e.Handled = true;
}
}
}
DecoderTextBox.cs
public class DecoderTextBox : TextBox
{
public static DependencyProperty DecoderPrefixProperty = DependencyProperty.Register("DecoderPrefix", typeof(string), typeof(DecoderTextBox), new PropertyMetadata(String.Empty));
public string DecoderPrefix
{
get { return (string)GetValue(DecoderPrefixProperty); }
set { SetValue(DecoderPrefixProperty, value); }
}
protected override void OnKeyDown(KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
string text = this.Text;
// If the if statement returns true the trigger will break.
if (text.Substring(0, Math.Min(DecoderPrefix.Length, text.Length)) == DecoderPrefix)
this.Text = text.Remove(0, DecoderPrefix.Length);
}
base.OnKeyDown(e);
}
}
Is there something specific to my implementation of OnKeyDown that is breaking this trigger?
There is an issue related to setting the value of a DependencyProperty locally. It appears as though you have to use SetCurrentValue to maintain the binding.
DecoderTextBox.cs
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
if (Text.StartsWith(DecoderPrefix))
SetCurrentValue(TextProperty, Text.Remove(0, DecoderPrefix.Length));
}
base.OnPreviewKeyDown(e);
}

Set TextBlock Visibility based on a bound bool

I am trying to link the visibility of a TextBlock to a bool property which is also linked to a checkbox using WPF and c#. I have the following code in two different sections of the same xaml file (one section is a summary, and the other is settings. I am very new to WPF, and am learning as I go. Currently, the TextBlock is visible no matter what the value of IsSecondaryMessageFilePath is.
<TextBlock Name="secondaryfolderinfo" Foreground="Red">
<ContentControl Content="Secondary message folder" Foreground ="Black" />
<ContentControl Content = "{Binding Path=SecondaryMessageFilePath}" ContentStringFormat="" ClipToBounds="False"></ContentControl>
<ContentControl Content = " "></ContentControl>
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsSecondaryMessageFilePath}" Value="True">
<Setter Property="Visibility" Value="Visible"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
Further down I have:
<CheckBox IsChecked="{Binding Path=IsSecondaryMessageFilePath, Mode=TwoWay}"
Name="SecondaryPathCheckBox"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Margin="320,7,0,0">Save additional locations</CheckBox>
Finally, in the code-behind, I have:
public bool IsSecondaryMessageFilePath
{
get { return _isSecondaryMessageFilePath; }
set
{
if (_isSecondaryMessageFilePath != value)
{
_isSecondaryMessageFilePath = value;
OnPropertyChanged("IsSecondaryMessageFilePath");
}
}
}
private bool _isSecondaryMessageFilePath;
public string SecondaryMessageFilePath
{
get { return _secondaryMessageFilePath; }
set
{
if (_secondaryMessageFilePath != value)
{
_secondaryMessageFilePath = value;
OnPropertyChanged("SecondaryMessageFilePath");
}
}
}
private string _secondaryMessageFilePath;
Any assistance would be appreciated.
EDIT
Working from the suggestion below, I tried adding the BooleanToVisibilityConverter, but am getting a missing assembly reference for it, and am to new to WPF to figure out how to resolve it. My opening code is as follows:
<UserControl x:Class="Sender_Receiver.SenderReceiverSetup"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:diagnostics="clr-namespace:System.Diagnostics;assembly=WindowsBase"
xmlns:m=...
xmlns:
<UserControl.Resources>
<BooleanToVisibiltyConverter x:Key="BooleanToVisibilityConverter"/>
...
Your code looks ok at first glance, but you really don't need to use a data trigger for this. WPF comes with a BooleanToVisibilityConverter class that you declare in your resources:
<BooleanToVisibiltyConverter x:Key="BooleanToVisibilityConverter"/>
Then in your TextBlock, you bind Visibility:
<TextBlock Visibility="{Binding Path=IsSecondaryMessageFilePath, Converter={StaticResource BooleanToVisibilityConverter}}"/>
Just so you know, there may be a simpler way to do this, just bind to the IsChecked property itself!
<CheckBox x:Name="UseSecondaryPath"/>
<TextBlock Visibility="{Binding ElementName=UseSecondaryPath, Path=IsChecked, Converter={StaticResource BooleanToVisibilityConverter}}"/>
Of course if you need the bool for something else that wouldn't be an ideal solution, but it is a little cleaner if its just for the UI.
The code for a custom BooleanToVisibilityConverter, if you are interested, is:
public class BooleanToVisibilityConverter : IValueConverter
{
public object Convert (object value, ...)
{
if ((bool)value)
return Visibility.Visible;
else
return Visibility.Collapsed;
}
public object ConvertBack(object value, ...)
{
return Binding.DoNothing;
}
}
Let me know if I can clarify anything or assist further.
private Boolean _IsChecked;
//Bind this to your checkbox
public Boolean IsChecked
{
get { return _IsChecked; }
set { _IsChecked= value; OnPropertyChanged("IsChecked"); OnPropertyChanged("TextBoxVis"); }
}
//Bind this to your TextBox's Visibility Property
public Visibility TextBoxVis
{
get { return IsChecked ? Visibility.Visible : Visibility.Collapsed; }
}

How to show a tooltip on invalid input into a textbox

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>

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