3 Comments
  •   Posted in: 
  • UWP

imageBoth the Windows Phone and Windows 8 apps have live tiles which can be controlled by the developer. But where the Windows Phone tiles have the front and back sides, Windows 8 tiles have a notification queue. Live tile’s notification queue can be used to show multiple different messages to the user, like the latest news items or recent emails.

Here’s some example code which shows how to use the notification queue in a Windows 8 Metro app and how to update the queue periodically using a ASP.NET Web Api backend.

The source code for the following examples is available from GitHub.

TileUpdater

TileUpdater-class is the key to updating your tiles.  It enables you to update your Metro app live tile both manually and automatically. You can get an instance of a TileUpdater using the following code:

var tileUpdater = TileUpdateManager.CreateTileUpdaterForApplication();

Then using the TileUpdater instance you usually call one the following methods:

The following scenarios shows how to use the Update and StartPeriodicUpdateBatch-methods.

Notification’s ID

With notification queue enabled, the app can show up to five notifications in its tile. When a new notification is added to the queue, it replaces an older notification. But it’s possible to add an ID (to tag) a notification.

Notifications can be given a tag so that a new notification with a specific tag will replace an older notification with the same tag, regardless of its place in the queue.

Notification’s expiration time

The notifications in the notification queue may have an expiration time. This can be used in situations where the tile is used to show time sensitive information, like TV shows.

If your notification content has a significantly different useful lifespan—shorter or longer—you can explicitly set an expiration time for each notification, and it is a best practice to do so. This prevents stale or irrelevant content from remaining in the queue.

Scenario 1: Updating notification queue every time the app is run

If it’s enough that the tile’s notification queue is updated only when the app is run, there’s no need for a backend. You can update the live tile for example in MainPage’s OnNavigateTo-method.

Step 1: Enable notification queue for your live tile

If you don’t enable the notification queue, the last notification will override the previous one. So it’s only possible to show one notification on your tile.

TileUpdateManager.CreateTileUpdaterForApplication().EnableNotificationQueue(true);

Step 2: Create the tiles and notifications

After you have enabled the notification queue, you create a tile for each notification and use the TileUpdateManager to add it to the queue. In the following example code the Generator.Generate method returns the correct XML for both the wide and the small tile. The ID and the expiration time are both set:

for (var i = 0; i < 5; i++)
            {
                var tileTitle = string.Format("Local Notification {0}", i);
                var tilesSubtitle = DateTime.UtcNow.AddHours(i);
                var myTile = Generator.Generate(tileTitle, tilesSubtitle);

                var notification = new TileNotification(myTile.ToXmlDoc()) { ExpirationTime = tilesSubtitle.AddMinutes(15), Tag = i.ToString()};

                TileUpdateManager.CreateTileUpdaterForApplication().Update(notification);
            }

 

Scenario 2: Updating notification queue periodically

In a case where the live tile should be updated automatically with new notifications, it’s necessary to create a backend. Your backend will return exactly the same XML as the local tile updates use. What’s maybe a little strange is that you’re backend won’t return all of the notification simultaneously.

For example if you want to show 5 most recent news items on your tile, your  app will call your backend five times and it’s up to the backend to return a different item each time. Probably the easiest way to handle this is to give the app five different urls. Each url contains a different parameter which triggers the backend to return the correct item.

Step 1: Create the backend using ASP.NET Web Api self-hosted server

Maybe the easiest way to create a custom backend for your app is to use self-hosted ASP.NET Web Api program. Just make sure that it returns XML and not JSON.

Here’s a Web Api controller which is used to create exactly same notifications as were created in Step 1:

public class TileUpdaterController : ApiController
    {
        public HttpResponseMessage Get(int position)
        {
            var msg = string.Format("Received request for position {0}", position);
            Console.WriteLine(msg);

            var tileTitle = string.Format("Backend Noti {0}", position);
            var tilesSubtitle = DateTime.UtcNow.AddHours(position);

            var result = Generator.Generate(tileTitle, tilesSubtitle);

            var response = Request.CreateResponse(HttpStatusCode.OK, result);
            response.Headers.Add("X-WNS-Expires", tilesSubtitle.AddMinutes(15).ToString("r"));
            response.Headers.Add("X-WNS-Tag", position.ToString());

            return response;
        }
    }
  • The response header X-WNS-Expires can be used to set the expiration time for the notification
  • The response header X-WNS-Tag can be used to set the id of the notification

Step 2: Start the periodic updates for your app

When the backend is ready, the TileUpdater’s StartPeriodicUpdateBatch can be used connect your app to the backend. MainPage’s OnNavigateTo is again a good place to call the TileUpdater.

var uris = new List<Uri>(); 
const string baseUri = "http://localhost:8080/api/tileupdater/get?position=";

for (var i = 0; i < 5; i++) 
{ 
    uris.Add(new Uri(baseUri + i, UriKind.Absolute)); 
}

TileUpdateManager.CreateTileUpdaterForApplication().StartPeriodicUpdateBatch(uris, 
                                                                             PeriodicUpdateRecurrence.HalfHour);

 

Step 3: Enable notification queue for your live tile

As with the local updates, the app must enable the notification queue for the app.

Scenario 3: Updating notification queue every time the app is run and also periodically

Given the following scenario:

  • The notification queue must be updated every time the app is run.
  • The notification queue must be updated periodically by calling a backend.

In a scenario like this, it’s enough to call the StartPeriodicUpdateBatch-method when the application is run. When you call that method it will start the updates and it will (based on Fiddler) immediately call the backend and update the tiles. There’s no need to call the Update-method of TileUpdater so the same code as in Scenario 2 can be applied in here.

Notifications queue and the Windows 8 simulator

Notification queue doesn’t seem to work if the app is run inside the Windows 8 simulator. Instead the local machine deployment should be used to test the code.

Generating the live tiles using VB.NET code in a portable class library

There’s many different ways to generate the XML required for the live tile notifications. One way is to create a VB.NET portable class library and use the XML Literals. This project can then be referenced from both the backend and from the Metro app. Here’s the XML generator’s code used in these examples:

Public Shared Function Generate(title As String, time As DateTime) As XElement

    Dim element = <tile><visual><binding template="TileWideText09"><text id="1"><%= title %></text><text id="2"><%= time.ToString() %></text></binding><binding template="TileSquareText04"><text id="1"><%= title %></text></binding></visual></tile>

    Return element

End Function

 

Source code

The source code for these examples is available from GitHub.

8 Comments
  •   Posted in: 
  • UWP

imageOne small tip if you’re creating a Windows 8 Metro app and want to update the live tile periodically using the TileUpdater-class: The request made by the OS for the live tile update doesn’t set the Content-Type so make sure that you backend is actually returning XML by default.

I encountered this when I built my app’s backend using ASP.NET Web Api and noticed that the live tile updates weren’t actually working. The backend was returning JSON instead of the XML because that’s the default format for the Web Api. In my case the backend only serves the live tile updates so I completely removed the JSON-formatter:

var config = new HttpSelfHostConfiguration("http://localhost:8080");

config.Routes.MapHttpRoute("Default", "api/{controller}/{id}", 
    new { id = RouteParameter.Optional });

config.Formatters.RemoveAt(0);

using (var server = new HttpSelfHostServer(config)) 
{ 
    server.OpenAsync().Wait();

    Console.WriteLine("Press Enter to quit."); 
    Console.ReadLine(); 
}

Now the backend is returning XML and the periodic live tile updates work properly.

3 Comments
  •   Posted in: 
  • UWP

imageThe Windows Phone OS has a global Theme setting available for the user to change. This one setting affects every application. The Windows 8 seems to take a different approach (at least in Windows 8 RP): There’s no global theme setting, instead it’s up to the developer to decide if her application uses the dark or the light theme.

Defining the application theme in XAML

Maybe the easies way to define the application theme for the Metro application is to use the App.xaml. The Application-element has a RequestedTheme-attribute which can be set to either Dark or Light.

<Application 
    x:Class="windows8_metro_themes.App" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:local="using:windows8_metro_themes" 
    xmlns:localData="using:windows8_metro_themes.Data" 
    RequestedTheme="Dark">

The App.xaml route works well if the theme is hard coded. But if the user can customize app’s theme, it’s maybe easier to set the theme in code.

Defining the application theme in code

The application theme can be set in the constructor of the App-class using code.

RequestedTheme = ApplicationTheme.Light;

The application theme cannot be changed when the application is running and trying to set it throws an exception. For example the OnLaunched-method in App.xaml.cs is already too late.

You could read the theme from application’s settings, making the theme configurable. Here’s an example where the current theme is stored in to application’s roaming settings:

var settings = Windows.Storage.ApplicationData.Current.RoamingSettings;

string currentTheme = null; 
if (settings.Values.ContainsKey("currentTheme")) 
{ 
    currentTheme = settings.Values["currentTheme"] as string; 
    settings.Values.Remove("currentTheme"); 
}

settings.Values.Add("currentTheme", currentTheme == "light" ? "dark" : "light");

And here’s App’s constructor where values is read:

public App() 
{ 
    this.InitializeComponent(); 
    this.Suspending += OnSuspending;

    var settings = Windows.Storage.ApplicationData.Current.RoamingSettings;

    if (settings.Values.ContainsKey("currentTheme") && (string) settings.Values["currentTheme"] == "light") 
        RequestedTheme = ApplicationTheme.Light; 
    else 
        RequestedTheme = ApplicationTheme.Dark; 
}

Note that the RequestedTheme-attribute in App.xaml seems to override the value set in the App’s constructor.

Defining the application theme for the Visual Studio designer

It’s possible to test both of the themes without running the app, using the Device-dialog.

image

You can switch between the Dark and the Light theme easily. The same dialog has other interesting options too, like the ability to switch the device inside the designer to the portrait mode.

Theme definitions

The XAML of the different themes are available from the following location: C:Program Files (x86)Windows Kits8.0Includewinrtxamldesignthemeresources.xaml

Links

5 Comments
  •   Posted in: 
  • UWP

The Release Preview of Windows 8 broke some things but fortunately there’s a quite good migration guide available which highlight most of the changes (Word document):

Migrating your Windows 8 Consumer Preview app to Windows 8 Release Preview

Unfortunately the guide seems to have missed some changes, like the removing of CoreDispatcher.Invoke and BeginInvoke –methods.

Breaking Change between Consumer Preview and Release Preview:

The following code worked in WinRT app with Consumer Preview:

dispatcher.Invoke(CoreDispatcherPriority.Normal, (s, e) => action(), dispatcher, null);

But the Invoke-method is now gone. If it’s OK that the action is executed asynchronously in UI thread (like BeginInvoke), this seems to be the way to go in Release Preview + WinRT combination:

dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => action());

But if the invoke must happen synchronously (like Invoke), async / await can be used:

await dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => action());

UIDispatcher - A helper class which allows the synchronous and asynchronous execution of methods in UI thread:

Here’s a complete UIDispatcher-class which can be used to execute actions in UI thread:

using System;
using System.Threading.Tasks;
using Windows.UI.Core;
using Windows.UI.Xaml;

namespace Helpers
{
    public static class UIDispatcher
    {
        private static CoreDispatcher dispatcher;

        public static void Initialize()
        {
            dispatcher = Window.Current.Dispatcher;
        }

        public static void BeginExecute(Action action)
        {
            if (dispatcher.HasThreadAccess)
                action();

            else dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => action());
        }

        public static void Execute(Action action)
        {
            InnerExecute(action).Wait();
        }

        private static async Task InnerExecute(Action action)
        {
            if (dispatcher.HasThreadAccess)
                action();

            else await dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => action());
        }
    }
}

Initialization:

Call the Initialize-method at the application startup, for example in App.xaml.cs's OnLaunched-method:

// Place the frame in the current Window and ensure that it is active
            Window.Current.Content = rootFrame;
            Window.Current.Activate();

            UIDispatcher.Initialize();

Without this the UIDispatcher won't work.

Invoking actions on UI thread:

After the UIDispatcher has been initialized, the Execute-method can be used to invoke actions in UI thread synchronously and BeginExecute to invoke action asynchronously:

ThreadPool.RunAsync(operation => UIDispatcher.Execute(() => this.Test.Text = "hello!"));

Links:

Here’s a good explanation by Jon Skeet about the differences of BeginInvoke and Invoke.

12 Comments
  •   Posted in: 
  • UWP

imageIn this WinRT tutorial we will build a single page application using the following building blocks:

  • XAML & C#
  • MVVM
  • Data binding
  • GridView
  • SemanticZoom –control

We will start from the scratch, using the “Blank application” template. Then we continue by creating the view model. After the view model is ready we will create the view by adding a page using the “Grouped Items Page” template. At last step we’re going to modify the page to support the SemanticZoom –control.

Update: Make sure to read about the GridView's performance problems.

Update: The examples and source code has been updated to work with Windows 8 RTM.

1. Creating the project

Let’s start by creating a new application using the “Blank application” template. This template contains a single page, “BlankPage.xaml”, which we can delete. Our application will contain only one page and we’re going to add this as a last step, using the built-in “Grouped Items Page” template. This template contains the GridView which we’ll use to to implement the semantic zoom.

image

But before creating the view, we’ll need a view model.

2. Creating the view model

Add a C# class and name it “MoviesPageViewModel.cs”. This is going to be our view model and it’ll work as the DataContext for our view.

image

The models

Our application shows movies grouped by their categories. For this we’ll need a new class which represents a single movie. This class can be added to the MoviesPageViewModel:

public class Movie

    {

        public string Title { get; set; }

        public string Subtitle { get; set; }

        public string Image { get; set; }

        public string Category { get; set; }

    }

We also need a model which represents a single movie category:

public class MovieCategory
    {
        public string Title { get; set; }
        public List<Movie> Items { get; set; }
    }

The data

We’re not going to connect our application into the net, instead we’re using static data. The movie instances can be created inside the MoviePageViewModel’s constructor:

var movies = new List<Movie>
                             {
                                 new Movie {Title = "The Ewok Adventure", Category = "Adventure", Subtitle = "The Towani family civilian shuttlecraft crashes on the forest moon of Endor.", Image = "http://cf2.imgobject.com/t/p/w500/y6HdTlqgcZ6EdsKR1uP03WgBe0C.jpg"},
                                 new Movie {Title = "The In-Laws", Category = "Adventure", Subtitle = "In preparation for his daughter's wedding, dentist Sheldon ", Image = "http://cf2.imgobject.com/t/p/w500/9FlFW9zhuoOpS8frAFR9cCnJ6Sg.jpg"},
                                 new Movie {Title = "The Man Called Flintstone", Category = "Adventure", Subtitle = "In this feature-length film based on the Flintstones TV show", Image = "http://cf2.imgobject.com/t/p/w500/6qyVUkbDBuBOUVVplIDGaQf6jZL.jpg"},

                                 new Movie {Title = "Super Fuzz", Category = "Comedy", Subtitle = "Dave Speed is no ordinary Miami cop--he is an irradiated Miami cop ", Image = "http://cf2.imgobject.com/t/p/w500/bueVXkpCDPX0TlsWd3Uk7QKO3kD.jpg"},
                                 new Movie {Title = "The Knock Out Cop", Category = "Comedy", Subtitle = "This cop doesn't carry a gun - his fist is loaded!", Image = "http://cf2.imgobject.com/t/p/w500/mzlw8rHGUSDobS1MJgz8jXXPM06.jpg"},
                                 new Movie {Title = "Best Worst Movie", Category = "Comedy", Subtitle = "A look at the making of the film Troll 2 (1990) ", Image = "http://cf2.imgobject.com/t/p/w500/5LjbAjkPBUOD9N2QFPSuTyhomx4.jpg"},

                                 new Movie {Title = "The Last Unicorn", Category = "Fantasy", Subtitle = "A brave unicorn and a magician fight an evil king", Image = "http://cf2.imgobject.com/t/p/w500/iO6P5vV1TMwSuisZDtNBDNpOxwR.jpg"},
                                 new Movie {Title = "Blithe Spirit", Category = "Fantasy", Subtitle = "An English mystery novelist invites a medium", Image = "http://cf2.imgobject.com/t/p/w500/gwu4c10lpgHUrMqr9CBNq2FYTpN.jpg"},
                                 new Movie {Title = "Here Comes Mr. Jordan", Category = "Fantasy", Subtitle = "Boxer Joe Pendleton, flying to his next fight", Image = "http://cf2.imgobject.com/t/p/w500/9cnWl7inQVX6wznjYNmQmJXVD6J.jpg"},
                             };

Grouping the data

Then we can group the movies by their categories and create a “MovieCategory” instance for each category:

var moviesByCategories = movies.GroupBy(x => x.Category)
                .Select(x => new MovieCategory { Title = x.Key, Items = x.ToList() });

Publishing the data from the view model to the view

We’re going to use data binding to create the view. As we now have the movies in the format we want, we’ll only have to create a public property which the view can bind against. For this we’ll add a new property to the view model:

public List<MovieCategory> Items { get; set; }

The property can be initialized after we have grouped the movies:

Items = moviesByCategories.ToList();

And that’s it. We now have the view model ready and it’s quite simple. The data is created inside the constructor and the view model only has one public property. The view can bind its GridView against this property in order to access the movies.

It’s important to notice that the view model’s public property contains grouped data. We want to display the data in a grouped format, where each movie category is displayed separately. This is why the property contains a list of data where every item has a title and a collection of child items.

The whole view model

Here's how our MoviesPageViewModel.cs ended up looking:

using System.Collections.Generic;
using System.Linq;

namespace WinRT_MVVM_GridView_SemanticZoom
{
    public class MoviesPageViewModel
    {
        public List<MovieCategory> Items { get; set; }

        public MoviesPageViewModel()
        {
            var movies = new List<Movie>
                             {
                                 new Movie {Title = "The Ewok Adventure", Category = "Adventure", Subtitle = "The Towani family civilian shuttlecraft crashes on the forest moon of Endor.", Image = "http://cf2.imgobject.com/t/p/w500/y6HdTlqgcZ6EdsKR1uP03WgBe0C.jpg"},
                                 new Movie {Title = "The In-Laws", Category = "Adventure", Subtitle = "In preparation for his daughter's wedding, dentist Sheldon ", Image = "http://cf2.imgobject.com/t/p/w500/9FlFW9zhuoOpS8frAFR9cCnJ6Sg.jpg"},
                                 new Movie {Title = "The Man Called Flintstone", Category = "Adventure", Subtitle = "In this feature-length film based on the Flintstones TV show", Image = "http://cf2.imgobject.com/t/p/w500/6qyVUkbDBuBOUVVplIDGaQf6jZL.jpg"},

                                 new Movie {Title = "Super Fuzz", Category = "Comedy", Subtitle = "Dave Speed is no ordinary Miami cop--he is an irradiated Miami cop ", Image = "http://cf2.imgobject.com/t/p/w500/bueVXkpCDPX0TlsWd3Uk7QKO3kD.jpg"},
                                 new Movie {Title = "The Knock Out Cop", Category = "Comedy", Subtitle = "This cop doesn't carry a gun - his fist is loaded!", Image = "http://cf2.imgobject.com/t/p/w500/mzlw8rHGUSDobS1MJgz8jXXPM06.jpg"},
                                 new Movie {Title = "Best Worst Movie", Category = "Comedy", Subtitle = "A look at the making of the film Troll 2 (1990) ", Image = "http://cf2.imgobject.com/t/p/w500/5LjbAjkPBUOD9N2QFPSuTyhomx4.jpg"},

                                 new Movie {Title = "The Last Unicorn", Category = "Fantasy", Subtitle = "A brave unicorn and a magician fight an evil king", Image = "http://cf2.imgobject.com/t/p/w500/iO6P5vV1TMwSuisZDtNBDNpOxwR.jpg"},
                                 new Movie {Title = "Blithe Spirit", Category = "Fantasy", Subtitle = "An English mystery novelist invites a medium", Image = "http://cf2.imgobject.com/t/p/w500/gwu4c10lpgHUrMqr9CBNq2FYTpN.jpg"},
                                 new Movie {Title = "Here Comes Mr. Jordan", Category = "Fantasy", Subtitle = "Boxer Joe Pendleton, flying to his next fight", Image = "http://cf2.imgobject.com/t/p/w500/9cnWl7inQVX6wznjYNmQmJXVD6J.jpg"},
                             };

            var moviesByCategories = movies.GroupBy(x => x.Category)
                .Select(x => new MovieCategory { Title = x.Key, Items = x.ToList() });

            Items = moviesByCategories.ToList();
        }
    }

    public class Movie
    {
        public string Title { get; set; }
        public string Subtitle { get; set; }
        public string Image { get; set; }
        public string Category { get; set; }
    }

    public class MovieCategory
    {
        public string Title { get; set; }
        public List<Movie> Items { get; set; }
    }
}

3. Creating the view

imageNow that the view model is ready, we can focus on the view. Let’s start by adding a new page using the “Grouped Items Page” template. We can name the page “Movies.xaml”.  This page template comes with a GridView configured to show grouped data, so it’s an ideal place to start.

Configuring application’s start-up page

After adding the page, we must modify the app.xaml.cs so that the new page will be used as the first page of the application. To do this we can modify the OnLaunched-method:

protected override void OnLaunched(LaunchActivatedEventArgs args) 
{  
    &hellip;     
    if (!rootFrame.Navigate(typeof(Movies)))
    &hellip;
}

Creating the connection between the page and the view model

We now have the page (the view) and the view model. Next step is to create an instance of the view model and set it as the data context of the page. This can be done in the page’s constructor:

public Movies()
        {
            this.InitializeComponent();

            var viewModel = new MoviesPageViewModel();
            this.DataContext = viewModel;
        }

Binding the GridView to a correct property

As you may recall, we named the view model’s public property as “Items”. By default the grouped items page is bound to a property called “Groups”. To change this we can modify the CollectionViewSource declared in the beginning of the Movies.xaml:

<CollectionViewSource
            x:Name="groupedItemsViewSource"
            Source="{Binding Items}"
            IsSourceGrouped="true"
            ItemsPath="Items"/>

Running the application the first time

Now that we have the view and the view model ready, we can run the application the first time:

image

And it actually looks pretty good! You may wonder how the view can display the movie poster, name and the description even though we haven’t created any data bindings between the items and the movies. The reason this works is because the default “Grouped Items Page” template defines the ItemTemplate like this:

image

The “Standard250x250ItemTemplate” is defined inside the CommonStandardStyles.xaml:

<DataTemplate x:Key="Standard250x250ItemTemplate">
        <Grid HorizontalAlignment="Left" Width="250" Height="250">
            <Border Background="{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}">
                <Image Source="{Binding Image}" Stretch="UniformToFill"/>
            </Border>
            <StackPanel VerticalAlignment="Bottom" Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}">
                <TextBlock Text="{Binding Title}" Foreground="{StaticResource ListViewItemOverlayForegroundThemeBrush}" Style="{StaticResource TitleTextStyle}" Height="60" Margin="15,0,15,0"/>
                <TextBlock Text="{Binding Subtitle}" Foreground="{StaticResource ListViewItemOverlaySecondaryForegroundThemeBrush}" Style="{StaticResource CaptionTextStyle}" TextWrapping="NoWrap" Margin="15,0,15,10"/>
            </StackPanel>
        </Grid>
    </DataTemplate>

Here’s our Movie-class again:

public class Movie
    {
        public string Title { get; set; }
        public string Subtitle { get; set; }
        public string Image { get; set; }
        public string Category { get; set; }
    }

If you compare the item template against the movie-class, you can see that the template’s binding match the class’ properties.

Now we just need the SemanticZoom.

4. Adding the SemanticZoom –control

Semantic zoom is an UI concept defined by Microsoft like the following:

Semantic Zoom is a touch-optimized technique used by Metro style apps in Windows 8 Consumer Preview for presenting and navigating large sets of related data or content within a single view (such as a photo album, app list, or address book).

SemanticZoom then is a XAML-control which can be used to add the semantic zoom concept into a WinRT application. The SemanticZoom –control requires that we have two representations of the same data: The ZoomedInView and the ZoomedOutView. In our case the ZoomednView is already done because we can use the working GridView. But we need an another List which displays the data when the user zooms out using the Ctrl+Mousewheel. The SemanticZoom only supports the GridView and the ListView controls.

Replacing the ScrollViewer with SemanticZoom

The first and most important thing to do is to completely remove the ScrollViewer named “itemGridScrollViewer” from the Movies.xaml. The SemanticZoom control won’t work correctly if it’s inside the ScrollViewer.

Many people have problems getting the ZoomedOutView and ZoomedInView “in sync”. The usual problem is that when the item in ZoomedOutView is clicked, the ZoomedInView doesn’t scroll to the correct position. The reason for this problem usually is that the SemanticZoom –control is inside a ScrollViewer.

We can then replace the ScrollViewer with a SemanticZoom. Note that the Margin and Grid.Row properties should be copied from the ScrollViewer to the SemanticZoom:

<SemanticZoom x:Name="Zoom" Grid.Row="1" Margin="0,-3,0,0">
     <SemanticZoom.ZoomedInView>     
     </SemanticZoom.ZoomedInView>     
     <SemanticZoom.ZoomedOutView>     
     </SemanticZoom.ZoomedOutView> 
</SemanticZoom>

Creating the ZoomedInView

Creating the ZoomedInView is easy because we’ll already done that. We just need to move the “itemGridView” inside the ZoomedInView-section.

Creating the ZoomedOutView

We don’t have the ZoomedOutView ready but the following GridView can do the trick:

<GridView VerticalAlignment="Center" Margin="200 -200 0 0"> 
    <GridView.ItemTemplate> 
        <DataTemplate> 
            <StackPanel> 
                <TextBlock HorizontalAlignment="Center" 
                           Text="{Binding Group.Title}" 
                           Style="{StaticResource SubheaderTextStyle}" 
                           /> 
            </StackPanel> 
        </DataTemplate> 
    </GridView.ItemTemplate>

    <GridView.ItemContainerStyle> 
        <Style TargetType="GridViewItem"> 
            <Setter Property="Margin" Value="4" /> 
            <Setter Property="Padding" Value="5" /> 
            <Setter Property="HorizontalContentAlignment" Value="Left" /> 
            <Setter Property="VerticalContentAlignment" Value="Center" /> 
        </Style> 
    </GridView.ItemContainerStyle>

    <GridView.ItemsPanel> 
        <ItemsPanelTemplate> 
            <WrapGrid ItemWidth="400" ItemHeight="70" 
                      Orientation="Horizontal" VerticalChildrenAlignment="Center" MaximumRowsOrColumns="3"></WrapGrid> 
        </ItemsPanelTemplate> 
    </GridView.ItemsPanel> 
</GridView>
The most important things to notice:
  • The GridView’s ItemsSource isn’t set in the XAML
  • The TextBox’s data binding is set correctly to “Group.Title”. In this case the Title is bound against the MovieCategory-instances Title-property

The whole SemanticZoom XAML

Overall, this is how the SemanticZoom -control is defined in the XAML:

<SemanticZoom x:Name="Zoom" Grid.Row="1" Margin="0,-3,0,0"> 
    <SemanticZoom.ZoomedInView> 
        <GridView 
        x:Name="itemGridView" 
        AutomationProperties.AutomationId="ItemGridView" 
        AutomationProperties.Name="Grouped Items" 
        Margin="116,0,40,46" 
        ItemsSource="{Binding Source={StaticResource groupedItemsViewSource}}" 
        ItemTemplate="{StaticResource Standard250x250ItemTemplate}">

            <GridView.ItemsPanel> 
                <ItemsPanelTemplate> 
                    <VirtualizingStackPanel Orientation="Horizontal"/> 
                </ItemsPanelTemplate> 
            </GridView.ItemsPanel> 
            <GridView.GroupStyle> 
                <GroupStyle> 
                    <GroupStyle.HeaderTemplate> 
                        <DataTemplate> 
                            <Grid Margin="1,0,0,6"> 
                                <Button 
                                AutomationProperties.Name="Group Title" 
                                Content="{Binding Title}" 
                                Style="{StaticResource TextButtonStyle}"/> 
                            </Grid> 
                        </DataTemplate> 
                    </GroupStyle.HeaderTemplate> 
                    <GroupStyle.Panel> 
                        <ItemsPanelTemplate> 
                            <VariableSizedWrapGrid Orientation="Vertical" Margin="0,0,80,0"/> 
                        </ItemsPanelTemplate> 
                    </GroupStyle.Panel> 
                </GroupStyle> 
            </GridView.GroupStyle> 
        </GridView> 
    </SemanticZoom.ZoomedInView> 
    <SemanticZoom.ZoomedOutView> 
        <GridView VerticalAlignment="Center" Margin="200 -200 0 0"> 
            <GridView.ItemTemplate> 
                <DataTemplate> 
                    <StackPanel> 
                        <TextBlock HorizontalAlignment="Center" 
                                   Text="{Binding Group.Title}" 
                                   Style="{StaticResource SubheaderTextStyle}" 
                                   /> 
                    </StackPanel> 
                </DataTemplate> 
            </GridView.ItemTemplate>

            <GridView.ItemContainerStyle> 
                <Style TargetType="GridViewItem"> 
                    <Setter Property="Margin" Value="4" /> 
                    <Setter Property="Padding" Value="5" /> 
                    <Setter Property="HorizontalContentAlignment" Value="Left" /> 
                    <Setter Property="VerticalContentAlignment" Value="Center" /> 
                </Style> 
            </GridView.ItemContainerStyle>

            <GridView.ItemsPanel> 
                <ItemsPanelTemplate> 
                    <WrapGrid ItemWidth="400" ItemHeight="70" 
                              Orientation="Horizontal" VerticalChildrenAlignment="Center" MaximumRowsOrColumns="3"></WrapGrid> 
                </ItemsPanelTemplate> 
            </GridView.ItemsPanel> 
        </GridView> 
    </SemanticZoom.ZoomedOutView> 
</SemanticZoom>

 

Configuring the ZoomedOutView’s ItemsSource:

The last step before running the app is to modify the Movies.xaml.cs. In this file we must define how the ListView inside the ZoomedOutView gets its data. We can add the required code to OnNavigatedTo-method:

protected override void OnNavigatedTo(NavigationEventArgs e)

        {
            var collectionGroups = groupedItemsViewSource.View.CollectionGroups;
            ((ListViewBase)this.Zoom.ZoomedOutView).ItemsSource = collectionGroups;
        }

Running the complete application

Our application is ready. The front page looks identical to the previous version:

image

But if you use the Ctrl+Mousewheel, you can see the semantic zoom in action:

image

The source code

This tutorial's source code is available from the GitHub.

Links