WPF ComboBox - showing something different when no items are bound - wpf

I have a ComboBox, and i want to change its look when the ItemsSource property is null. When it is in that state, i want to show a TextPanel with the text "Retrieving data" in it, and give it a look kind of similar to the watermarked textbox.
I figure to do this i need a ControlTemplate, and a trigger. I have the ControlTemplate here:
<ControlTemplate x:Key="LoadingComboTemplate" TargetType="{x:Type ComboBox}">
<Grid>
<TextBlock x:Name="textBlock" Opacity="0.345" Text="Retrieving data..." Visibility="Hidden" />
</Grid>
<!--
<ControlTemplate.Triggers>
<Trigger Property="ComboBox.ItemsSource" Value="0">
<Setter Property="Visibility" Value="Visible" />
</Trigger>
</ControlTemplate.Triggers>
-->
</ControlTemplate>
but my issue is how do i set up the trigger to show this when the ItemsSource property is null? I have tried a couple of different ways, and each way has given me the error message "Value 'ItemsSource' cannot be assigned to property 'Property'. Invalid PropertyDescriptor value.". My ComboBox xaml is this (including the attempted trigger):
<ComboBox Margin="112,35,80,0"
Name="MyComboBox"
Height="22.723"
VerticalAlignment="Top"
DisplayMemberPath="FriendlyName"
SelectedValuePath="Path"
TabIndex="160"
>
<Trigger>
<Condition Property="ItemsSource" Value="0" />
<Setter Property="Template" Value="{StaticResource LoadingComboTemplate}" />
</Trigger>
</ComboBox>
now should the trigger go on the ComboBox, or on the ControlTemplate? How do i access the ComboBox's ItemsSource property? Should i even be using a trigger?
Thanks!

Try putting {x:Null} for the value of the condition instead of 0.
Also I got it working by moving the Trigger to a style and modifing it slightly, see below.
<Style TargetType="ComboBox" x:Key="LoadingComboStyle">
<Style.Triggers>
<Trigger Property="ItemsSource" Value="{x:Null}">
<Setter Property="Template" Value="{StaticResource LoadingComboTemplate}" />
</Trigger>
</Style.Triggers>
</Style>
<ComboBox Style="{StaticResource LoadingComboStyle}" .... >
The reason it only works in a style, is that only EventTriggers are allowed in the triggers collection directly on the Framework Element. For property triggers (like above) you need to use a style (I learn something every day).
See FrameworkElement.Triggers
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.

Related

wpf visibility based on a condition

I want to show a StackPanel based on a particular condition. In this example I've used the BorderThickness property:
<ContentControl x:Name="gridDati" VirtualizingPanel.VirtualizationMode="Recycling" VirtualizingPanel.ScrollUnit="Item" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Items}" Value="{x:Null}">
<Setter Property="BorderThickness" Value="0" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=Items.Count}" Value="0">
<Setter Property="BorderThickness" Value="12" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" x:Name="pnlLoading" Visibility="Visible">
<Label Content="">
<Label.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=BorderThickness, ElementName=gridDati, UpdateSourceTrigger=PropertyChanged}" Value="0">
<Setter Property="TextBlock.Text" Value="" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=BorderThickness, ElementName=gridDati, UpdateSourceTrigger=PropertyChanged}" Value="12">
<Setter Property="TextBlock.Text" Value="STAND BY" />
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
</StackPanel>
Basically when in the code behind I apply a template on gridDati, while the item counter is still zero, the border is set correctly to 12. After that it turns to zero (item binded) and this behevior is what I want.
So, I also would like to show a StackPanel at the same condition, so I used a DataTrigger but seems that is not fired at all. How can I "link" these two condition? so show a stackpanel when I have items in the datagrid?
This is the proper way to declare the Label so you get the desired result.
<Label>
<Label.Style>
<Style TargetType="Label">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Items.Count}" Value="0">
<Setter Property="Content" Value="STAND BY"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
But there are a few points I need to explain to make sure you understand what I changed and why. I'm going to go through the XAML from the inside out.
First, I changed the Setter to use the right property name. You're using a Label, and your old Setter had Property="TextBlock.Text". TextBlock.Text is not a valid property name for a Label (no such property exists), so that wasn't going to work. The property you want is called Content.
Moving up one level to the DataTrigger. Instead of binding to gridDati that is binding to Items, I just bound directly to Items. You could do it the other way, but in my opinion it would be unusual and it might cause unforeseen bugs.
Next, you'll notice I removed the first DataTrigger. WPF dependency properties can be set in a number of different ways, and there is an order of precedence for which value will be taken over others. The default value (lowest precedence) for a Label's content is for it to be empty. When the DataTrigger applies the Setter, it overrides that value (it has higher precedence). When the DataTrigger condition is no longer fulfilled (Items.Count != 0), WPF stops applying the Setter and the value reverts back to the default, because there is no longer any value of higher precedence overriding it. So you don't need to add a second DataTrigger resetting to default, wit ill do that automatically.
Moving up further you'll see I changed the opening Style tag to <Style TargetType="Label">. It's common practice to set the TargetType of a Style. Doing this also gives you IntelliSense options for Setters in that Style, which might have helpped you catch the mistake you made by trying to use TextBlock.Text as a property name.
Finally, I removed Content="" from the opening Label tag. Setting the value of a property directly on an element in XAML has a very high precedence, which overrides all Styles and DataTriggers. As long as this was there, nothing you did in any Style would change anything for the Label's Content.

Trouble binding to a property of templatedparent to set the value inside of a style setter in wpf

My problem:
I haven't been able to figure out how I inside the 'Setter.Value' field of a setter for a property A in a style targeting a particular control can do a binding to a property B of that particualr control. More specifically I want to use the Foreground brush value on a graphical element inside the visual tree of the Content property of a Button. this will ensure that the graphical element always has the foreground color set for this button control.
What I try to achive:
I'm working on a WPF-application where I have three button controls:
DefaultButton
SpecialButton
ExtendedSpecialButton
The DefaultButton is where I define the style of buttons in the application through a style with a ControlTemplate.
The SpecialButton introduces a new property not supposed to be used for general buttons. This property will be represented by one visual state that I define through a style setter. Else from that it shall be identical in apperance to the DefaultButton.
I define the style of this SpecialButton by basing it on the style of the DefaultButton. In this style there is no ControlTemplate only a MultiTrigger-response on the basis of a couple of property conditions setting av a couple of visual properties:
<Style x:Key="SpecialButtonStyle" TargetType="{x:Type MyControls:SpecialButton}" BasedOn="{StaticResource DefaultButtonStyle}">
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsActive" Value="false"/>
<Condition Property="IsMouseOver" Value="false"/>
</MultiTrigger.Conditions>
<Setter Property="BorderBrush" Value="{DynamicResource ButtonDisabledBorder}" />
<Setter Property="Background" Value="{DynamicResource ButtonDisabledBg}" />
</MultiTrigger>
</Style.Triggers>
</Style>
All this worked great.
The next step is also no problem:
I wanted to base the ExtendedSpecialButton on the SpecialButton and set a default shape content inside the button.
<Style x:Key="ExtendedSpecialButtonStyle" TargetType="{x:Type MyControls:ExtendedSpecialButton}" BasedOn="{StaticResource SpecialButtonStyle}">
<Setter Property="Content">
<Setter.Value>
<Rectangle Fill="Black" Height="5" Width="15"></Rectangle>
</Setter.Value>
</Setter>
</Style>
The original style of DefaultButton is still present - the added visual state responding to the IsActiveProperty of the SpecialButton is still with us - and the ExtendedSpecialButton also inherited the visual behaviour created by the MultiTrigger of the SpecialButton.
I also successfully displayed a graphical element that this ExtendedSpecialButton should have.
However I wanted the fill of this graphical element to use the Foreground color. This foreground color is originally styled in the DefaultButton and works just fine for the two first buttons.
The code below is how I currently thought such a binding should be done. But this does not work:
<Style x:Key="ExtendedSpecialButtonStyle" TargetType="{x:Type MyControls:ExtendedSpecialButton}" BasedOn="{StaticResource SpecialButtonStyle}">
<Setter Property="Content">
<Setter.Value>
<Rectangle Fill="{Binding RelativeSource={RelativeSource TemplatedParent}}" Height="6" Width="20"></Rectangle>
</Setter.Value>
</Setter>
</Style>
Does anyone know what I could do to set up the binding so that it does what I intended it to do?

ContentControl change ContentTemplate on GotFocus

I have a UserControl which contains a ContentControl. When the user clicks this ContentControl I want to change its ContentTemplate, to make it "editable" (instead of labels display textboxes for example).
What I have is this:
<StackPanel>
<ContentControl Style="{DynamicResource ContainerStyleEditable}" GotFocus="ContentControl_GotFocus"></ContentControl>
</StackPanel>
and in userControl resources i have
<Style TargetType="{x:Type ContentControl}" x:Key="ContainerStyleEditable">
<Setter Property="ContentTemplate" Value="{DynamicResource ItemTemplateReadOnly}" />
<Style.Triggers>
<Trigger Property="IsFocused" Value="True">
<Setter Property="ContentTemplate" Value="{DynamicResource ItemTemplateEditable}" />
</Trigger>
</Style.Triggers>
</Style>
This doe not work, it seems the GotFocus event never fires. What is the way to to this?
I usually base my triggers of IsKeyboardFocusWithin instead of IsFocused because often the focused element usually isn't the actual ContentControl, but rather a control inside it's Content.
Also, be sure that at least one control inside the ContentControl can accept focus so the control can get focus. If nothing inside the control can accept focus, your trigger will never fire.

How do I do this in XAML?

I want to do this in XAML with a trigger, how do I do it?
If ListBox1.SelectedIndex > -1 Then
Border1.Visibility = Windows.Visibility.Visible
Else
Border1.Visibility = Windows.Visibility.Hidden
End If
This XAML code does NOT work. SelectedIndex member is not valid because it does not have a qualifying type name.
<ListBox.Triggers>
<Trigger SourceName="ListBox1" Property="SelectedIndex" Value="False">
<Setter TargetName="Border1" Property="Visibilty" Value="Hidden" />
</Trigger>
</ListBox.Triggers>
Can you show me how are you trying to do this in xaml?
In case of this error message you need to mention type name also with the property inside trigger.
<Trigger SourceName="ListBox1" Property="ComboBox.SelectedIndex" Value="-1">
<Setter TargetName="Border1" Property="Border.Visibility" Value="Hidden" />
</Trigger>
Further, it seems that you are adding a Trigger in <ListBox.Triggers> collection, but you can only add EventTrigger in to this collection. So you need to declate a Style for your ListBox to add a Trigger for that and your Border element should be inside the ControlTemplate of ListBox, but in your case Border seems to be outside of ListBox, so declaring a style will not be a solution. Instead you should use Binding with SelectIndex property with the help of a ValueConverter(say IndexToVisibilityConverter). You need to define this converter in codebehind and add it in resources.
<Border Visibility={Binding Path=SelectedIndex, ElementName=ListBox1, Converter={StaticResource IndexToVisibilityConverter}}/>
Choice is totally on your requirements.
It is as simple as this:
<Border x:Name="Border1" ... Visibility="Visible" ... />
...
<Trigger SourceName="ListBox1" Property="ListBox.SelectedIndex" Value="-1">
<Setter TargetName="Border1" Property="UIElement.Visibilty" Value="Hidden" />
</Trigger>
Update
I see from the new code you posted that you're trying to use a trigger directly on the ListBox. The trigger must be in a ControlTemplate to use SourceName. If your UI is done with "custom controls" (my preference), you will already have a ControlTemplate to put it in. If not, you can easily add one by wrapping your XAML in a generic "ContentControl" (base class, not any subclass) and setting its template, like this:
<Window ...>
...
<ContentControl>
<ContentControl.Template>
<ControlTemplate TargetType="{x:Type ContentControl}">
<Grid>
...
<ListBox x:Name="ListBox1" ... />
...
<Border x:Name="Border1">
...
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger SourceName="ListBox1" Property="ListBox.SelectedIndex" Value="-1">
<Setter TargetName="Border1" Property="UIElement.Visibility" Value="Hidden" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</ContentControl.Template>
</ContentControl>
...
</Window>
A better solution would probably be to use custom controls. Using a binding with a converter is possible but not as elegant IMHO.

How do I trigger a style change if DataContext is null or not using WPF

I have a page with several controls. The controls are bound to display values which they get from the page's DataContext. What I would like to do is display another look of the page should the DataContext be null. In some cases the controls of the page should display differently if "their" property is set or not.
Is is possible to create a binding to see if the DataContext is set?
What I did as a workaround was to add a IsDataContextSet property to the page and the specify a binding like:
Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Page}}, Path=IsDataContextSet}" Value="false"
This works as I expect but I have a feeling that their is more elegant way to do this. Or at least or more WPFish way.
Given the scenario you describe, I would set the properties with a style and a data trigger. The data trigger would use the default binding which is the data context.
An example might look like this:
<Border>
<Border.Style>
<Style TargetType="Border">
<Setter Property="Background"
Value="Orange" />
<Style.Triggers>
<DataTrigger Binding="{Binding}"
Value="{x:Null}">
<Setter Property="Background"
Value="Yellow" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
The border will be orange unless the data context is null, in which case the background is yellow.

Resources