Once upon a time, I wrote a post about using Blend to explore the templates that you find in a ListBox in Silverlight as part of a series of a bunch of posts I wrote about using Blend. – some of those would be out of date today with respect to Windows 8.1 Store or Windows 8.0 Phone applications but some of them more than likely still apply.
Today I got a mail from a developer about a scenario where you have a Windows 8.1 Store app which has a ListView like this one;
<ListView> <ListView.Items> <x:String>One</x:String> <x:String>Two</x:String> <x:String>Three</x:String> </ListView.Items> <ListView.ItemTemplate> <DataTemplate> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <TextBlock Grid.Column="1" Text="{Binding}" VerticalAlignment="Center"/> <Ellipse Grid.Column="0" Width="48" Height="48" Margin="5" Fill="Red" /> </Grid> </DataTemplate> </ListView.ItemTemplate> </ListView>
which displays as below;
and at runtime if I hover over one of those items they look as below where the mouse is hovering over item “Two” in the list;
So…where is that grey selection rectangle coming from? As talked about in that previous Blend post, it’s coming from the ItemContainerStyle but in a Windows Store app this is done a little differently from how it was done for a Silverlight app that I wrote about in that previous article. In fact, I think it’s possibly being done differently in Windows 8.1 from how it was done in Windows 8.0.
If I open up this project in Blend and edit the ItemContainerStyle;
then that copy of the ItemContainerStyle presents a mysterious beast – the ListViewItemPresenter;
What’s that? I think it’s new in Windows 8.1 and looks to have arrived with its friend GridViewItemPresenter. Normally when editing one of these things I’d perhaps expect to see some kind of template containing at least a ContentPresenter and then a bunch of other UI pieces that are shown/hidden depending on the visual state of the ListViewItem (i.e selected, focused, etc).
However, this ListViewItemPresenter isn’t such a deviation away from that sort of idea because it is itself derived from ContentPresenter and it looks like it exists to package up that functionality into a handy control by adding a whole bunch of properties such as these brushes;
so it would seem that rather than setting up a visual state to change a foreground colour when an item is selected it’s possible to just set the SelectedForeground property and presumably the ListViewItemPresenter is doing its own work to monitor for state changes and is then applying that Foreground brush as appropriate.
For example, if I change that SelectedForeground to be Red and then select an item;
it’s clear what effect that has had and I haven’t had to go and play around with any visual states. Similarly, if I want to lose that visual tickmark on the end of a selected item;
Then there’s just a property to turn off;
rather than me having to do lots of work with visual states and various elements in a control template.
Taking a look at the runtime elements that this way of working creating using the always excellent XAML Spy reveals what’s actually present in the tree of elements;
and I can see that I have a ListViewItem containing a ListViewItemPresenter which then contains the elements from my DataTemplate – kind of as expected.
What’s also interesting is that if I select the first item in the list and then have a look at the properties of the ListViewItem and the ListViewItemPresenter representing that item;
Then I can clearly see that the ListViewItemPresenter has set its own Foreground property to Red based on the item being selected and the value of the SelectedForeground property.
The TextBlock within my DataTemplate therefore paints its Text using a red brush because it is picking up that Foreground property from its parent ListViewItemPresenter.
The specific question I got from a developer though was how an element like the Ellipse in the DataTemplate up above at the top of this post could pick up that same modified value of Foreground and use it as its Fill property when it is part of the content displayed for the selected item.
That is, if the Ellipse was originally coloured white;
<Ellipse Grid.Column="0" Width="48" Height="48" Margin="5" Fill="White" />
then when it is the selected item, it stays white;
which isn’t a surprise in itself because it’d be asking a lot for it to pick up the value of the “ambient” Foreground property and apply it to its own Fill property.
How could that value be picked up though and applied to that Ellipse? “Not very easily” seemed to be the answer that I came up with.
My first thought was to attempt to tie together the Ellipse and the TextBlock via ElementName binding. That is;
But that doesn’t work. What I seem to see happening is that when I select an item in the ListView, sure enough the Foreground of the TextBlock changes to Red. However, the Ellipse remains unchanged.
I suspect this is because the TextBlock.Foreground property isn’t explicitly set. That TextBlock is picking up an “ambient” Foreground value from its parent which will be the ListViewItemPresenter and that control looks to replace the value of its Foreground property with the value of its SelectedForeground property for an item that is selected. However, I don’t think the change in that property is going to flow through to the Ellipse because of the ambient way in which it’s being picked up.
As an aside, I found it a bit odd that the ListViewItemPresenter seems to be an “all or nothing” sort of control. I partly expected to be able to edit its template and see the various UI pieces that it was made up of but I don’t seem to be able to do that.
What I seem to need is to be able to bind the Fill of my Ellipse to the ListViewItemPresenter.Foreground property. The blocker to that is that t the Ellipse is in a DataTemplate and the ListViewItemPresenter is present in the ControlTemplate for the ListViewItems that are being dynamically created at runtime by the control.
There’s not really a link to be made between those two things in the XAML. They exist without knowledge of each other (or at least I think they do).
One way for the Ellipse to reach out and bind to that ListViewItemPresenter would be with an “ancestor” style binding but, in Windows 8.1 apps, I don’t have the ability to use a RelativeSource binding with the Mode=Ancestor so I can’t just set up a binding on the Ellipse which goes up the tree of elements trying to find a ListViewItemPresenter. I think that I need to find another way to make the binding.
My best “solution” so far is to make the Brush in question part of the “viewmodel”. That is, if I make a little view model class like this with change notification;
class DataItem : ViewModelBase { public DataItem() { } public string Label { get { return (this._label); } set { base.SetProperty(ref this._label, value); } } string _label; public Brush HighlightBrush { get { return (this._highlightBrush); } set { base.SetProperty(ref this._highlightBrush, value); } } Brush _highlightBrush; }
then I can change my XAML such that my ListView is using these DataItems for its Items and such that my Ellipse is binding its Fill to this HighlightBrush;
<ListView ItemContainerStyle="{StaticResource ListViewItemStyle1}"> <ListView.Items> <local:DataItem Label="One" /> <local:DataItem Label="Two" /> <local:DataItem Label="Three" /> </ListView.Items> <ListView.ItemTemplate> <DataTemplate> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <TextBlock Grid.Column="1" Text="{Binding Label}" VerticalAlignment="Center" x:Name="txtBlock" /> <Ellipse Grid.Column="0" Width="48" Height="48" Margin="5" Fill="{Binding HighlightBrush, TargetNullValue=White}" /> </Grid> </DataTemplate> </ListView.ItemTemplate> </ListView>
and I can change my ItemContainerStyle such that the ListViewItemPresenter is binding its Foreground property to this HighlightBrush too in a 2-way manner;
and that seems to “kind of work” in the sense that the colour of my Ellipse and my TextBlock seem to track each other when an item is selected;
but it feels a bit clunky and I wonder whether there might not be a better way of getting this kind of binding to work in this situation?
Any suggestions? Maybe I got drawn in and missed something obvious? Drop me a line via the comments below.