Blog

posts

Windows 8 Store Search Problems

0 Comments
By Fons Sonnemans, 11-4-2013

Before I start I want to make clear that I love Windows 8 and I love to develop for it. But I really have a problem with the implementation of the Search Charm of the Store app. The order in which apps are shown as a result of a search makes no sense. Let me demonstrate this with an example.

Search for sudoku

As you maybe know I have developed a very popular Sudoku game called Sudoku Free. It has almost 440.000 downloads and 2150 ratings with an average of 4 stars. The reviews are very positive. The game is usually in the ranked around place 25 in the Free games section of the US Store. It is the only Sudoku game in the list.  60% of the downloads are from the United States and it has 763 reviews. It is played a lot and has an average app usage of about 25 minutes per day. Quality reports show a very low crash rate between 0 and 0.01%. All statistics indicate this app is the most popular and downloaded Sudoku game in the store.

On my device a search for 'Sudoku' results in 127 apps. But you don't see my Sudoku Free app. My app has position 55 in the list. You have to scroll to the 10th column to see my app. I'm using a tablet with a 1366x768 resolution. Most of the games in front of mine have no reviews and just a few ratings (about 10 or even none). There are even 2 apps in the top 20 which have the default tile, see screenshot below.

I find this very strange. I think search should show popular apps first, and I’m sure tons of other developers and users will agree.

Search Recommendations

If I search on 'Sudoku Free' I get a recommendation for the 'Sudoku (Free)' app. This app has only 3 ratings and no reviews. It is not in the top 100 free apps so it will probably not have many downloads.

I find this also very strange. I think search should show the most popular apps in the recommendations.

Conclusion

I know Microsoft has had similar problems with the search in the Windows Phone store, as you can read here and here. I think they fixed this problem by now. If I search for Sudoku I get the most popular apps first.

I hope someone at Microsoft is listening, because they are only shooting themself in the foot by pissing off developers and users. Maybe you can leave a comment if you agree, hopefully that will persuade Microsoft to fix it (soon).

Cheers,

Fons

 

Tags: Windows 8, Store

Black Belt XAML Data Binding TechDays Video

0 Comments
By Fons Sonnemans, 20-3-2013

De video van mijn 'Black Belt XAML Data Binding' TechDays NL sessie staat online op Channel 9. Vorige week heb ik de presentatie en demo reeds gepubliceerd. Bekijk ook de overige videos van TechDays 2013 the Netherlands of TechDays Belgium.

Cheers,

Fons

TechDays 2013 Presentaties en Demo's

0 Comments
By Fons Sonnemans, 10-3-2013

Op 7 en 8 maart heb ik weer bij het TechDays 2013 event van Microsoft een aantal sessies gepresenteerd. Op veler vezoek publiceer ik hier de presentaties en demo's van mijn sessies. Op Channel 9 zal binnenkort de video van mijn Black Beld XAML DataBinding presentatie getoond worden.

Black Belt XAML DataBinding presentatie
Black Belt XAML DataBinding demo
Designing Windows Store apps using XAML and Blend presentatie
Designing Windows Store apps using XAML and Blend demo

Doordat Tom Verhoeff ziek was heb ik zijn 'Monetizing Windows (Phone/8) apps' sessie overgenomen.
Monetizing Windows (Phone - 8) apps presentatie

Cheers,

Fons

Windows 8 XAML Tips - Master Pages

5 Comments
By Fons Sonnemans, 26-2-2013

Besides Windows Phone and Windows 8 apps I develop Web sites using ASP.NET. In ASP.NET Web Forms you can define a MasterPage. This technique is called Layout pages in ASP.NET MVC. A simular technique can also be used in your Windows Store apps using XAML. In this blog I will explain how.

Master Page and Content Pages

In the example above all pages have a red header and a blue footer.

Master pages allow you to create a consistent layout for the pages in your application. A single master page defines the look and feel and standard behavior that you want for all of the pages (or a group of pages) in your application. You can then create individual content pages that contain the content you want to display. When users request the content pages, they merge with the master page to produce output that combines the layout of the master page with the content from the content page. 

XAML MasterPage

I have created a Windows Store app using the 'Grid App (XAML)' template in Visual Studio. With this template you get a project in which 3 pages are predefined. These pages will be used as the content pages in my demo project.

Next I have added a Blank Page to my project named MasterPage.xaml. In this page I have added my header, footer and a Frame control.

<Page x:Class="MasterPageDemo.MasterPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:MasterPageDemo"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">

    <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">

        <!-- Header -->
        <Rectangle Fill="#FF0000"
                   Height="130"
                   VerticalAlignment="Top" />

        <!-- Footer -->
        <Rectangle Fill="#00B0F0"
                   Height="30"
                   VerticalAlignment="Bottom" />

        <TextBlock Text="© 2013 Reflection IT"
                   VerticalAlignment="Bottom"
                   HorizontalAlignment="Left"
                   Foreground="White"
                   Margin="120,5"
                   FontSize="16" />

        <!-- Frame -->
        <Frame x:Name="frameBody" />
    </Grid>
</Page>

In the codebehind (MasterPage.xaml.cs) I have added a public property named ContentFrame which returns the Frame control.

namespace MasterPageDemo {
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MasterPage : Page {
        public MasterPage() {
            this.InitializeComponent();
        }

        /// <summary>
        /// Invoked when this page is about to be displayed in a Frame.
        /// </summary>
        /// <param name="e">Event data that describes how this page was reached.  The Parameter
        /// property is typically used to configure the page.</param>
        protected override void OnNavigatedTo(NavigationEventArgs e) {
        }

        public Frame ContentFrame {
            get { return this.frameBody; }
        }
    }
}

Next I have modified the OnLaunched() method of the App.xaml.cs. In this method I have initialized the rootFrame with a MasterPage object instead of a Frame (line 9 and 18). Instead of using the Frame I now use the ContentFrame of the MasterPage for navigation (line 23, 39 and 45).

/// <summary>
/// Invoked when the application is launched normally by the end user.  Other entry points
/// will be used when the application is launched to open a specific file, to display
/// search results, and so forth.
/// </summary>
/// <param name="args">Details about the launch request and process.</param>
protected override async void OnLaunched(LaunchActivatedEventArgs args) {
    //Frame rootFrame = Window.Current.Content as Frame;
    var rootFrame = Window.Current.Content as MasterPage;

    // Do not repeat app initialization when the Window already has content,
    // just ensure that the window is active

    if (rootFrame == null) {
        // Create a Frame to act as the navigation context and navigate to the first page
                
        //rootFrame = new Frame();
        rootFrame = new MasterPage();
                
        //Associate the frame with a SuspensionManager key                                
                
        //SuspensionManager.RegisterFrame(rootFrame, "AppFrame");
        SuspensionManager.RegisterFrame(rootFrame.ContentFrame, "AppFrame");

        if (args.PreviousExecutionState == ApplicationExecutionState.Terminated) {
            // Restore the saved session state only when appropriate
            try {
                await SuspensionManager.RestoreAsync();
            } catch (SuspensionManagerException) {
                //Something went wrong restoring state.
                //Assume there is no state and continue
            }
        }

        // Place the frame in the current Window
        Window.Current.Content = rootFrame;
    }
    //if (rootFrame.Content == null)
    if (rootFrame.ContentFrame.Content == null) {
        // When the navigation stack isn't restored navigate to the first page,
        // configuring the new page by passing required information as a navigation
        // parameter
                
        //if (!rootFrame.Navigate(typeof(GroupedItemsPage), "AllGroups")) {
        if (!rootFrame.ContentFrame.Navigate(typeof(GroupedItemsPage), "AllGroups")) {
            throw new Exception("Failed to create initial page");
        }
    }
    // Ensure the current window is active
    Window.Current.Activate();
}

Finally I have removed the Background property from the LayoutRootStyle in the StandardStyles.xaml. This will make it transparent which allows you to view the content of the MasterPage.

<Style x:Key="LayoutRootStyle"
        TargetType="Panel">
    <!--<Setter Property="Background"
            Value="{StaticResource ApplicationPageBackgroundThemeBrush}" />-->
    <Setter Property="ChildrenTransitions">
        <Setter.Value>
            <TransitionCollection>
                <EntranceThemeTransition />
            </TransitionCollection>
        </Setter.Value>
    </Setter>
</Style>

I did some minor appearance changes like setting the RequestedTheme property of the Application (App.xaml) to "Light". I also changed the Foreground property of the PageHeaderTextStyle (StandardStyles.xaml) to "White".

Result

The result is an application in which all 3 content pages have the same header and footer.

Closure and download

I hope you like my solution. You can download my code below.

Cheers,

Fons

Windows 8 XAML Tips - App Background Image

11 Comments
By Fons Sonnemans, 28-12-2012

Adding a background image to a Windows 8 Store application in XAML can be done in a few different ways. If your app supports navigation between pages you don't want to reload the background image when you navigate to a different page. This doesn't look right on slower ARM devices like the Surface, you will see the image flicker. In this blog I will demonstrate how to set the background image in 3 different ways. The last one without the image flickering.

Setup Demo App

I created a new project in Visual Studio using the 'Grid App (XAML)' template. This creates a project in which 3 pages are defined. The user can navigate between those pages.

Then I added a background image to the Assets folder and named it 'Background.jpg'. I found this image by using this Bing Image search in which I searched for 'Background Black'. Instead of using the default black background I want to use my 'Background.jpg' for all my pages.

Solution 1 - Set Grid.Background property in XAML

The root control of all pages (GroupDetailPage.xaml, GroupDetailPage.xaml and ItemDetailPage.xaml) is a Grid control. You can set the Background property of this Grid control to an ImageBrush. The ImageSource is set to the '/Assets/Background.jpg' image. Make sure you do this for all pages in your project.

<Grid Style="{StaticResource LayoutRootStyle}">
    <Grid.RowDefinitions>
        <RowDefinition Height="140"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
        
    <Grid.Background>
        <ImageBrush ImageSource="/Assets/Background.jpg"
                    Stretch="UniformToFill" />
    </Grid.Background>

Solution 2 - Set Grid.Background property in the LayoutRootStyle

Instead of setting the Grid.Background property in each page you can also set it once in the 'LayoutRootStyle' style. The All my pages have this style set on the root Grid control. This style is defined in the 'StandardStyles.xaml' which can be found in the 'Common' folder. The 'Background' property of the style is set to the 'ApplicationPageBackgroundThemeBrush' static resource which makes it black. You have to replace it with the ImageBrush.

<Style x:Key="LayoutRootStyle"
        TargetType="Panel">
    <!--<Setter Property="Background"
            Value="{StaticResource ApplicationPageBackgroundThemeBrush}" />-->

    <Setter Property="Background">
        <Setter.Value>
            <ImageBrush ImageSource="/Assets/Background.jpg"
                        Stretch="UniformToFill" />
        </Setter.Value>
    </Setter>

Don't forget to remove the Grid.Background from all pages.

    <Grid Style="{StaticResource LayoutRootStyle}">
        <Grid.RowDefinitions>
            <RowDefinition Height="140"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        
        <!--<Grid.Background>
            <ImageBrush ImageSource="/Assets/Background.jpg"
                        Stretch="UniformToFill" />
        </Grid.Background>-->

Solution 3 - Set Frame.Background property in the App.xaml.cs

Each time you navigate to a new page in Solution 1 and 2 the background image is loaded and shown. On slow devices you will see the image flicker. To solve this problem you can use my preferred solution.

Navigation is implemented using a Frame control which is set as the root of the application in the App.xaml.cs file. You can set the Background of this Frame to an ImageBrush (lines 13 - 18).

protected override async void OnLaunched(LaunchActivatedEventArgs args)
{
    Frame rootFrame = Window.Current.Content as Frame;

    // Do not repeat app initialization when the Window already has content,
    // just ensure that the window is active
            
    if (rootFrame == null)
    {
        // Create a Frame to act as the navigation context and navigate to the first page
        rootFrame = new Frame();

        // Set the application background Image
        rootFrame.Background = new ImageBrush {
            Stretch = Windows.UI.Xaml.Media.Stretch.UniformToFill,
            ImageSource =
                new BitmapImage { UriSource = new Uri("ms-appx:///Assets/Background.jpg") }
        };

        //Associate the frame with a SuspensionManager key                                
        SuspensionManager.RegisterFrame(rootFrame, "AppFrame");

Don't forget to remove the Grid.Background from the style.

<Style x:Key="LayoutRootStyle"
        TargetType="Panel">
    <!--<Setter Property="Background"
            Value="{StaticResource ApplicationPageBackgroundThemeBrush}" />-->

    <!--<Setter Property="Background">
        <Setter.Value>
            <ImageBrush ImageSource="/Assets/Background.jpg"
                        Stretch="UniformToFill" />
        </Setter.Value>
    </Setter>-->

The background image is now shown on each page and only loaded once on startup. This makes navigation fast en fluid.

Closure and download

I hope you like my solution. You can download my code below.

Cheers,

Fons

ShareMediaTask on Windows Phone 8

9 Comments
By Fons Sonnemans, 3-12-2012

The new Windows Phone 8 SDK has a new ShareMediaTask class which can be used to share your pictures from code. I wanted to use this class to share a WriteableBitmap. You can use it to share the picture using NFC (Tap+Send), apps or to social media like Twitter or Facebook. In this post I will explain how to implement this.

Demo App

I have created a demo app in which a Rectangle and a Ellipse are drawn inside the default ContentPanel grid. When you tap the Share button the ContentPanel is used as the input for a WriteableBitmap. This WriteableBitmap is than saved so it can be shared using the ShareMediaTask().

The XAML

The XAML is really basic.

<Grid x:Name="LayoutRoot"
      Background="Transparent">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition />
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>

    <StackPanel x:Name="TitlePanel"
                Grid.Row="0"
                Margin="12,17,0,28">
        <TextBlock Text="SHARE MEDIA DEMO"
                    Style="{StaticResource PhoneTextNormalStyle}"
                    Margin="12,0" />
        <TextBlock Text="mainpage"
                    Margin="9,-7,0,0"
                    Style="{StaticResource PhoneTextTitle1Style}" />
    </StackPanel>

    <!--ContentPanel - place additional content here-->
    <Grid x:Name="ContentPanel"
          Grid.Row="1"
          Margin="12,0,12,0"
          Grid.RowSpan="2">
        <Rectangle Fill="Blue"
                    HorizontalAlignment="Left"
                    Height="253"
                    Margin="101,113,0,0"
                    Stroke="Black"
                    VerticalAlignment="Top"
                    Width="192" />
        <Ellipse Fill="#FF42B51B"
                  HorizontalAlignment="Left"
                  Height="236"
                  Margin="175,237,0,0"
                  Stroke="Black"
                  VerticalAlignment="Top"
                  Width="226" />
    </Grid>
        
    <Button Content="Share"
            VerticalAlignment="Top"
            Grid.Row="2"
            Click="ButtonShare_Click" />

</Grid>

The code

In the ButtonShare_Click() method a screenshot of the ContentPanel is created using a WriteableBitmap. This WriteableBitmap is then saved to the MediaLibrary. The Path of the ShareMediaTask object is set using the GetPath() extension method on the picture class. The Microsoft.Xna.Framework.Media.PhoneExtensions namespace is brought into scope using a 'using' statement in the header of the class. Finally I called the Show() method on the ShareMediaTask object.

using Microsoft.Xna.Framework.Media.PhoneExtensions;

...

private void ButtonShare_Click(object sender, RoutedEventArgs e) {
    var bmp = new WriteableBitmap(this.ContentPanel, null);
    var width = (int)bmp.PixelWidth;
    var height = (int)bmp.PixelHeight;
    using (var ms = new MemoryStream(width * height * 4)) {
        bmp.SaveJpeg(ms, width, height, 0, 100);
        ms.Seek(0, SeekOrigin.Begin);
        var lib = new MediaLibrary();
        var picture = lib.SavePicture(string.Format("test.jpg"), ms);

        var task = new ShareMediaTask();

        task.FilePath = picture.GetPath();

        task.Show();
    }
}

I have also tried to save the WriteableBitmap to the IsolatedStorage. But the ShareMediaTask couldn't cope with that (bummer). I hope Microsoft will fix that in a future version of the SDK.

I really want to thank Clemens Schotte for solving this part of the puzzle.

Closure and download

I hope you like my solution. You can download my code here.

Cheers,

Fons

Windows 8 XAML Tips - Rotated GridViewItems

0 Comments
By Fons Sonnemans, 1-11-2012

I'm working on a new Windows 8 Store app and in this app I needed a GridView in which the GridView items are rotated a few degrees randomly. In this blog I will explain how I implemented this using Styling and Templating and a few lines of C# code.

The result of this all will look like this.

Create project and GridViewItem style

To demonstrate my solution I created a new project in Blend for Visual Studio using the 'Grid App (XAML)' project template. This template creates a project in which the start page (GroupedItemsPage.xaml) shows a GridView with some sample data. In this page I selected the itemGridView control. From the Object menu I selected the Edit Additional Styles -> Edit Generated Item Container -> Edit a Copy... option. This will create a GridViewItem style including a copy of the Template.

In the Create Style Resource dialog I didn't change the 'Define in' radio buttons. It must be set to 'This document' because I will implement an event in the GridViewItem Template which is not allowed when you select another option.

The ItemContainerStyle property of the GridView is set to the new GridViewItem style.

Rotate randomly

Blend shows me the Template of each GridViewItem. I selected the OuterContainer (Border) in the Objects and Timeline window. In the Property window I changed the RenderTransform rotation angle to 5. This created a CompositeTransform item inside the RenderTransform property of the border.

Then I saved the project and edited it in Visual Studio. I do this because I want to write some code which is much easier in VS2012 because of its better IntelliSense support.

In Visual Studio I added the Loaded event to the OuterContainer (Border). In the OuterContainer_Loaded_1 event handler I can rotate the angle randomly using a few lines of code. I have tried other solutions like Binding and TemplateBinding but that didn't work.

The code of the OuterContainer_Loaded_1 event handler is relatively simple. It just generated a random angle value from -6 to + 6. and sets it on the CompositeTransform.

private static Random random = new Random((int)DateTime.Now.Ticks);

private void OuterContainer_Loaded_1(object sender, RoutedEventArgs e) {
    var border = sender as Border;
    var transform = border.RenderTransform as CompositeTransform;
    transform.Rotation = random.Next(12) - 6;
}

The Catch

As is often the case, there is a limitation with this solution. If the GridView has a SelectionMode set other than None you cannot use the 'Swipe to Select' touch gesture. Using the right mouse button to select an item works perfectly. I hope I can find a solution for this problem.

Closure and download

I hope you like my solution. You can download my code here.

Cheers,

Fons

Windows 8 Javascript Tips - TypeScript support

0 Comments
By Fons Sonnemans, 9-10-2012

Microsoft announced TypeScript last week. TypeScript is a superset of JavaScript that combines type checking and static analysis, explicit interfaces, and best practices into a single language and compiler. It is an open source programming language developed by Microsoft. If you haven't investigated yet make sure you do. The related links below might help you.

What Microsoft didn't do (yet) is add TypeScript support to JavaScript Windows Store apps. In this blog I will explain how to add this support to Visual Studio so you can use TypeScript in all your Windows 8 apps.

Step 1 - Install TypeScript

You can install TypeScript for Visual Studio 2012 using an MSI setup which you can download here. Make sure you first close all Visual Studio instances to avoid installation problems.

Step 2 - Install Web Essentials 2012

The latest version of Web Essentials 2012 extension adds support for TypeScript preview and compilation. When you save a TypeScript (.ts) file it will be compiled into a JavaScript (.js) file. So make sure you install it using the Visual Studio menu Tools, Extensions and Updates. If you already installed it make sure you update it to the latest version.

Step 3 - Add TypeScript ItemTemplate

If you try to add a TypeScript item to the 'js' folder you will see that there is no TypeScript template available. The setup from Microsoft did not (yet) add this template to Visual Studio. My solution for this problem was quite easy. I copied the following file 'C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\Extensions\msfz1qy5.oca\~IC\IT\CSharp\1033\f.zip' to my '\My Documents\Visual Studio 2012\Templates\ItemTemplates\JavaScript' folder. I also renamed the file to TypeScript.zip. Next I opened the zip file and edited the 'f.vstemplate' file. In the file I changed the ProjectType element from CSharp to JavaScript.

To make life easy you can download my TypeScript.zip file so you don't have to repeat my steps. Make sure you place it in the right ItemTemplate folder.

Step 4 - Create a JavaScript Project

This step is really easy. Just create a Project in Visual studio using one of the JavaScript Windows Store templates.

Step 5 - Add TypeScript item

When you add a new Item to the 'js' folder you can now select the TypeScript template.

The preview feature of Web Essentials 2012 shows you how your TypeScript file is compiled into JavaScript.

You should also change the Package Action of the Demo.ts file to 'None'. Otherwise the file will also be included in the APPX file which will make it larger. Only the Demo.js file is required.

TypeScript options

Web Essentials 2012 also supports some options which you might want to change. I only want to compile my TypeScripts on Build and not on every Save. You can also turn preview off but that didn't work on my computer. I hope they will fix it.

Closure

Now it is up to you to write your own TypeScript files. I love the ability to write clean OO code using: classes, interfaces, modules (namespaces), type annotations, compile time type checking and arrow functions (similar to Lambda Expression in C#). I hope this blog will help you write great Windows Store apps.

Cheers,

Fons

Related Links

Windows 8 XAML Tips - Detect App Deactivation

1 Comments
By Fons Sonnemans, 17-9-2012

Detecting when an Windows 8 Store app is deactivated isn't as easy as you would expect. In the Windows Phone project templates you get the Application_Launching(), Application_Activated(), Application_Deactivated() and Application_Closing() methods in the App.xaml.cs. But those methods don't exists in the Windows Store project templates. There is an OnSuspending() method but you can't use it to detect deactivation. You use it to store your data when suspending the app. This will problably go off a few seconds later than deactivation.

Solution

To solve this problem I have used the Activated event of the CoreWindow class. This event can also be used to detect deactivation inspite of it's name. The WindowActivatedEventArgs has a WindowActivationState property of the type CoreWindowActivationState. This enum holds the values CodeActivated, Deactivated and PointerActivated. This event also is fired when the app is closed.

Example

In the following example I have created an app in which a Timer updates a counter every second. In the constructor I have subscribed the page on the Activated event of the CoreWindow object using the GetForCurrentThread() method. The eventhandler stops the timer when the app is deactivead or closed. It restarts the timer when the app is activated again. The counter value is written to the LocalSettings on deactivation and close of the app. In the constructor of the MainPage the value is read from the LocalSettings so the app continues with the last used counter value.

public sealed partial class MainPage : Page {

    private DispatcherTimer _timer = new DispatcherTimer();
    private int _counter;
    private const string Key = "Counter";

    public MainPage() {
        this.InitializeComponent();

        this._timer.Interval = TimeSpan.FromSeconds(1);
        this._timer.Tick += _timer_Tick;

        object value;
        if (ApplicationData.Current.LocalSettings.Values.TryGetValue(Key, out value)) {
            _counter = (int)value;
            this.textBlockCounter.Text = _counter.ToString();
        }

        CoreWindow.GetForCurrentThread().Activated += MainPage_Activated;
    }

    void _timer_Tick(object sender, object e) {
        this._counter++;
        this.textBlockCounter.Text = _counter.ToString();
    }

    void MainPage_Activated(CoreWindow sender, WindowActivatedEventArgs args) {

        if (args.WindowActivationState == CoreWindowActivationState.Deactivated) {
            _timer.Stop();
            Windows.Storage.ApplicationData.Current.LocalSettings.Values[Key] = _counter;
        } else {
            _timer.Start();
        }
    }
}

The MainPage.xaml only contains a Grid and the TextBox which displays the counter value.

<Page
    x:Class="DetectAppDeactivation.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:DetectAppDeactivation"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <TextBlock x:Name="textBlockCounter"
                   Text="0"
                   FontSize="150"
                   VerticalAlignment="Center"
                   HorizontalAlignment="Center" />
    </Grid>
</Page>

Closure and download

I hope you like my solution. You can download my code here.

Cheers,

Fons

Windows 8 XAML Tips - Async Search Suggestions

3 Comments
By Fons Sonnemans, 22-8-2012

There are a lot of examples on the web how you can add Search Suggestions to the Search Charm of a Windows 8 Metro application. But all the examples I found only demonstrate how to implement a synchronous version. In this blog I will show you how you can do this also asynchronous. I use this technique to fill my Search Suggestion by calling a web service asynchronous. Something which is very common because your data will problably be stored in the Web/Cloud.

Implement Search Charm

To demonstrate the solution I created a new project in Visual Studio 2012 using the 'Grid App (XAML)' project template. Next I added the 'Search Contract' item template to the project. This adds an SearchResultsPage to the project and adds the Search Declaration to the Package.appxmanifest.

In my App.cs file I added the InitSearch() method which I call from the OnLaunched() method. In this InitSearch() method I subscribe the app on SuggestionsRequested event of the current SearchPane view.

sealed partial class App : Application
{
    /// <summary>
    /// Initializes the singleton Application object.  This is the first line of authored code
    /// executed, and as such is the logical equivalent of main() or WinMain().
    /// </summary>
    public App()
    {
        this.InitializeComponent();
        this.Suspending += OnSuspending;
    }

    /// <summary>
    /// Invoked when the application is launched normally by the end user.  Other entry points
    /// will be used when the application is launched to open a specific file, to display
    /// search results, and so forth.
    /// </summary>
    /// <param name="args">Details about the launch request and process.</param>
    protected override async void OnLaunched(LaunchActivatedEventArgs args)
    {
        // Do not repeat app initialization when already running, just ensure that
        // the window is active
        if (args.PreviousExecutionState == ApplicationExecutionState.Running)
        {
            Window.Current.Activate();
            return;
        }

        // Create a Frame to act as the navigation context and associate it with
        // a SuspensionManager key
        var rootFrame = new Frame();
        SuspensionManager.RegisterFrame(rootFrame, "AppFrame");

        if (args.PreviousExecutionState == ApplicationExecutionState.Terminated)
        {
            // Restore the saved session state only when appropriate
            await SuspensionManager.RestoreAsync();
        }

        if (rootFrame.Content == null)
        {
            // When the navigation stack isn't restored navigate to the first page,
            // configuring the new page by passing required information as a navigation
            // parameter
            if (!rootFrame.Navigate(typeof(GroupedItemsPage), "AllGroups"))
            {
                throw new Exception("Failed to create initial page");
            }
        }

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

        InitSearch();
    }

    private void InitSearch() {
        var view = SearchPane.GetForCurrentView();
        view.SuggestionsRequested += view_SuggestionsRequested;

        //view.QueryChanged += view__QueryChanged;
        //view.QuerySubmitted += view_QuerySubmitted;
        //view.ResultSuggestionChosen += view_ResultSuggestionChosen;
        //view.VisibilityChanged += view_VisibilityChanged;
    }

    private void view_SuggestionsRequested(SearchPane sender, 
                                    SearchPaneSuggestionsRequestedEventArgs args) {
        try {
            var list = Enumerable.Range(1, 4).Select(n => args.QueryText + " " + n);
            args.Request.SearchSuggestionCollection.AppendQuerySuggestions(list);
        } catch (Exception ex) {
            Debug.WriteLine(ex.Message);
        }
    }

In this example the SuggestionsRequested() method creates 4 query suggestions which are the querytext with the suffix 1 to 4. This will look like this if you run the app and start a search for the word 'demo'.

Problem

To simulate an async network call I added an awaited call to the Task.Delay() method in my view_SuggestionsRequested() method (line 5). I also added the async keyword to make the compiler happy. It is required if you use the await keyword.

private async void view_SuggestionsRequested(SearchPane sender, 
                                             SearchPaneSuggestionsRequestedEventArgs args) {
    try {
        // Simulate an async network call
        await Task.Delay(100);
        var list = Enumerable.Range(1, 4).Select(n => args.QueryText + " " + n);

        args.Request.SearchSuggestionCollection.AppendQuerySuggestions(list);
    } catch (Exception ex) {
        Debug.WriteLine(ex.Message);
        // A method was called at an unexpected time. (Exception from HRESULT: 0x8000000E)
    }
}

If I run the app the Search Suggestions don't work anymore. The catch blocks writes this message of the exception to the output window: 'A method was called at an unexpected time. (Exception from HRESULT: 0x8000000E)'.

Solution

The solution is quite simple but badly documented in the MSDN documentation of the SearchPane.SuggestionsRequested event. It states in a note that if you want to respond to this event asynchronously, you must use SearchPaneSuggestionsRequestedEventArgs.Request.GetDeferral. So in my code I retrieved the Deferral (line 5). And I called the Complete() method on it after the list is appended to the SearchSuggestionCollection (line 14).

private async void view_SuggestionsRequested(SearchPane sender,
                                             SearchPaneSuggestionsRequestedEventArgs args) {
    try {
        // Get the deferral
        var deferral = args.Request.GetDeferral();

        // Simulate an async network call
        await Task.Delay(100);
        var list = Enumerable.Range(1, 4).Select(n => args.QueryText + " " + n);

        args.Request.SearchSuggestionCollection.AppendQuerySuggestions(list);

        // Mark the deferral as Complete
        deferral.Complete();
    } catch (Exception ex) {
        Debug.WriteLine(ex.Message);
    }
}

This fixed the problem and my app runs like a charm ;-)

Closure and download

I hope you like my solution. You can download my code here.

Cheers,

Fons

All postings/content on this blog are provided "AS IS" with no warranties, and confer no rights. All entries in this blog are my opinion and don't necessarily reflect the opinion of my employer or sponsors. The content on this site is licensed under a Creative Commons Attribution By license.