Blog

posts

Windows 8 XAML Tips - Inline data-driving Unit Testing

0 Comments
By Fons Sonnemans, 13-8-2013

While watching the Automated Testing of XAML-Based Windows Store Apps Build session on video I learned something new. There is a new way in Visual Studio 2013 to do data-driven unit testing using the DataRowAttribute. I couldn't find much documentation or samples so I will try to explain it in this blog post.

Calculator App

For this demo I created a 'silly' Calculator Windows Store application which uses C# and XAML. It has a public Calculator class which is used by the MainPage to add or subtract two values. This is the class which I want to unit test.

using System;

namespace UnitTestDemo {

    public class Calculator {

        public int Add(int a, int b) {
            return a + b;
        }

        public int Subtract(int a, int b) {
            return a - b;
        }
    }
}

The MainPage.xaml has two TextBox controls, two Buttons and a TextBlock to show the result in.

<Page x:Class="UnitTestDemo.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:UnitTestDemo"
      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}">
        <StackPanel VerticalAlignment="Center"
                    HorizontalAlignment="Center">
            <TextBox x:Name="textBoxA"
                     Text="3"
                     Header="A"
                     Margin="3"
                     Width="100" />
            <TextBox x:Name="textBoxB"
                     Text="2"
                     Header="B"
                     Margin="3"
                     Width="100" />
            <Button Content="Add"
                    Width="106"
                    Click="ButtonAdd_Click" />
            <Button Content="Subtract"
                    Width="106"
                    Click="ButtonSubtract_Click" />
            <TextBlock x:Name="textBlockResult"
                       Style="{StaticResource SubtitleTextBlockStyle}"
                       Width="100" />
        </StackPanel>
    </Grid>
</Page>

The MainPage.xaml.cs contains the click eventhandlers which use the Calculator class to execute the Add() and Subtract() methods. These methods are not Monkey proof, they don't contain any logic for checking valid integer input values.

using System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace UnitTestDemo {

    public sealed partial class MainPage : Page {

        private Calculator _calc = new Calculator();

        public MainPage() {
            this.InitializeComponent();
        }

        private void ButtonAdd_Click(object sender, RoutedEventArgs e) {
            int result = _calc.Add(int.Parse(textBoxA.Text), int.Parse(textBoxB.Text));
            textBlockResult.Text = result.ToString();
        }

        private void ButtonSubtract_Click(object sender, RoutedEventArgs e) {
            int result = _calc.Subtract(int.Parse(textBoxA.Text), int.Parse(textBoxB.Text));
            textBlockResult.Text = result.ToString();
        }
    }
}

If you run the app it will look like this.

UnitTest Project

Next I added an extra 'Unit Test Library' project to the Solution.

I added a Reference from this UnitTest project to my Windows Store app. This will allow me to create an instance of the Calculator class which I want to test.

I renamed the default generated UnitTest1.cs file containing the UnitTest1 class to CalculatorUnitTest.cs. This will prompt to rename the class name too.

DataRow

In the CalculatorUnitTest class I added two test methods: TestAdd() and TestSubtract(). Both methods are decorated with the TestMethodAttribute. These methods have 3 parameters: a, b and expected. These parameters are used to calculate the result (actual) and to assert whether actual is equal to expected. The methods have also two DataRowAttributes which will be used to execute the test method twice using the given values (test data) for the parameter values. This makes it possible to do inline data-driven unit testing. You don't have to create a database or csv file with the test data.

using System;
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
using UnitTestDemo;

namespace UnitTestLibrary1
{
    [TestClass]
    public class CalculatorUnitTest
    {
        [TestMethod]
        [DataRow(1, 2, 3)]
        [DataRow(4, 5, 9)]
        public void TestAdd(int a, int b, int expected)
        {
            Calculator c = new Calculator();
            int actual = c.Add(a, b);

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        [DataRow(4, 2, 2)]
        [DataRow(4, 1, 1, DisplayName = "Should fail for demo purposes")] 
        public void TestSubtract(int a, int b, int expected) {
            Calculator c = new Calculator();
            int actual = c.Subtract(a, b);

            Assert.AreEqual(expected, actual);
        }
    }
}

When you run all tests you will get a Failed Test report for the TestSubtract method. The expected value should have been set to 3.

Closure

I will use the new DataRow attribute in my Unit Tests a lot. It will help me to write them more easily. I imagine you will do that too.

Cheers,

Fons

Microsoft MVP

1 Comments
By Fons Sonnemans, 2-7-2013

Gisteren ben ik voor de 1ste keer uitgeroepen tot Microsoft Most Valuable Professional (MVP) op het gebied van Client Development. De titel Microsoft MVP wordt uitsluitend toegekend aan geselecteerde specialisten die zich actief inzetten voor een community rondom een Microsoft product. Microsoft beloont deze personen voor hun inzet en hulp aan de gemeenschap door middel van de titel MVP.

Microsoft MVP Logo

Tags: MVP

Windows 8 XAML Tips - Peeking AppBar

1 Comments
By Fons Sonnemans, 21-5-2013

I have many app is the Windows 8 store now. My Sudoku Free app is the most popular one. It has almost 500.000 downloads and it is played around 13.000 a day. Until recently I received many mails on how to use the app. My support email address is in the Settings panel. Most of these mails where request to add features to the game like 'can I validate a puzzle?', 'can I solve a puzzle?', 'can I get statistics?' or 'can I get a hint?'. Those features are all available in the AppBar of the game. My conclusion is that the average user is still not aware that the AppBar can exist.

To make my users aware of the AppBar I added a peek behavior to it. When you start a sudoku puzzle the AppBar peeks out for just a second, see video. After the implementation I got almost no more of those emails any more.

Solution

The implementation of a peeking AppBar is very easy. For my demo I used Blend for Visual Studio to generate a new project using the 'Grid App (XAML)' template. Next I add an AppBar to the ItemDetailPage page.

I named the AppBar 'MyBottomAppBar' and set the Background color to Green. I also added a Button to the first StackPanel of the AppBar. I have reset the Content property of the Button and selected the 'AddAppBarButtonStyle' Style. Before I could do that I had to uncomment this Style from the 'Common\StandardStyles.xaml' resource dictionary.

<common:LayoutAwarePage x:Name="pageRoot"
                        x:Class="PeekingAppBar.ItemDetailPage"
                        DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"
                        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                        xmlns:local="using:PeekingAppBar"
                        xmlns:data="using:PeekingAppBar.Data"
                        xmlns:common="using:PeekingAppBar.Common"
                        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                        mc:Ignorable="d">

    <Page.Resources>

        <!-- Collection of items displayed by this page -->
        <CollectionViewSource x:Name="itemsViewSource"
                              Source="{Binding Items}"
                              d:Source="{Binding AllGroups[0].Items, Source={d:DesignInstance Type=data:SampleDataSource, IsDesignTimeCreatable=True}}" />
    </Page.Resources>
    <common:LayoutAwarePage.BottomAppBar>
        <AppBar x:Name="MyBottomAppBar"
                Background="#FF00CD00">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition />
                    <ColumnDefinition />
                </Grid.ColumnDefinitions>
                <StackPanel Orientation="Horizontal">
                    <Button HorizontalAlignment="Stretch"
                            VerticalAlignment="Stretch"
                            Style="{StaticResource AddAppBarButtonStyle}" />
                </StackPanel>
                <StackPanel Grid.Column="1"
                            HorizontalAlignment="Right"
                            Orientation="Horizontal" />
            </Grid>
        </AppBar>
    </common:LayoutAwarePage.BottomAppBar>

    <!--
        This grid acts as a root panel for the page that defines two rows:
        * Row 0 contains the back button and page title
        * Row 1 contains the rest of the page layout
    -->
    <Grid Style="{StaticResource LayoutRootStyle}"
          DataContext="{Binding Group}"
          d:DataContext="{Binding AllGroups[0], Source={d:DesignInstance Type=data:SampleDataSource, IsDesignTimeCreatable=True}}">

        <Grid.RowDefinitions>
            <RowDefinition Height="140" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <!--
            The remainder of the page is one large FlipView that displays details for
            one item at a time, allowing the user to flip through all items in the chosen
            group
        -->
        <FlipView x:Name="flipView"
                  AutomationProperties.AutomationId="ItemsFlipView"
                  AutomationProperties.Name="Item Details"
                  TabIndex="1"
                  Grid.RowSpan="2"
                  ItemsSource="{Binding Source={StaticResource itemsViewSource}}">

To implement the peeking behavior I wrote just a few lines of C# code in the Code Behind of the page. I added a subscription to the Loaded event of the page in the constructor. In the ItemDetailPage_Loaded method I used the Task.Delay() method to pause for a half second. Then I opened the AppBar, paused again but now for 1 second and then closed it again. The ItemDetailPage_Loaded was marked as async to allow the 'await' on the Task.Delay() call. This async keyword is not allowed in a constructor. This is the reason why I wrote my code in the Loaded eventhandler and not in the constructor of the page.

public sealed partial class ItemDetailPage : PeekingAppBar.Common.LayoutAwarePage
{
    public ItemDetailPage()
    {
        this.InitializeComponent();

        this.Loaded += ItemDetailPage_Loaded;
    }

    private async void ItemDetailPage_Loaded(object sender, RoutedEventArgs e) {
        await Task.Delay(500);
        MyBottomAppBar.IsOpen = true;
        await Task.Delay(1000);
        MyBottomAppBar.IsOpen = false;
    }

Closure and download

I hope you like my solution. Maybe you can use it in your apps too. You can download my code below.

Cheers,

Fons

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

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.