WPF Validation.Errors Contains Nothing - wpf

I am having problems displaying the Validation.Errors in my ItemsControl. The Validation.Errors does not contain anything. I am NOT using BindingGroup but I use my own custom TextBoxes. Here is the ItemsControl code:
<ItemsControl x:Name="errorList" ItemsSource="{Binding Path = (Validation.Errors), ElementName=gvAddCustomer}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock FontSize="18" Text="{Binding Path=ErrorContent}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
My TextBox uses the ErrorTemplate to display the errors beside the TextBox control and it displays correctly with the error message. Here is the style:
<Style x:Key="TextBoxStyle" TargetType="TextBox">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel LastChildFill="True">
<TextBlock DockPanel.Dock="Right"
Foreground="Orange"
FontSize="12pt"
Text="{Binding ElementName=MyAdorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">
</TextBlock>
<Border BorderBrush="Red" BorderThickness="2">
<AdornedElementPlaceholder Name="MyAdorner" />
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Can anyone explain why the Validation.Errors contains nothing when I bind to the ItemsControl?

I did my validation very similarly:
<TextBox
Style="{StaticResource TextBoxValidationError}"
Name="PatientFirstName" TabIndex="0">
<TextBox.Text>
<Binding Path="Patient.PatientFirstName" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<bs:NameRequiredRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
The only drawback is that it does not fire the validation until you enter the first character. You can force it do it in the constructor like this:
System.Windows.Data.BindingExpression be;
DependencyProperty txtProp = System.Windows.Controls.TextBox.TextProperty;
be = PatientFirstName.GetBindingExpression(txtProp);
be.UpdateSource();

Related

WPF - Custom ErrorTemplate for all TextBoxes in App

I have a form with many textboxes, each require the same validation Error-template.
Now, i don't wanna write these validation error-templates for every textbox. So where do i have to put that, so that all textboxes are affected?
Textbox with Validation.ErrorTemplate:
<TextBox x:Name="textBox3" TextWrapping="Wrap" Height="23" Text="{Binding User_Id, UpdateSourceTrigger=PropertyChanged, NotifyOnValidationError=True}" VerticalAlignment="Top">
<Validation.ErrorTemplate>
<ControlTemplate>
<StackPanel>
<AdornedElementPlaceholder x:Name="textBox"/>
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ErrorContent}" Foreground="Red"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ControlTemplate>
</Validation.ErrorTemplate>
</TextBox>
My CustomControl:
public class ValidationTextBox : TextBox
{
static ValidationTextBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ValidationTextBox), new FrameworkPropertyMetadata(typeof(ValidationTextBox)));
//Validation.SetErrorTemplate(new ValidationTextBox(), )
}
public ValidationTextBox() { }
}
You need to define new Style for TextBox inside a "Resourse" tag of textbox's container. This style will be implemented for each textbox inside container.
Example:
<StackPanel>
<StackPanel.Resources>
<Style TargetType=TextBox>
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<StackPanel>
<AdornedElementPlaceholder x:Name="textBox"/>
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ErrorContent}" Foreground="Red"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</StackPanel.Resources
<TextBox/>
<TextBox/>
<TextBox/>
</StackPanel>

To know the hosting Control of a Control

I have a custom control, its control template will be looks below.
<Style TargetType="local:CustomButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:CustomButton">
<Grid>
<Border x:Name="CtrlBorder">
<StackPanel Orientation="Horizontal">
<TextBox Name="Tbox"
BorderThickness="1,1,0,1"
Text="{Binding TextBoxText,
RelativeSource={RelativeSource TemplatedParent},
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" />
<Button Width="20"
Background="#FFF0F0F0"
BorderThickness="0,1,1,1"
IsTabStop="False">
</Button>
</StackPanel>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
In an event, i got the Tbox and i need to get the CustomButton with this Tbox.
Any idea on this?
Can get throught TemplatedParent property of that control.
Custom control will lie in Visual Tree as parent so FindAncestor will work here:
Text="{Binding TextBoxText,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=local:CustomButton},
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" />

Change DataTemplate style in ListBoxItem if the item is selected

I have a listbox with an expander inside the ItemTemplate. I managed to bind the expander's IsExpanded property to the ListBoxItem's IsSelected property ok. Now I want to apply a style to the ListBoxItem's content also bound to the IsSelected property.
<ListBox.ItemTemplate>
<DataTemplate>
<Border Name="myBorder">
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Description}" />
<StackPanel Orientation="Horizontal">
<TextBlock Text="Date:"/>
<TextBlock Text="{Binding Date}"/>
</StackPanel>
<dx:DXExpander Name="expanderDetails"
IsExpanded="{Binding Mode=TwoWay, Path=IsSelected,
RelativeSource={RelativeSource AncestorType=ListBoxItem, Mode=FindAncestor}}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Count:"/>
<TextBlock Text="{Binding Count}"/>
</StackPanel>
</dx:DXExpander>
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
What I want to do is somehow set the style of the "myBorder" Border to "NotSelectedBorderStyle" for unselected ListBoxItems, and to "SelectedBorderStyle" for the SelectedItem (ListBox with single selection).
FYI, the styles define background, border and that kind of stuff, just to make the clear which item is selected, nothing fancy in these styles.
I tried the accepted answer here but if I completely switch styles I loose the nice expanding animation my DXExpander has.
I guess there must be some solution using triggers, but I can't just hit the right spot.
Finally I got it, I'm posting it here in the hope that this will save someone else time and pain :-P
This code does some additional things: the EventSetter and the corresponding Handler method are there to capture clicks to the elements inside the DataTemplate, in order to select the ListBoxItem which contains the element (if you don't you might type text inside an item, while a different one is selected).
The inner Border ("myBorder") is just a container for the stackpanels, I had to wrap everything inside another border ("backgroundBorder") which gets the style changed when the ListBoxItem gets selected.
<Style x:Key="FocusedContainer" TargetType="{x:Type ListBoxItem}">
<Setter Property="Background" Value="LightGray"/>
<EventSetter Event="GotKeyboardFocus" Handler="OnListBoxItemContainerFocused" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border x:Name="backgroundBorder" Width="Auto" Style="{StaticResource NotSelectedBorderStyle}">
<ContentPresenter Content="{TemplateBinding Content}">
<ContentPresenter.ContentTemplate>
<DataTemplate>
<Border Name="myBorder">
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Description}" />
<StackPanel Orientation="Horizontal">
<TextBlock Text="Date:"/>
<TextBlock Text="{Binding Date}"/>
</StackPanel>
<dx:DXExpander Name="expanderDetails"
IsExpanded="{Binding Mode=TwoWay, Path=IsSelected,
RelativeSource={RelativeSource AncestorType=ListBoxItem, Mode=FindAncestor}}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Count:"/>
<TextBlock Text="{Binding Count}"/>
</StackPanel>
</dx:DXExpander>
</StackPanel>
</Border>
</DataTemplate>
</ContentPresenter.ContentTemplate>
</ContentPresenter>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="backgroundBorder" Property="Style" Value="{StaticResource SelectedBorderStyle}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Then I set the ItemContainerStyle in my ListBox to the above style:
<ListBox Background="#7FFFFFFF" HorizontalContentAlignment="Stretch"
ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True"
ItemContainerStyle="{StaticResource FocusedContainer}"/>
To finish, the code behind for the GotKeyBoardFocus handler:
private void OnListBoxItemContainerFocused(object sender, RoutedEventArgs e)
{
(sender as ListBoxItem).IsSelected = true;
}
A mess in code, but pretty neat in the UI. Hope this helps someone!

Clicking TreeView item to select not working

I have a tree with a hierarchical data template
<HierarchicalDataTemplate DataType="{x:Type local:TreeItem}" ItemsSource="{Binding Path=Children}" >
<TreeViewItem Focusable="True" ToolTip="{Binding ToolTipText}" >
<TreeViewItem.Header>
<StackPanel Orientation="Horizontal" Focusable="True" >
<Image Width="16" Height="16" Margin="3,0">
<Image.Source>
<Binding
Path="IsLeaf" Converter="{StaticResource cnvIsBooleanToStringArrayItemConverter}">
<Binding.ConverterParameter>
<x:Array Type="sys:String">
<sys:String>..\Images\document_plain.png</sys:String>
<sys:String>..\Images\folder.png</sys:String>
</x:Array>
</Binding.ConverterParameter>
</Binding>
</Image.Source>
</Image>
<TextBlock MaxWidth="300" Text="{Binding Desc}" Focusable="True" />
</StackPanel>
</TreeViewItem.Header>
</TreeViewItem>
</HierarchicalDataTemplate>
I want to select an item by clicking at the TextBlock containing "Desc", but the only way to select an item is by clicking in the space left of the text (the image area)
Any clues what is missing?
Regards
Klaus
Your data template specifies a TreeViewItem at its root, but the TreeView will automatically create a TreeViewItem around your template, having a TreeViewItem in a TreeViewItem confuses the selection mechanism.
Do something like this:
<HierarchicalDataTemplate DataType="{x:Type local:TreeItem}" ItemsSource="{Binding Path=Children}">
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="ToolTip" Value="{Binding ToolTipText}"/>
<Setter Property="Focusable" Value="True"/>
<Setter Property="Header">
<Setter.Value>
...
</Setter.Value>
</Setter>
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
</HierarchicalDataTemplate>
Edit:
After some testing it turns out that messing with the container is quite troublesome, i did not get it display the tooltip that way, unless you found a way to do it i recommend you stick to only setting HierarchicalDataTemplate.VisualTree (the default content of HierarchicalDataTemplate) which will be placed in the header of the auto-generated TreeViewItem.
As H.B. says, you should not put a TreeViewItem inside your hierarchical data template, since WPF will automatically create one to wrap your content.
If you want to bind to the ToolTip, you can do it inside the ItemContainerStyle, which will apply to all your treeview items within the TreeView.
<TreeView .... your parameters >
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="ToolTip" Value="{Binding ToolTipText}"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
I hope this helps.
Although I haven't tested it, I think your hierarchical data template should look like this:
<HierarchicalDataTemplate DataType="{x:Type local:TreeItem}" ItemsSource="{Binding Path=Children}" >
<StackPanel Orientation="Horizontal">
<Image Width="16" Height="16" Margin="3,0">
<Image.Source>
<Binding Path="IsLeaf" Converter="{StaticResource cnvIsBooleanToStringArrayItemConverter}">
<Binding.ConverterParameter>
<x:Array Type="sys:String">
<sys:String>..\Images\document_plain.png</sys:String>
<sys:String>..\Images\folder.png</sys:String>
</x:Array>
</Binding.ConverterParameter>
</Binding>
</Image.Source>
</Image>
<TextBlock MaxWidth="300" Text="{Binding Desc}"/>
</StackPanel>
</HierarchicalDataTemplate>
You may need to set the Background="Transparent" on the StackPanel and/or remove the Focusable setting on the TextBlock.

WPF GroupBox HeaderTemplate and DataBinding

I define a headertemplate into a wpf groupbox and the databinding doesn't work. I don't understand why.
<GroupBox>
<GroupBox.HeaderTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<Image Source="/PopuAssuNetApplication.UI.Control;component/Images/Members.png" Width="24" />
<TextBlock VerticalAlignment="Center">
<TextBlock.Text>
<MultiBinding StringFormat="{x:Static Member=resx:Resources.PersonsInContractGroupBox}">
<Binding Path="CurrentContract.Federation" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type GroupBox}}">
</Binding>
<Binding Path="CurrentContract.Type" Converter="{StaticResource contractTypeConverter}" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type GroupBox}}">
</Binding>
<Binding Path="CurrentContract.Number" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type GroupBox}}">
</Binding>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<WpfComponent:WaitControl Margin="7,0,0,0" VerticalAlignment="Top" Width="24" Height="24" MarginCenter="4">
<WpfComponent:WaitControl.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsMembersOfContractBusy, UpdateSourceTrigger=PropertyChanged, ElementName=PersonsInContract}" Value="true">
<Setter Property="WpfComponent:WaitControl.Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsMembersOfContractBusy, UpdateSourceTrigger=PropertyChanged, ElementName=PersonsInContract}" Value="false">
<Setter Property="WpfComponent:WaitControl.Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</WpfComponent:WaitControl.Style>
</WpfComponent:WaitControl>
</StackPanel>
</DataTemplate>
</GroupBox.HeaderTemplate>
The problem is that the HeaderTemplate is used for templating the Header thus within the HeaderTemplate your DataContext is whatever you bind or assign to the Header property of your GroupBox.
Think of the Header property as almost like the DataContext for the header of the control. Normally the DataContext property inherits its value from its parent but since not every control has a Header the Header is blank unless you set it.
By binding your Header explicitly to the current DataContext Header="{Binding}" your example should work as you expect. To help illustrate how this works I've created a simple example below that shows how the Header and DataContext work independently from each other for providing data to either the body or header of the control.
<GroupBox Header="HEADER TEXT" DataContext="BODY TEXT">
<GroupBox.HeaderTemplate>
<DataTemplate>
<Button Content="{Binding}"
Background="LightGreen" />
</DataTemplate>
</GroupBox.HeaderTemplate>
<CheckBox HorizontalAlignment="Center"
VerticalAlignment="Center" Content="{Binding}" />
</GroupBox>
This will yield a GroupBox that looks like the following.
I think that by default in databinding, wpf always gets data from the DataContext property. Seems not in datatemplate
Your assumption is correct about DataContext and it does work in the DataTemplate as I've demonstrated it's just that in the Header's template the DataContext is the value from the Header Property and not the DataContext itself.
The GroupBox does not have a member called "CurrentContract". Most probably, you want to accesss a property called "CurrentContract" from the corresponding ViewModel?! The ViewModel is the GroupBox's DataContext, so you have to change the Binding Paths to something like...
<Binding Path="DataContext.CurrentContract.Type" Converter="{StaticResource contractTypeConverter}" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type GroupBox}}">
<GroupBox >
<GroupBox.HeaderTemplate>
<DataTemplate>
<RadioButton Content="myR"
IsChecked="{Binding rIsChecked, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type GroupBox}}}" />
</DataTemplate>
</GroupBox.HeaderTemplate>
<GroupBox.Content>
<Grid IsEnabled="{Binding rIsChecked}">
</Grid>
</GroupBox.Content>
</GroupBox>
Just propagate the GroupBox DC to the DataTemplate content...works like a charm...
The lesson learned above is useful in general for DataTemplates, but I actually found out recently there is a better way to change the header of a groupbox:
<GroupBox>
<GroupBox.Header>
<CheckBox IsChecked="{Binding Path=mSomeBoolean}"/>
</GroupBox.Header>
</GroupBox>
This way there is no need to define a relative source in the bindings.
Also please note this issue with GroupBoxes and the header.
This is what worked for me:
<HeaderedContentControl Header="{Binding}" Style="{StaticResource TallHeaderedContentStyle}">
<HeaderedContentControl.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=HeaderText"} />
</DataTemplate>
</HeaderedContentControl.HeaderTemplate>

Resources