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.
Related
I have following ControlTemplate which works fine
<ControlTemplate x:Key="TotalCostsStatisticTemplate">
<StackPanel x:Name="ContentHolderPanel" Visibility="Collapsed"
Orientation="Horizontal" HorizontalAlignment="Center">
<TextBlock x:Name="ValueTextBlock" VerticalAlignment="Center"
Style="{DynamicResource PhasingValueTextStyle}">
<TextBlock.Text>
<MultiBinding Converter="{ttConverters:CustomDisplayFormatConverter}">
<Binding Path="FormatSettings" />
<Binding Path="AvailableStatistics.CostsFormat"/>
<Binding Path="TotalCosts" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding ShowTotalCosts}" Value="True">
<Setter TargetName="ContentHolderPanel" Property="Visibility" Value="Visible"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
But if I add another DataTrigger then the MultiBinding on TextBlock.Text doesn't work when control using this template is loaded for first time i.e. the converter CustomDisplayFormatConverter fires only once with all values as UnsetValue and doesn't fire again (it works fine on reloading the window again).
<ControlTemplate x:Key="TotalCostsStatisticTemplate">
<StackPanel x:Name="ContentHolderPanel" Visibility="Collapsed"
Orientation="Horizontal" HorizontalAlignment="Center">
<TextBlock x:Name="ValueTextBlock" VerticalAlignment="Center"
Style="{DynamicResource PhasingValueTextStyle}">
<TextBlock.Text>
<MultiBinding Converter="{ttConverters:CustomDisplayFormatConverter1}">
<Binding Path="FormatSettings" />
<Binding Path="AvailableStatistics.CostsFormat"/>
<Binding Path="TotalCosts" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding ShowTotalCosts}" Value="True">
<Setter TargetName="ContentHolderPanel" Property="Visibility" Value="Visible"/>
</DataTrigger>
<!--Trigger causing problem (breaking TextBlock.Text multi binding on first load) -->
<DataTrigger Binding="{Binding IsCostsComplete}" Value="False">
<Setter TargetName="ValueTextBlock" Property="Foreground"
Value="{DynamicResource ManagerErrorBrush}"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
If I place this DataTrigger inside TextBlock.Style it works fine!
Any idea what's wrong?
Update:
Here's how its getting used
<ControlTemplate x:Key="PhasingStatisticValuesTemplate">
<StackPanel x:Name="ContentHolderPanel" Orientation="Horizontal" Visibility="{Binding IsValid, Converter={StaticResource boolToVisibilityConverter}}">
<!-- Other control elements based on various templates -->
<Control Margin="20,10" Template="{DynamicResource TotalCostsStatisticTemplate}"/>
</StackPanel>
</ControlTemplate>
This PhasingStatisticValuesTemplate is used in another ControlTemplate, which is then used inside a DataTemplate, so there is a long hierarchy of ControlTemplates -
<ControlTemplate x:Key="PhasingStatisticsTemplate">
<Grid>
<ScrollViewer Style="{DynamicResource CompactHorizontalScrollViewerStyle}" Name="ScrollContainer">
<Grid Name="ScrollViewerGrid" Background="Transparent">
<ContentControl Template="{DynamicResource PhasingStatisticValuesTemplate}" Name="ScrollViewerContent" />
</Grid>
</ScrollViewer>
</Grid>
</ControlTemplate>
Another useful info. might be that existing style on that TextBlock also updated Foreground, not sure how that can cause this behavior -
<Style x:Key="PhasingValueTextStyle" TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsComplete}" Value="False">
<Setter Property="Foreground" Value="{DynamicResource ManagerErrorBrush}"/>
</DataTrigger>
</Style.Triggers>
</Style>
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
I'm trying to do the following. I have a label bound to an object that have two properties. One I want to display and one I want to use for the datatrigger.
Here what's I've come up with yet :
<Label Grid.Row="5" Content="{Binding ElementName=InformationUserControl, Path=Info.ObjectBound}">
<Label.Style>
<Style TargetType="{x:Type Label}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource self}, Path=Content.InterpretationValue}">
<DataTrigger.Value>
<enums:DataInterpretation>Neutral</enums:DataInterpretation>
</DataTrigger.Value>
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Label}">
<TextBlock Text="{TemplateBinding Content.Value}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Label.Style>
</Label>
The problem is that my Template overrides the default one so it display nothing. Is there a way to make it work?
Thanks !
I think the problem is not that you override the template but that the binding is broken, i would try this:
<TextBlock Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=Content.Value}" />
When making controls non-amendable we display them as a TextBox to keep a consistent style. The problem is that a ComboBox can have any type of data so binding the Text property of the ControlTemplate TextBox is not as simple as using SelectedItem.
<Style TargetType="{x:Type ComboBox}">
<Style.Triggers>
<Trigger Property="IsReadOnly" Value="True">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<TextBox Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type ComboBox}}, Path=????, Converter={StaticResource ResourceKey=ComboToTextConverter}, UpdateSourceTrigger=PropertyChanged}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
The idea I have is to use a Converter and send the whole ComboBox so it can be handled by the Converter code. Is there anyway to do this?
Any other suggestions are welcome!
you need to use the SelectedValue and SelectedValuePath properties:
<Style TargetType="ComboBox" x:Key="cStyle">
<Style.Triggers>
<Trigger Property="IsReadOnly" Value="True">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ComboBox">
<TextBox Text="{Binding RelativeSource=
{RelativeSource TemplatedParent},
Path=SelectedValue}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
and heres your ComboBox
<ComboBox Name="cbox" ItemsSource="{Binding}"
Style="{StaticResource cStyle}"
SelectedValuePath="SomeText"
DisplayMemberPath="SomeText" />
now when you set the IsReadOnly property to true on the ComboBox, it turns into a TextBox with the selected value as its text.
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}"/>