I'm currently going through a process of bringing together code and controls I've been using in the last few applications and compiling my own "toolkit". The idea will be a very opinionated way of building WP7 applications rather than a generic control library. Some parts will be controls like the "empty list box" discussed in this post and other parts will be extensions to the Caliburn.Micro MVVM framework for how I like to work.
The first control is an extension of the ListBox control to display certain content when there are no items in the list. It's a simple extension but most of the time we forget to add this functionality especially when we're dealing with lots of lists in our application.
The first thing we'll do is create our class ListBox (not the best name, but no other one like EmptyListBox suites, thankfully we have namespaces for conflicting names) obviously inheriting from the system ListBox, we'll had two dependency properties, EmptyContent and EmptyContentTemplate. This will allow two different ways of defining the content to display, much like Button or ContentControl.
public static readonly DependencyProperty EmptyContentTemplateProperty =
DependencyProperty.Register("EmptyContentTemplate", typeof(DataTemplate), typeof(ListBox), null);
public static readonly DependencyProperty EmptyContentProperty =
DependencyProperty.Register("EmptyContent", typeof(object), typeof(ListBox), null);
public ListBox()
{
DefaultStyleKey = typeof(ListBox);
}
public object EmptyContent
{
get
{
return GetValue(EmptyContentProperty);
}
set
{
SetValue(EmptyContentProperty, value);
}
}
public DataTemplate EmptyContentTemplate
{
get
{
return (DataTemplate)GetValue(EmptyContentTemplateProperty);
}
set
{
SetValue(EmptyContentTemplateProperty, value);
}
}
Our default Style in Generic.xaml will be pretty simple.
<Style TargetType="controls:ListBox">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="{StaticResource PhoneForegroundBrush}"/>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/>
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:ListBox">
<Grid>
<ContentPresenter x:Name="EmptyContent" Content="{TemplateBinding EmptyContent}" ContentTemplate="{TemplateBinding EmptyContentTemplate}" RenderTransformOrigin="0.5,0.5" >
<ContentPresenter.RenderTransform>
<CompositeTransform/>
</ContentPresenter.RenderTransform>
</ContentPresenter>
<ScrollViewer x:Name="ScrollViewer" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Foreground="{TemplateBinding Foreground}" Padding="{TemplateBinding Padding}">
<ItemsPresenter/>
</ScrollViewer>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
We'll then override the OnItemsChanged to add our new behaviour when items are added or removed.
There are a few different ways we can implement the required functionality each with their own pros and cons.
Using Control Template Parts
The first is using Template Parts, this allows controls to tell developers which sub-controls they expect in the control template. The can then interact with them using the FindTemplateChild method. The ListBox control already defines a ScrollViewer as a part, we would then add a ContentPresenter as a second part. In our overriden method we could then set the Visibility of each control appropriately. This approach is the simplest and the code is as follows.
private ContentControl emptyContent;
private ScrollViewer scrollViewer;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
emptyContent = (ContentControl)GetTemplateChild("EmptyContent");
scrollViewer = (ScrollViewer)GetTemplateChild("ScrollViewer");
}
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
{
base.OnItemsChanged(e);
ApplyTemplate();
var hasItems = Items.Count > 0;
emptyContent.Visibility = hasItems ? Visibility.Collapsed : Visibility.Visible;
scrollViewer.Visibility = hasItems ? Visibility.Visible : Visibility.Collapsed;
}
Using Visual States
The second approach is to use the VisualStateManager to hide and show parts of our control template. I like this approach better as it gives better flexibility to developers extending the control. By defining two states "Empty" and "NonEmpty" we put it back in the developers hands about how each state should look, rather than arbitrarily defining the behavior ourselves. This approach makes the control code itself simpler than the first but we have more xaml in the Control Template due to having to define the new states.
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
{
base.OnItemsChanged(e);
ApplyTemplate();
VisualStateManager.GoToState(this, Items.Count > 0 ? "NonEmpty" : "Empty", true);
}
The xaml for the Visual States is pretty long so I won't post it here but it will be available in the download available soon.
Over all I prefer the second approach due it's freedom and the ability to add animations. The default template has a simple "continuum" animation bringing the content into view.
Download the source.
Context Menus in all the xaml based frameworks (WPF, Silverlight and WP7) are tricky little beasts waiting to trip people up. In WP7 they're provided by the awesome Silverlight Toolkit. The major problem is that they don't exist in the visual tree in the way standard elements do. This inhibits things in Caliburn Micro (CM) like action bubbling (the process where an Action bubbles through the visual tree to find a Target that can handle it).
However the first thing we need to do it set up some conventions for the Context Menu, it CM doesn't know about the element it'll use the Loaded event to fire actions, we'd much rather use the Click event. Add the following to the Configure method of the Bootstrapper.
ConventionManager.AddElementConvention<MenuItem>(ItemsControl.ItemsSourceProperty, "DataContext", "Click");
The method to solving this is the use of the attached dependency property Action.TargetWithoutContext from CM. By default the Target for Actions is the ViewModel bound as the DataContext, with this property we can set the target for the Actions to be something else. What this is will depend a bit on the scenario, I'll cover two such scenarios in this post.
Both of the scenarios will deal the most common uses for a context menu which is a set of actions on a list of data. For the example we'll have a list of Purchases that we'll want to edit and delete.
The first and slightly simpler scenario is the one where the Edit and Delete methods exist on the PurchaseViewModel.
public class PurchaseViewModel : PropertyChangedBase
{
private decimal cost;
private string description;
public PurchaseViewModel(decimal cost = 0.00m, string description = "")
{
this.cost = cost;
this.description = description;
}
public decimal Cost
{
get { return cost; }
set
{
cost = value;
NotifyOfPropertyChange("Cost");
}
}
public string Description
{
get { return description; }
set
{
description = value;
NotifyOfPropertyChange("");
}
}
public void Edit()
{
MessageBox.Show("Editing " + Description, "Edit", MessageBoxButton.OK);
}
public void Delete()
{
MessageBox.Show("Deleting " + Description, "Delete", MessageBoxButton.OK);
}
}
public class PurchaseListViewModel : Screen
{
public PurchaseListViewModel()
{
Purchases = new BindableCollection<PurchaseViewModel>
{
new PurchaseViewModel(4.50m, "Coffee"),
new PurchaseViewModel(15.95m, "Lunch")
};
}
public IObservableCollection<PurchaseViewModel> Purchases
{
get;
set;
}
}
In this scenario we're pretty good right out of the box, and don't need to deal with the action bubbling as the current Target (the PurchaseViewModel) will be able to handle the Action.
<ListBox x:Name="Purchases">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Width="456" d:DesignWidth="275" Margin="0,0,0,12" Background="Transparent">
<toolkit:ContextMenuService.ContextMenu>
<toolkit:ContextMenu>
<toolkit:MenuItem Header="edit" caliburn:Message.Attach="Edit"/>
<toolkit:MenuItem Header="delete" caliburn:Message.Attach="Delete"/>
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>
<TextBlock Text="{Binding Description}" Style="{StaticResource PhoneTextLargeStyle}" VerticalAlignment="Bottom"/>
<TextBlock Text="{Binding Cost, ConverterParameter=\{0:C\}, Converter={StaticResource StringFormat}}" Style="{StaticResource PhoneTextSmallStyle}" VerticalAlignment="Bottom" Foreground="{StaticResource PhoneAccentBrush}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The second scenario (and in my opinion the more likely) is where the Edit and Delete methods exist on the PurchaseListViewModel and take the PurchaseViewModel as a parameter.
public void Edit(PurchaseViewModel purchase)
{
MessageBox.Show("Editing " + purchase.Description, "Edit", MessageBoxButton.OK);
}
public void Delete(PurchaseViewModel purchase)
{
MessageBox.Show("Deleting " + purchase.Description, "Delete", MessageBoxButton.OK);
}
This one is a little more complicated, not only do we need to set the Target but we don't have easy access to the PurchaseListViewModel. I've seen solutions in WPF and Silverlight using RelativeSource bindings which aren't available, thankfully we can still used ElementName bindings. The idea is that we can get the DataContext of the ListBox (the PurchaseListViewModel) through an element name binding and set that as the Target). We then use the $dataContext shortcut to pass the current DataContext (the PurchaseViewModel) as a parameter.
<ListBox x:Name="Purchases">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Width="456" d:DesignWidth="275" Margin="0,0,0,12" Background="Transparent">
<toolkit:ContextMenuService.ContextMenu>
<toolkit:ContextMenu caliburn:Action.TargetWithoutContext="{Binding DataContext, ElementName=Purchases}">
<toolkit:MenuItem Header="edit" caliburn:Message.Attach="Edit($dataContext)"/>
<toolkit:MenuItem Header="delete" caliburn:Message.Attach="Delete($dataContext)"/>
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>
<TextBlock Text="{Binding Description}" Style="{StaticResource PhoneTextLargeStyle}" VerticalAlignment="Bottom"/>
<TextBlock Text="{Binding Cost, ConverterParameter=\{0:C\}, Converter={StaticResource StringFormat}}" Style="{StaticResource PhoneTextSmallStyle}" VerticalAlignment="Bottom" Foreground="{StaticResource PhoneAccentBrush}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Hope this helps someone as it can be pretty tricky.
A few months ago I released download details for the first three months of sales of To Do Today. Just after that release I moved the price down from $2.99 to $0.99 as an experiment in pricing. Here's the results.
Download numbers weren't changed by a noticeable amount, averaging around 300 downloads a month. The big change was the shift in conversion rate, previously 20% of people who downloaded the trial ended up purchasing the application, with the price drop this number jumped to around 35%.
If you've done the maths in your head you'll realise that while the increase in numbers is great to see it's actually a change that means I'm earning less money. I figured this would be the case, however I was hoping that a lower selling price would increase downloads and sales and push To Do Today up the rankings in the Marketplace which would in turn trigger more downloads and sales in a magical spiral of upwardsness. Sadly this hasn't seemed to be occurring.
In the coming month I'll be updating To Do Today to 1.5 to add some customisation features that people have been requesting such as the home screen, and a feature that I've really had to use lately, the ability to postpone tasks. During this release I'll be shifting the price to around $1.99 to see how this impacts downloads and sales.
Also coming out this month will be another app I've been using to keep track of my spending and giving myself a daily allowance. More about this soon.
The email app on Windows Phone 7 has slick delete animation whenever you delete a single email, the whole page shrinks in size and then falls of the bottom of the page, once I'd seen it I knew I wanted to bring a similar animation to To Do Today. Here's how I created it in Blend.
While the UI for Blend is great for laying up my pages on the phone it's a little difficult to work with animations using the default layout. Thankfully Blend has the concept of Workspaces, if you go to the Window / Workspaces menu and select Animation Blend will rearrange the user interface to better deal with things like timelines.
The first thing we do create the storyboard, under the storyboard section select the New button and then give the storyboard a name, I'm calling mine AnimateDelete (original I know).
Once the storyboard is created Blend is in record mode, anything we do to our page is recorded as part of the storyboard.
To animate the entire page we'll be manipulating the element LayoutRoot, so locate that in the Objects tree. The first step is to create a keyframe on the timeline about half a second in, this will be the frame where we've finished shrinking the page and start the drop. Move the timeline marker out to half a second into the timeline and hit the record keyframe button.

With that keyframe selected set the Scale transform to 0.5 to halve the size of the page. We then create the keyframe to tell the animation when to stop the drop. At around a second into the timeline create a second keyframe and with that selected set the Translate Y transform to 600.
To make the animations feel a little more fluid lets set some easing functions to both keyframes. Select each keyframe and in the properties panel set the easing function to ExponentialInOut with an exponent of 6.

You can preview the animation using the timeline and tweak the values to suit your own taste.
Now lets plug the animation into the page, I have an event handler for the delete button on the application bar, we'll use the method I created in a previous blog post to call the Delete method on the view model when the animation is complete. If we didn't do this then our business logic would complete so quickly we wouldn't see the animation.
private void OnDeleteAll(object sender, EventArgs e)
{
AnimateDelete.Begin(ViewModel.DeleteAll);
}
Hope this helps some people get an idea how easy adding animation to your apps can be.
There's been a lot of awesome announcements coming from Microsoft about the Windows Phone 7 at Mix this year. I really wish I had the chance to be there in person but sadly with a fledgling business in Compiled Experience tickets from Auckland to Las Vegas are a bit too much. Maybe next year.
It really looks like they've taken a lot of developer feedback to heart, especially in the areas of integration between the phone and the app. It's an area that was very locked down in the first version and is gradually being opened up. This seems to be a theme in Silverlight as well, hopefully it continues for future versions.
I'm especially happy to see the live tile and background agent support, it's a feature users have been screaming out for in To Do Today that couldn't be built to my satisfaction using the current methods. It's the first thing I'll be doing as soon as Mango is released. The deep linking functionality of live tiles and toast notifications will certainly be interesting. It's certainly going to make in app navigation a lot more complex given that you won't entirely be sure of the navigation stack any more as you'll need to add home buttons.
One thing that wasn't really announced but only briefly mentioned (as part of the KIK demo if I remember correctly) is that Mango will include Silverlight 4. This is a great thing for me if it includes Binding to non Framework Elements (always a bugbear of mine for Silverlight in the browser). This means binding to things like behaviours without hacks. Hopefully it also adds some support for validation as there's pretty much nothing really supported in WP7 as it stands.
Better binding really adds value to users of the Caliburn Micro framework, one awesome feature is convention based binding to parameters of actions which currently isn't possible due to the binding infrastructure. By having the binding on parameters it makes the guard actions more useful as they're refreshed whenever a dependent control changes. It'll make view models feel more natural rather than property bags with a lot of parameter-less actions.
SQL CE on the phone will be helpful, it'll be interesting to see the performance of it compared to serializing documents to Isolated Storage. I'd love to see an embedded document database ala Raven or Mongo.
Lately I've been using the EQATEC profiler but one that's built into Visual Studio with real hooks into the visual tree and storyboards will be very useful. And best of all these tools look to be available next month! There won't be a need to build in fake accelerometers and GPS services any more and it will be nice to profile my existing apps.
A number of things are missing for me from the announcements, the first was anything around design resources. It really appears that a lot of developers are equating Metro to not needing to put some thought into design and layout. There's a huge amount of "fugly" applications out there that have really put me off purchasing them. What's missing from a lot of applications is good animation and movement, it's a huge part of Metro and not having a standard library of animations is really hurting user built applications.
The second thing was anything around the marketplace for developers, better break downs of downloads versus sales, being able to export the data to other formats. Revenue reports and things like time between download of the trial and sale. Some of this can be mitigated by using your own analytics solution, but if it's baked it then it'd certainly be better. Also being able to submit an update to a patch and to have to repopulate all the details of the app.
Microsoft really will need to keep on top of the update process, if the same issues with NoDo come up again with Mango there will be hell to pay.