WPF Display TextBlock with validation error message below control - wpf

Is there a way to display the error contents in a TextBlock below the control similar to how the following sets the Tooltip to contain the error text?
<Style x:Key="textBoxInError" TargetType="Control">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel>
<TextBlock DockPanel.Dock="Left" Foreground="Red" FontWeight="Bold">*</TextBlock>
<TextBlock Text="WOULD LIKE TO SHOW WHAT TOOLTIP IS SHOWING" DockPanel.Dock="Bottom" Foreground="Red"/>
<Border BorderBrush="Red" BorderThickness="2">
<AdornedElementPlaceholder/>
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
In other words, I rather show the error message in a TextBlock below the control instead of a Tool Tip.

The DataContext of the ErrorTemplate is already the value of Validation.Errors, so you can just do:
<TextBlock Text="{Binding [0].ErrorContent}" DockPanel.Dock="Bottom" Foreground="Red"/>
or
<TextBlock Text="{Binding ErrorContent}" DockPanel.Dock="Bottom" Foreground="Red"/>

I struggled with this exact problem, and all the SO answers I could find to this or to similar questions were either using the "Validation.ErrorTemplate" property which is unfortunately rendered on a separate UI layer which means the parent control won't resize its content like #statikuz mentionned and hide the following controls, or just not generic enough.
I finally ended up with this solution :
<Style x:Key="hiddenTextblockErrorText" TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed"/>
<Setter Property="Foreground" Value="DarkRed"/>
<Setter Property="TextWrapping" Value="Wrap"/>
<Setter Property="Text" Value="{Binding RelativeSource={RelativeSource Self}, Path=Tag.(Validation.Errors)/ErrorContent}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Tag.(Validation.HasError)}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
And you use it like this :
<TextBox Name="someField" Height="20">
<TextBox.Text>
<Binding Path="SomeProperty" Mode="TwoWay"UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:IsRequired/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<TextBlock Tag="{Binding ElementName=someField}" Style="{StaticResource hiddenTextblockErrorText}"/>
So you basically just bind your field to the Tag property of your error TextBlock, and you can use it from there to get the (Validation.Errors) attached property.
Note that you will get an harmless warning "Property Errors is not attachable to element of type Object" but it is working perfectly fine (I wasn't able to find how to do a cast here).
Alternatively you can use a Label and the Target property instead of Tag and you won't get any warning, but you lose the TextWrapping feature unless you also override the template so it's a bit more verbose :
<Style x:Key="hiddenLabelErrorText" TargetType="Label">
<Setter Property="Visibility" Value="Collapsed"/>
<Setter Property="Foreground" Value="DarkRed"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Label}">
<TextBlock TextWrapping="Wrap" Text="{Binding RelativeSource={RelativeSource AncestorLevel=1, AncestorType={x:Type Label}},
Path=Target.(Validation.Errors)/ErrorContent}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Target.(Validation.HasError)}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
<Label Target="{Binding ElementName=someField}" Style="{StaticResource hiddenLabelErrorText}"/>

Related

change properties inside datatemplate of listboxItem if selected

I have the following style:
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock x:Name="Text" Text="{Binding Name}" Margin="0, 5" FontSize="16"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
I want to change the foreground color of "Text" if the "ListBoxItem" is selected. I know from here: Change WPF DataTemplate for ListBox item if selected how to change the DataTemplate. but since I just want to change colors, this solution makes unnecessary duplication in codd - It would be a greater problem if my DataTemplate would be very complex and long.
How do I achieve change of a single property of an object inside the DataTemplate?
If you actually want the same item template always (whether the item is selected or not), but you want a different foreground color when the item is selected, that's easy. Don't set the ContentTemplate in a trigger, because ContentTemplate doesn't change. Just set the foreground color with a trigger. If you don't mess with the foreground color in the template, it will inherit whatever the ListBoxItem has for that property.
<Style TargetType="ListBoxItem">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock
x:Name="Text"
Text="{Binding Name}"
Margin="0, 5"
FontSize="16"
/>
</DataTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Foreground" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
A more elaborate version of the same style:
<Style TargetType="ListBoxItem">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Ellipse
Height="12"
Width="12"
VerticalAlignment="Center"
x:Name="Ellipse"
Fill="Yellow"
Stroke="DeepSkyBlue"
StrokeThickness="1"
/>
<TextBlock
x:Name="Text"
Text="{Binding Name}"
Margin="5,5,0,5"
FontSize="16"
/>
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger
Binding="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListBoxItem}}"
Value="True"
>
<Setter
TargetName="Ellipse"
Property="Stroke"
Value="Orange"
/>
<Setter
TargetName="Ellipse"
Property="StrokeThickness"
Value="3"
/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Foreground" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>

Dynamic StringFormat in WPF

I have the following TextBlock Style:
<Style TargetType="TextBlock" x:Key="MyValues">
<Setter Property="FontStyle" Value="Italic"/>
<Setter Property="Foreground" Value="DarkBlue"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsMetric}" Value="True">
<Setter Property="Text">
<Setter.Value>
<MultiBinding StringFormat="F1">
</MultiBinding>
</Setter.Value>
</Setter>
<Setter Property="Foreground" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsMetric}" Value="False">
<Setter Property="Text">
<Setter.Value>
<MultiBinding StringFormat="F3">
</MultiBinding>
</Setter.Value>
</Setter>
<Setter Property="Foreground" Value="Green"/>
</DataTrigger>
</Style.Triggers>
I then use TextBlocks as follows:
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Path=Breadth}" Style="{StaticResource MyValues}"/>
<TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding Path=Depth}" Style="{StaticResource MyValues}"/>
<TextBlock Grid.Row="3" Grid.Column="1" Text="{Binding Path=Area}" Style="{StaticResource MyValues}" />
The intention is to set the StringFormat depending on a bound property IsMetric. The Binding in the style are left empty because i want to apply the same style for multiple TextBlocks all bound to different properties. The triggers are working but the StringFormat` is ignored, any ideas?
Here you set Text property to be different things in TextBlock declaration and in DataTriggers. In the first case it's an instance of Binding class. In the second case it's an instance of MultiBinding class. Finally it is one of these. It cannot be both at the moment.
The following markup
<Setter Property="Text">
<Setter.Value>
<MultiBinding StringFormat="F1">
</MultiBinding>
</Setter.Value>
</Setter>
instantiates MultiBinding class instance and sets it to Text property.
The Text="{Binding Path=Breadth}" instantiates Binding class instance and sets it to Text property.

How to get rid of the red border when a DataGrid cell is invalid?

How do I do this? I want to get rid of that annoying red border that shows on each invalid datagrid cell.
You can just add this line to your DataGrid:
<DataGrid Validation.ErrorTemplate="{x:Null}" />
I had this same issue but in my case I wasn't using IDataError or INotifyDataErrorInfo. I'm using a custom ValidationRule and styles to handle my validation logic and presentation. I was already using the DataGrid RowStyle to display a custom style for the row that has errors so I thought it would be easy to do something similar with the DataGridCell.
Things to note:
You can't just define a style and set the DataGrid.CellStyle. Instead you have to use the ElementStyle and/or the EditingElementStyle.
The style TargetType has to match the DataGridColumn type that the cell is using. So for a DataGridComboBoxColumn the TargetType should be ComboBox
DataGrid Column XAML:
<DataGridComboBoxColumn Header="Column1"
ItemsSource="{Binding Path=Column1Path}"
DisplayMemberPath="Column1DisplayPath"
ElementStyle="{StaticResource DGComboColValidationStyle}"
EditingElementStyle="{StaticResource DGComboColValidationStyle}">
<DataGridComboBoxColumn.SelectedItemBinding>
<Binding Path="Column1Path" UpdateSourceTrigger="LostFocus">
<Binding.ValidationRules>
<Validation:CustomValidationRule ValidationStep="UpdatedValue" ValidatesOnTargetUpdated="True" />
</Binding.ValidationRules>
</Binding>
</DataGridComboBoxColumn.SelectedItemBinding>
</DataGridComboBoxColumn>
Style Definitions
<Style x:Key="DGComboColValidationStyle" TargetType="{x:Type ComboBox}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<Grid>
<Border BorderThickness="1"
BorderBrush="{Binding ElementName=adorner1, Path=DataContext[0].ErrorContent.ValidationType, Converter={StaticResource ValidationTypeColorConverter}}"
CornerRadius="3">
</Border>
<AdornedElementPlaceholder Name="adorner1"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="DGTextColValidationStyle" TargetType="{x:Type TextBlock}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<Grid>
<Border BorderThickness="1"
BorderBrush="{Binding ElementName=adorner2, Path=DataContext[0].ErrorContent.ValidationType, Converter={StaticResource ValidationTypeColorConverter}}"
CornerRadius="3">
</Border>
<AdornedElementPlaceholder Name="adorner2"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="DGRowValidationStyle" TargetType="{x:Type DataGridRow}">
<Setter Property="ValidationErrorTemplate">
<Setter.Value>
<ControlTemplate x:Name="DGRowValidationTemplate">
<Grid>
<Ellipse Width="12" Height="12" Stroke="Black" StrokeThickness="0.5"
Fill="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridRow}}, Path=(Validation.Errors)[0].ErrorContent.ValidationType, Converter={StaticResource ValidationTypeColorConverter}}"/>
<TextBlock FontWeight="Bold" Padding="4,0,0,0"
Margin="0" VerticalAlignment="Top" Foreground="White" Text="!"
ToolTip="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridRow}}, Path=(Validation.Errors)[0].ErrorContent.ValidationMessage}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="BorderThickness" Value="2"/>
<Setter Property="BorderBrush" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent.ValidationType, Converter={StaticResource ValidationTypeColorConverter}}"/>
</Trigger>
<Trigger Property="Validation.HasError" Value="false">
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="BorderBrush" Value="Transparent" />
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="DGCellStyle" TargetType="{x:Type DataGridCell}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="false">
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="BorderBrush" Value="Transparent" />
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridRow}}, Path=(Validation.Errors)[0].ErrorContent.ValidationMessage}"/>
</Trigger>
</Style.Triggers>
</Style>
Reference:
http://social.msdn.microsoft.com/Forums/vstudio/en-US/6d2d6513-7bca-4359-a12b-46da3c380b0a/wpf-4-datagrid-editingelementstyle-and-validationerrortemplate-adorner-layer?forum=wpf
Set the ValidatesOnDataErrors and ValidatesOnExpcetions to False for your binding of your cell.
In case you want your validations, then you have to override the Validation Template for your control. Please refer to my answer here -
Validation Error Style in WPF, similar to Silverlight
I hated the red border because it was put in the adorner and adorners sit on top of the window, meaning that if your element is partially/fully hidden by another element (like it is in a grid) the full adorner still shows :(
That didn't mean I didn't want some customization though, so I can still highlight my items in pink, or change their foreground and have the DataGridCell correctly clip everything off. You can do this by using a style trigger.
Hope this helps!
<DataGrid.Resources>
<Style TargetType="{x:Type TextBlock}" x:Key="TextBlockErrorStyle">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<!-- Just the adorned element means NO RED BORDER -->
<AdornedElementPlaceholder Name="controlWithError" />
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="Foreground" Value="Red"/>
<Setter Property="Background" Value="Pink"/>
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
...
<DataGridTemplateColumn Header="Description" MinWidth="150">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Description, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, NotifyOnValidationError=True}"
Style="{StaticResource ResourceKey=TextBlockErrorStyle}"
/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
You can check this as well
How to: Implement Validation with the DataGrid Control

WPF Listbox - Empty List Display Message

Can anyone suggest the best way to display a Textblock (with a text such as "List Empty") so that it's visibility is bound to the Items.Count.
I have tried the following code and can't get it to work, so think that I must be doing it wrong.
<ListBox x:Name="lstItems"
ItemsSource="{Binding ListItems}">
</ListBox>
<TextBlock Margin="4" FontStyle="Italic" FontSize="12" Text="List is empty" Visibility="Collapsed">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=lstItems, Path=Items.Count}" Value="0">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
The problem in your code is that setting the value of Visibility in the text block itself has higher priority than setting it in the style. So, even when the trigger occurs, the setting inside the trigger has no effect. Change the XAML to:
<TextBlock Margin="4" FontStyle="Italic" FontSize="12" Text="List is empty" >
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=lstItems, Path=Items.Count}" Value="0">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
Where the setting of Visibility is all in the style and it works (at least in my demo project).

How can I "Click Through" a control in WPF?

I have an order entry form that has a ListBox with a list of line items. I have my items template, and one of the values is a ComboBox in each of my Items.
Now, my form can also create Credit memo's in addition to purchase orders, but when I am creating a credit memo, I want to put the words "Credit Memo" over the list box, however, the TextBlock covers the ComboBox in two of my line items. I would like to pass my click event through the TextBlock to the ComboBoxes but I'm not sure how to do it.
This is what I have, ( Maybe I am coming at this totally wrong, I am kinda a noob with WPF )
<ListBox SelectionMode="Single" Grid.Row="2"
ItemsSource="{Binding Path=LineItems}" HorizontalContentAlignment="Stretch"
IsSynchronizedWithCurrentItem="True" Background="#66FFFFFF">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Background" Value="WhiteSmoke"/>
<Setter Property="BorderThickness" Value="1" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsPartBackOrder}" Value="True">
<Setter Property="Background" Value="Orange" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type Entities:SalesOrderLineItem}" >
<OrderEntry:SalesOrderLineItemCreate DataContext="{Binding}" DeleteSalesOrderLineItem="DeleteSalesOrderLineItem" Margin="0,3,3,0" >
<OrderEntry:SalesOrderLineItemCreate.Resources>
<Style TargetType="{x:Type OrderEntry:SalesOrderLineItemCreate}">
<Style.Triggers>
<DataTrigger
Binding="{Binding RelativeSource=
{
RelativeSource
Mode=FindAncestor,
AncestorType={x:Type ListBoxItem}
},
Path=IsSelected
}" Value="True">
<Setter Property="Background" Value="LightBlue" />
<Setter Property="Foreground" Value="Black" />
</DataTrigger>
</Style.Triggers>
</Style>
</OrderEntry:SalesOrderLineItemCreate.Resources>
</OrderEntry:SalesOrderLineItemCreate>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBlock Grid.Row="2"
Text="Credit Memo"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="48" Height="Auto"
FontStyle="Italic"
Foreground="Red"
Opacity=".25">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=OrderType}" Value="CR">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=OrderType}" Value="CU">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock IsHitTestVisible="False" .../>

Resources