Enforce validation on a TexBox when text changes in WPF - c#

I'm using Validation on TextBox as follows
<TextBox BorderThickness="1" Style="{DynamicResource TextBoxInError}"
Validation.ErrorTemplate="{StaticResource ValidationTemplate}">
<TextBox.Text>
<Binding Path="TimeBeforeDeletingPicture" Mode="TwoWay">
<Binding.ValidationRules>
<helpers:TimeBeforeDeletingRule/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
The validation fires when I leave the TextBox (apparently when it looses focus), I want to validate the input every time the text changes, I'm using MVVM so I don't want to mess with events, what's the correct clean way to achieve that.

Set UpdateSourcetrigger as follows
<TextBox.Text>
<Binding Path="TimeBeforeDeletingPicture" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged"/>

Related

ConvertBack is not called

I'm trying to write a universal control in which I can pass different data models (all implement INotifyPropertyChanged) with a converter. The data is passed in the control without problems and is displayed correctly (some of it also uses IMultivalueConverts, which work flawlessly). Although the data is modified, the IMultiValueConverter of the MainControl is not called.
The universal control should just show rectangles calculated from coordinates in an ObservableCollection.
I put DebugConverters on all bindings, and everything seems to be updated except the ConvertBack to the top. The SourceUpdate from the ListBox is also called.
I tried this converter with different NotifyOn...Updated, Mode and UpdateSourceTrigger, I always see the values changing in the control, but never the ConvertBack of the Main Control.
The partial data template used (Updates are done correctly), all the DegreeTo... converters are called both ways
<DataTemplate x:Key="RectangleWithLabel">
<Canvas IsHitTestVisible="True">
<Rectangle x:Name="RectangleROI" MouseLeftButtonDown="myCanvas_PreviewMouseLeftButtonDown" >
<!--
<Rectangle.Visibility>
<Binding Path="ROI" Converter="{StaticResource NullToVisibilityConverter}"/>
</Rectangle.Visibility>
-->
<Canvas.Left>
<MultiBinding Converter="{StaticResource DegreeToScreenPixelConverterH}" Mode="TwoWay" NotifyOnSourceUpdated="True" NotifyOnTargetUpdated="True" UpdateSourceTrigger="PropertyChanged" >
<Binding Path="ROI.Begin.PosH" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged" NotifyOnSourceUpdated="True" NotifyOnTargetUpdated="True" />
<Binding Path="DataContext.UsedCoordinateSystem" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type UserControl}}" NotifyOnSourceUpdated="True" Mode="TwoWay" NotifyOnTargetUpdated="True" UpdateSourceTrigger="PropertyChanged" />
</MultiBinding>
</Canvas.Left>
<Canvas.Top>
<MultiBinding Converter="{StaticResource DegreeToScreenPixelConverterV}" Mode="TwoWay" NotifyOnSourceUpdated="True" NotifyOnTargetUpdated="True" UpdateSourceTrigger="PropertyChanged">
<Binding Path="ROI.Begin.PosV" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged" NotifyOnSourceUpdated="True" NotifyOnTargetUpdated="True" />
<Binding Path="DataContext.UsedCoordinateSystem" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type UserControl}}" NotifyOnSourceUpdated="True" Mode="TwoWay" NotifyOnTargetUpdated="True" UpdateSourceTrigger="PropertyChanged"/>
</MultiBinding>
</Canvas.Top>
<Rectangle.Width>
<MultiBinding Converter="{StaticResource DegreeToScreenPixelWidthConverter}" Mode="TwoWay" NotifyOnSourceUpdated="True" NotifyOnTargetUpdated="True" UpdateSourceTrigger="PropertyChanged">
<Binding Path="ROI.Begin.PosH" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged" NotifyOnSourceUpdated="True" NotifyOnTargetUpdated="True" />
<Binding Path="ROI.End.PosH" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged" NotifyOnSourceUpdated="True" NotifyOnTargetUpdated="True" />
<Binding Path="DataContext.UsedCoordinateSystem" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type UserControl}}" NotifyOnSourceUpdated="True" Mode="TwoWay" NotifyOnTargetUpdated="True"/>
</MultiBinding>
</Rectangle.Width>
<Rectangle.Height>
<MultiBinding Converter="{StaticResource DegreeToScreenPixelHeightConverter}" Mode="TwoWay" NotifyOnSourceUpdated="True" NotifyOnTargetUpdated="True" UpdateSourceTrigger="PropertyChanged">
<Binding Path="ROI.Begin.PosV" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged" NotifyOnSourceUpdated="True" NotifyOnTargetUpdated="True"/>
<Binding Path="ROI.End.PosV" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged" NotifyOnSourceUpdated="True" NotifyOnTargetUpdated="True" />
<Binding Path="DataContext.UsedCoordinateSystem" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type UserControl}}" NotifyOnSourceUpdated="True" Mode="TwoWay" NotifyOnTargetUpdated="True" UpdateSourceTrigger="PropertyChanged"/>
</MultiBinding>
</Rectangle.Height>
<Rectangle.Fill>#33FF0000</Rectangle.Fill>
<Rectangle.Stroke>#FF00FF00</Rectangle.Stroke>
<Rectangle.IsHitTestVisible>true</Rectangle.IsHitTestVisible>
</Rectangle>
The List View containing all the data:
<ListView ItemsSource="{Binding Rectangles, Mode=TwoWay, NotifyOnTargetUpdated=True, NotifyOnSourceUpdated=True, NotifyOnValidationError=True, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}" x:Name="listBox" Width="{Binding ActualWidth, ElementName=ImageControl, Mode=OneWay,UpdateSourceTrigger=PropertyChanged}" Height="{Binding ActualHeight, ElementName=ImageControl, Mode=OneWay,UpdateSourceTrigger=PropertyChanged}" Background="{x:Null}" BorderBrush="{x:Null}" Foreground="{x:Null}" ItemTemplate="{StaticResource RectangleWithLabel}" MouseMove="ListBox_MouseMove" DataContextChanged="ListBox_DataContextChanged" SourceUpdated="ListBox_SourceUpdated" IsSynchronizedWithCurrentItem="True" TargetUpdated="ListBox_TargetUpdated" />
The call to the ImageViewer from the parent, here the converter is called on the way to the ImageViewer, but the ConvertBack is never called:
<common:ImageViewer x:Name="ctrlImage" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
<common:ImageViewer.DataContext>
<MultiBinding Converter="{StaticResource ConverterWhichIsOnlyCalledOneWay}" Mode="TwoWay" NotifyOnSourceUpdated="True" NotifyOnTargetUpdated="True" UpdateSourceTrigger="PropertyChanged">
<Binding Path="." UpdateSourceTrigger="PropertyChanged" Mode="TwoWay" NotifyOnSourceUpdated="True" NotifyOnTargetUpdated="True"/>
</MultiBinding>
</common:ImageViewer.DataContext>
</common:ImageViewer>
How can I get the ConvertBack to be called?
EDIT:
I made an overview of the controls and the converters. I think I was wrong, that the ConvertBack should be called, everything is updating, even without the big converter. But I'm still stuck when adding some elements to the ObservableCollection. When I add some values, the values appear in the DataContext of the MainWindow. But the Converter is not triggered. When I add the Observable.Count as Binding to the Big Converter, the update is triggered, but all bindings are lost.
Changes from the original code were: Changing all internal variables to DependencyProperties, this made updates reliable for the single entries in the ObservableCollection.
Overview of the GUI and the used converters
Time to answer the question myself:
I was wrong when assuming that a changes travels down throught the converter when there is a direct connection between two elements.
So if I have an element (just consider it as a leave of a tree) in a deeply nested and it is once displayed once through a converter (which changes some other objects) and the same time without a converter, the element is updated without calling the converter. WPF is so clever to see that the value is the same and does not pipe the change through the converter up and down.

Change the default ValidationRule error behavior of a TextBox

I created a custom ControlTemplate for my TextBoxes and I can't manage to override the default behaviour of the ValidationRule errors.
The TextBox border just turn red and i can't find where to override this.
The thing i'd like to to in my ControlTemplate is something like this :
<EventSetter Event="HasError" Handler="TextBox_HasErrors"/>
And in my code behind :
private void TextBox_HasErrors(...)
{
//Change few things in my TextBox
}
As I Overrided the default ControlTemplate of the TextBox, i have this ScrollViewer x:Name="PART_ContentHost" which I think is responsible for the border coloration, but i don't know how and where to change that
Actually my usage of the textBox is like this :
<TextBox Tag="Email">
<TextBox.Text>
<Binding Path="Email" UpdateSourceTrigger="LostFocus">
<Binding.ValidationRules>
<local:TextBoxEmailValidationRule Domain=".com"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Does some one know how i could change the red border behavior ?
The red border is defined in the default Validation.ErrorTemplate of the control. You can easily create your own error template by setting the attached property to a custom ControlTemplate:
<TextBox Tag="Email">
<TextBox.Text>
<Binding Path="Email" UpdateSourceTrigger="LostFocus">
<Binding.ValidationRules>
<local:TextBoxEmailValidationRule Domain=".com"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
<Validation.ErrorTemplate>
<ControlTemplate>
<StackPanel>
<!-- Placeholder for the TextBox itself -->
<AdornedElementPlaceholder x:Name="textBox"/>
<TextBlock Text="{Binding [0].ErrorContent}" Foreground="Red"/>
</StackPanel>
</ControlTemplate>
</Validation.ErrorTemplate>
</TextBox>
Please refer to this blog post for more information about this and data validation in WPF in general.

System.Windows.Controls.TextBox.Text property has already been set and can only be set once

I'm trying to create a simple validation for a textBoxhowever I'm geting the fallowing error in my XAML.
System.Windows.Controls.TextBox.Text property has already been set and
can only be set once.
As you can see from my code I don't havn't set the property twice. Not sure why this is happening.
My code is as fallows
<TextBox x:Name="tbxName" HorizontalAlignment="Center" Grid.Row="4" Grid.Column="1" VerticalAlignment="Top" Height="23" TextWrapping="Wrap" Width="120" Validation.ErrorTemplate="{StaticResource validationErrorTemplate}">
<TextBox.Text>
<Binding Path="Name" Mode="TwoWay" UpdateSourceTrigger="LostFocus">
<Binding.ValidationRules>
<local:NameValidator></local:NameValidator>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>

Binding the Value of StringFormat

I have a Textbox with a Value and a Unit bound like this:
<TextBox Text="{Binding Path=Value,StringFormat='{}{0} mm'}" />
The Unit mm should be also bound to the ViewModel Property Unit. This can be done via a Multibinding:
<TextBox>
<TextBox.Text>
<MultiBinding StringFormat="{}{0} {1}">
<Binding Path="Value"
Mode="TwoWay" />
<Binding Path="Unit"
Mode="OneWay" />
</MultiBinding>
</TextBox.Text>
</TextBox>
But with this I lose my Two Way Binding and I don't want to edit the Unit aswell. If ths user deletes "8 mm" and enteres an "8" the binding should automatically reevaluate the binding and add the unit as it is done via normal string format binding.
So finally I need something like this:
<TextBox>
<TextBox.Text>
<Binding Path="Value"
StringFormat="{Binding Path=ValueUnitStringFormat}" />
</TextBox.Text>
</TextBox>
But unfortunally StringFormat Property on BindingBase is not a DependencyProperty.
Anyone got a solution for this?

multi binding in wpf

I am trying to do multiple binding in wpf.
here is my code:
<CheckBox>
<CheckBox.IsChecked>
<MultiBinding Converter="{View:MultiConvertorOr}">
<Binding ElementName="txtbox1" Path="Text" Converter="{View:TextToBool}" Mode="TwoWay">
<Binding RelativeSource="{RelativeSource AncestorType={x:Type ListBoxItem}}" Path="IsSelected" Mode="TwoWay">
</MultiBinding>
</CheckBox.IsChecked>
</CheckBox>
My goal is when I enter text I want the checkbox to be checked (what happens) and the listBoxItem to be selected. (this part is not working).
I also want to keep the binding between the itemListBox and the checkBox (this also works).
Any Ideas how can I do what I want? maybe with event triggers or something like it?
Thanks ahead.

Categories

Resources