WPF Applying a trigger on binding failure - wpf

This question is a follow on from this one...
I am binding to a heterogeneous collection of objects, not all objects have the same set of properties. I am doing this in a datagrid. I would like to gray out the cell if the binding fails. Is there a way to apply a trigger if a binding fails?
EDIT: The answer below was suitable for my purposes, but i followed up with this question because I would like to know how to do it (in a non hack fashion - i do love the hack however, don't get me wrong)

As far as I know, you can't do this directly. However, if you can identify a value that will never be returned from successful bindings, you can create a DataTrigger whose binding has that value as its FallbackValue, and trigger on that same value:
<!-- Hibble returns only positive values -->
<DataTrigger Binding="{Binding Hibble, FallbackValue=-1}" Value="-1">
<Setter Property="Background" Value="Red" />
</DataTrigger>
In theory it might be possible to omit the FallbackValue and trigger on {x:Static DependencyProperty.UnsetValue}, which would be much cleaner, but this doesn't appear to work in practice.

Related

WPF Setter Value inlining

I have this code:
<Setter Property="WindowChrome.WindowChrome">
<Setter.Value>
<WindowChrome CaptionHeight="{x:Static SystemParameters.CaptionHeight}"/>
</Setter.Value>
</Setter>
The application starts correctly, but it's verbose.
I tried to inline my code, so I wrote this:
<Setter Property="WindowChrome.WindowChrome"
Value="{x:Static SystemParameters.CaptionHeight}"/>
But now if I run the application, it doesn't start. Why?
The property is of type WindowChrome, so it expects values of type WindowChrome.
In the first case, it happens well. Also, you give to the WindowChrome instance a value of the correct type for its property CaptionHeight.
In the second case, you're trying to assign to the WindowChrome property a value of a totally different type (the type of CaptionHeight).
Now, if in your application there is only one single instance of WindowChrome, you can declare it as a StaticResource:
<App.Resources>
<WindowChrome x:Key="WindowChromeResource"
CaptionHeight="{x:Static SystemParameters.CaptionHeight}"/>
</WindowChrome>
</App.Resources>
And then call it every time you need:
<Setter Property="WindowChrome.WindowChrome"
Value="{StaticResource WindowChromeResource}"/>
If instead you need a dynamic number of different instances, it's definitely not possible to do this inline.
Many developers had claimed about the WPF verbosity many times before you, but the WPF team has never improved that aspect. See this and this for wider discussion.

Let one control be enabled/disabled based on another control

I have two DataGrid's that I want do have enabled/disabled based on whether precisely 1 element is selected in another DataGrid. What is the simplest way to accomplish this dependency control in WPF?
You could use a trigger:
<DataGrid.Style>
<Style TargetType="DataGrid">
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedItems.Count,
ElementName=datagrid1}"
Value="1">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Style>
You could:
Create an IValueConverter, perhaps it is called NotEqualToOneBooleanConverter
Bind IsEnabled from one DataGrid to the SelectedItems.Count on the other
Set the Converter on this Binding to be the NotEqualToOneBooleanConverter
This approach is nice since once your converter is created, it can be applied throughout your XAML and to any type and any property (not just DataGrid or SelectedItems.Count). To make it even more flexible, you could have a more generic version of this converter that could compare any two values specified directly from XAML (one from the Binding and one specified as property on the Converter).
The downside to this approach - it's XAML only, and difficult to test especially if what you are trying to achieve is a business requirement and not just a graphical effect.
Hope this helps!
This is my quick hack:
tablesControl.SelectionChanged += (sender, sce) =>
{
var c = tablesControl.SelectedItems.Count;
var orderingPossible = c == 1;
itemsControl.IsEnabled = orderingPossible;
};
In the first Grid have an event or Command that is fired when you click on that cell, in this event you need to have some bool property you can set to false, then bind the Enabled property to this bool. If you are using MVVM this will be very easy, have a look at this to see how - http://www.youtube.com/watch?v=tKfpvs7ZIyo

WPF: Applying multiple data templates?

Let's say I am displaying a data component, such as TreeView.
Let's say it is bound to a tree structure, of base type TreeViewItem.
TreeViewItem
TreeViewItem
TreeViewItem
TreeViewItem
and so on.
But some of those items are more specific implementations of TreeViewItem such as AnimalTreeViewItem and even more granular ZebraTreeViewItem
TreeViewItem
AnimalTreeViewItem
ZebraTreeViewItem
PlantTreeViewItem
Now, let's say i want these items to be rendered in a similar fashion, but there would be slight differences in rendering depending on underlying type.
One way i've gotten this to work, is using DataTemplate.
Problem is that i have to create a separate template for each type, with 100% of contents defined in the same way (minus small difference in layout / color etc)
Is there a way to define data templates, that share most of their contents together? meaning, w/out having to create 2 templates, that are almost identical in their markup, just to change the background color of some textbox etc..
You might be interested in the solution shown in this article. It works fine if the differences between are minor, e.g. a different color for some element, but it can also handle more complex scenarios through the use of triggers.
The answer to this question is DataTriggers
<DataTemplate x:Key="myTaskTemplate">
...
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=TaskType}">
<DataTrigger.Value>
<local:TaskType>Home</local:TaskType>
</DataTrigger.Value>
<Setter TargetName="border" Property="BorderBrush" Value="Yellow"/>
</DataTrigger>
</DataTemplate.Triggers>
...
</DataTemplate>
This data template will only be 'triggered' when the TaskType is Home.
http://msdn.microsoft.com/en-us/library/ms742521.aspx#adding_more_to_datatemplate

Giving a WPF ItemsControl a different look based off whether ItemsSource holds a single value or multiple values?

Are there any slick ways to style/template a WPF ItemsControl differently based off whether ItemsSource holds a single value or multiple values?
What I've done so far is to create a custom ItemsControl class which among other things displays the list of bound items as a horizontally oriented comma separated list. So far I'm pretty happy with the results however I want to show a more brief view of the bound data in cases where multiple values are bound and if only a single value is bound then I want to show a more extended view of the bound data with a longer string description. I figure this is probably best solved by dynamically choosing the template either based off a trigger or possibly by using a template selector but it's not yet clear to me how this would be done.
You could use a DataTrigger in your style to replace the template:
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=Items.Count}" Value="1">
<Setter Property="Template">
<Setter.Value>
<!-- Insert Template here -->
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
You could also add one for where the Value is 0 if you wanted to display a "no records" template.
You should use a StyleSelector.
Here is a sample.

Change TreeViewItem Header by trigger

Having such Style
<Style TargetType="TreeViewItem">
<Style.Triggers>
<Trigger Property="IsExpanded" Value="True">
<Setter Property="Header" Value="Pink"></Setter>
</Trigger>
</Style.Triggers>
</Style>
I would expect the text of the expanded TreeViewItems to be "Pink", but nothing set actually. If I change to Property="Background" instead, it works. How Header differs from Background?
I think I'll need more info to answer this more completely. However, if I have to guess, I'd say you're probably setting the Header property on the TreeViewItem explicitly like this:
<TreeView>
<TreeViewItem
Header="Blue"/>
</TreeView>
And, in this case, setting it explicitly will override anything that you put in the style.
To elaborate on ascalonx's answer:
copied from Josh Smith's blog:
There is a well-defined set of rules
which is used internally by WPF to
figure out what the real value of a DP
is. Here is a brief summary of the
rules of precedence used when
resolving the value of a DP (from
highest to lowest priority):
Property system coercion
Active animations, or animations
with a Hold behavior
Local value
TemplatedParent template
Style triggers
Template triggers
Style setters
Theme style
Inheritance
Default value from dependency
property metadata
So if you explicitly set the Header property, (or if you bind it I think), you have that problem.

Resources