getting validation into a style - wpf

I use IDataErrorInfo on my viewmodels and I have a style (below) for a TextBox with an error template that works ok. I know that "ValidatesOnDataErrors=True" used like:
<TextBox Text="{Binding Path=LastName, ValidatesOnDataErrors=True}"
Style="{StaticResource TextBoxStyle}" />
will force WPF to use IDataErrorInfo but am wondering how to get that baked into my style.
Cheers,
Berryl
<Style x:Key="TextBoxStyle" TargetType="{x:Type TextBox}">
...
<!--
Error handling
-->
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel LastChildFill="True">
<TextBlock DockPanel.Dock="Right" Text=" *"
Foreground="Red"
FontWeight="Bold" FontSize="16"
ToolTip="{Binding ElementName=placeholder, Path=AdornedElement.(Validation.Errors).CurrentItem.ErrorContent}"/>
<Border BorderBrush="Red" BorderThickness="1">
<AdornedElementPlaceholder Name="placeholder"></AdornedElementPlaceholder>
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="Background" Value="LightYellow"/>
</Trigger>
</Style.Triggers>
</Style>

If I understand what you're asking, you want to be able to be able to use the ValidatesOnDataError=True in your style, that so you don't have to repeat it every time.
If that's the case you can't, because that is a property of the data binding and not the style; and you can't template the data binding.

I just wonder if you use a Label instead of a TextBox, then in the style of the Label you can probably do something like this,
<ControlTemplate TargetType="sdk:Label">
<TextBlock x:Name="textBlock" Text="{Binding Content, RelativeSource={RelativeSource TemplatedParent}, ValidatesOnDataErrors=True}">

You can't because that is part of the definition of the binding to your property, not how the error is visualized.

Related

Setting background of content control

<UserControl .....>
<DataTemplate DataType="{x:Type vm:AViewModel}">
<vw:AView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:BViewModel}">
<vw:BView />
</DataTemplate>
<ContentControl x:Name="chartScreen" Content="{Binding Screen}" Background="Yellow" />
</UserControl>
As you can see from above code, ContentControl is setting its content through binding to ViewModel's Screen property. Screen property will return an instance of AViewModel or BViewModel, depending on some condition. The problem is, when UserControl is loaded on screen, Screen property is null, so there is no content set yet. At this point, I would like to set some background for the ContentControl, but I cannot find a way how to do this? Background="Yellow" does nothing...
Any ideas how to set the background of ContentControl? This backgound should be applied always, even when content is displaying AView or Biew, or null.
Just wrap your ContentControl in a Border
<Border Background="Yellow">
<ContentControl x:Name="chartScreen"
Content="{Binding Screen}" />
</Border>
If all you have in your UserControl is your ContentControl, just set the Background on the UserControl itself. That would remove the extra Border as well.
try something like this :
<ContentControl x:Name="chartScreen" Content="{Binding Screen}" Background="Yellow">
<ContentControl.Triggers>
<Trigger Property="Content" Value="{x:Null}">
<Trigger.Value>
<Border Background="Yellow"/>
</Trigger.Value>
</Trigger>
</ContentControl.Triggers>
</ContentControl>
try something like this in WPF:
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding Content}" Value="{x:Null}">
<Setter Property="Content">
<Setter.Value>
<Rectangle Width="100" Height="100" Fill="Blue" />
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>

WPF Validation ErrorTemplate for Custom TextBox

Branching off of this question -
When attaching a validation error template to my custom textbox like this -
<local:CustomTextBox CustomText="{Binding ViewModelProperty}" Validation.ErrorTemplate="{StaticResource errorTemplate}"/>
<ControlTemplate x:Key="errorTemplate">
<DockPanel>
<Border BorderBrush="Red" BorderThickness="1">
<AdornedElementPlaceholder x:Name="controlWithError"/>
</Border>
<TextBlock Foreground="Red" FontSize="20" FontFamily="Segoe UI" Margin="3,0,0,0" MouseDown="Exclamation_MouseDown" Tag="{Binding AdornedElement.(Validation.Errors)[0].ErrorContent, ElementName=controlWithError}">!</TextBlock>
</DockPanel>
</ControlTemplate>
If there was a validation error in the ViewModelProperty, my application was throwing an exception -
Key cannot be null.
Parameter name: key
I'm not sure why this is happening. Is there something that needs to be done in order to assign a new error template to a custom control?
UPDATE:
I've figured out that the issue is with the Tag property in the error template. If I remove the Tag, it works just fine.
Thanks
Alright so the way I managed to fix the issue was by removing the AdornedElement keyword and changing the error template as follows:
<local:CustomTextBox CustomText="{Binding ViewModelProperty}">
<Validation.ErrorTemplate>
<ControlTemplate>
<DockPanel>
<Border BorderBrush="Red" BorderThickness="1">
<AdornedElementPlaceholder x:Name="controlWithError"/>
</Border>
<TextBlock Foreground="Red" FontSize="20" FontFamily="Segoe UI" Margin="3,0,0,0" MouseDown="Exclamation_MouseDown">!</TextBlock>
</DockPanel>
</ControlTemplate>
</Validation.ErrorTemplate>
<local:CustomTextBox.Style>
<Style TargetType="{x:Type local:CustomTextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
<Setter Property="Tag" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</local:CustomTextBox.Style>
</local:CustomTextBox>
What I don't understand is why it behaves differently when using the AdornedElement keyword but works fine when binding the Tag/Tooltip using the RelativeSource. While the problem is solved, I would welcome any ideas as to why this happened.
Thanks

How to make CollectionView grouping respect data template settings

I have ListBox with group style:
<GroupStyle HidesIfEmpty="True" x:Key="GroupStyle">
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Margin" Value="0,0,0,5"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True" BorderBrush="#FFA4B97F" BorderThickness="0,0,0,1">
<Expander.Header>
<DockPanel Background="LightSkyBlue"
Width="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Expander}},
Path=ActualWidth}">
<TextBlock FontWeight="Bold" Text="{Binding Path=Name}" Margin="5,0,0,0" Width="100"/>
<TextBlock FontWeight="Bold" Text="{Binding Path=ItemCount}"/>
</DockPanel>
</Expander.Header>
<Expander.Content>
<ItemsPresenter/>
</Expander.Content>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
Any change in bound collection causes expanders to reopen.
Is there any way to remember user preferences and leave expanders folded?
The question is what you are doing with the bound collection.
I agree with Nikolay that the best option is to have your viewmodel have a property that you will bind the expander to.
As for the question I've asked at the beginning: Are you somewhat refreshing the CollectionView?
Refresh causes the UI to be recreated. (Meaning, it is like Reset on an ObservableCollection. It tells the UI to create the user controls and load the XAML of your templates again which is bad for performance).
I can suggest a simple hack - just add IsExpanded property to your model class and bind Expander.IsExpanded to it.

HeaderTemplate with multiple items

I'm trying to write a HeaderTemplate for an extender. So far, I've noticed all the examples use the {Binding} keyword to get the data from the header. However, what happens if there are multiple controls within the Header? How do I specify that those controls should be inserted at a specific location?
<Window.Resources>
<Style x:Key="ExpanderStyle" TargetType="{x:Type Expander}">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<!-- what do I put in here? -->
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Expander Style="{StaticResource ExpanderStyle}">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock>Some Text</TextBlock>
<TextBlock Text="{Binding SomeBinding}" />
<Button />
</StackPanel>
</Expander.Header>
<Image Source="https://www.google.com/logos/2012/steno12-hp.jpg" />
</Expander>
Should I be moving my binding into the HeaderTemplate in the style and just overwriting whatever the Header in the Expander is?
You can use ContentPresenter to insert whatever the usual content would be into your Template
For example:
<Style x:Key="ExpanderStyle" TargetType="{x:Type Expander}">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<Border BorderBrush="Blue" BorderThickness="2">
<ContentPresenter />
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
Header's property Content could contain only one object.
If you merge these object in one panel:
<StackPanel>
<TextBlock>Some Text</TextBlock>
<TextBlock Text="{Binding SomeBinding}" />
<Button />
</StackPanel>
then in template you could use {binding}

How can I change the font size of a label in my ControlTemplate

In my WPF ListBox, I have a style with a ControlTemplate for a ListBoxItem. Inside that ControlTemplate I have a label defined. Based on some details, I need to change the font size of the label. So from my code-behind, I need to determine what the font should be and then I need to set it.
Here is my style with the ControlTemplate (I've stripped out some irrelevant controls)
<Style x:Key="RecordTabList" TargetType="{x:Type ListBoxItem}">
<Setter Property="Background" Value="{DynamicResource RecordIndexTabBackcolor}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Label
x:Name="myLabel" Grid.Column="0" Grid.ColumnSpan="1" Grid.Row="0" Grid.RowSpan="1" Margin="3,-2,0,-2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Foreground="{DynamicResource RecordIndexTabForeground}"
FontSize="10" Height="Auto" BorderThickness="3,0,0,0"
Content="{Binding Path=Name}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
How can I do this?
If I understand you correctly, you can probably do something similar to the following, and simply change the FontSize property on the ListBoxItem itself; it will be reflected automatically on your Label. Copy this into VS and see it in action!
<Window.Resources>
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Label Content="{TemplateBinding Content}" FontSize="{TemplateBinding FontSize}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<ListBox Margin="12">
<ListBoxItem Content="Test 1" FontSize="14"/>
<ListBoxItem Content="Test 2" FontSize="18"/>
<ListBoxItem Content="Test 3" FontSize="22"/>
</ListBox>
</Grid>
You might be able to use a ValueConverter on the FontSize property.. but I'm not 100% sure if they work inside a ControlTemplate.. I seem to remember Silverlight having issues with it, but I can't remember if it worked in WPF.
If you want to set the FontSize in the code behind, you should remove FontSize from the ControlTemplate, then set it for the ListBoxItem in the code-behind. If you want to set the same size for all the ListBoxItems just set the FontSize of the ListBox in the code-behind.

Resources