I was the kind of kid who took apart toasters to find out how they worked. Much later in life, I graduated to disassembling operating systems and application programs.
I don’t do a lot of disassembling these days. But sometimes when I see a particularly interesting UI, I try to figure out how to code it on my own. It’s an engineering exercise, of course, but I also like to think of myself as an art student who goes to the museum to paint copies of existing masterworks. In my own code, I strive for simplicity, of course, and to make use of pre-existing elements and controls. But mostly I like to give myself a challenge and hope I learn something new from it.
Recently I’ve been exploring Windows Phone 7, and I became intrigued by the pages used to set date and time, as shown in Figure 1. These controls (which are not publicly available) struck me as interesting touch interfaces. [The controls were released in a Silverlight for Windows Phone Toolkit following completion of this article. It’s available at silverlight.codeplex.com/releases/view/52297.—Ed.]
.jpg)
Figure 1 Windows Phone 7 Date and Time Pickers
I started wondering: How would I code these controls? In the past several issues, I’ve been exploring multi-touch in the Windows Presentation Foundation (WPF), so I decided to target that platform for my first shot at duplicating them. If successful, I could think about moving the code to Silverlight.
Exploring the Controls
When you first navigate to one of the date or time pages on Windows Phone 7, you see the current setting in gray squares centered in the middle of the page. Touch one of these squares and a list of other choices pops up on the top and bottom. Often this list is circular; as you can see in Figure 1, the month of December is followed by January. Circular lists are also used for the days of the month, hours and minutes. Non-circular lists are used for the year—the list ranges from 1601 to 3000—or (as you can see) to select AM or PM.
How different from a ListBox! A conventional ListBox displays the currently selected item with a highlight, but as you scroll through the ListBox, that selected item is sometimes scrolled completely out of view. In contrast, these date and time controls always tend to display the selected item in the center, and I began to think of these center areas as a type of “window,” with the list functioning similar to the mechanical reels of old-fashioned slot machines, which in my mind I began referring to as “bands.”
These controls seem to respond to touch in three distinct ways:
- If you simply tap another visible item in the band (such as the month of December in Figure 1), the item becomes highlighted when your finger leaves the screen, and that item shifts into the center window area.
- Instead of tapping, you can move one of the bands of items up or down with your finger. During that time, nothing in that band is highlighted. As soon as you lift your finger, the item closest to the window becomes highlighted and it shifts into the center.
- The third type of interface involves inertia. If the band of items is moving when your finger leaves the screen, it will continue moving while slowing down. As it gets close to stopping, the item closest to the window becomes highlighted and moves to the center. If necessary, sometimes the band reverses direction right at the end before stopping. That little effect—very natural, I had to admit—was something I knew would be one of the more “interesting” challenges in duplicating these controls.
Overall Architecture
Another “interesting” challenge involved the circular list of items, where December is followed by January and 12:00 is followed by 1:00. These circular bands are crucial aspects of the design—particularly in combination with inertia. The bands can be flicked in either direction, and inertia carries them forward to any item in the band without reaching a dead end.
After rolling around ideas in my mind for several days, I simply couldn’t think of a better solution to the circular list than a custom panel, and the name WrappableStackPanel suggested itself. Such a panel would sometimes position its children from top to bottom, and sometimes start with a child other than the first, and position the early children after the latter children. Of course, rendering more than one version of a particular child is prohibited in WPF, so the stack would always have a definite end.
The WrappableStackPanel would need a property (called StartIndex, for example) to denote the index of the child that should be displayed at the top of the panel, with the rest of the children following sequentially and looping back to child zero. In the general case, no child would be aligned precisely at the top of the panel. The topmost child would usually be partially above the top of the panel. This implied that the StartIndex property would be a floating point value rather than an integer.
But that meant the overall control would probably function in two distinct ways. One mode requires a WrappableStackPanel to host the items, where the WrappableStackPanel is fixed in position relative to the control, and the panel handles the positioning of children relative to itself. The other mode is for cases when a regular StackPanel is adequate (such as for the Year or the AM/PM band); in this mode, the children are fixed relative to the StackPanel, and the StackPanel is then scrolled relative to the control.
Would this control derive from ListBox? I decided it would not. ListBox incorporates its own selection logic, and the control I wanted has a rather different type of selection logic. If I derived from ListBox, I’d probably find it to be hindering rather than assisting me as I wrestled with the existing selection logic.
But I knew I wanted the control to maintain its own collection of items and—particularly—to let me define a template in XAML for displaying these items. This need suggested that an ItemsControl would be involved. ItemsControl is the parent class to Selector, from which ListBox and ComboBox derive, and is the obvious choice for displaying collections where no selection logic is necessary, or where the programmer will be handling customized selection logic. This was me.
The default control template for a ListBox includes a ScrollViewer; the ItemsControl doesn’t. If you need to scroll the items displayed by an ItemsControl, you put the whole ItemsControl itself in a ScrollViewer.
I knew I couldn’t use the existing ScrollViewer for this job. Although the existing ScrollViewer handles inertia, it doesn’t have any logic to orient a particular item in the center. My first stab at actual coding was to create an alternative to ScrollViewer called WindowedScrollViewer, which hosted an ItemsControl containing either the months, the days of the months or the years. In its MeasureOverride method, this WindowedScrollViewer could easily obtain the full size of the ItemsControl and the number of items being displayed—and therefore the uniform height of the individual items—so it could use this information to move the ItemsControl relative to itself based on the Manipulation events.
This approach worked fine when the ItemsControl didn’t need to display wrappable bands of items. For those cases, I needed to set the ItemsPanel property of the ItemsControl to an ItemsPanelTemplate referencing the WrappableStackPanel. The big problem involved the WindowedScrollViewer communicating with the WrappableStackPanel through the ItemsControl sitting in the middle of the visual tree.
This seemed difficult. Moreover, I also slowly came to realize that my WindowedScrollViewer was unlike the regular ScrollViewer in that it had no visuals of its own. It was doing nothing more than sliding the ItemsControl relative to itself.
I decided to abandon that approach and instead derive from ItemsControl and do everything in there. I called it WindowedItemsControl class, and it implements selection logic, manipulation handling, scrolling and communicating with an optional WrappableStackPanel.
The downloadable source code for this article is a solution named TouchDatePickerDemo with a project of that name and a library project called Petzold.Controls. The library project includes WindowedItemsControl (divided into three files), WrappableStackPanel and a UserControl derivative named TouchDatePicker. The TouchDatePicker combines three WindowedItemsControls (for month, day and year) with a DatePresenter class and a property named DateTime.
Figure 2 shows the TouchDatePicker in action. A TextBlock is bound to the DateTime property to display the currently selected date.
.jpg)
Figure 2 The TouchDatePicker Control in Action
The TouchDatePicker is basically a Grid with three columns for the month, day and year. Figure 3 shows the XAML for the first WindowedItemsControl to handle the month. The DataContext is an object of type DatePresenter, which has properties AllMonths and SelectedMonth referenced by the WindowedItemsControl tag itself. The AllMonths property is a collection of MonthInfo objects, and SelectedMonth is also of type MonthInfo. The MonthInfo class has properties named MonthNumber and MonthName that you’ll see referenced in the DataTemplate. The WrappableStackPanel is referenced down at the bottom.
Figure 3 One-Third of the TouchDatePicker Control
<local:WindowedItemsControl x:Name="monthControl"
Grid.Column="0"
ItemsSource="{Binding AllMonths}"
SelectedItem="{Binding SelectedMonth, Mode=TwoWay}"
IsActiveChanged="OnWindowedItemsControlIsActiveChanged">
<local:WindowedItemsControl.ItemTemplate>
<DataTemplate DataType="local:MonthInfo">
<Border Width="60" Height="60"
BorderThickness="1"
BorderBrush="{Binding ElementName=monthControl,
Path=Foreground}"
Margin="2">
<Grid>
<Rectangle Fill="{DynamicResource
{x:Static SystemColors.ControlLightBrushKey}}">
<Rectangle.Visibility>
<MultiBinding Converter="{StaticResource multiConverter}">
<Binding />
<Binding ElementName="monthControl" Path="SelectedItem" />
</MultiBinding>
</Rectangle.Visibility>
</Rectangle>
<TextBlock Text="{Binding MonthNumber, StringFormat=D2}"
VerticalAlignment="Center"
FontSize="24"
FontWeight="Bold" />
<TextBlock Text="{Binding MonthName}"
VerticalAlignment="Bottom"
FontSize="10" />
<Rectangle Fill="#80FFFFFF">
<Rectangle.Visibility>
<MultiBinding Converter="{StaticResource multiConverter}"
ConverterParameter="True">
<Binding />
<Binding ElementName="monthControl" Path="SelectedItem" />
</MultiBinding>
</Rectangle.Visibility>
</Rectangle>
</Grid>
<Border.Visibility>
<MultiBinding Converter="{StaticResource multiConverter}">
<Binding />
<Binding ElementName="monthControl" Path="SelectedItem" />
<Binding ElementName="monthControl" Path="IsActive" />
</MultiBinding>
</Border.Visibility>
</Border>
</DataTemplate>
</local:WindowedItemsControl.ItemTemplate>
<local:WindowedItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<local:WrappableStackPanel IsItemsHost="True" />
</ItemsPanelTemplate>
</local:WindowedItemsControl.ItemsPanel>
</local:WindowedItemsControl>
Selection Logic
The Items collection maintained by ItemsControl is defined to accept items of type object. Each of these items is rendered based on a DataTemplate set to the ItemTemplate property of ItemsControl.
The ListBox does a little more. The ListBox wraps each of its items in a ListBoxItem control for the purpose of implementing selection logic. It’s the ListBoxItem that has an IsSelected property along with the Selected and Unselected events, and which is responsible for visually indicating that an item is selected.
My goal with WindowedItemsControl was to implement selection logic without any kind of wrappers, and implement it in such a way so I could define the visuals of the selected item entirely in XAML. To help, the WindowedItemsControl has a SelectedIndex property (which it uses internally to determine where items should be positioned) and a SelectedItem property, which is the particular object in its Items collection that corresponds to the SelectedIndex.
In the XAML in Figure 3, this SelectedItem property is referenced in the DateTemplate property like so:
<Binding ElementName="monthControl" Path="SelectedItem" />
Within that same DataTemplate, the item itself can be referenced with a much simpler Binding expression:
If the objects referenced by these two bindings are equal, then the DataTemplate should have some special markup to indicate a selected item—in this case, gray background shading and non-dimmed text.
Normally in XAML you can’t determine whether two bindings reference the same object, but I wrote a multi-binding converter specifically for that purpose. It’s called EqualsToVisibilityMultiConverter and returns Visibility.Visible or Visibility.Hidden based on whether two objects are equal. Here’s how it’s used in the DataTemplate for the gray background:
<Rectangle Fill="{DynamicResource {x:Static SystemColors.ControlLightBrushKey}}">
<Rectangle.Visibility>
<MultiBinding Converter="{StaticResource multiConverter}">
<Binding />
<Binding ElementName="monthControl" Path="SelectedItem" />
</MultiBinding>
</Rectangle.Visibility>
</Rectangle>
And it worked!
Unfortunately, this binding converter got more complex as I needed it to perform other duties. I wanted another Rectangle to dim unselected items, and I wanted this Rectangle to be hidden for the selected item, so I added a converter parameter. When this parameter is set to “true,” the Visibility return values from the multi-binding converter are swapped.
But I also needed to switch between showing just the selected item and showing the whole band of items I associated with an IsActive property. If IsActive is true, all items need to be displayed; if IsActive is false, only the selected item need to be displayed. I added a facility to the multi-binding converter for a third object of type bool. If true, then the multi-binding converter always returns Visibility.Visible (unless the parameter is set to “true,” in which case it returns Visibility.Hidden). This facility was used to make the entire item visible or hidden:
<Border.Visibility>
<MultiBinding Converter="{StaticResource multiConverter}">
<Binding />
<Binding ElementName="monthControl" Path="SelectedItem" />
<Binding ElementName="monthControl" Path="IsActive" />
</MultiBinding>
</Border.Visibility>
Although the actual multi-binding converter isn’t complex, it’s a rather messy array of functionality. But I was able to implement selection visuals entirely in XAML without the use of any wrappers, and that made me happy.
Communicating with the Panel
The WindowedItemsControl needs to implement different scrolling logic if the panel set through its ItemsPanel property is a WrappableStackPanel. How can it tell? And how can it funnel information to this panel?
Determining the type of panel is fairly easy: Override the OnItemsPanelChanged property. Whenever the ItemsPanel property changes, this method is called with the old ItemsPanelTemplate and the new ItemsPanelTemplate. Call LoadContent on this new template and you get an instance of the panel in the template.
However, the panel returned from LoadContent isn’t the same instance as the panel actually being used by the ItemsControl! This technique is suitable only for determining if the panel is of a particular type, not for communicating with that panel.
If the ItemsControl wishes to set properties on that panel, another technique is required. It could find the actual panel by traversing the visual tree, or it could use an inheritable event.
I chose the latter technique. In WindowedItemsControl, I defined a StartIndex property backed by a dependency property, to which I set the Inherits flag of FrameworkPropertyMetadataOptions (it’s also well-known that to persuade this Inherits flag to work, you should register an attached property rather than a normal dependency property).
Here it is:
public static readonly DependencyProperty StartIndexProperty =
DependencyProperty.RegisterAttached("StartIndex",
typeof(double),
typeof(WindowedItemsControl),
new FrameworkPropertyMetadata(0.0,
FrameworkPropertyMetadataOptions.Inherits));
When the ItemsPanel is a WrappableStackPanel, the WindowedItemsControl implements scrolling simply by setting a value to this StartIndex property.
The WrappableStackPanel adds an owner to the StartIndex attached property and sets the FrameworkPropertyMetadataOptions flag AffectsArrange. The ArrangeOverride method uses the value of the StartIndex property inherited from the ItemsControl to determine what item should appear at the top of the panel, and how much of it should be above the panel.
The Manipulation Events
As I suspected from the very outset, implementing the actual Manipulation events would be the hardest part of the whole job. It turned out that my analysis of the three types of touch events was right on target. They all had to be handled pretty much separately. (The code is in the WindowedItemsControl.Manipulation.cs file.)
Much of the determination of the three types of touch events occurs in the OnManipulationInertiaStarting override. This event indicates that the user’s finger has left the screen.
If the ManipulationInertiaStarting event follows the ManipulationStarting event without any intervening ManipulationDelta events, that’s a tap. The code in the ManipulationInertiaStarting event determines how far the tapped item needs to travel to move to the center, and then determines the velocity necessary to accomplish this in a fixed period of time (set at 250 milliseconds). It then initializes the DesiredDisplacement and InitialVelocity properties of the TranslationBehavior property of the event arguments for that amount of inertia.
Keep in mind that the user just tapped the item. The user didn’t move the item, hence there’s no actual velocity or inertia! But the ManipulationInertiaStarting event allows setting inertia parameters to force an object to move even if it hasn’t been moving. In the context of handling the Manipulation events, this approach is much easier than using animation or CompositionTarget.Rendering for scrolling.
The logic is quite similar if the user has moved the band manually, but the new selected item is obvious because there’s insufficient velocity to move the band beyond that item. Again, all that needs to be done is to set the DesiredDisplacement and InitialVelocity properties to slide it into place.
The really messy code occurs when there’s actual inertia, and the new selected item won’t be known until the velocity decreases and the scrolling almost stops. This is where velocity must be analyzed to examine if the new selected item will actually scroll beyond the center, and to reverse the direction if necessary. Some early code actually reversed the scrolling several times, back and forth, and that wasn’t desirable at all.
How’d It Turn Out?
I must admit that the resultant control seems more like a “first draft” than a final version. It doesn’t seem as smooth or as natural as the version implemented in Windows Phone 7, and it lacks some amenities. For example, when you activate the control by pressing on it, the bands in the Windows Phone 7 version fade into view and later fade out. Mine just pop.
But I’m happy for the experience, and I’m convinced even more that good multi-touch coding is a lot harder than it might at first seem.
Charles Petzold is a longtime contributing editor to MSDN Magazine. His new book, “Programming Windows Phone 7,” will be published as a free downloadable ebook in the fall of 2010.
Thanks to the following technical expert for reviewing this column: Doug Kramer
Comments (3)