70 Comments

The Problem

Panorama is one of the most popular WP7-app controls and it's no wonder: The control is easy to use and it makes the UI look nice. But many of the app codebases I've seen have a common problem: The XAML inside the Panorama isn't split between different controls but instead it is all tucked into the one panorama control. The apps written like this may end up as a maintenance nightmare.

The Solution

My advice is to split the panorama so that every PanoramaItem is represented by one UserControl. This small change will make a dramatic change for the readability of your XAML-files. Instead of looking like this:

    <Grid x:Name="LayoutRoot" Background="Transparent">
        <controls:Panorama Title="SM-Liiga Center" >
            <controls:PanoramaItem x:Name="LiveScores" Header="scores">
                <StackPanel x:Name="LayoutRoot" Background="Transparent" d:DataContext="{d:DesignData /SampleData/LiveScoresViewModelSampleData.xaml}">

                    <toolkit:DatePicker Margin="-10 0 0 0" PickerPageUri="/LiveScores/Calendar/CalendarView.xaml" Value="{Binding ScoreDay, Mode=TwoWay}" Style="{StaticResource MyDatePicker}" />
                    <Grid>
                        <TextBlock x:Name="Status" Visibility="{Binding ElementName=Status, Path=Text, Converter={StaticResource TextVisibilityConverter}}" Style="{StaticResource PhoneTextTitle3Style}" TextWrapping="Wrap" />
                        <ListBox x:Name="Scores" Margin="0 -15 0 0" Grid.Row="1" DataContext="{Binding}" ItemsSource="{Binding Scores}" ItemContainerStyle="{StaticResource DefaultListBoxItemStyle}" Micro:Message.Attach="[Event SelectionChanged] = [Action Open($eventArgs)]">
                            <ListBox.ItemTemplate>
                                <DataTemplate>
                                    <Grid x:Name="grid" Margin="0 0 0 12">
                                        <Grid.RenderTransform>
                                            <CompositeTransform/>
                                        </Grid.RenderTransform>
                                        <VisualStateManager.VisualStateGroups>
                                            <VisualStateGroup x:Name="VisualStateGroup">
                                                <VisualState x:Name="Default" />
                                                <VisualState x:Name="Touched">
                                                    <Storyboard>
                                                        <DoubleAnimation Duration="0" To="0.98" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleX)" Storyboard.TargetName="grid" d:IsOptimized="True"/>
                                                        <DoubleAnimation Duration="0" To="0.98" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleY)" Storyboard.TargetName="grid" d:IsOptimized="True"/>
                                                    </Storyboard>
                                                </VisualState>
                                            </VisualStateGroup>
                                        </VisualStateManager.VisualStateGroups>
                                        <Custom:Interaction.Triggers>
                                            <Custom:EventTrigger EventName="ManipulationStarted">
                                                <ic:GoToStateAction StateName="Touched" UseTransitions="False" />
                                            </Custom:EventTrigger>
                                            <Custom:EventTrigger EventName="ManipulationCompleted">
                                                <ic:GoToStateAction StateName="Default" UseTransitions="False"/>
                                            </Custom:EventTrigger>
                                        </Custom:Interaction.Triggers>
                                        <VisualStateManager.CustomVisualStateManager>
                                            <ic:ExtendedVisualStateManager/>
                                        </VisualStateManager.CustomVisualStateManager>
                                        <Grid.ColumnDefinitions>
                                            <ColumnDefinition Width="auto"></ColumnDefinition>
                                            <ColumnDefinition Width="*"></ColumnDefinition>
                                        </Grid.ColumnDefinitions>
                                        <Grid.RowDefinitions>
                                            <RowDefinition></RowDefinition>
                                            <RowDefinition></RowDefinition>
                                        </Grid.RowDefinitions>
                                        <Rectangle Grid.RowSpan="2" Fill="Red" Stroke="Red" Opacity="0" Grid.ColumnSpan="2" />
                                        <StackPanel Orientation="Horizontal" >
                                            <TextBlock Text="{Binding HomeTeam}" Style="{StaticResource PhoneTextTitle3Style}" />
                                            <TextBlock Text="-" Style="{StaticResource PhoneTextTitle3Style}"/>
                                            <TextBlock Text="{Binding AwayTeam}" Style="{StaticResource PhoneTextTitle3Style}"/>
                                        </StackPanel>
                                        <TextBlock Grid.Row="0" Grid.Column="1"  Text="{Binding Time}" Style="{Binding Status, Converter={StaticResource ScoreColorConverter}}"  HorizontalAlignment="Right" VerticalAlignment="Bottom"/>
                                        <TextBlock Grid.Column="0" Grid.Row="1" Text="{Binding ScoreString}" Margin="14 -7 0 0" Style="{Binding Status, Converter={StaticResource ScoreColorConverter}}" />
                                    </Grid>
                                </DataTemplate>
                            </ListBox.ItemTemplate>
                        </ListBox>
                    </Grid>
                </StackPanel>
            </controls:PanoramaItem>
            <controls:PanoramaItem x:Name="TeamStatistics" Header="stats">
                <Grid x:Name="LayoutRoot" Background="Transparent" d:DataContext="{d:DesignData /SampleData/TeamStatisticsViewModelSampleData.xaml}">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="auto"></RowDefinition>
                        <RowDefinition Height="auto"></RowDefinition>
                    </Grid.RowDefinitions>
                    <StackPanel Grid.Row="0">
                        <StackPanel Orientation="Horizontal">
...
                        </StackPanel>
                    </StackPanel>
                    <TextBlock Grid.Row="1" x:Name="StatusBox" Style="{StaticResource PhoneTextTitle3Style}" Infrastructure:VisibilityChangingText.VisibilityText="{Binding Status}" TextWrapping="Wrap"  />
                    <ListBox Grid.Row="1" x:Name="TeamStatistics" DataContext="{Binding}" ItemTemplate="{StaticResource TeamStatTemplate}" ItemsSource="{Binding TeamStatistics}" />
                </Grid>
            </controls:PanoramaItem>
            <controls:PanoramaItem x:Name="PlayerStatistics" Header=" ">More xaml</controls:PanoramaItem>
            <controls:PanoramaItem x:Name="News" Header="news">Even more xaml</controls:PanoramaItem>
        </controls:Panorama>
    </Grid>

The main page of the SM-Liiga Center app looks like this:

    <Grid x:Name="LayoutRoot" Background="Transparent">
        <controls:Panorama Title="SM-Liiga Center" >
            <controls:PanoramaItem x:Name="LiveScores" Header="scores"></controls:PanoramaItem>
            <controls:PanoramaItem x:Name="TeamStatistics" Header="stats"></controls:PanoramaItem>
            <controls:PanoramaItem x:Name="PlayerStatistics" Header=" "></controls:PanoramaItem>
            <controls:PanoramaItem x:Name="News" Header="news"></controls:PanoramaItem>
        </controls:Panorama>
    </Grid>

The example above takes advantage of Caliburn.Micro. The framework automates many data binding scenarios with the help of conventions. In the next section we go through all the pieces that the developer has to take care of so that Caliburn.Micro can wire things up in your app.

Details

The MainPage.xaml of SM-Liiga Center is like any other PhoneApplicationPage out there:

<phone:PhoneApplicationPage x:Class="smliiga.client.MainPage"
                           ...
                            shell:SystemTray.IsVisible="False" >
    
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <controls:Panorama Title="SM-Liiga Center" >
            <controls:PanoramaItem x:Name="LiveScores" Header="scores"></controls:PanoramaItem>
            <controls:PanoramaItem x:Name="TeamStatistics" Header="stats"></controls:PanoramaItem>
            <controls:PanoramaItem x:Name="PlayerStatistics" Header=" "></controls:PanoramaItem>
            <controls:PanoramaItem x:Name="News" Header="news"></controls:PanoramaItem>
        </controls:Panorama>
    </Grid>

</phone:PhoneApplicationPage>

The application also has a class called MainPageViewModel which contains the driving logic for the main page. Caliburn.Micro takes care of all the wiring so the developer doesn’t have to worry about how the view should bind to the view model. Here’s how the MainPageViewModel.cs looks like:

    public class MainPageViewModel : Screen
    {
        public LiveScoresViewModel LiveScores { get; set; }
        public NewsViewModel News { get; protected set; }
        public TeamStatisticsViewModel TeamStatistics { get; protected set; }
        public PlayerStatisticsViewModel PlayerStatistics { get; protected set; }

        public MainPageViewModel(LiveScoresViewModel liveScores, NewsViewModel news, TeamStatisticsViewModel teamStatistics, PlayerStatisticsViewModel playerStatistics)
        {
            LiveScores = liveScores;
            News = news;
            TeamStatistics = teamStatistics;
            PlayerStatistics = playerStatistics;
        }
    }

Note the class from which the view model inherits. As you remember, every PanoramaItem was represented by one user control. Those user controls are “injected” to the MainPageViewModel through its constructor (Though to be precise, the controls aren’t injected: It’s the view models which drive the user controls.) What Caliburn.Micro does is that it sees that we have a PanoramaItem named News and then it goes searching into our view model, looking for a property called News. In our case we have that property and it is of type NewsViewModel. It’s then a simple task for Caliburn.Micro to find the user control which NewsViewModel is driving and add it to the main page.

We’re mentioned the NewsViewModel quite many times so lets take a look at it:

    public class NewsViewModel : Screen
    {
        private readonly INavigationService navigationService;
        private readonly NewsService service;

        public ObservableCollection<NewsItem> Items { get; private set; }

        private string status;
        public string Status
        {
            get { return status; }
            set { status = value; NotifyOfPropertyChange(() => Status); }
        }

        public NewsViewModel(NewsService service, INavigationService navigationService)
        {
            this.navigationService = navigationService;
            this.service = service;
            this.service.NewsLoaded += OnNewsLoaded;

            Status = "loading...";
            ThreadPool.QueueUserWorkItem(x => this.service.LoadNews());
        }

        void OnNewsLoaded(object sender, NewsLoadedEventArgs e)
        {
            this.service.NewsLoaded -= OnNewsLoaded;

            if (e.Error)
            {
                Status = "error when loading. please try again later.";
                return;
            }

            var result = new ObservableCollection<NewsItem>();
            foreach (var newsItem in e.Result)
            {
                result.Add(newsItem);
            }

            Items = result;
            NotifyOfPropertyChange(() => Items);

            Status = "";
        }
    }

Nothing strange about the class. It again inherits from the Screen-class but overall it looks like most of the view models out there. The NewsView is little more interesting, mainly because it isn’t a normal PhoneApplicationPage but an UserControl:

<UserControl
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	...
    d:DesignHeight="480" d:DesignWidth="480">
    <UserControl.Resources>
        <DataTemplate x:Key="NewsItemTemplate">
       ...
     </UserControl.Resources>

    <Grid x:Name="LayoutRoot" Background="Transparent" d:DataContext="{d:DesignData /SampleData/NewsViewModelSampleData.xaml}">
        <Grid.RowDefinitions>
            <RowDefinition Height="208*" />
            <RowDefinition Height="272*" />
        </Grid.RowDefinitions>
        <TextBlock x:Name="StatusBox" Style="{StaticResource PhoneTextTitle3Style}" Infrastructure:VisibilityChangingText.VisibilityText="{Binding Status}" TextWrapping="Wrap" Margin="12,0" Grid.RowSpan="2" />
        <ListBox x:Name="Items" IsSynchronizedWithCurrentItem="{x:Null}" ItemContainerStyle="{StaticResource DefaultListBoxItemStyle}" DataContext="{Binding}" ItemTemplate="{StaticResource NewsItemTemplate}" ItemsSource="{Binding Items}" Micro:Message.Attach="[Event SelectionChanged] = [Action Open($eventArgs)]" Grid.RowSpan="2" />
    </Grid>
</UserControl>

But even then, it’s all about the familiar XAML.

There’s only one more thing required: The developer has to add the view models into the container so that Caliburn.Micro can use the correct classes when needed. The container is a core piece of Caliburn.Micro and without it, it wouldn’t know what to do if a class out there requires a NewsViewModel in its constructor. You can configure the container through the AppBootstrapper’s method Configure:

            container.RegisterPerRequest(typeof(MainPageViewModel), "MainPageViewModel", typeof(MainPageViewModel));
            container.RegisterSingleton(typeof(NewsViewModel), null, typeof(NewsViewModel));
            container.RegisterSingleton(typeof(LiveScoresViewModel), null, typeof(LiveScoresViewModel));
            container.RegisterSingleton(typeof(TeamStatisticsViewModel), null, typeof(TeamStatisticsViewModel));
            container.RegisterSingleton(typeof(PlayerStatisticsViewModel), null, typeof(PlayerStatisticsViewModel));

And that’s it. By splitting the panorama into multiple user controls we now have XAML which is much more readable. The nice side effect of this is that also the view models are split into logical pieces. So instead of having one gigantic MainPageViewModel with 9 different services injected through its constructor, we have much more focused view model implementations.

Note

If the above isn’t enough and you still want to simplify your code some more, you can probably do it by using the feature called the conductor from the Caliburn.Micro.

13 Comments

Background

Weak references are an easy and effective way to keep your app’s memory usage in order. Wikipedia sums up the idea behind weak references very neatly:

a weak reference is a reference that does not protect the referenced object from collection by a garbage collector. An object referenced only by weak references is considered unreachable (or "weakly reachable") and so may be collected at any time.

Implementation

WP7’s framework contains a System.WeakReference –class which you can use in your application. But keep in mind that you can’t derive from this class because it’s constructor is marked with SecuritySafeCritical-attribute. To get around this limitation and to get a generic version as an added bonus, you can use the following class to implement the WeakReference in your app (I’m sorry but I have lost the original source of the following code):

  public class WeakReference<T>
  {
    public WeakReference(T target)
    {
      reference = new WeakReference(target);
    }

    public bool IsAlive
    {
      get
      {
        return reference.IsAlive;
      }
    }

    public T Target
    {
      get
      {
        return (T)reference.Target;
      }
      set
      {
        reference.Target = value;
      }
    }
  }

Usage

Using the class presented above is easy. Here’s an example of a code where we aren’tusing WeakReference yet:

        protected WriteableBitmap bitmapImage;
        public ImageSource ImageSource
        {
            get
            {
                if (bitmapImage != null)
                {
                    return bitmapImage;
                }

                if (ImageUri != null)
                    ThreadPool.QueueUserWorkItem(DownloadImage, ImageUri);

                return null;
            }
        }

Here’s an updated version where WeakReference is used:

        protected WeakReference<WriteableBitmap> bitmapImage;
        public ImageSource ImageSource
        {
            get
            {
                if (bitmapImage != null)
                {
                    if (bitmapImage.IsAlive)
                    {
                        return bitmapImage.Target;
                    }

                    Debug.WriteLine("Source has been garbage collected. Create new.");
                }

                if (ImageUri != null)
                    ThreadPool.QueueUserWorkItem(DownloadImage, ImageUri);

                return null;
            }
        }

Benefits

Without using the WeakReference-class, the memory usage of Teletext Finland app looks like this after a couple minutes:

image

With the updated version, the memory usage is drastically better:

image

0 Comments

Background

The ListBox control in WP7 is a great and easy way to provide long scrolling lists of items, like images and text. It provides you the ability to format the items in your list anyway you want with the help of datatemplates. But it’s missing one functionality: The option to use different datatemplates for items when the ListBox is scrolling compared to the situation where the ListBox is stopped.

Example

Imagine a situation where every item in your ListBox contains items which need to download an image from the net. For example:

    public class Item
    {
        public int Number { get; set; }

        public Uri ImageUri { get; set; }

        public Item(int number, Uri imageUri)
        {
            Number = number;
            ImageUri = imageUri;
        }
    }

And the datatemplate:

            <ListBox x:Name="Items">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <TextBlock Text="{Binding Number}"></TextBlock>
                            <Image Source="{Binding ImageUri}"></Image>
                        </StackPanel>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>

When you scroll the list up and down, your app is downloading every image. With complex datatemplates this starts to affect the performance really fast. And your app is probably doing unnecessary work because even if you just want to rapidly scroll from item 1 to item 100, every item (and image) between those two is loaded.

LazyListBox

The solution for this is to use  LazyListBox.LazyListBox contains (at least) two different datatemplates for your ListBox items: One for the situation where the ListBox is scrolling and one for the ListBox standing still. Peter Torr has written a great and easy-to-follow article about his LazyListBoxand his control is very easy to use: Just download the zip-package included in his post (LazyListBoxBlogPost.zip), open and compile the included solution and add reference to the LazyListBox.dll into your project.

The default ItemTemplate in LazyListBox is for situations where the ListBox is scrolling. In addition to ItemTemplate, it contains LoadedItemTemplate which is used when the ListBox is still. The idea is to show only some “light” information like text when the ListBox is scrolling, making the scrolling much more smooth. Our previous example could look like this with the help of LazyListBox:

            <lazy:LazyListBox>
                <lazy:LazyListBox.ItemTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding Number}"></TextBlock>
                    </DataTemplate>
                </lazy:LazyListBox.ItemTemplate>
                <lazy:LazyListBox.LoadedItemTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <TextBlock Text="{Binding Number}"></TextBlock>
                            <Image Source="{Binding ImageUri}"></Image>
                        </StackPanel>
                    </DataTemplate>
                </lazy:LazyListBox.LoadedItemTemplate>
            </lazy:LazyListBox>

When the list is scrolling, only the Number information is shown. And when the list stops, it automatically switches to LoadedItemTemplate. Peter shows in his post how this transition can be made smooth with the help of triggers and animations.

Conclusion

So, LazyListBox brings you two major benefits:

  • Your app needs to work less.
  • Your app provides a smoother and more user-friendly experience.

Resources

0 Comments

I'm going to post few "lessons learned" articles about the Windows Phone 7 app development in the coming days. Thus far couple apps have seen the daylight, both of which are quit different.

First app is Teletext Finland, which was quite an interesting development experience from the performance's point of view. The app only has one view, but it contains a smooth scrolling listbox of up to 900 images (teletext pages), where every image can contain around 20 child images (the child pages). Keeping in mind the WP7's limit of 90Mb's mem per app, getting rid of the not visible images was maybe the most important technical aspect of the application's architecture.

The second app is SM-Liiga Center, which is an app for all the Finnish elite league hockey fans. From architecture's perspective it is completely different than Teletext Finland. SM-Liiga Center takes heavy use of MVVM and Caliburn.Micro and it provides push notifications.

But more about both of the apps later.

4 Comments

I’m building a live sports app and because it’s going to be commercial one, I wanted it to support the trial mode. In my case it was easy to decide on how to restrict the trial version: Instead of showing scores from all of the matches, the trial version shows only first two. Same with news and statistics. I’m using Caliburn.Micro to build the app and building on its features made the adding of the trial mode a really simple task.

Caliburn.Micro has a built-in dependency injection tool called the SimpleContainer and he container is initialized in the AppBootstrapper’s Configure-method. This is also a perfect place to add support for the trial-mode: Instead of always injecting the same services, check if the app is run in the trial mode and if it is, inject the services which are modified to return only a subset of results.

To do this, the first thing required is a method to check if the app is in trial mode. This can be added to the AppBootstrapper.cs:

   1: private static bool isTrial()
   2: {
   3:     return new LicenseInformation().IsTrial();
   4: }

The MSDN has a quite good article about the subject from which you can find more information about the LicenseInformation-class.

The next thing you need to do is to check for that value when adding implementations into the container and insert the correct ones based on the value. It maybe better to keep the check for trial mode in minimum (or at least adding caching to the isTrial-method) because every check for IsTrial-method takes around 60 ms.

   1: // If in trial mode, don't use the full versions of the following services
   2: if (isTrial())
   3: {
   4:     container.RegisterSingleton(typeof(ITeamStatsService), null, typeof(TrialTeamStatsService));
   5:     container.RegisterSingleton(typeof(NewsService), null, typeof(TrialNewsService));
   6:     container.RegisterSingleton(typeof(IPlayerStatsService), null, typeof(TrialPlayerStatsService));
   7:     container.RegisterSingleton(typeof(IScoreService), null, typeof(TrialSimpleScoreService));
   8: }
   9: else
  10: {
  11:     container.RegisterSingleton(typeof(ITeamStatsService), null, typeof(TeamStatsService));
  12:     container.RegisterSingleton(typeof(NewsService), null, typeof(NewsService));
  13:     container.RegisterSingleton(typeof(IPlayerStatsService), null, typeof(PlayerStatsService));
  14:     container.RegisterSingleton(typeof(IScoreService), null, typeof(SimpleScoreService));
  15: }

You can write the trial-versions of your service classes in any way you prefer. I decided to just derive from the full implementation and limit the data they return. For example:

   1: public class TrialSimpleScoreService : SimpleScoreService
   2: {
   3:     public TrialSimpleScoreService(AllMatchesParser parser, IHtmlProvider pageLoader) : base(parser, pageLoader)
   4:     {
   5:     }
   6:
   7:     protected override List<GameOverview> CreateResults(List<GameOverview> results)
   8:     {
   9:         if (results.Count < 1)
  10:             return base.CreateResults(results);
  11:
  12:         results = results.Take(2).ToList();
  13:
  14:         var trialScore = new GameOverview(long.MaxValue, "Trial Mode","Limited to 2 games", 0, 0, "", GameStatus.InProgress);
  15:         results.Add(trialScore);
  16:
  17:         return base.CreateEventArgs(results);
  18:     }
  19: }

The trial class removes everything else but the two first items from the list (results.Take(2)) and then adds one new item to the list. This item is shown on the UI to remind the user that the app is running in the trial mode.

image

And this is all that is required.

To test and debug an app running in the trial mode, I recommend the approach which Eric Malamisura presented in his blog post.