SharpGIS

#GIS from a .NET developer's perspective

Why Windows Phone 8 Excites Me

I'm here at the build conference, where it was announced that we finally got access to the Windows Phone 8 SDK. I’ve been browsing the SDK reference, and found a bunch of stuff that I think is going to make this phone huge.
To me, it’s not about the new improved tile interface, the camera lenses or skype integration. It’s what’s under the hood that is really going to make a difference, allowing us to finally build amazing apps that sometimes were hard or flat out impossible.

So a lot of it is missing features that I felt has been holding back the WP7.5 platform (and will continue with 7.8 which is a sorry excuse of an update that’ll confuse consumers), but I can honestly say that I think the feature-set in the WP8 SDK is a home run and there’s nothing big missing from there that I need (even some stuff I didn’t think I need that I now do :-). I know some of these features are available on other platforms already (flamers move on), but the combination of all of these is what makes this all great.

Enterprise ready

We finally get a full enterprise story with your own “enterprise store”. Before WP8, the only option to deploy apps in the enterprise was developer unlock all the phones, or put them in the store as betas (that expires after 3 months) or make them publicly available but hidden behind a login. None of them was a very good solution. We now get a proper enterprise marketplace for easy deployment and update of enterprise apps.

Bluetooth devices

We now get access to bluetooth devices. This is huge and can spawn a new type of eco system. In my work area that means could mean super-high-precision GPS receivers and laser range finders just to start. But in other areas this could be for instance credit card readers and barcode scanners to do on-the-spot purchasing.

Background downloader

Allows you to queue up large downloads while your app is not running. Great for loading larger amounts of data that your app needs for a specific job or for being offline for extended time.

Hot-swappable storage

With hot-swappable storage you can quickly provision large amounts of data to bring with you in the field. Again this could be huge for the enterprise solution. Having to provision data over the air could be an issue even with the background downloader (think gigabytes of data). Imagine highly detailed maps for a specific region you’re about to enter – you bring the right SD card for that area, and someone else brings another SD card with data for their area.
And for those who just want to throw music on there and needs more space: It’s greatc for that too :-)

Direct3D

Direct3D support! We now get direct access to the GPU. This allows us to write high-performance rendering for games, maps, etc. Note: There’s no Direct2D support (but I’m sure someone will build that on top of D3D for us soon, since it really just an abstraction on top of the 3D libraries).

Native code support

We can now write/reuse/run native code on our smartphones. You might not want to write C++, but there are huge amounts of libraries today written in C++ that we can now use. Think of for instance the Sqlite Database. Where I work we have huge amounts of native libraries that has taken years to develop and would be near-impossible to port to .NET, not to mention they require the fastest possible processing of large amounts of data, and C++ is more or less unmatched for that purpose. This doesn’t mean you have to write your entire app in C++. It could just mean that you bring in a native library (like Sqlite) and code against it from C#. The integration is very similar to WinRT (in fact they call it WinPRT – ‘P’ for Phone, and shares a lot of libraries too). So you might never have to worry about native code, but you can still get the benefit of other 3rd parties’ hard work!

.NET

Having .NET (and C#/VB.NET) is not really a new feature, but having worked in the ISV space for many years, and delivering SDKs to smaller ISVs, I know how important it is to have a .NET SDK. Many smaller businesses often have a developer or two, and most of them I find to be .NET developers. The fact that they can write a quick app for their enterprise in an environment that are familiar to them will be huge. This is probably the biggest differentiator to all the other phone platforms.

 

 

New Windows Phone 8 SDKs (slide from Build Keynote)

Shortcut Key Handling in Windows Store Apps

I wanted to create a simple ALT+S shortcut in my app to jump to a TextBox in my Windows Store App (no this is not the Win+Q search charm shortcut). However, this is not that obvious to get working app-wide, so I’ll share it here:

The obvious way to do this is assign a KeyDown event to your page using the typical “this.KeyDown += MyKeyDownHandler”, or override OnKeyDown. However this has one problem: If any control that has focus currently handles key down events (like TextBox), this event won’t be raised due to how event bubbling works. However there is another way to create an event handler that overrides the bubbling: UIElement.AddHandler. In the 3rd parameter of that, you can specify that you want to be notified even if the event has been handled. Here’s what that looks like for listening to the event app-wide:

Window.Current.Content.AddHandler(UIElement.KeyDownEvent, new KeyEventHandler(App_KeyDown), true);
//...
private void App_KeyDown(object sender, KeyRoutedEventArgs e)
{
     //Handle Key Down
}

If you attach to Window.Current.Content, be sure to detach again in OnNavigatingFrom, or you’ll risk having a memory leak, and also still get events firing in that page when it’s not loaded any longer. If you just want this within the page, use myPage.AddHandler of Window.Current.Content.AddHandler, but beware that if anything outside this page has focus the event won’t be raised. – at least in that case you don’t need to worry about unhooking again though.

Now second is to handle the key combination. You can check the menu/alt key status using the following line of code:

bool isMenuKeyDown = CoreWindow.GetForCurrentThread().GetAsyncKeyState(VirtualKey.Menu) == CoreVirtualKeyStates.Down;

So the obvious handler code would look like this:

private void App_KeyDown(object sender, KeyRoutedEventArgs e)
{
    if (e.Key == Windows.System.VirtualKey.S)
    {
        if(CoreWindow.GetForCurrentThread().GetAsyncKeyState(VirtualKey.Menu) == CoreVirtualKeyStates.Down)
        {
            //Handle key combination… 
} } }

Turns out the above code only works every other time. When reading the documentation on GetAsyncKeyState it states that “Determines whether a key is up or down at the time the function is called, and whether the key was pressed after a previous call to GetAsyncKeyState.

So basically this method changes its result based on whether it was called before, and not solely whether the key is down or not. This makes no sense to me why it was designed like this (but do feel free to explain in the comments if you know).

Anyway, if we just make sure this method is always called in the handler it now starts working predictably. So here’s my snippet that ckecks if ALT+S is pressed, sets focus on my textbox and selects all the text:

private void App_KeyDown(object sender, KeyRoutedEventArgs e)
{
    bool isMenuKey = CoreWindow.GetForCurrentThread().GetAsyncKeyState(Windows.System.VirtualKey.Menu) == CoreVirtualKeyStates.Down;
    if (isMenuKey && e.Key == VirtualKey.S)
    {
        queryTextBox.Focus(FocusState.Keyboard);
        queryTextBox.SelectAll();
    }
}

Using User-Provided Images for Secondary Tiles

Often when you are creating a secondary tile in Windows 8, it will be based on images coming from the internet.  However a requirement of secondary tile images are that they need to be stored locally. I initially had some problems getting this working right and the streams closed correctly for this to work, so here’s the code for other to use and save the hazzle:

public async static Task CreateSecondaryTileFromWebImage(
    string tileId, Uri imageUri, string shortName, string displayName,
    string arguments, Rect selection)
{
    //Download image to LocalFolder and use the tileId as the identifier
    string filename = string.Format("{0}.png", tileId);
    HttpClient httpClient = new HttpClient();
    var response = await httpClient.GetAsync(imageUri);
    var imageFile = await ApplicationData.Current.LocalFolder.CreateFileAsync(filename, CreationCollisionOption.ReplaceExisting);
    using (var fs = await imageFile.OpenAsync(FileAccessMode.ReadWrite))
    {
        using (var outStream = fs.GetOutputStreamAt(0))
        {
            DataWriter writer = new DataWriter(outStream);
            writer.WriteBytes(await response.Content.ReadAsByteArrayAsync());
            await writer.StoreAsync();
            writer.DetachStream();
            await outStream.FlushAsync();
        }
    }
    //Create tile
    Uri image = new Uri(string.Format("ms-appdata:///local/{0}", filename));
    SecondaryTile secondaryTile = new SecondaryTile(tileId, shortName, displayName, arguments, TileOptions.ShowNameOnLogo, image);
    secondaryTile.ForegroundText = ForegroundText.Light;
    await secondaryTile.RequestCreateForSelectionAsync(selection, Windows.UI.Popups.Placement.Above);
}

Often this is enough, but there is still a small problem. What if the image is very light, and the text you display on top of it is white, thus drowning in the background image? You could set the tile text to black, but then what happens if the image is dark? And if it has a lot of texture in it, the text still gets very unreadable. Since you might not know up front what the image looks like, whether it’s dark or bright, or textures, we will need a way to ensure the text will still look good on top of the image.

image

Unfortunately full WriteableBitmap support in WinRT isn’t there to help us out modifying the image, but we do get low-level access to the pixel buffer of the image, so we could fairly simple darken or brighten the bottom a bit to ensure the image looks good as a backdrop for the text.

I wrote a little utility that loads the image, gradually darkens the bottom 40% of the image before saving it back. I’ve found that doing a slight graduated darkening on photos isn’t too noticeably, while making the text in front of it much more readable. So with out further ado, here’s my simple image pixelbuffer modifier:

private async static Task DarkenImageBottom(string filename, string outfilename)
{
    var file = await ApplicationData.Current.LocalFolder.GetFileAsync(filename);
    BitmapDecoder decoder = null;
    byte[] sourcePixels = null;
    using (IRandomAccessStream fileStream = await file.OpenReadAsync())
    {
        decoder = await BitmapDecoder.CreateAsync(fileStream);
        // Scale image to appropriate size 
        BitmapTransform transform = new BitmapTransform();
        PixelDataProvider pixelData = await decoder.GetPixelDataAsync(
            BitmapPixelFormat.Bgra8,
            BitmapAlphaMode.Straight,
            transform,
            ExifOrientationMode.IgnoreExifOrientation, // This sample ignores Exif orientation 
            ColorManagementMode.DoNotColorManage
        );
        // An array containing the decoded image data, which could be modified before being displayed 
        sourcePixels = pixelData.DetachPixelData();
        fileStream.Dispose();
    }
    if (decoder != null && sourcePixels != null)
    {
        for (uint col = 0; col < decoder.PixelWidth; col++)
        {
            for (uint row = (uint)(decoder.PixelHeight * .6); row < decoder.PixelHeight; row++)
            {
                uint idx = (row * decoder.PixelWidth + col) * 4;
                if (decoder.BitmapPixelFormat == BitmapPixelFormat.Bgra8 ||
                    decoder.BitmapPixelFormat == BitmapPixelFormat.Rgba8)
                {
                    var frac = 1 - Math.Sin(((row / (double)decoder.PixelHeight) - .6) * (1 / .4));
                    byte b = sourcePixels[idx];
                    byte g = sourcePixels[idx + 1];
                    byte r = sourcePixels[idx + 2];
                    sourcePixels[idx] = (byte)(b * frac);
                    sourcePixels[idx + 1] = (byte)(g * frac);
                    sourcePixels[idx + 2] = (byte)(r * frac);
                }
            }
        }

        var file2 = await ApplicationData.Current.LocalFolder.CreateFileAsync(outfilename, CreationCollisionOption.ReplaceExisting);

        var str = await file2.OpenAsync(Windows.Storage.FileAccessMode.ReadWrite);
        BitmapEncoder enc = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, str);
        enc.SetPixelData(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Ignore, decoder.PixelWidth, decoder.PixelHeight,
            decoder.DpiX, decoder.DpiY, sourcePixels);
        await enc.FlushAsync();
        str.Dispose();
    }
}

So we can call this utility prior to creating the secondary tile request. Compare the before(left) and after (right) here:

imageimage

The image difference is barely noticeable, but the text is much more readable.

You can download the tile utility class and sample app here.

Rotating Elements in XAML While Maintaining Proper Flow

I recently had to lay out some text vertically stacked on top of each other in Windows 8, similar to how tabs in Visual Studio are laid out.

image

The obvious way to do that would be to first place the texts in a stack panel, and then rotate them like so:

<StackPanel>
    <TextBlock Text="Text 1">
        <TextBlock.RenderTransform>
            <RotateTransform Angle="90" />
        </TextBlock.RenderTransform>
    </TextBlock>
    <TextBlock Text="Text 2">
        <TextBlock.RenderTransform>
            <RotateTransform Angle="90" />
        </TextBlock.RenderTransform>
    </TextBlock>
    <TextBlock Text="Text 3">
        <TextBlock.RenderTransform>
            <RotateTransform Angle="90" />
        </TextBlock.RenderTransform>
    </TextBlock>
</StackPanel>

This is what it looks like without the RotateTransform:

image

And after adding rotation:

image

Notice how the text is now outside the containing StackPanel, and overlapping each other? So what happened? The problem is that RenderTransform is applied AFTER the layout cycle occurs so StackPanel has no way of placing these elements, since it already did it’s job prior to rotating the text. We’ll get back to how to resolve this, but let’s first cover the layout cycle, which consists of two steps: Measure and Arrange.

In the Measure step, each TextBlock is measured. This is basically the step where the parent control – in this case the StackPanel – tells each TextBlock “If you have [x,y] space, how much of that would you like to have?”. It does this by calling TextBlock.Measure(Size) on each of them. The TextBlock then reports back the size it would like to have using the .DesiredSize property. You will notice that controls’ DesiredSize property will always return (0,0) until the Measure step has run. Usually for TextBlocks it would report back the size of the text. If the text doesn’t fit within the width that the StackPanel provided and TextWrapping was enabled on the TextBlock, the TextBlock might choose to break the text and report a taller height instead, so it can keep inside the width it was provided with.

The next step is the Arrange step. This occurs after all children has been measured, and the StackPanel here decides how much space it will provide to each control. While the TextBlocks provided a certain DesiredSize, they might not actually get that much space – that’s all up to the parent control – for instance for a Grid’s columns and rows with auto and * sizes, the measure step helped it determine how much space each row and column will be, and then applies that to each element during arrange. Arrange is done by calling .Arrange(Rect) on each element, providing them with a rectangle to place itself within.

So back to our problem: How can we use this knowledge to get the layout cycle to proper place my rotated textblocks?

Well first of all, when we rotate an element 90 degrees, the width of the text becomes the height, and vice-versa. So if we were to “swap” the width and height during Arrange, we should be able to prevent the overlapping and ensure that there’s enough width, errr height for each TextBlock so they don’t overlap. During the arrange step, we can ensure that the elements gets placed right and doesn’t end up outside the containing control.

To do that, the first thing we’ll do is create a new control. Add a new TemplatedControl to your project:

image

A new class inheriting from Control will be created, as well as a new \Themes\Generic.xaml template (if you already have a Generic.xaml file, the following will be added):

Let’s get rid of the Border in this template, and add a simple ContentControl instead:

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:RotateSample">

    <Style TargetType="local:RotateContentControl">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:RotateContentControl">
                    <Border
                        Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}">
                    </Border>
<ContentControl x:Name="Content" Content="{TemplateBinding Content}" />
</ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary>

Next let’s add a Content dependency property to our class so that we can bind to the content. We’ll also add a Content property to the class, to signify that anything that is used as content inside this control in XAML is meant to be assigned to the Content property. Our control now looks like this:

[Windows.UI.Xaml.Markup.ContentProperty(Name="Content")]
public sealed class RotateContentControl : Control
{
    public RotateContentControl()
    {
        this.DefaultStyleKey = typeof(RotateContentControl);
    }

    public object Content
    {
        get { return (object)GetValue(ContentProperty); }
        set { SetValue(ContentProperty, value); }
    }

    public static readonly DependencyProperty ContentProperty =
        DependencyProperty.Register("Content", typeof(object), typeof(RotateContentControl), null);      
}

Now if we were to run the app using this control, it’ll basically be the same as using a ContentControl.

<local:RotateContentControl>
    <TextBlock Text="Text 1" FontSize="32" Margin="5" />
</local:RotateContentControl>

Of course this is not much fun, so let’s first use the OnApplyTemplate to grab the content and apply the rotation to the content:

private ContentControl m_Content;
private const double rotation = 90;
protected override void OnApplyTemplate()
{
    m_Content = GetTemplateChild("Content") as ContentControl;
    if (m_Content != null)
    {
        m_Content.RenderTransform = new RotateTransform() { Angle = rotation };
    }
    base.OnApplyTemplate();
}

If you run the sample now, we’ll basically be back to where we started with the texts offset and placed outside the parent container. You can see that the StackPanel is highlighted below with the size it thinks it needs to be to hold the TextBlocks, which doesn’t match the actual size of the TextBlocks:

image

So let’s first override the Measure step and swap width and heights:

protected override Windows.Foundation.Size MeasureOverride(Windows.Foundation.Size availableSize)
{
    if (m_Content != null)
    {
        m_Content.Measure(new Size(availableSize.Height, availableSize.Width));
        return new Size(m_Content.DesiredSize.Height, m_Content.DesiredSize.Width);
    }
    else
        return base.MeasureOverride(availableSize);
}

You’ll now see the following happen – notice how the height and width is now correct for the StackPanel if the TextBlocks were rendered in the right place, but we start seeing clipping on the TextBlocks:

image

This happens because the ArrangeStep still uses the unswapped width/height and causes clipping. Let’s next override the Arrange and swap width and height here as well:

protected override Size ArrangeOverride(Size finalSize)
{
    if (m_Content != null)
    {
        m_Content.Arrange(new Rect(new Point(0, 0), 
new Size(finalSize.Height, finalSize.Width))); return finalSize; } else return base.ArrangeOverride(finalSize); }

And the result we get is:

image

Now the text are not overlapping any longer nor are they clipped, but we still get them placed outside the parent StackPanel. This is because the rotation happens around the upper left corner and pushes the text out, as illustrated here:

image

Luckily the fix is easy because the Arrange step allows us to specify where to place the element as well. We basically have to move the TextBlock over to the left by the height of the text, so instead of specifying (0,0) for the rectangle corner, we use (width,0), so our Arrange looks like this:

protected override Size ArrangeOverride(Size finalSize)
{
    if (m_Content != null)
    {
        m_Content.Arrange(new Rect(new Point(finalSize.Width, 0), new Size(finalSize.Height, finalSize.Width)));
        return finalSize;
    }
    else
        return base.ArrangeOverride(finalSize);
}

And our controls now flows correctly within the StackPanel:

image

It also plays nice with other controls and respects alignments:

image

If you want to rotate the content –90 degrees, the offset in arrange changes slightly to:

m_Content.Arrange(new Rect(new Point(0, finalSize.Height), 
new Size(finalSize.Height, finalSize.Width)));

We could make this a property on our control, so you can easily change direction on the fly. We’ll add a new Direction enumeration and a DependencyProperty that triggers Arrange when it changes:

public RotateDirection Direction
{
    get { return (RotateDirection)GetValue(DirectionProperty); }
    set { SetValue(DirectionProperty, value); }
}

public static readonly DependencyProperty DirectionProperty =
    DependencyProperty.Register("Direction", typeof(RotateDirection),
typeof(RotateContentControl), new PropertyMetadata(RotateDirection.Down, OnDirectionPropertyChanged)); public static void OnDirectionPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { (d as RotateContentControl).InvalidateArrange(); //Trigger reflow }

We’ll also use remove the RenderTransform setting from OnApplyTemplate, and instead set it in ArrangeOverride, so that now looks like this:

protected override Size ArrangeOverride(Size finalSize)
{
    if (m_Content != null)
    {
        m_Content.RenderTransform = new RotateTransform() { Angle = (int)this.Direction };
        if (Direction == RotateDirection.Down)
            m_Content.Arrange(new Rect(new Point(finalSize.Width, 0), 
new Size(finalSize.Height, finalSize.Width))); else if (Direction == RotateDirection.Up) m_Content.Arrange(new Rect(new Point(0, finalSize.Height),
new Size(finalSize.Height, finalSize.Width))); return finalSize; } else return base.ArrangeOverride(finalSize); }

So here’s what that looks like in the designer:

image

That’s it! Below is the entire source code including support for 0 and 180 degree rotations as well:

using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;

namespace RotateSample
{
    [Windows.UI.Xaml.Markup.ContentProperty(Name="Content")]
    public sealed class RotateContentControl : Control
    {
        private ContentControl m_Content;

        public RotateContentControl()
        {
            this.DefaultStyleKey = typeof(RotateContentControl);
        }

        protected override void OnApplyTemplate()
        {
            m_Content = GetTemplateChild("Content") as ContentControl;
            base.OnApplyTemplate();
        }

        protected override Windows.Foundation.Size MeasureOverride(Windows.Foundation.Size availableSize)
        {
            if (m_Content != null)
            {
                if (((int)Direction) % 180 == 90)
                {
                    m_Content.Measure(new Windows.Foundation.Size(availableSize.Height, availableSize.Width));
                    return new Size(m_Content.DesiredSize.Height, m_Content.DesiredSize.Width);
                }
                else
                {
                    m_Content.Measure(availableSize);
                    return m_Content.DesiredSize;
                }
            }
            else
                return base.MeasureOverride(availableSize);
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            if (m_Content != null)
            {
                m_Content.RenderTransform = new RotateTransform() { Angle = (int)this.Direction };
                if (Direction == RotateDirection.Up)
                    m_Content.Arrange(new Rect(new Point(0, finalSize.Height),
                                      new Size(finalSize.Height, finalSize.Width)));
                else if (Direction == RotateDirection.Down)
                    m_Content.Arrange(new Rect(new Point(finalSize.Width, 0), 
                                      new Size(finalSize.Height, finalSize.Width)));
                else if (Direction == RotateDirection.UpsideDown)
                    m_Content.Arrange(new Rect(new Point(finalSize.Width, finalSize.Height), finalSize));
                else
                    m_Content.Arrange(new Rect(new Point(), finalSize));
                return finalSize;
            }
            else
                return base.ArrangeOverride(finalSize);
        }


        public object Content
        {
            get { return (object)GetValue(ContentProperty); }
            set { SetValue(ContentProperty, value); }
        }

        public static readonly DependencyProperty ContentProperty =
            DependencyProperty.Register("Content", typeof(object), typeof(RotateContentControl), null);

        public enum RotateDirection : int
        {
            Normal = 0,
            Down = 90,
            UpsideDown = 180,
            Up = 270
        }

        public RotateDirection Direction
        {
            get { return (RotateDirection)GetValue(DirectionProperty); }
            set { SetValue(DirectionProperty, value); }
        }

        public static readonly DependencyProperty DirectionProperty =
            DependencyProperty.Register("Direction", typeof(RotateDirection),
            typeof(RotateContentControl), new PropertyMetadata(RotateDirection.Down, OnDirectionPropertyChanged));

        public static void OnDirectionPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if(((int)e.OldValue) % 180 == ((int)e.NewValue) % 180)
                (d as RotateContentControl).InvalidateArrange(); //flipping 180 degrees only changes flow not size
            else
                (d as RotateContentControl).InvalidateMeasure(); //flipping 90 or 270 degrees changes size too, so remeasure
        }
    }
}

Note: While this article was written for Windows Store apps, these concepts apply directly to Silverlight, WPF and Windows Phone as well, albeit they already provide controls in the Toolkit (LayoutTransformer) to handle this.