I am currently finishing off a tiny little WP7 application for a client here in New Zealand, which as such is not that very impressive. It seems like there is a LOT of apps on the way for the phone. I like it! At least the developers seem to have adopted the phone as their newborn child…or at least as a potential platform for future mobile apps…
But I am not going to be writing about that app. At least not right now. Instead, I wanted to write a little about something that surprised me. I have not been doing too much WP7 development. At least not the kind that includes a load of input controls. So I was kind of stumped when I found out that the phone does not have a ComboBox. Or rather, it does, but it doesn’t have a template suitable for the phone.
I found this to be rather odd, but could on the other hand understand that a ComboBox is probably not a control that is very well suited for a device like the phone. The OS itself seems to be using a list picker thingy instead, but that control seem to have stayed in Microsoft’s own control library and is not available. At least not that I have found.
So I guess the only solution is to do it yourself…at least after you Google it, which was what I did. I did find a couple of interesting ideas on how to style it, but they didn’t seem to follow the Metro style well enough to make me happy. Finally I found http://dotnet.dzone.com/articles/metro-style-combobox-windows, which got to act as the foundation for my control…
Luckily there is already a ComboBox available, so I thought I would just style it according to the Metro guidelines. Unfortunately, I ran into some issues that actually forced me to subclass the ComboBox.
The goal was to have a control that looked like this

It sort of mimics the list picker that Microsoft uses, but it does not expand in the same way. Instead, it uses a Popup to place the list of items on top of everything. In my situation that worked a lot better.
So…how much work is there to get this whole thing going? And how much code do we have to write? Well…to be honest, there isn’t a whole lot of code, but there is a bit of Xaml for the template…
The first thing to do is to create a project that can host the control (preferably a separate class library project for just controls so that they can be reused across several apps). After that, a Themes folder needs to be created, as well as a generic.xaml file.
It still annoys me that there is no way to add a templated control to a WP7 project. In Silverlight projects, this option creates all the basic stuff that you need and hook it up. Except for a tiny bug if you place your control class in another namespace than the default…but that is a discussion for another time…
After this is done, a class needs to be added. I have just simply called mine ComboBox as follows
using System;
using System.Windows.Shapes;
using System.Windows;
using System.Windows.Media;
namespace DarksideCookie.WP7.Controls
{
public class ComboBox : System.Windows.Controls.ComboBox
{
public ComboBox()
{
DefaultStyleKey = typeof(ComboBox);
}
}
}
There will be more code in there, but I want to cover the template first…
So the generic.xaml needs to look something like this before we can get started
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:DarksideCookie.WP7.Controls"
xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows"
xmlns:System="clr-namespace:System;assembly=mscorlib"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Style TargetType="local:ComboBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ComboBox">
<Border x:Name="WrapperBorder" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
As you might have noticed, there are 3 “non standard” namespaces in there. There is the “local” one that will contain the actual control, the vsm that is the namespace that contains the VisualStateManager and there is the System one that will give us access to system types.
The first thing I did, was to add the visual states from the dzone control I mentioned earlier
<Border x:Name="WrapperBorder" Padding="12">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="MouseOver"/>
<VisualState x:Name="Disabled/>
</VisualStateGroup>
<VisualStateGroup x:Name="FocusStates">
<VisualState x:Name="Focused"/>
<VisualState x:Name="Unfocused"/>
<VisualState x:Name="FocusedDropDown"/>
</VisualStateGroup>
<VisualStateGroup x:Name="ValidationStates">
<VisualState x:Name="Valid"/>
<VisualState x:Name="InvalidUnfocused" />
<VisualState x:Name="InvalidFocused" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Border>
This Xaml is available in he download below. It was just too much mundane boring Storyboard code to put in the post… :)
As you might have seen, the Border has a Padding set. This is to make sure that the control gets and oversized hit area. Something that is defined in the Metro guidelines. It means that the control will react to “clicks” in a larger area than the control actually looks to be.
The control consists of two main pieces. The box and content that is shown when the control is collapsed, and the pop up that is shown when the control is expanded. The first one to look at is the collapsed UI.
It consists of a Border with a 2 pixel border in the correct color. And with correct color, I mean one coming from the built in static resources. Using these resources, makes it very easy to make sure that the control can handle both the light and the dark theme on the phone.
<Grid>
<Border x:Name="ContentPresenterBorder" BorderBrush="{StaticResource PhoneBorderBrush}" BorderThickness="2">
...
</Border>
</Grid>
Yeah…I wrapped the whole thing in a Grid as the WrapperBorder only supports a single child…
Inside the Border, I placed a Grid. It was set up to have 2 columns. A “star” one and an “Auto” one. The “Auto” column will contain an optional arrow that can be shown and hidden by setting a property.
In the first column, a ContentPresenter is placed, with its Horizontal- and VerticalAlignment bound to the corresponding ContentAlignment properties. And in the second, the fabled Path of the shape of a downward arrow. The arrow is also given a big of a margin, and the Fill is once again set from one of the built in resources.
The Grid also contains a ToggleButton, which is the element responsible for getting the whole thing to actually expand. It has a ColumnSpan of 2, making sure that it covers the whole thing, even if the arrow is visible. And to make sure that the oversized hit area is working, the Margin on the button is set the to inverse value of the Padding on the WrapperBorder.
As I don’t want the ToggleButton to have a UI, but still work, I have replaced the Template with a transparent Grid.
<Border x:Name="ContentPresenterBorder" BorderBrush="{StaticResource PhoneBorderBrush}" BorderThickness="2">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ContentPresenter x:Name="ContentPresenter" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" IsHitTestVisible="False" />
<Path x:Name="BtnArrow" Data="F1 M 301.14,-189.041L 311.57,-189.041L 306.355,-182.942L 301.14,-189.041 Z " HorizontalAlignment="Right" Height="25" Margin="0,0,12,0" Grid.Column="1" Stretch="Uniform" Width="20" Fill="{StaticResource PhoneBorderBrush}"/>
<ToggleButton x:Name="DropDownToggle" Margin="-12" Grid.ColumnSpan="2">
<ToggleButton.Template>
<ControlTemplate TargetType="ToggleButton">
<Grid Background="Transparent" />
</ControlTemplate>
</ToggleButton.Template>
</ToggleButton>
</Grid>
</Border>
The next important part is the pop up. The thing that will display the list of items when the control is expanded. It is, as I mentioned several times, a Popup. The Popup, if you haven’t seen it, is a control that is rendered on top of everything. Making it the topmost UI element wherever it is placed in the Xaml.
Inside the Popup, I place a a Border. And since I want the list of items to be the same width as my control, I have bound the Width of the Border to the ActualWidth of the control. I have also given it a right side Margin that is twice the width of the Padding on the WrapperBorder. Making sure it gets the right size.
Inside of the Border, I have placed a ScrollViewer, which contains an ItemsPresenter. This should make sure that the content scrolls if it gets too big. However, it doesn’t really seem to work perfectly… So if you solve that, give me a yell…. Please!!!
I have also given the Popup a RenderTransform. The reason for this is that the Popup will by default end up below the containing element. But as I want it to cover it, I will use a CompositeTransform to translate transform it to the right position… More about this later…
<Popup x:Name="Popup" IsOpen="False">
<Popup.RenderTransform>
<CompositeTransform x:Name="PopupTransform" />
</Popup.RenderTransform>
<Border x:Name="PopupBorder" Padding="0,0,24,0" Width="{TemplateBinding ActualWidth}" BorderBrush="{StaticResource PhoneBorderBrush}">
<ScrollViewer x:Name="ScrollViewer" BorderThickness="2" Padding="0" Background="{StaticResource PhoneBackgroundBrush}" BorderBrush="{StaticResource PhoneBorderBrush}" Foreground="{StaticResource PhoneForegroundBrush}">
<ItemsPresenter/>
</ScrollViewer>
</Border>
</Popup>
The template in the download contains a bit more Xaml as it also contains a DisabledVisualElement, FocusVisualElement and ValidationErrorElement. They do exactly what you would expect them to do based on their names. But I will not cover them in the actual post as it is fairly standard stuff…
The last thing to note in my ComboBox style is that I set the HorizontalContentAlignment and VerticalContentAlignment using two Setters. And I also add a new ItemTemplate. The ItemTemplate is very simple, as it just makes sure that there is a bit of padding in the list
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<Grid>
<ContentPresenter Content="{Binding}" Margin="12" />
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
Ok…so that was the template. So all that is left is to write the code that is needed to get it going…
My ComboBox declares one new property called ShowArrow. This controls the visibility of the arrow in the UI. It is declared as a DependencyProperty. This is more because I want it to work nicely in the tools. Not so much to make it bindable…
public class ComboBox : System.Windows.Controls.ComboBox
{
public static readonly DependencyProperty ShowArrowProperty =
DependencyProperty.Register("ShowArrow", typeof(bool), typeof(ComboBox), new PropertyMetadata(false, OnShowArrowChanged));
...
private static void OnShowArrowChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
((ComboBox)sender).OnShowArrowChanged(e);
}
private void OnShowArrowChanged(DependencyPropertyChangedEventArgs e)
{
if (_btnArrow != null)
_btnArrow.Visibility = ShowArrow ? Visibility.Visible : Visibility.Collapsed;
}
public bool ShowArrow
{
get { return (bool)GetValue(ShowArrowProperty); }
set { SetValue(ShowArrowProperty, value); }
}
}
As you can see in the OnShowArrowChanged method, I am depending on a field called _btnArrow. The _btnArrow is supposed to be a reference to the Path that shows the arrow. However, since this is a templated control, I need to get a reference to it by hooking into the OnApplyTemplate method.
While I do this to get access to the arrow, I will take the opportunity to get a reference to the CompositeTransform for the Popup as well…
CompositeTransform _popupTransform;
UIElement _btnArrow;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_popupTransform = GetTemplateChild("PopupTransform") as CompositeTransform;
if (_popupTransform != null)
_popupTransform.TranslateY = -this.ActualHeight;
_btnArrow = GetTemplateChild("BtnArrow") as UIElement;
if (_btnArrow != null && !ShowArrow)
_btnArrow.Visibility = System.Windows.Visibility.Collapsed;
}
As you can see in the above code, I make sure to set the default values for those elements at the same time… There is now only one thing left to do, and that is to make sure that the Popup is re-translated if the control changes size. This is easy to do by listening to the SizeChanged event
public ComboBox()
{
DefaultStyleKey = typeof(ComboBox);
this.SizeChanged += (s, e) =>
{
if (_popupTransform != null)
_popupTransform.TranslateY = -this.ActualHeight;
};
}
That’s all there is to it! The only thing I have done on top of that, is to once again make sure it plays nice in the tools. And one thing that you should do to ensure this, is to declare any template parts that you access from code. This is done by using attributes
[TemplatePart(Name = "PopupTransform", Type = typeof(CompositeTransform))]
[TemplatePart(Name = "BtnArrow", Type = typeof(UIElement))]
public class ComboBox : System.Windows.Controls.ComboBox
...
As you see, I define the arrow path as a UIElement. This makes it possible to use any UIElement derived class to define it if the control is re-templated. Always go for the most generic type possible when declaring TemplateParts.
The different visual states should also be defined using TemplateVisualState attributes. These are however already defined on the base class, so we can skip that part… As well as the different TemplateParts that the base class uses and has already defined…
Code is available here: DarksideCookie.WP7.Controls.zip (43.35 kb)
Hope it has made your life a little bit easier not having to restyle the ComboBox on your own…
Cheers!