WPF validation - displaying errors for elements in StackPanel - wpf

I'm having problems with how validation errors are displayed for TextBox elements in vertical StackPanel. I'm trying to display error messages below TextBox.
I have this error template:
<Style TargetType="{x:Type TextBox}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<StackPanel>
<AdornedElementPlaceholder />
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding (ValidationError.ErrorContent)}" Foreground="Red" Margin="5"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
If I have enough white space below the TextBox, error is displayed fine, but in StackPanel (for example), it does not add extra margin or padding for error messages when there are some, because of adorner layer.
It is expected to be so, according to this source:
Note that the Validation.ErrorTemplate will be displayed on the adorner layer. Elements in the adorner layer are rendered on top of the rest of the visual elements and they will not be considered when the layout system is measuring and arranging the controls on the adorned element layer.
How can I display validation error messages, so that they won't show over other elements in StackPanel?

You can also consider to include your error template in the TextBox's template.
Something like that (of course it can be improved):
<Style x:Key="eTextBox" TargetType="TextBox" BasedOn="{StaticResource {x:Type TextBox}}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<StackPanel>
<Border BorderBrush="Gray" BorderThickness="1" CornerRadius="1" Padding="2">
<ScrollViewer Name="PART_ContentHost" Focusable="False"
ScrollViewer.HorizontalScrollBarVisibility="Hidden"
ScrollViewer.VerticalScrollBarVisibility="Hidden"
Background="#00FFFFFF" />
</Border>
<ItemsControl ItemsSource="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=(Validation.Errors)}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding (ValidationError.ErrorContent)}" Foreground="Red" Margin="5"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
In this way the ItemsControl is considered for the layout computation.

Ok, I found the solution with converter. I ended up with style similar to this:
<Style TargetType="{x:Type TextBox}">
<Setter Property="Margin" Value="10" />
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<StackPanel>
<AdornedElementPlaceholder />
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding (ValidationError.ErrorContent)}" Foreground="Red" Margin="0,5"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Trigger.Setters>
<Setter Property="Margin" Value="{Binding (Validation.Errors).Count, RelativeSource={RelativeSource Self}, Converter={StaticResource ErrorsToMarginConverter}}"/>
</Trigger.Setters>
</Trigger>
</Style.Triggers>
</Style>
and converter:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is int)
{
var errors = (int)value;
return new Thickness(10, 10, 10, (errors * 20));
}
return value;
}

Related

Merge Style.Triggers with property Template in WPF

I want to set a style for my DataGrid, but I do not know where is the problem
the backgroud property does not work with its value in the presence of the Template property.
my code:
<Style x:Key="DataGridStyle1" TargetType="{x:Type DataGrid}">
<Setter Property="CellStyle" Value="{DynamicResource GridStyle1}"/>
</Style>
<Style x:Key="GridStyle1" TargetType="DataGridCell">
<Style.Triggers>
<Trigger Property="IsSelected" Value="True" >
<Setter Property="Background" Value="SeaGreen"/>
</Trigger>
</Style.Triggers>
<Style.Setters>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridCell}">
<Border Name="DataGridCellBorder">
<ContentControl Content="{TemplateBinding Content}">
<ContentControl.ContentTemplate>
<DataTemplate>
<TextBlock Background="Transparent" TextWrapping="WrapWithOverflow" TextTrimming="CharacterEllipsis"
Height="auto" Width="auto" Text="{Binding Text}"/>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
Help me please.
You have explicitly set TextBlock background to Transparent, so it won't pick value from DataGridCell. You should bind with background of DataGridCell using RelativeSource like this:
<TextBlock Background="{Binding Background, RelativeSource={RelativeSource
Mode=FindAncestor, AncestorType=DataGridCell}}"
TextWrapping="WrapWithOverflow" TextTrimming="CharacterEllipsis"
Height="auto" Width="auto" Text="{Binding Text}"/>

ListView Style Trigger affecting one control and not another

I have a ListView control with a list of system messages in it, and a corresponding "action" button that will help my user resolve the message:
If the message is an error message, I want the Foreground of the text and the action button (the [more...] part) to turn red. As you can see, it's not doing that.
I'm using a Style.Trigger bound to the message data's Severity to set the Foreground. This works fine for the TextBlock in the DataTemplate, but fails to affect the Button in that Template. My XAML looks like this:
<ListView x:Name="ImageCenterStatus" Background="{DynamicResource SC.ControlBrush}" Margin="10,22,10,0" FontSize="12" Grid.Column="1" ClipToBounds="True" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListView.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock TextWrapping="WrapWithOverflow" cal:Message.Attach="[Event MouseUp] = [Action DoStatusAction($dataContext)]" Text="{Binding Path=DisplayedMessage}"/>
<Button x:Name="ActionButton" Content="{Binding Path=DisplayedActionLabel}" FontSize="12" cal:Message.Attach="DoStatusAction($dataContext)" Style="{DynamicResource SC.ActionButtonHack}"></Button>
</WrapPanel>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Severity}" Value="Warning">
<Setter Property="Foreground" Value="#FFCE7A16" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=Severity}" Value="Error">
<Setter Property="Foreground" Value="#FFD13636" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
</ListView>
The Button has its own Style, SC.ActionButtonHack, but I am very careful not to override the Foreground anywhere that style:
<Style x:Key="SC.ActionButtonHack" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<ContentPresenter x:Name="contentPresenter" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Why does the TextBlock in the DataTemplate respond to the Foreground change in the Trigger, but not the Button?
What do I need to do to get the button to respect the Foreground change?
Bind TextElement.Foreground of ContentPresenter to ListViewItem's foreground to get it work:
<Style x:Key="SC.ActionButtonHack" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<ContentPresenter x:Name="contentPresenter"
TextElement.Foreground="{Binding Foreground,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=ListViewItem}}"
HorizontalAlignment="Center"
VerticalAlignment="Center" Margin="0"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
OR
Either bind it on Button itself:
<Button x:Name="ActionButton"
Foreground="{Binding Foreground, RelativeSource={RelativeSource
Mode=FindAncestor, AncestorType=ListViewItem}}"
Content="{Binding Path=Name}" FontSize="12"
Style="{DynamicResource SC.ActionButtonHack}"/>

ListBox of expanders not acting like radiobuttons

I have created a listbox of expanders like this question: Expander Auto Open/Close
The solution works with content in the expanders when the listbox is the only item on the window.
But when I try to use the same code with my custom control, I'm getting this error:
System.Windows.Data Error: 4 : Cannot find source for binding with
reference 'RelativeSource FindAncestor,
AncestorType='System.Windows.Controls.ListBoxItem',
AncestorLevel='1''. BindingExpression:Path=IsSelected; DataItem=null;
target element is 'Expander' (Name=''); target property is
'IsExpanded' (type 'Boolean')
I've tried adding the IsSelected Property in the ListBox.ItemContainerStyle as one poster suggested in another thread but that failed.
<ListBox Margin="5"
SelectionMode="Single"
ScrollViewer.VerticalScrollBarVisibility="Auto">
<ListBox.Resources>
<Style TargetType="{x:Type Expander}">
<Setter Property="IsExpanded"
Value="{Binding Path=IsSelected, RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}}" />
</Style>
<Style TargetType="{x:Type controls:SelectionCriteriaControl}">
<Setter Property="MaxHeight"
Value="200" />
</Style>
</ListBox.Resources>
<Expander Header="Fund Family" Margin="2">
<StackPanel>
<controls:SelectionCriteriaControl DataContext="{Binding FundFamilySelectionCriteria, Mode=TwoWay}" />
</StackPanel>
</Expander>
<Expander Header="Asset Class" Margin="2">
<StackPanel>
<controls:SelectionCriteriaControl DataContext="{Binding AssetClassSelectionCriteria, Mode=TwoWay}" />
</StackPanel>
</Expander>
<ListBox.Template>
<ControlTemplate TargetType="{x:Type ListBox}">
<ItemsPresenter />
</ControlTemplate>
</ListBox.Template>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<ContentPresenter Content="{TemplateBinding Content}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
Fails miserably!!!!!
Any help appreciated :)
It's a bit of a large scenario for me to set up at the moment, but something that comes to mind to try:
Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsSelected}" />
I don't think where templates are used that you can define relative sources by type.
Edit: This code worked fine: Based on your original, the TemplatedParent wasn't necessary.
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<ListBox x:Name="testList" Margin="5"
SelectionMode="Single"
ScrollViewer.VerticalScrollBarVisibility="Auto">
<ListBox.Resources>
<Style TargetType="{x:Type Expander}">
<Setter Property="IsExpanded"
Value="{Binding Path=IsSelected, RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}}" />
</Style>
</ListBox.Resources>
<Expander Header="Fund Family" Margin="2">
<StackPanel>
<TextBlock Text="First"/>
<TextBlock Text="Second"/>
</StackPanel>
</Expander>
<Expander Header="Asset Class" Margin="2">
<StackPanel>
<TextBlock Text="First"/>
<TextBlock Text="Second"/>
</StackPanel>
</Expander>
<ListBox.Template>
<ControlTemplate TargetType="{x:Type ListBox}">
<ItemsPresenter />
</ControlTemplate>
</ListBox.Template>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<ContentPresenter Content="{TemplateBinding Content}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
<Button HorizontalAlignment="Left" Content="Test" Grid.Row="1" Width="60" Click="Button_Click"/>
</Grid>
And a quick code-behind to trigger a programmatic SelectedIndex set...
private void Button_Click(object sender, RoutedEventArgs e)
{
testList.SelectedIndex = 1;
}
Seems to work fine for me. Clicking on a list item expands, and even using the button to set it specifically by setting the selected index it expands. Something very fishy is affecting your specific scenario... :]

WPF - ListBox ignores Style When ItemsSource is bound

I have created styled a ListBox in WPF so that it is rendered as a checkbox list.
When I populate the ListBox's items manually, the styling works perfectly. However, when I instead bind the ItemsSource of the ListBox to a static resource (an ItemsControl containing the required items), the styling is completely dropped.
Here's the style:
<Style x:Key="CheckBoxListStyle" TargetType="ListBox">
<Style.Resources>
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Grid Margin="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<CheckBox IsChecked="{Binding IsSelected, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"/>
<ContentPresenter
Grid.Column="1"
Margin="2,0,0,0" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Style.Resources>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Background" Value="Transparent" />
</Style>
Here's the code for the ListBox that shows the style correctly:
<ListBox x:Name="ColumnsList"
Grid.Column="0"
Grid.Row="0"
Style="{StaticResource CheckBoxListStyle}"
BorderThickness="1">
<ListBox.Items>
<ListBoxItem>Test</ListBoxItem>
<ListBoxItem>Test2</ListBoxItem>
<ListBoxItem>Test3</ListBoxItem>
</ListBox.Items>
</ListBox>
Here's the code for the ListBox that ignores the style:
<ListBox x:Name="ColumnsList2"
Grid.Column="0"
Grid.Row="0"
Style="{StaticResource CheckBoxListStyle}"
BorderThickness="1"
ItemsSource="{Binding Source={StaticResource Test1}, Path=Items}">
</ListBox>
Hoping someone can help - I'm pretty new to all this and have tried everything I can think of, but everything I've read leads me to believe that setting ItemsSource should have the same outcome as setting the items manually, so I can't see any reason why this would not work.
Thanks,
AT
Change the Style.Resources to setting the ItemContainerStyle property and it should work like a charm.
<Style x:Key="CheckBoxListStyle" TargetType="ListBox">
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Grid Margin="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<CheckBox IsChecked="{Binding IsSelected, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"/>
<ContentPresenter
Grid.Column="1"
Margin="2,0,0,0" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Background" Value="Transparent" />
</Style>
In older versions (before SP1), when you define Styles in Style, one of those style will be ignored. Alternatively, you can set the Resources of Style in the parent resources..
Hope this helps!
This is because your TargetType in the CheckListBoxStyle is targetting a ListBoxItem, but when you set the ItemSource property of the ListBox you are binding to a list of other elements (ints for example). This means your target type should be int instead of ListBoxItem.
Alternatively do not specify a target type.

Underline Text in Label which is in a DataTemplate

i have a ListView which contains objects bound from an collection. The representation of the objects I have set with a DataTemplate. Now I want to do the following.
There are two TextBlocks in my DataTemplate:
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"></TextBlock>
<TextBlock Text="{Binding Path}"></TextBlock>
</StackPanel>
</DataTemplate>
I already have specified an ItemContainerStyle which I use to realize a hover-effect.
<Style TargetType="ListViewItem" x:Key="ContainerStyle">
<Style.Triggers>
<EventTrigger RoutedEvent="Mouse.MouseEnter">
... and so on
My aim is to underline the TextBlock which contains the Name, when user moves mouse over the ListViewItem. The Path shouldn't be underlined. How can this be realized? How can an element in DataTemplate can be accessed for each ListViewItem?
Greetings,
Martin
You can do this either by specifying the ControlTemplate for the ListViewItem, or by changing the DataTemplate. The example below shows both methods. Note that you lose the blue background for the selected ListViewItem when you use the ControlTemplate (when you comment it out it returns)
EDIT:
I did not read your requirement well. You only want to underline the Name. Then the only possibility is to use the Datatemplate, or to rewrite the ControlTemplate of the TextBox.
<Window x:Class="Underlining.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
>
<StackPanel>
<ListView ItemsSource="{Binding}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<Border Name="UnderlineInControlTemplate" BorderThickness="2,0,2,0"
BorderBrush="Transparent">
<ContentPresenter HorizontalAlignment="Left"
VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="UnderlineInControlTemplate"
Property="BorderBrush"
Value="BlueViolet"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<Border Name="UnderlineInDataTemplate" BorderThickness="0,0,0,2"
BorderBrush="Transparent">
<TextBlock Text="{Binding Name}"/>
</Border>
<TextBlock Text="{Binding Path}"/>
</StackPanel>
<DataTemplate.Triggers>
<Trigger Property="TextBlock.IsMouseOver" Value="True">
<Setter TargetName="UnderlineInDataTemplate"
Property="BorderBrush"
Value="Red"/>
</Trigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Window>

Resources