WPF: Applying multiple data templates? - wpf

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

Related

Rolling effect in ListBox

This is a follow-up to this question
Increase FontSize for hovered element in WPF ListBox
I have a ListBox where I want a "rolling" effect. From start all my items in the ListBox have a FontSize of 12, and when IsMouseOver="True" they get FontSize=18. Now I want the item below and above the IsMouseOvered item to get FontSize 16. I've got a feeling this is not so straight forward. Anyone got any idea?
This is my ListBox now
<ListBox Name="ListBox" ItemsSource="{Binding MyList}" DisplayMemberPath="Property1">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="TextBlock.FontSize" Value="18"/>
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
Thanks
This is going to be difficult to accomplish in straight XAML, because there's no property that you can examine that tells you if the mouse is currently over the next (or previous) element in the list.
The way I'd implement this: I'd create a wrapper class for the items in your list that exposes the properties IsMouseOver, FontSize, and Content. I'd implement a parent class that maintains some kind of indexable collection of these items (like an array or a list), and that registers for its items' PropertyChanged events so that it can know which item in the list the mouse is presently hovering over. The parent class would then be responsible for adjusting the FontSize on the child objects whenever the currently moused-over item changes. All the XAML does is bind to the FontSize on the child objects.
Note, by the way, that if you implement some kind of cool effect (like the one in the example Aaron linked to, which incidentally doesn't do what you've said you want) that, in resizing elements when IsMouseOver changes, moves them on the screen in such a way that the element that the mouse is over changes too, your users will hunt you down and kill you in your sleep.
It appears you are going for the fish eye effect. There are varying solutions out there for WPF which provide this behavior for you in the form of a reusable panel for instance. They generally focus on images however the concept is the same. This solution shows an example in pure XAML.
I wrote this user control that would mimic the dashboard of the Mac's (Fish eye effect).

WPF Applying a trigger on binding failure

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.

WPF Dynamic GUI elements

In WinForms it was relatively easy to swap out Panels at runtime for other panels. In WPF this seems to be rather more complex (especially from XAML).
Can anyone provide clear guidance on the 'best practice' way of swapping gui elements at runtime (think pages in a wizard type situation).
Many thanks.
This can be approached in XAML using datatemplates and/or triggers. For example, if each page in your wizard were represented in an underlying model as a separate class or object, you could use one of the following two options... Both use a ContentControl, which is the perfect control for when the content will vary greatly between different views of the same data.
Please note that the bindings are intended as pseudocode examples, just to convey intent!
DataTemplate-based, using different classes for each page:
<Grid>
<Grid.Resources>
<DataTemplate DataType="{x:Type WizardPageOne}">
<!-- page 1 layout here -->
</DataTemplate>
<DataTemplate DataType="{x:Type WizardPageTwo}">
<!-- page 2 layout here -->
</DataTemplate>
<!-- ... etc -->
</Grid.Resources>
<ContentControl Content="{Binding CurrentPageModel, Source=Wizardmodel}" />
</Grid>
Or Trigger based, using a property that indicates the current page:
<ContentControl Content="{Binding WizardModel}">
<ContentControl.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding CurrentPageIndex} Value="1">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<!-- page 1 layout here -->
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding CurrentPageIndex} Value="2">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<!-- page 2 layout here -->
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<!-- .... etc -->
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
Both options will only load the control for each page as it's required, so you don't have all of the controls "loaded but hidden" in the window.
The underlying concepts of WinFomrs and WPF is different. In WPF it is not advisable to play around with UIElements(Controls) directly. Make use of DataBinding/DataContexts and just operate on the data and then the UI will function accordingly. This concept is all about WPF MVVM pattern. You can look in to some MVVM samples and try it before doing more complex WPF projects.
A simple example, Suppose you need to dynamically disply a number of items in a ListBox, The typical winform way to do this is to create Items and add directly to the ListBox. But in WPF you create an ObservableCollection<Customer> and bind that to the ListBox.ItemsSource. then define a DataTemplate for Customer Data Type, this ensure the WPF system to understand how a Collection of Customers being displayed in the application. So when you add a new customer instance to the collection, magically your ListBox will get updated with one more item. Seems pretty straight forward and a very loosely coupled way of Data and View right?.
Best wishes on your WPF learning. -
http://www.bing.com/search?q=WPF+MVVM
So the high level clue to your question is, make the View appropriately for the Data and when Data/Property Change happens, WPF will take care of changing the Panels/Controls. So it is really simple than WinForms way when you approach from the Data and View perceptive.
A couple options come to mind. If you create your components as UserControls, and make use of Data Binding, then you should be able to do what you need with minimal fuss.
Option one is to load each component into your parent container (grid, canvas, whatever) with Visibility="Collapsed", and then show and hide them as needed. This has the advantage that you can do this declaratively in XAML.
The other option is to load the components as you need them, so in the event handler of a button, or some other UI element. In this case you would probably want to remove the current displaying item from the Children collection of your host component, and then instantiate your next control, set the DataContext (this is why binding is important), and add it to the Children collection.
(disclaimer: this is based on my experience doing basically what you are asking in Silverlight 3.0, so there may be some WPF quirks I am unaware of).
The MVVM suggestions here are all good. But if you're designing a page-oriented UI that needs to be navigable, you can use Structured Navigation, too.
I got no idea if this is considered good practice, but what we did on one of our project is quite simple. We defined panels that were all on top of each other and would simply set the visibility to either hidden or visible when it was needed.

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.

How to expose properties of a WPF DataTemplate?

I have a data template that I use in many pages, the data template contains a few buttons, I want to hide some of these buttons by triggers (I mean setting the IsEnabled Property of these buttons in the page where I use this DataTemplate).
In other words, I would even like to set in style triggers/setters a property 'ButtonXIsEnabled', 'ButtonYIsEnabled' as part of the DataTemplate settable from the ListBox where I use this DataTemplate.
I really hope I am clear enough, please leave comments for any further details.
Any discussion will be really appreciated!
Thanks in advance.
Basically this depends on what object your using for your datatemplate. Instead of using some ButtonYIsEnabled, etcs. Try to use some words that fit better in to your domain model.
For example say you have a list of customers, and some of those customers have the ability to purchase discounted products. Then add a property to your Customer called CanPurchaseDiscountedProducts, and use that property in your DataTemplate
<DataTemplate TargetType="{x:Type local:Customer}">
<!-- Other Items -->
<Button Content="Purchase Discounted Products" x:Name="discounts" Visibility="Hidden" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding CanPurchaseDiscountedProducts}" Value="True">
<Setter TargetName="discounts" Property="Visibility" Value="Visible"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
A WPF data template is a view of a certain object type... how you want an instance of ObjectTypeX to look. The data template can bind to properties on the underlying instance.
So if you have a ButtonXIsEnabled property on your instance, you can bind the corresponding Button's Visibility property to the instance property. The button would be shown or hidden based on the value in the underlying object.

Resources