Databinding to XML in a DataTrigger in WPF - wpf

In a WPF application, I have correctly bound a DataTemplate to an XML node that looks like:
<answer answer="Tree", correct="false" score="10" />
In my application, I have a TextBlock with the answer in it. At first, I want it invisible, but when the correct attribute in the XML file changes to "true", it must become visible.
My DataTemplate is hooked up correctly, because everything else works. For example, if I change the answer attribute in the XML file (just for testing), it changes in my WPF view. But I'm having troubles with the visibility. This is my XAML:
<TextBlock Text="{Binding XPath=#answer}" Visibility="Hidden">
<TextBlock.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding XPath=#correct}" Value="true">
<Setter Property="TextBlock.Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
I'm guessing the Databinding in the DataTrigger isn't working correctly. Anyone have a clue?

I have run into the same problem with databound ToggleButtons. Try removing the Visibility="False" and replacing it with another DataTrigger that handles the incorrect case.

I think the issue is that the Visibility property is hard-coded. Try setting the Visibility in the style:
<TextBlock Text="{Binding XPath=#answer}">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Visibility" Value="Hidden"/>
<Style.Triggers>
<DataTrigger Binding="{Binding XPath=#correct}" Value="true">
<Setter Property="TextBlock.Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>

Sure, it works if you give a specific else case instead of just false. As in my case, it was {x:Null} and value. So when its value to bind is present, it will be true and TextBlock.Visibilty will be set using setters value and when binding path does not have any value inside it, i.e. null in my case, its simply {x:Null} :)

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.

Binding to the Items properties of an ItemsControl

I'm looking to a way to bind a Button Text or IsEnabled property for example to the IsChecked property in an ItemsControl
Here is my simplified source code :
<StackPanel>
<ItemsControl ItemsSource="{Binding Tasks}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Margin="10,0,10,0" Text="{Binding Name}"/>
<CheckBox IsChecked="{Binding InProgress}"/>
<CheckBox IsChecked="{Binding Done}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button/>
</StackPanel>
I would like (for example) if all the "Done" CheckBoxes are Checked, to set Button Text to some value or to enable it. I thought doing this with Data Binding in Xaml using DataTriggers but I don't know how to do it.
Ca anyone give me a full xaml solution ?
New Answer
Sorry, I misunderstood the question. I would expose another property from your DataContext that simply returns true/false if all the items in the collection are checked or not, and base your Button's Text/IsEnabled off that property using a DataTrigger
<Style TargetType="{x:Type Button}">
<Setter Property="IsEnabled" Value="False" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsAllChecked}" Value="True">
<Setter Property="IsEnabled" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
Old Answer
DataTriggers simply take a binding, and check if the result is equal to some value.
<Style TargetType="{x:Type TextBox}">
<Setter Property="IsEnabled" Value="False" />
<Style.Triggers>
<DataTrigger Binding="{Binding Done}" Value="True">
<Setter Property="IsEnabled" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
If you need to test more than one condition, you need a MultiDataTrigger
<Style TargetType="{x:Type TextBox}">
<Setter Property="IsEnabled" Value="False" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=InProgress}" Value="True" />
<Condition Binding="{Binding Path=Done}" Value="True" />
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="True" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
Note that the default value of setter is part of the style. This is important because if you set the default value on the <TextBox> then it has a higher priority than triggered values, so triggered values are unable to change the current value.
You can change this line...
<Button/>
To this
<Button Content={Binding ButtonContent}/>
And set the 'ButtonContent' property in your ViewModel whenever your 'Done' check box gets checked.
Usually, the best way to do that is to synthesize the state you want to apply to your button into a property of your ViewModel.
The WPFy way in that case is for your ViewModel to expose a command that implements the action of your button.
The command in turns can react to changes in your data to signal if it is available or not, and thus will automaticaally disable/enable your button. An added bonus is that you can easily implement a menu for example, that will be properly enabled/disabled at no cost once you have your command.
It's been a long time since I had any chance to touch WPF, so I do not have any really useful links at hand, but if I were you I would learn about commands in WPF, they are a very important part of the MVVM architecture.

TextBlock trigger instead of using converter

I want to show a number on screen. If that number is 0 I don't want it to show at all.
<TextBlock Text="{Binding Path=Class.Count}" FontSize="20" FontWeight="Bold">
<TextBlock.Triggers>
<DataTrigger Binding="{Binding Path=Class.Count}" Value="0">
<Setter Property="TextBlock.Text" Value=""/>
</DataTrigger>
</TextBlock.Triggers>
</TextBlock>
I attempted the above piece of code after a regular trigger failed to solve my problem. I do not want to write a converter to act upon one specific number. Is there a way to create a trigger that will hide the number if it is 0?
EDIT: When I try to use a regular trigger or a data trigger I get a xaml parse error telling me I need to use an event trigger.
I tried to set the value in the setter to a number to make sure that having a blank value was not causing the problem
You are fighting with the binding to set the Text Property.
I'd make the control collapsed/hidden instead of setting the text to String.Empty.
Less confusion.
EDIT
<TextBox>
<TextBox.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Name.Length}" Value="0">
<Setter Property="UIElement.Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
OR
<TextBox>
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding Name.Length}" Value="0">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
Only a Style allows DataTriggers.
The xaml parser wants to check the property's existence so it needs the ownertype. Visibility is declared on the UIElement class. There are two ways of specifying that as I have shown in both examples.

WPF ComboBox that shows nothing selected when disabled (IsEnabled == false)

I'm thinking out different ways to have a WPF ComboBox show blank as if nothing is selected when IsEnabled is set to false. Like always I'm trying to do this without having to redefine the whole control template for the ComboBox which is always a struggle I have with WPF. If anybody has any solutions more elegant than redefining the whole ComboBox control template please let me know.
The reason for what I'm trying to do is I have a CheckBox that represents an "All" option and when checked it disables the ComboBox which is used to pick only a single individual item. If my CheckBox is checked it is sometimes confusing to the users to see a value remaining in the ComboBox since that value has no meaning in that state of the UI.
Another requirement is that the solution cannot modify the SelectedValue, SelectedIndex, or SelectedItem values of the ComboBox since I would like to retain the previuosly selected item in the case that the users unchecks the "All" CheckBox.
Solution based on HCL's answer:
<ComboBox IsEnabled="{Binding ElementName=myCheckBox, Path=IsChecked}"
ItemsSource="{Binding Path=MyItems}"
SelectedValue="{Binding Path=MySelectedItem}">
<ComboBox.ItemTemplate>
<DataTemplate>
<ContentControl x:Name="content" Content="{Binding MyItemDescription}" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ComboBox}, Path=IsEnabled}"
Value="False">
<Setter TargetName="content"
Property="Visibility"
Value="Hidden" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
You can do something with triggers:
Try setting the ItemTemplate to an empty DataTemplate when the box is disabled. This will affect the rendering of the selected item and therefore hide it.
Another simple but not very nice solution would be to set the foreground color to the same as a background color.
I believe you can do this with a Style, rather than redefining the control template. Use a Trigger on the IsEnabled property to set the text shown in the ComboBox. Altering the SelectedItem would be my first approach, but since you don't want to do that, you may find success setting the DisplayMemberPath. Something like this (untested)...
<Style TargetType="{x:Type ComboBox}">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Trigger.Setters>
<Setter Property="DisplayMemberPath" Value="{x:Null}"/>
</Trigger.Setters>
</Trigger>
</Style.Triggers>
</Style>
Here's a style that does what you want. It employs a technique that I use all the time: a grid that contains multiple versions of the control, and data triggers that ensure that only one version is visible at any one time.
<ComboBox.Style>
<Style TargetType="ComboBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ComboBox">
<DockPanel>
<CheckBox x:Name="IsActive" DockPanel.Dock="Left"/>
<Grid>
<ComboBox
ItemsSource="{TemplateBinding ItemsSource}"
SelectedItem="{TemplateBinding SelectedItem}"
SelectedIndex="{TemplateBinding SelectedIndex}"
SelectedValue="{TemplateBinding SelectedValue}">
<ComboBox.Style>
<Style TargetType="ComboBox">
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=IsActive, Path=IsChecked}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
<ComboBox>
<ComboBox.Style>
<Style TargetType="ComboBox">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=IsActive, Path=IsChecked}" Value="False">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
</Grid>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ComboBox.Style>
This preserves the selected item, selected index, and selected value, just as you want. In fact, it does this a little too well; there's not actually a way of telling that the user deactivated the combo box, since there's no property on ComboBox that exposes this information. I'd probably actually implement this as a custom control derived from ComboBox that exposed the value of the check box as an IsActive property. There are lots of other ways to do it.

Changing TextBlock.Text in trigger didn't work

I have the next code in my view:
<Style x:Key="documentFileNameStyle">
<Setter Property="TextBlock.Foreground" Value="Gray"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Untitled}" Value="True">
<Setter Property="TextBlock.FontStyle" Value="Italic"/>
<Setter Property="TextBlock.Text" Value="no file name"/>
</DataTrigger>
</Style.Triggers>
</Style>
<DataTemplate x:Key="documentTemplate">
<TextBlock Text="{Binding Path=FileName}" Style="{StaticResource documentFileNameStyle}"/>
</DataTemplate>
But setting TextBlock.Text to a string didn't work. TextBlock.FontStyle changes to Italic, so whole trigger works properly. What is wrong?
Local assignment of Properties has a higher precedence than setting the values in triggers.
Also you are using Binding (Path=FileName) to set the Text-Property of the TextBlock. So changing the Text in Triggers doesn´t effect the Property.
As you are using Binding. I would change the Property "FileName" to return "no file name" if the Property "Untitled" is "true".

Resources