WPF Listbox - Empty List Display Message - wpf

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).

Related

Using DataTrigger for TextBlock HorizontalAlignment property

On my Grid I Have TextBlock and Button.
If Button is not visible I want my TextBlock.HorizontalAlignment to be set to Center.
If Button is visible I want my TextBlock.HorizontalAlignment to be set to Right. Here is my code:
<TextBlock Grid.Row="0" VerticalAlignment="Center" Name="myTextBlock" Text="{Binding TileTextId}" TextWrapping="Wrap" TextAlignment="Center" >
<TextBlock.Triggers>
<DataTrigger Binding="{Binding ElementName=myButton, Path=IsVisible}" Value="True">
<Setter Property="HorizontalAlignment" Value="Right" />
</DataTrigger>
</TextBlock.Triggers>
</TextBlock>
I get the error:
'HorizontalAlignment' member is not valid because it does not have a qualifying type name.
So I tried to add TextBlock.HorizontalAlignment, like this:
<TextBlock Grid.Row="0" VerticalAlignment="Center" Name="myTextBlock" Text="{Binding TileTextId}" TextWrapping="Wrap" TextAlignment="Center" >
<TextBlock.Triggers>
<DataTrigger Binding="{Binding ElementName=myButton, Path=IsVisible}" Value="True">
<Setter Property="TextBlock.HorizontalAlignment" Value="Right" />
</DataTrigger>
</TextBlock.Triggers>
</TextBlock>
I get the error:
XamlParseException
How should I do that?
Don't try to use TextBlock.Triggers, instead go for a Style with Style.Triggers.
<StackPanel>
<TextBlock Text="TextBlock Content" Margin="5">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=myButton,Path=IsVisible}" Value="True">
<Setter Property="HorizontalAlignment" Value="Right"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<Button x:Name="myButton" Content="Click Me!" Margin="5"/>
</StackPanel>
Take note of the documentation, as it mentions, why style triggers are needed here.
Note that the collection of triggers established on an element only
supports EventTrigger, not property triggers (Trigger). If you require
property triggers, you must place these within a style or template and
then assign that style or template to the element either directly
through the Style property, or indirectly through an implicit style
reference.
Try to do this with Style
<TextBlock Grid.Row="0" VerticalAlignment="Center" Name="myTextBlock" Text="{Binding TileTextId}" TextWrapping="Wrap" TextAlignment="Center" >
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=myButton, Path=IsVisible}" Value="True">
<Setter Property="HorizontalAlignment" Value="Right" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>

Setting different styles for Dual level grouping with DataTrigger

I have a dual level grouping and thought I could define different styles with DataTriggers.
Thinking that GroupStyles.HeaderTemplate would bind to CollectionViewGroup I tried DataBinding to IsBottomLevel property.
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock x:Name="GroupName"
Text="{Binding Path=Name}"
Foreground="Red" />
<DataTemplate.Triggers>
<DataTrigger Binding="IsBottomLevel" Value="True" >
<Setter TargetName="GroupName" Property="Foreground" Value="Blue" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
Can I get this to work somehow?
Define your trigger in the Style of the TextBlock itself, TargetName normally is for ControlTemplates, then you can just drop that.
This is not a binding:
Binding="IsBottomLevel"
You should replace it with the following of course:
Binding="{Binding IsBottomLevel}"
<TextBlock Text="{Binding Name}">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="Red" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsBottomLevel}" Value="True">
<Setter Property="Foreground" Value="Blue" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
If you set the red Foreground directly in the TextBlock declaration the trigger will have no effect due to precedence.

WPF Display TextBlock with validation error message below control

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}"/>

Can't update textbox property using DataBinding

I have a custom window which have two depencency properties: Boolean? ValidationStatus, and string ValidationMessage. Binding these properties works fine but trigger doesn't seem to be triggered when these values change. What am I doing wrong?
<TextBlock x:Name="validationTextBox"
Grid.Row="1"
Grid.ColumnSpan="2"
Text="{Binding ElementName=_this, Path=ValidationMessage}"
TextAlignment="Center"
Background="Green">
<TextBlock.Style>
<Style>
<Style.Triggers>
<DataTrigger Value="False" Binding="{Binding ElementName=_this, Path=ValidationStatus}">
<Setter Property="Panel.Background" Value="Red"/>
<Setter Property="TextBox.Text" Value="Outer checkbox is not checked"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
Style Setters do not override local attribute settings. Therefore the data trigger's values are being ignored because you have specified the Text and Background properties on the TextBlock. To fix the problem set the default values of these properties in the style as shown in the following code:
<TextBlock x:Name="validationTextBox"
Grid.Row="1"
Grid.ColumnSpan="2"
TextAlignment="Center">
<TextBlock.Style>
<Style>
<Setter Property="TextBox.Text" Value="{Binding ElementName=_this, Path=ValidationMessage}"/>
<Setter Property="TextBox.Background" Value="Green"/>
<Style.Triggers>
<DataTrigger Value="False" Binding="{Binding ElementName=_this, Path=ValidationStatus}">
<Setter Property="TextBox.Background" Value="Red"/>
<Setter Property="TextBox.Text" Value="Outer checkbox is not checked"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>

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