WPF TextChanged event fired only when I change the value - wpf

In my project I have several textboxes with a method binded to TextChanged event:
<TextBox Grid.Column="12" Style="{StaticResource txtDataStyle1}" Width="100" TextChanged="Data_TextChanged">
<Binding Path="ConfigObject.Edit.Default" UpdateSourceTrigger="LostFocus">
<Binding.ValidationRules>
<local:GenericValidationRule>
<local:GenericValidationRule.Wrapper>
<local:Wrapper TipoInterno="{Binding Path=Content, Source={x:Reference txtTipo}}"/>
</local:GenericValidationRule.Wrapper>
</local:GenericValidationRule>
</Binding.ValidationRules>
</Binding>
</TextBox>
When the page loads and the textboxes are valorized with "ConfigObject.Edit.Default", the Data_TextChanged event is fired. How can omit it?
I would "use" that method only when I change its value. Any help?

Instead of handling the the TextChanged event in the view, you should implement your logic in the setter of the source property.
If you can't do this for some reason, you could simply return from the event handler if the TextBox hasn't yet been loaded:
private void Data_TextChanged(object sender, TextChangedEventArgs e)
{
TextBox textBox = (TextBox)sender;
if (textBox.IsLoaded)
{
//your code...
}
}

Related

WPF MulitBinding of Slider and TextBox not updating property

I'm trying to use MultiBinding to bind a slider to a textbox (which works) and bind the TextBox to a property (which doesn't work). The TextBox/Property binding works fine with single binding, but when I introduce MultiBinding, it breaks.
Here's my XAML
<Slider
Name="SliderExportQuality"
Value="100"
Minimum="0"
Maximum="100"
HorizontalAlignment="Left"
Margin="10,5,0,0"
VerticalAlignment="Top"
Width="239"/>
<TextBox>
<TextBox.Text>
<MultiBinding StringFormat="N2">
<Binding ElementName="SliderExportQuality" Path="Value"/>
<Binding Path="ExportQuality" UpdateSourceTrigger="PropertyChanged"/>
</MultiBinding>
</TextBox.Text>
</TextBox>
Here's the dialog box. The TextBox is trimmed with red after I try entering a value directly into it, which is telling me something's wrong?
I read up a bit on MultiBinding and think I may be going awry with my Converter but am clueless with what it should be.
As others have mentioned in the comments, unless I am misunderstanding the question, you should not need a multibinding to accomplish what you are trying to do. To get the slider value to display in the text box ( and the other way around ) you just need to bind the value to a common property in your view model.
For example, given the following xaml:
<Grid Margin="20" VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<Slider Grid.Column="0" Minimum="0" Maximum="100" Value="{Binding FileSize}"/>
<TextBox Grid.Column="1" Text="{Binding FileSize}"/>
</Grid>
You have a slider which binds its value to the FileSize property in your view model.
The associated ViewModel:
class MainWindowViewModel : INotifyPropertyChanged
{
public int FileSize
{
get
{
return mFileSize;
}
set
{
if(mFileSize != value)
{
mFileSize = value;
OnPropertyChanged(nameof(FileSize));
}
}
} private int mFileSize = 50;
private void OnPropertyChanged(String propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
public event PropertyChangedEventHandler PropertyChanged;
}
This allows the value to be changed by either the slider, or by typing directly into the text box. There still needs to be error checking on the text box as a user could type in anything... But this shows the basic concept.
This produces the following UI.
I hope that addresses the question you were asking.
When you use that Binding it will try to set the "75" (notice it's string) and value of ExportQuality (don't know the type of this though) on the Slider.Value property, which is double, it has red border because the type is wrong (i.e. TextBox is trying to use incorrect type for the Binding, this also happens if you Bind TextBox to an int property and you type in "a"), should you use a converter this is the case when it will ConvertBack();
FYI MultiValueConverter would be used in this case.
What I think you wanted to use here was PriorityBinding. Which would be used like this:
<TextBox>
<TextBox.Text>
<PriorityBinding>
<Binding ElementName="SliderExportQuality" Path="Value"/>
<Binding Path="ExportQuality" UpdateSourceTrigger="PropertyChanged"/>
</PriorityBinding>
</TextBox.Text>
</TextBox>
This way if the first Binding fails the second will kick in.

How do I properly implement Textbox Validation

I am new to WPF and creating an application which as few text boxes. Text boxes are bound to some source using MVVM. Now when I click on save button, it should fire validation for all empty text boxes and save event should not be fired. How can I achieve this in WPF.
I have written validater but it is not called. See my code below:
<TextBox Width="250" Grid.Row="0" Grid.Column="1" Margin="10">
<TextBox.Text>
<Binding Path="ContinuousModel.FileName" ValidatesOnDataErrors="True" NotifyOnValidationError="True" Mode="TwoWay" ValidatesOnExceptions="True">
<Binding.ValidationRules>
<validate:RequiredFieldValidatation />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
public class RequiredFieldValidatation:ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
if (value == null || value.ToString() == string.Empty)
return new ValidationResult(false, "Value cannot be empty");
return ValidationResult.ValidResult;
}
}
The above validator is getting called only if some text is written then change focus then come back and remove value and then change focus.
NOTE: Using MVVM and datatemplates, I am loading varios user controls and those controls are bound to viewmodel. Save button is in different user control so I cannot validate all text boxes manually on save button click event.
Add the UpdateSourceTrigger property to your binding;
<Binding Path="ContinuousModel.FileName" UpdateSourceTrigger=PropertyChanged ValidatesOnDataErrors="True" NotifyOnValidationError="True" Mode="TwoWay" ValidatesOnExceptions="True">
<Binding.ValidationRules>
<validate:RequiredFieldValidatation />
</Binding.ValidationRules>
</Binding>

WPF validation launched on focus gained

I'm developing a IDataErrorInfo to validate the textboxes I have inside my application. I have the following code:
The .cs class to validate:
public class UserInformation : IDataErrorInfo
{
public string _name;
public string _surname;
public string Name
{
get { return _name; }
set { _name = value; }
}
public string Surname
{
get { return _surname; }
set { _surname = value; }
}
public override string ToString()
{
return Name + " " + Surname;
}
public string this[string columnName]
{
get
{
if (columnName == null) return string.Empty;
string result = string.Empty;
if (columnName.Equals("Name"))
{
if (string.IsNullOrEmpty(_name))
result = "Name cannot be empty.";
}
return result;
}
}
public string Error { get; private set; }
}
The .xaml:
<TextBox Grid.Column="3" Grid.Row="0" Name="TextBoxName"
Style="{DynamicResource InnerTextBox}"
Validation.ErrorTemplate="{StaticResource ValidationErrorTemplate}">
<TextBox.Text>
<Binding Path="Name" Source="{StaticResource UserInformation}"
ValidatesOnDataErrors="True" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<ExceptionValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
And the ErrorTemplate:
<ControlTemplate x:Key="ValidationErrorTemplate">
<DockPanel >
<StackPanel Orientation="Horizontal" DockPanel.Dock="Top">
<Grid Width="20" Height="20">
<Ellipse Width="20" Height="20" Fill="Tomato" HorizontalAlignment="Center" VerticalAlignment="Center" />
<TextBlock Foreground="White" FontWeight="Heavy" FontSize="8" HorizontalAlignment="Center" VerticalAlignment="Center" TextAlignment="Center"
ToolTip="{Binding ElementName=ErrorAdorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">X</TextBlock>
</Grid>
<TextBlock Foreground="Tomato" FontWeight="12" Margin="2,0,0,0" FontSize="20"
Text="{Binding ElementName=ErrorAdorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}" />
</StackPanel>
<AdornedElementPlaceholder x:Name="ErrorAdorner" />
</DockPanel>
</ControlTemplate>
The code works fine when I'm typing. But when the TextBox is loaded, the validation occurs too. And I don't want it to happen when it gains focus, only when it looses it or I change the text (like the one published here).
How can I avoid the validation error to be considered on first TextBox load?
NOTE: Even if I set the UpdateSourceTrigger to LostFocus, it is still making the validations.
To acheive you goal you need to:
First, remove ValidatesOnDataErrors="True" on your Binding. As said in docs:
Setting this property provides an alternative to using the
DataErrorValidationRule element explicitly
And we're gonna use it explicitly. Then use DataErrorValidationRule instead of ExceptionValidationRule for correctly working with IDataErrorInfo and data errors.
And last, we need to use some properties that this rule gives us:
<Binding.ValidationRules>
<DataErrorValidationRule ValidatesOnTargetUpdated="False" />
</Binding.ValidationRules>
ValidatesOnTargetUpdated on false will not trigger validation when target itself changes (i.e. on load). You can also play with ValidationStep property for additional control.
Edit:
Ok, I see that you need to skip validation on load and you need to validate on lost focus even if the value was not changed. Well, validation rules does not support that, because if the value was not updated, then no changed events will be called and no validation will occur, regardless of UpdateSourceTrigger setting.
The easy way out is to emulate this functionality by adding LostFocus handler to TextBox itself:
private void ValidatedTextBox_LostFocus(object sender, RoutedEventArgs e)
{
var txt = (TextBox)sender;
txt.GetBindingExpression(TextBox.TextProperty).UpdateSource();
}
If you need this for several TextBoxes, you can move the code to some static class.
The same results can be achieved using Explicit update source trigger, wich can be a little bit more cleaner.
I dont have any example atm, because I moved to what you have. But You will need to create a class which will Inherit from ValidationRule which exist in system.windows.controls, and then override Validate method.
Then your xaml textbox would look something like this instead
<TextBox.Text>
<Binding Path="your binding here" UpdateSourceTrigger="LostFocus" >
<Binding.ValidationRules>
<validationClass:yourRule/> define this at the top of xaml page
</Binding.ValidationRules>
</Binding>
You should be able to find examples on msdn, and here about validation rules

How to clear a TextBox in MVVM?

I have a TextBox in a DataTemplate declared as follows:
<TextBox Grid.Row="1" Grid.Column="1" Margin="0,4,0,0">
<i:Interaction.Triggers>
<i:EventTrigger EventName="LostFocus">
<cmd:EventToCommand Command="{Binding DataContext.NotesEnteredCommand,
RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}">
<cmd:EventToCommand.CommandParameter>
<MultiBinding Converter="{StaticResource SimpleMultiValueConverter}">
<Binding Path="Row.OID" />
<Binding Path="Text" RelativeSource="{RelativeSource FindAncestor, AncestorType=TextBox}" />
</MultiBinding>
</cmd:EventToCommand.CommandParameter>
</cmd:EventToCommand>
</i:EventTrigger>
</i:Interaction.Triggers>
<TextBox.InputBindings>
<KeyBinding Key="Enter" Command="{Binding DataContext.NotesEnteredCommand, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}">
<KeyBinding.CommandParameter>
<MultiBinding Converter="{StaticResource SimpleMultiValueConverter}">
<Binding Path="Row.OID" />
<Binding Path="Text" RelativeSource="{RelativeSource FindAncestor, AncestorType=TextBox}" />
</MultiBinding>
</KeyBinding.CommandParameter>
</KeyBinding>
</TextBox.InputBindings>
What this TextBox basically does is execute a MVVM-Light RelayCommand when the Enter key is pressed or when losing focus.
My problem is that I cannot figure out a way in MVVM to clear the TextBox's Text value through XAML in the above two scenarios. It's very easy with in code-behind, but I can't figure it out in MVVM.
Any ideas?
If the text is part of your data layer and application logic, a string should exist in your Model or ViewModel and be cleared from there
For example,
<TextBox Text="{Binding NewNote}" ... />
and
void NotesEntered(int oid)
{
SaveNewNote(oid);
NewNote = string.Empty;
}
If it's part of the UI layer only, it should just be cleared with code-behind. It's perfectly acceptable to have UI-specific logic in the code-behind the UI, as that still maintains the separation of layers.
NewNoteTextBox_LostFocus(object sender, EventArgs e)
{
(sender as TextBox).Text = string.Empty;
}
NewNoteTextBox_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Keys.Enter)
(sender as TextBox).Text = string.Empty;
}
You can use a UpdateSourceHelper. This really helped me out calling an event with no code-behind.
See here http://www.wiredprairie.us/blog/index.php/archives/1701
All you have to do is create a class "UpdateSourceHelper", connect it with your xaml like this
xmlns:local="using:WiredPrairie.Converter
and bind it to your TextBox (or whatever you want to bind to)...
<TextBox Height="Auto" Margin="0,6" Grid.Row="1" TextWrapping="Wrap" TabIndex="0"
Text="{Binding Value}"
local:UpdateSourceHelper.IsEnabled="True"
local:UpdateSourceHelper.UpdateSourceText="{Binding Value, Mode=TwoWay}"/>
If you want call LostFocus Event in the Helper, you simply have to add these 2 lines in your Helper:
tb.LostFocus += AttachedTextBoxLostFocus;
tb.LostFocus -= AttachedTextBoxLostFocus;
So it would look like this:
TextBox tb = (TextBox)obj;
if ((bool)args.NewValue)
{
tb.LostFocus += AttachedTextBoxLostFocus;
}
else
{
tb.LostFocus -= AttachedTextBoxLostFocus;
}
Right click on AttachedTextBoxLostFocus and generate the method. Now you can handle the Event like a code-behind event.

TextBox BindingExpression UpdateSourceTrigger=Explicit

Need a TextBox in a ListView DataTemplate to call set on either LostFocus or enter key. Used UpdateSourceTrigger = Explicit and events for LostFocus and KeyUp. Problem is that I cannot get a valid reference to the BindingExpression.
XAML
<ListView x:Name="lvMVitems" ItemsSource="{Binding Path=DF.DocFieldStringMVitemValues, Mode=OneWay}">
<ListView.View>
<GridView>
<GridViewColumn x:Name="gvcExistingValue">
<GridViewColumnHeader Content="Value"/>
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox x:Name="tbExistingValue"
Text="{Binding Path=FieldItemValue, Mode=TwoWay, ValidatesOnExceptions=True, ValidatesOnDataErrors=True, NotifyOnValidationError=True, UpdateSourceTrigger=Explicit}"
Validation.Error="Validataion_Error"
LostFocus="tbExistingValue_LostFocus" KeyUp="tbExistingValue_KeyUp" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
Code Behind NOT working
private void tbExistingValue_LostFocus(object sender, RoutedEventArgs e)
{
BindingExpression be = lvMVitems.GetBindingExpression(ListView.SelectedItemProperty);
be.UpdateSource();
}
be is null. I have tried ListView.SelectedValueProperty and ListView.SelectedPathProperty. If it try tbExistingValue it fails with a message "does not exists" and will not even compile. How do I get the proper BindingExpression?? Thanks.
If I set UpdateSourceTrigger = LostFocus and remove the event handlers it does call set properly. There is a valid twoway binding there. I just cannot get a valid reference to BindingExpression (be) using explicit.
It works fine for a TextBox directly on page (in a grid cell). The xaml below works:
<TextBox Grid.Row="1" Grid.Column="1" x:Name="strAddRow"
Text="{Binding Path=DF.NewFieldValue, Mode=TwoWay, ValidatesOnExceptions=True, NotifyOnValidationError=True, UpdateSourceTrigger=Explicit}"
Validation.Error="Validataion_Error"
LostFocus="strAddRow_LostFocus" KeyUp="strAddRow_KeyUp"/>
This BindingExpression works fine:
private void strAddRow_LostFocus(object sender, RoutedEventArgs e)
{
BindingExpression be = strAddRow.GetBindingExpression(TextBox.TextProperty);
be.UpdateSource();
}
Since you apply the binding on your Textbox's Text DP, so you need to fetch the binding from there only like this -
private void tbExistingValue_LostFocus(object sender, RoutedEventArgs e)
{
BindingExpression be = (sender as TextBox).GetBindingExpression(TextBox.TextProperty);
be.UpdateSource();
}
Moreover, you haven't bind the ListView SelectedItem with any property of your ViewModel. To retrieve the binding, it should be atleast binded to some value. So, you should bind it to your FieldValueProperty then you won't get null value with your code in place.
You do not need to use UpdateSourceTrigger on TextBox using the event LostFocus.
It's the functioning by default.
Answer here : https://msdn.microsoft.com/en-gb/library/system.windows.data.binding.updatesourcetrigger(v=vs.110).aspx

Resources