WPF Validation Rule on ComboBox calling the Setter of binded propery twice - c#

I have a ComboBox like this:
<ComboBox Name="TipoVisitante" ItemsSource="{Binding TiposVisitante}" SelectedValue="{Binding TipoVisitante}" Style="{StaticResource ComboBoxStyle}">
<ComboBox.Text>
<Binding Path="TipoVisitante" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<validations:SimpleIsRequiredValidation/>
</Binding.ValidationRules>
</Binding>
</ComboBox.Text>
</ComboBox>
This ComboBox is binded to the TipoVisitante property from the ViewModel. The TipoVisitante variable looks like this:
private string _TipoVisitante;
public string TipoVisitante {
get =>_TipoVisitante;
set {
if (ValidarTipoVisita(value) == true) {
_TipoVisitante = value;
OnPropertyChanged();
}
else {
MessageBox.Show("YA EXISTE UNA VISITA ÍNTIMA ACTIVA", "VISITA INTIMA ACTIVA", MessageBoxButton.OK, MessageBoxImage.Error);
_TipoVisitante = null;
OnPropertyChanged();
}
}
}
When TipoVisitante is set, I want to check if the value is valid using a method that return true or false depending on which the value is valid or not. If the the values isn't valid, I show a message saying that the value is not valid. The problem is, because of the validation on rule on the ComboBox, the set property of the TipoVisitante variable is called twice, and the error message is shown twice. The Validation Rule of the ComboBox looks like this:
public class SimpleIsRequiredValidation: ValidationRule {
public override ValidationResult Validate(object value, CultureInfo cultureInfo) {
if (!string.IsNullOrEmpty(value ? .ToString()))
return new ValidationResult(true, null);
else
return new ValidationResult(false, null);
}
}
The validation rule checks if the user has selected some value of the combobox, checking if the text is null or a empty string. Also in my code behind I have this:
private void Button_Click(object sender, RoutedEventArgs e) {
if (string.IsNullOrWhiteSpace(TipoVisitante.Text))
TipoVisitante.Text = "";
}
This method is the Click property of a button. When the button is click, I check if the user hasn't selected some value of the ComboBox I set the text to empty string to trigger the validation and show the red border in the ComboBox.
How can I avoid the setter of the binded variable TipoVisitante to be called twice?

Try to add extra property that check was it called once then use it to block call it twice.

The solution was change the validation from ComboBox.Text to ComboBox.SelectedValue. After that in the button Click action, change TipoVisitante.Text = "" to TipoVisitante.SelectedValue = null. Doing that it worked fine.

Related

Combobox value changes, but visually SelectedValue stays the same

I have a combobox with a custom enum (just true/false). I have a function that checks conditions if the SelectedValue changes from false to true and if the conditions are wrong it changes the combobox SelectedValue back to false. This changes the SelectedValue to false if you check it in code, but when you look at the UI it's still on true.
Here's the xaml for the combobox:
<ComboBox x:Name="comboEnabled1" Width="80" Height="26"
ItemsSource="{Binding Path=TrueFalseChoices}"
SelectedValue="{Binding Path=Enable1, Mode=TwoWay}"/>
Here's the viewmodel
private TrueFalse _enable1 = TrueFalse.False;
public TrueFalse Enable1
{
get { return _enable1; }
set
{
if (_enable1 != value)
{
_enable1 = value;
base.OnPropertyChanged("Enable1");
OnEnableChanged(EventArgs.Empty);
}
}
}
And here's the function that I'm using to check the conditions
public void HandleEnable(object sender, EventArgs e)
{
if(Enable1 == TrueFalse.True)
{
if(!connected)
{
HandleMessage("Can't enable, not connected");
Enable1 = TrueFalse.False;
}
else if (!_main.CBCheck(_main.cbReason))
{
Enable1 = TrueFalse.False;
}
}
Console.WriteLine("Enabled {0}", Enable1);
}
Was thinking I'm changing the value too rapidly, but the last Console.Writeline produces the right outcome each time.
Any help appreciated!
Edit: Calling Handleenable here:
protected void OnEnableChanged(EventArgs e)
{
EventHandler handler = EnableChanged;
if (handler != null)
handler(this, e);
}
And in the ViewModel funct:
EnableChanged += HandleEnable;
Changing the Enable1 in any other place worked as it should have, only having issues in HandleEnable function.Also tried changing other comboboxes in the HandleEnable function and that worked as it should have.
I would recommend actually disabling the ComboBox if the requirements are not met.
But if you insist on reverting Enable1 back to False if conditions are not met, you should push the notification properly through the dispatcher.
set
{
var effectiveValue = condition ? value : TrueFalse.False;
if (effectiveValue == TrueFalse.False && value == TrueFalse.True)
System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke(
new Action(() => base.OnPropertyChanged("Enable1"), null));
//your regular set-code follows here
}
It happens because WPF is already responding to that event, and therefore ignoring the subsequent calls until it's done. So you immediately queue another pass as soon as the current one is finished.
But I would still recommend disabling the ComboBox when it is effectively disabled. Accessing the dispatcher from a viewmodel does not smell good no matter how you look at it.
UPD: You can also solve that with {Binding Enable1, Delay=10} if your framework is 4.5.1+.

TargetNullValue = '' not working WPF C#

I have the following textbox , not sure why the formatting won't go to normal but you get the point.
<TextBox LostFocus="TextBox_LostFocus">
<TextBox.Text>
<Binding Path="InputVolts"
TargetNullValue=''
FallbackValue=''>
<Binding.ValidationRules>
<u:NonEmptyStringValidator/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
This textbox is bound to the following property:
public int? InputVolts
{
get { return _InputVolts; }
set { Set(ref _InputVolts, value);}
}
In my form, if I type a number like 240 into the textbox the view model will update with that value. If I try and delete the 240 from the textbox, the view model does not update the InputVolts property accordingly.
I am aware of the TargetNullValue solution from the following Post
EDIT:
Okay I found the problem, my NonEmptyStringValidator is causing this issue. I want to still have this validation rule on my text box though. Is there anyway to adjust this to still keep the validation rule but make the textbox return back to null when deleted?
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
ValidationResult validationResult = new ValidationResult(false, "Value cannot be empty.");
if (value != null)
{
string valueAsString = value as string;
if (valueAsString.Length > 0)
validationResult = ValidationResult.ValidResult;
}
return validationResult;
}
I also tried using the FallBackValue and nothing seems to be working. Any help would be greatly appreciated!
Thank you,
The <u:NonEmptyStringValidator/> might prevent the update to an empty string.

RaisePropertyChanged fails to update the UI

I've been working on an application in MVVM Light lately. I have a TextBox in my XAML bound to my UI. I'd like to validate any input and ensure that only numbers are entered. I've tried the following code:
My TextBox:
<TextBox TabIndex="1" Height="23" MinWidth="410" DockPanel.Dock="Left"
HorizontalAlignment="Left"
Text="{Binding Input, UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{Binding IsEnabled}"
AcceptsReturn="False"
local:FocusExtension.IsFocused="{Binding IsFocused}">
And in my ViewModel:
private string input;
public string Input
{
get { return this.input; }
set
{
decimal test;
if(decimal.TryParse(value, out test))
{
this.input = value;
}
else
{
this.input = "";
}
RaisePropertyChanged("Input");
}
}
This fails to update the UI. If I enter "B" and check the debugger, it runs through the setter, but fails to actually update the UI.
Curiously, if I set this.input = "TEST"; in the else block, the UI updates, but, if I attempt to set it to "", string.Empty, or the value of input before the validation, the UI fails to update.
Is this by design? Possibly a bug? Is there something I'm doing wrong?
Edit I mistakenly forgot to include RaisePropertyChanged in my example code. I've updated it. Raising it isn't the problem as I've watched the debugger run all the way through raising it and returning input via the getter.
Way you use strign type property and then convert to decimal, easier to change lik this:
public decimal Input
{
get { return this.input; }
set
{
this.input = value;
RaisePropertyChanged("Input");
}
}
And for validate use IDataErrorInfo (read more: http://blogs.msdn.com/b/wpfsdk/archive/2007/10/02/data-validation-in-3-5.aspx)
What we have done is created a Custom Control, since we use it for a Currency Text Box. I warn you I have no validation that this is a good idea, or falls in line with MVVM model because all manipulation of the control are done in code behind.
In the control on the textbox we have an event on PreviewTextInput that does this
e.Handled = Functions.DoubleConverter(Convert.ToChar(e.Text), ((TextBox)sender).Text.Replace("$", ""));
Then for the function (which isnt perfect, I have a few issues with it still) is:
static public bool DoubleConverter(char c, string str)
{
if (!char.IsDigit(c))
{
if (c == '.' && (str.Contains('.')))
{
return true;
}
else if (c != '.')
{
return true;
}
}
return false;
}
Please use this as a reference, not exactly as is because it is a very crude implementation.

Two way binding settings problem

I am having a problem using two way binding with a listpicker. I am able to set the value using c# but not in the SelectedItem=".." in xaml. The binding is returning the correct value (and is a value in the listpicker) as i have texted it by assigning the text to a textblock.
When the page loads, the binding used on the listpicker causes a System.ArgumentOutOfRangeException
The code i am using to set it is:
// Update a setting value. If the setting does not exist, add the setting.
public bool AddOrUpdateValue(string key, Object value)
{
bool valueChanged = false;
try
{
// If new value is different, set the new value
if (settingsStorage[key] != value)
{
settingsStorage[key] = value;
valueChanged = true;
}
}
catch (KeyNotFoundException)
{
settingsStorage.Add(key, value);
valueChanged = true;
}
catch (ArgumentException)
{
settingsStorage.Add(key, value);
valueChanged = true;
}
catch (Exception e)
{
Console.WriteLine("Exception occured whilst using IsolatedStorageSettings: " + e.ToString());
}
return valueChanged;
}
// Get the current value of the setting, if not found, set the setting to default value.
public valueType GetValueOrDefault<valueType>(string key, valueType defaultValue)
{
valueType value;
try
{
value = (valueType)settingsStorage[key];
}
catch (KeyNotFoundException)
{
value = defaultValue;
}
catch (ArgumentException)
{
value = defaultValue;
}
return value;
}
public string WeekBeginsSetting
{
get
{
return GetValueOrDefault<string>(WeekBeginsSettingKeyName, WeekBeginsSettingDefault);
}
set
{
AddOrUpdateValue(WeekBeginsSettingKeyName, value);
Save();
}
}
And in the xaml:
<toolkit:ListPicker x:Name="WeekStartDay"
Header="Week begins on"
SelectedItem="{Binding Source={StaticResource AppSettings},
Path=WeekBeginsSetting,
Mode=TwoWay}">
<sys:String>monday</sys:String>
<sys:String>sunday</sys:String>
</toolkit:ListPicker>
The StaticResource AppSettings is a resource from a seperate .cs file.
<phone:PhoneApplicationPage.Resources>
<local:ApplicationSettings x:Key="AppSettings"></local:ApplicationSettings>
</phone:PhoneApplicationPage.Resources>
Thanks in advance
I used Reflector to find the source of this exception. In ListPicker.cs the following method is overridden.
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
In this method the following line will cause the exception if SelectedItem is set and SelectedIndex is -1 (which it is unless it's set before it's loaded). If SelectedItem isn't set this line is never reached (hence no exception).
else if (!object.Equals(base.get_Items().get_Item(this.SelectedIndex), this.SelectedItem))
To work around this (until they get this fixed) I have some suggestions.
Workaround 1
If you know the starting index which will be produced by the TwoWay binding then you can set the SelectedIndex property as well and the TwoWay Binding will work
<toolkit:ListPicker x:Name="WeekStartDay"
Header="Week begins on"
SelectedItem="{Binding Source={StaticResource AppSettings},
Path=WeekBeginsSetting,
Mode=TwoWay}"
SelectedIndex="1">
<sys:String>monday</sys:String>
<sys:String>sunday</sys:String>
</toolkit:ListPicker>
Workaround 2
Use the Loaded event and set the Binding from there instead
<toolkit:ListPicker x:Name="WeekStartDay"
Header="Week begins on"
Loaded="WeekStartDay_Loaded">
<sys:String>monday</sys:String>
<sys:String>sunday</sys:String>
</toolkit:ListPicker>
private void WeekStartDay_Loaded(object sender, RoutedEventArgs e)
{
Binding binding = new Binding();
binding.Source = this.Resources["AppSettings"] as ApplicationSettings;
binding.Path = new PropertyPath("WeekBeginsSetting");
binding.Mode = BindingMode.TwoWay;
WeekStartDay.SetBinding(ListPicker.SelectedItemProperty, binding);
}
Are you Firing the relevant property changed events?
Make sure that SelectedItem can have a two way binding.If not then try defining an ItemContainerStyle and bind the IsSelected property to a corresponding property on your object.Getting the selected item then becomes trivial.
If AppSettings is a collection then this is not going to work. You need to bind SelectedItem to a scalar value and unfortunately the "Silverlight 3.7" on WP7 doesn't support indexers in bindings.
Also, please don't use exceptions as flow control in your program, instead do something like this:
try
{
// If new value is different, set the new value
if(!settingsStorage.ContainsKey(key))
{
settingsStorage.Add(key, value);
valueChanged = true;
}
else if(settingsStorage[key] != value)
{
settingsStorage[key] = value;
valueChanged = true;
}
}
catch (Exception e)
{
Console.WriteLine("Exception occured whilst using IsolatedStorageSettings: " + e.ToString());
}
Instead of using binding I simply set the selecteditem when the page loaded and used a selectionchanged event handler to update the value without confirmation (having a save button).

C# Set the visibility of a label using the result of String.IsNullOrEmpty

Hi I am trying to set the visibility of a label based on a textbox string being empty. I have the following code:
MyLabel.Visible = String.IsNullOrEmpty(MyTextBox.Text);
Why does MyLabel not appear when the textbox is left empty?
Update
I have tried placing this code in the Text_Changed event of the textbox and it still doesn't work.
This was an update issue, it does work on the Text_Changed event. However the issue is it does not work when triggered on the proccessing of the form.
Here is the code triggered from my controller class to give everyone a better insight as to what is going on:
using (var frm = new frmAdd(PersonType.Carer))
{
var res = frm.ShowDialog();
if (res == System.Windows.Forms.DialogResult.OK)
{
if (frm.ValidateInformation()) // the above code is called in here
{
// process the information here...
}
}
}
Also I forgot to mention that this form is in a Class Library project (dll).
Depends on where the code is being run. If you need interactivity, i.e. the label disappears when a character is typed into the textbox, you need to run it on the Keyup event of the textbox. You may also need to repaint the label.
If you are updating the Visible property in the text changed event you are probably running into the following problem. When the form first starts up the text is set to an empty string. But because this is the initial value it hasn't changed so to speak and hence no event is raised.
You may need to perform this update directly in your Form constructor. See if that fixes the problem.
I would add a Trim, to be more consistent to the User in case of spaces left:
MyLabel.Visible = String.IsNullOrEmpty(MyTextBox.Text.Trim());
For the rest it is a matter of triggering the code at the right time. TextChanged should cover everything but the inital state, as addressed by JaredPar. Although I would use Form_Load, not the constructor.
Edit, after the clarification: If your Label an TextBox are on frmAdd then the question is moot, the form itself is no longer shown after ShowDialog returns.
I suspect you want to use data binding to set the visibility of the label.
This discussion might help you: WPF Data Binding : enable/disable a control based on content of var?
Update, some code:
public string MyText
{
get { return _myText; }
set { _myText = value; OnPropertyChanged("MyText"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private void theTextBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
{
_myText = theTextBox.Text;
OnPropertyChanged("MyText");
}
}
[ValueConversion(typeof(string), typeof(System.Windows.Visibility))]
public class StringToVisibilityConverter : System.Windows.Data.IValueConverter
{
public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
System.Windows.Visibility vis;
string stringVal = (string)value;
vis = (stringVal.Length < 1) ? Visibility.Visible : Visibility.Hidden;
return vis;
}
public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
In the XAML:
<TextBlock Background="AliceBlue" >
<TextBlock.Visibility>
<Binding ElementName="window1" Path="MyText" Converter="{StaticResource stringToVisibilityConverter}"/>
</TextBlock.Visibility>
</TextBlock>

Categories

Resources