31 Days of Windows 8 | Day #24: Light Sensor

This article is Day #24 in a series called 31 Days of Windows 8.  Each of the articles in this series will be published for both HTML5/JS and XAML/C#. You can find additional resources, downloads, and source code on our website.

advertisementsample

Today, we are taking a look another one of the sensors we might find in a Windows 8 device: the Light Sensor.  With the Light Sensor, we can determine the brightness of the light around the user’s machine, and help to accommodate things like contrast, brightness, and other values that would make our app easier to read in high and low light.

I’ve put together yet another horribly produced video to show you how a light sensor works, and what kinds of values we can expect:

 

To make this application work, we’re going to follow a very similar pattern to the one we used yesterday for the Compass sensor. 

  • Initialize the Light Sensor.
  • If it’s available, create a ReadingChanged event handler.
  • In the event handler, grab the data from the sensor and write it to the screen.

Here’s my entire MainPage.xaml.cs file:

using System;
using Windows.Devices.Sensors;
using Windows.UI.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
 
namespace Day24_LightSensor
{
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();
        }
 
        LightSensor sensor;
 
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            sensor = LightSensor.GetDefault();
            if (sensor != null)
            {
                sensor.ReadingChanged += sensor_ReadingChanged;
                Data.Visibility = Visibility.Visible;
            }
            else
            {
                NoSensorMessage.Visibility = Visibility.Visible;
            }
        }
 
        async void sensor_ReadingChanged(LightSensor sender, LightSensorReadingChangedEventArgs args)
        {
            await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
            {
                Lux.Text = args.Reading.IlluminanceInLux.ToString();
                TimeStamp.Text = args.Reading.Timestamp.ToString();
            });
 
        }
    }
}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

 

There’s nothing surprising about getting this data, but I was surprised to see how diverse the values on different machines (that were sitting next to each other) could be.  For instance, my Qualcomm ARM device (the one featured in the video above) generally rated the room I’m currently sitting in around 59 lux.  My Samsung tablet, however, which is a Windows 8 Pro device, rates this room around 42 lux.  Finally, my Surface RT device says this place is about 115 lux.

This is likely due to the accuracy and quality of the light sensor in each device, but in general, they’re really not that far apart on the scale of lux values.  Here’s an example from the Wikipedia article on Lux.

24-XAML-LuxChart

As you can see, even 100 lux is still a pretty dim value.  Just flipping the lightswitch on in my office jumped my sensor values up closer to 175.  Using the chart above, however, you should be able to create “ranges” of values that behave differently depending on the brightness of the light available.

For example, if you recognize that the user is in a low-light environment, you might switch their display to show a dark background and white text, because that is easier to read in that kind of light.  In a bright room, you might want to switch to black text on a white background.

In either case, you now know how to recognize the data from the light sensor, and use it effectively in your app.

Summary

Today we talked about the Light Sensor, and how it can be used to alter a user’s interface to make it more readable.  Ultimately, there are dozens of creative ways to leverage the lux data in your applications, and I’m looking forward to hearing how you’ll use it in your app.

If you’d like to download my working sample that uses the code from this article, click the icon below:

downloadXAML

Tomorrow, we’re going to get involved with a more robust sensor, the Accelerometer.  We can use this data to determine the rotation of the user’s device.  See you then!

downloadTheTools

31 Days of Windows 8 | Day #23: The Compass

This article is Day #23 in a series called 31 Days of Windows 8.  Each of the articles in this series will be published for both HTML5/JS and XAML/C#. You can find additional resources, downloads, and source code on our website.

advertisementsample

Today, we’re going to talk about the compass.  If you’ve done any Windows Phone development previously, you might have read my article on the Windows Phone compass, which provides magnetic and true north headings, as well as X, Y, and Z rotation data.

From what I have found so far, the compass in Windows 8 provides significantly less data from the sensor.  In fact, we only get the values related to our heading, with no consideration for rotation data.  In addition, you’re going to find that since we’re building apps for Windows 8, there’s going to be a wide variety of sensors we’ll encounter.  Here’s a quick video of the 4 different machines that I use regularly, and how they each respond differently to the same code:

 

Writing Some Code to Access the Compass

The compass, like many of the sensors we will cover in the next few days, is actually very accessible and easy to use.  With a few lines of code, and one event handler, we can gather rich data from our user’s device very quickly.  Here’s my entire MainPage.xaml.cs file for my simple compass app:

using System;
using Windows.Devices.Sensors;
using Windows.UI.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;

namespace Day23_Compass
{
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();
        }

        Compass c;

        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            c = Compass.GetDefault();
            if (c != null)
            {
                c.ReadingChanged += c_ReadingChanged;
                Data.Visibility = Visibility.Visible;
            }
            else NoSensorMessage.Visibility = Visibility.Visible;
        }

        protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
        {
            c.ReadingChanged -= c_ReadingChanged;
        }

        async void c_ReadingChanged(Compass sender, CompassReadingChangedEventArgs args)
        {
            await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
            {
                MagneticNorth.Text = args.Reading.HeadingMagneticNorth.ToString();
                if (args.Reading.HeadingTrueNorth != null)
                {
                    TrueNorth.Text = args.Reading.HeadingTrueNorth.ToString();
                }
                TimeStamp.Text = args.Reading.Timestamp.ToString();
            });

        }
    }
}

To explain the code above, in my OnNavigatedTo method, I try to initialize my Compass object, and if it’s not null, I create an event handler for each time that the reading of the compass changes.  (Unless the user’s device is sitting completely still, it’s likely you’ll consistently get new readings.)  In the ReadingChanged event handler, we use a Dispatcher to get back to the UI thread, where we then assign our three values, HeadingMagneticNorth, HeadingTrueNorth, and Timestamp to TextBlock values I’ve placed in my XAML file.

That’s it.  As you probably saw, however, most of the sensors you’re going to encounter will not provide a HeadingTrueNorth value, so relying on the HeadingMagneticNorth is going to be a more reliable value for you.

Finally, both of the Heading values are measured in degrees relative to their associated heading.  This means that you’ll see a range of values from 0 to 360, with zero being the value headed directly north.

Summary

This was a quick but exhaustive look at the Compass in Windows 8.  We saw that you can easily access the Compass data by using an event handler, but that we receive a limited amount of data from this sensor, and your mileage will vary from device to device.

If you would like to download the sample app featured in this article, click the icon below:

downloadXAML

Tomorrow, we’re going to look at the light sensor, and how we can manipulate our application to take advantage of knowing if the user is sitting in a light or dark room.

downloadTheTools

31 Days of Windows 8 | Day #22: Using Play To

This article is Day #22 in a series called 31 Days of Windows 8.  Each of the articles in this series will be published for both HTML5/JS and XAML/C#. You can find additional resources, downloads, and source code on our website.

advertisementsample

Today, we get to talk about another really cool feature to Windows 8: Play To.  At its core, this is the ability to share content from your computer to a television, another computer, or an Xbox 360.  Imagine finding a cool video on YouTube that you want to share with the other people in the room.  Play To would allow you share that content to your television so that everyone can see it without having to huddle around your tablet.

When I first started investigating this technology, I was concerned that it would be difficult to test effectively.  While I have a couple of nice TVs in my house, they’re just old enough to not have any idea what Play To is.  (If you’d like to check your devices, check out Microsoft’s Compatibility Center.)  Anyways, what I’ve discovered is that you really only need a second machine running Windows 8 on it, or an Xbox 360 to test this out.

In my setup, I’ve deployed my application to my Samsung Series 7 Slate running Windows 8 Pro, and I am able to use Play To on my home network to share content with Windows Media Player on a second PC that I have on my desk.  I’ve also tested it on my Xbox 360, and it works marvelously.

Before we get into the coding, I want to make sure you have a way to test this functionality.  In Windows 8, open the Windows Media Player app.  (You know, the app that you haven’t opened on a Windows machine in 10 years?  Yeah, that one.)

22-XAML-WindowsMediaPlayer

Select the “Allow remote control of my Player…” option.  You’ll be asked to confirm your choice, just to make sure this is actually what you want to do.

22-XAML-AllowRemoteControl

Once you’ve confirmed this choice, your machine will be registered on your network as a device available for Play To content.  OK, let’s get to coding, so that we can see what this really means.

Making Your App a Play To Source

This is the easy half of this article, but the second half is just more involved, not necessarily difficult.  When I first ventured out to learn how to do this, I fully expected a trip to the package.appxmanifest file, where we would simply treat this experience much like we did with Sharing on Day #7, where we have to do most of the work in configuration.

I was wrong.  It’s WAY easier.

First, we need to create a PlayToManager object, which will activate our ability to share our content via Play To.  We create an event handler which handles when the user connects their device to a target, and finally, we set the source of the content we want to send.  The entire operation looks like this:

PlayToManager manager = null;
CoreDispatcher dispatcher = null;

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    dispatcher = Window.Current.CoreWindow.Dispatcher;
    manager = PlayToManager.GetForCurrentView();
    manager.SourceRequested += manager_SourceRequested;
}

void manager_SourceRequested(PlayToManager sender, PlayToSourceRequestedEventArgs args)
{
    var deferral = args.SourceRequest.GetDeferral();
    var handler = dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
    {
        args.SourceRequest.SetSource(MusicSource.PlayToSource);
        deferral.Complete();
    });
}

protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
    manager.SourceRequested -= manager_SourceRequested;
}

That’s seriously ALL the code you need to make this happen.  Remember Day #20 when we talked about printing?  It took over 400 lines of code to get a printer to put ink on paper.  Getting a song to stream wirelessly over a network and play on another device without any need for credentials?  We can do that in 10 lines.  Here’s a little video to illustrate:

The only real “magic” that is happening in the code above is when we set our source.  I am grabbing the Source of a MediaElement I have on my page, but this seems perfectly reasonable.  The standard use case for Play To is that the user will start watching the content on their tablet or personal device, and then want to share it to another Play To device, so you’ll already have a MediaElement in use.

The next step is to make our app capable of being the source as well.

Making Your Windows 8 App a Play To Target

In this example, we’re going to add functionality to our app so that we can be on the receving end of any Play To content that might be flying around our local network.  To do this, we will start with a PlayToReceiver object, and a giant pile of REQUIRED event handlers to go with it.  I’ve modified my OnNavigatedTo method from earlier to accomodate this new code:

PlayToManager manager = null;
CoreDispatcher dispatcher = null;
PlayToReceiver receiver = null;

protected async override void OnNavigatedTo(NavigationEventArgs e)
{
    dispatcher = Window.Current.CoreWindow.Dispatcher;
    manager = PlayToManager.GetForCurrentView();
    manager.SourceRequested += manager_SourceRequested;

    receiver = new PlayToReceiver();
    receiver.PlaybackRateChangeRequested += receiver_PlaybackRateChangeRequested;
    receiver.PlayRequested += receiver_PlayRequested;
    receiver.PauseRequested += receiver_PauseRequested;
    receiver.StopRequested += receiver_StopRequested;
    receiver.MuteChangeRequested += receiver_MuteChangeRequested;
    receiver.VolumeChangeRequested += receiver_VolumeChangeRequested;
    receiver.TimeUpdateRequested += receiver_TimeUpdateRequested;
    receiver.CurrentTimeChangeRequested += receiver_CurrentTimeChangeRequested;
    receiver.SourceChangeRequested += receiver_SourceChangeRequested;
    receiver.SupportsAudio = true;
    receiver.SupportsVideo = true;
    receiver.SupportsImage = true;

    receiver.FriendlyName = "Day #22 - Play To";

    await receiver.StartAsync();
}

As you can see, there are NINE event handlers that need to be implemented.  I’m stressing this fact because if you don’t implement every single one of them, you will not be able to call the StartAsync() method on your PlayToReceiver object.  (I just spent the last 30 minutes discovering that cold, hard fact.)

Once you’ve done it, however, and have your nine new methods, it’s up to you which ones you actually implement (though it’s recommended that you implement all of them.)  Here are each of my nine event handlers, and the code that goes with each one:

SourceChangeRequested

In this method (probably the most important of the nine), we detect which type of content is being sent our way, grab it, and send it to the appropriate XAML control in our UI.  The ShowSelectedPanel() method is one I built to manage the switching of visibilities and stop/start methods for the MediaElements.  You can certainly choose to disregard those in your app.

async void receiver_SourceChangeRequested(PlayToReceiver sender, SourceChangeRequestedEventArgs args)
{
    if (args.Stream != null)
    {
        if (args.Stream.ContentType.Contains("image"))
        {
            await Dispatcher.RunAsync(CoreDispatcherPriority.High, () =>
            {
                BitmapImage bmp = new BitmapImage();
                bmp.SetSource(args.Stream);
                PhotoSource.Source = bmp;
                ShowSelectedPanel(1);
            });
        }
        else if (args.Stream.ContentType.Contains("video"))
        {
            await Dispatcher.RunAsync(CoreDispatcherPriority.High, () =>
            {
                VideoSource.SetSource(args.Stream, args.Stream.ContentType);
                ShowSelectedPanel(3);
            });
        }
        else if (args.Stream.ContentType.Contains("audio"))
        {
            await Dispatcher.RunAsync(CoreDispatcherPriority.High, () =>
            {
                MusicSource.SetSource(args.Stream, args.Stream.ContentType);
                ShowSelectedPanel(2);
                MusicSource.Play();
            });
        }
    }
}

PlayRequested

This, and all of the subsequent event handlers simply make a call of the appropriate type (this example is obviously “Play”), and then notify our source that the call has been made.

async void receiver_PlayRequested(PlayToReceiver sender, object args)
{
    await Dispatcher.RunAsync(CoreDispatcherPriority.High, () =>
    {
        MusicSource.Play();
        VideoSource.Play();
        receiver.NotifyPlaying();
    });
}

 

PauseRequested

Practically identical to the PlayRequested method, this one implements Pause.

async void receiver_PauseRequested(PlayToReceiver sender, object args)
{
    await Dispatcher.RunAsync(CoreDispatcherPriority.High, () =>
    {
        MusicSource.Pause();
        VideoSource.Pause();
        receiver.NotifyPaused();
    });
}

The Rest

As you can see below, the last 5 event handlers follow the exact same format for their properties.

private async void receiver_PlaybackRateChangeRequested(PlayToReceiver sender, PlaybackRateChangeRequestedEventArgs args)
{
    await Dispatcher.RunAsync(CoreDispatcherPriority.High, () =>
    {
        VideoSource.PlaybackRate = args.Rate;
    });
}

private async void receiver_CurrentTimeChangeRequested(PlayToReceiver sender, CurrentTimeChangeRequestedEventArgs args)
{
    await Dispatcher.RunAsync(CoreDispatcherPriority.High, () =>
    {
        if (VideoSource.CanSeek)
        {
            {
                VideoSource.Position = args.Time;
                receiver.NotifySeeking();
            }
        }
    });
}

private async void receiver_TimeUpdateRequested(PlayToReceiver sender, object args)
{
    await Dispatcher.RunAsync(CoreDispatcherPriority.High, () =>
    {
        receiver.NotifyTimeUpdate(VideoSource.Position);
    });
}

private async void receiver_VolumeChangeRequested(PlayToReceiver sender, VolumeChangeRequestedEventArgs args)
{
    await Dispatcher.RunAsync(CoreDispatcherPriority.High, () =>
    {
        VideoSource.Volume = args.Volume;
    });
}

private async void receiver_MuteChangeRequested(PlayToReceiver sender, MuteChangeRequestedEventArgs args)
{
    await Dispatcher.RunAsync(CoreDispatcherPriority.High, () =>
    {
        VideoSource.IsMuted = args.Mute;
    });
}

 

Finally, we’ve got a working application that both send and receive content via Play To.  Here’s a video of the final product in action:

 

Summary

Today, we dove in deep to the Play To protocol, which allows us to send and receive media files from another app on another device.  I think we’ve done a pretty good job of giving you the fine-grained control necessary to make this a success in your applications.

If you’d like to download the working sample code from this article, click the icon below:

downloadXAML

Tomorrow, we’re going to dive into using another sensor: the Compass.  Navigate your way back here tomorrow to learn more about it.  See you then!

downloadTheTools

31 Days of Windows 8 | Day #21: Camera Capture

This article is Day #21 in a series called 31 Days of Windows 8.  Each of the articles in this series will be published for both HTML5/JS and XAML/C#. You can find additional resources, downloads, and source code on our website.

advertisementsample43

Today, we’re going to kick off a long line of article on the sensors that Windows 8 has access to.  We are starting with capturing data from the camera.  This can be both photos and video, and we’ll take a look at both in this article.

Updating our Manifest

As we have had to do for many of our topics in this series, we need to start by updating our package.appxmanifest file to enable the use of the user’s webcam and microphone.  You do that by checking two boxes in the Capabilities section, like this:

21-XAML-Manifest

Without these, you won’t be able to use these technologies, so make sure you’ve handled this first.

Capturing a Still Image

One of the great things about capturing media in Windows 8 is that practically all of the work is done for you.  That being said, there are some things to consider.  First, your app will prompt the user for permission to use your camera and microphone when you try to access that hardware.

21-XAML-Permission

Much like Lock Screen access, you only get one shot at this.  Thankfully, if the user clicks “Block” for whatever reason (maybe because it’s the highlighted option?), the camera “display” dialog will prompt the user to change their settings, like this:

21-XAML-NoPermission

And if the user opens their Settings charm, they’ll find a “Permissions” option, like this:

21-XAML-SettingsPermissions

Clicking the “Permissions” link will bring up this menu, which is generated based on the choices we made in our appxmanifest file.

21-XAML-SettingsPermissionsMenu

The user will always have the ability to turn this on and off, but since the Camera Dialog handles this case for us, we only need to make sure we actually had an image object returned to us.  Here’s the three lines of code that are required to capture an image from the camera, and return it as a StorageFile object:

CameraCaptureUI camera = new CameraCaptureUI();
camera.PhotoSettings.CroppedAspectRatio = new Size(16, 9);
StorageFile photo = await camera.CaptureFileAsync(CameraCaptureUIMode.Photo);

Technically, only two of those lines are required.  The 2nd line, which sets the CroppedAspectRatio is not required, but highly recommended.  When the Camera Dialog opens, the user is presented with a full-screen view of their webcam.

21-XAML-InitialCameraScreen

They have a couple of options (like resolution and using a timer), and they can tap on the screen (or click) to initiate the camera capture.  If we specify a CroppedAspectRatio, this is what the user will see (feel free to comment on the beard, the EMPTY Red Bull fridge, or the overall messiness of my home office):

21-XAML-AspectRatio

You can see that there are 4 white circles on the screen which indicate the cropping region.  The user has the ability to change the size and position of this box, but by specifying a CroppedAspectRatio, we can force them to choose an image that is the shape we’re expecting.  I used 16:9 in my example, but you could also do 1:1 to require a square image.  Without this option, the user is not forced to crop, but has the option to crop it any size, from the bottom menu options:

21-XAML-CropMenu

Which, if they choose Crop, will allow them to select any portion of the captured image:

21-XAML-NoRatio

Ultimately, we’re waiting for the user to click the OK button, which will return the image data to our application, letting us then use this image however we’d like.  Here’s a quick video of that entire process:

 

And here’s the code I’m using to make all of this happen:

CameraCaptureUI camera = new CameraCaptureUI();
camera.PhotoSettings.CroppedAspectRatio = new Size(16, 9);
StorageFile photo = await camera.CaptureFileAsync(CameraCaptureUIMode.Photo);

if (photo != null)
{
    BitmapImage bmp = new BitmapImage();
    IRandomAccessStream stream = await photo.OpenAsync(FileAccessMode.Read);
    bmp.SetSource(stream);
    ImageSource.Source = bmp;
    ImageSource.Visibility = Visibility.Visible;
}

As you can see, our first three lines are there, and then I check to see if we had actual data returned to us.  If we do, we read the data into a BitmapImage object, and set that as the source of an Image control I have on my page named ImageSource.  Finally, I turn the visibility of that Image control on, so that the user can see the image.  That’s pretty much all you need to do to capture an image in Windows 8 using C# and XAML.  Next, we’ll go though the same process for video.

Capturing a Video

Thankfully, video is practically identical to video.  There are a few differences related to how we interact with the data once it’s been returned, but in general, the process is very similar.

When we were talking about capturing photos, I didn’t dive too deep into the additional options that were available because they were limited (you can decide between JPG or PNG files, for example).  With video, however, I thought I’d mention a few of them.  So, here’s my code for capturing video, with a few additional options sprinked in:

CameraCaptureUI videocamera = new CameraCaptureUI();
videocamera.VideoSettings.Format = CameraCaptureUIVideoFormat.Mp4;
videocamera.VideoSettings.AllowTrimming = true;
videocamera.VideoSettings.MaxDurationInSeconds = 30;
videocamera.VideoSettings.MaxResolution = CameraCaptureUIMaxVideoResolution.HighestAvailable;

StorageFile video = await videocamera.CaptureFileAsync(CameraCaptureUIMode.Video);

if (video != null)
{
    IRandomAccessStream stream = await video.OpenAsync(FileAccessMode.Read);
    VideoSource.SetSource(stream, "video/mp4");
    VideoSource.Visibility = Visibility.Visible;
}

Let’s talk about a few of these things.  First, we can specify between MP4 video or WMV.  You won’t see a difference between the two formats until you try to share that video with other systems and platforms.  MP4 is far more widely used than WMV, but it’s also a larger format, so that’s worth considering.

Second, I’ve turned on “AllowTrimming” which allows the user to select the segment of their recorded video that they would like to use.  They’ll get a user interface similar to this, where they can drag either end of the video using the white crop circles:

21-XAML-VideoCrop

Third, I’ve specified a MaxDurationInSeconds, which limits the maximum length of video a user can provide.  If they record more than that duration, they’ll be able to select which 30 seconds (or the duration you specify) that they’d like to use.  I can see this being useful for businesses like Match.com where people make profile videos, but they don’t want them to run on forever.

Finally, I’ve also specified a MaxResolution.  This allows us to manage the resolution that the user can record with, which can also help with the final file size we end up with.  You have four choices:

21-XAML-ResolutionList

Otherwise, we end up with a very similar result to our camera efforts.  I set the source of my MediaElement, VideoSource to our new data stream, and turn the visibility of that MediaElement to Visible.

Summary

In short, capturing photos and videos from your user’s camera is very easy, as well as customizable.  Most of the complicated UI is handled directly by Windows 8, so we don’t have to worry about most of the interaction with the user.  I’ve provided screenshots and a video in this article, but the best way to get a feel for this process is to work with a real application.

To download a working solution that uses all of the code from this article, click the icon below:

downloadXAML

Tomorrow, we’re going to play with a very cool technology called Play To.  It allows us to stream media from a device to televisions, Xbox 360 devices, and more.  See you then!

downloadTheTools

31 Days of Windows 8 | Day #20: Printing

This article is Day #20 in a series called 31 Days of Windows 8.  Each of the articles in this series will be published for both HTML5/JS and XAML/C#. You can find additional resources, downloads, and source code on our website.

advertisementsample4

Today, we’re going to talk about printing from your Windows 8 app.  In my past as a software developer, the only time I’ve ever been concerned with printing was from a web page.  It was a simple call: window.print() and several things happened:

  • A print dialog box was opened with options for the user.
  • A print preview was created.
  • Pagination options were available based on the actual “length” of my page.

Despite all of my experience with XAML over the years, printing is not something I have had to do.  As such, I was surprised at how much code is necessary to print a simple page, but I certainly understand why it happens.  We will have to create all of those things from code, and for good reason.

Our pages are not laid out for the size or shape of paper.

Therefore, we are going to need to supplement a “printable” version of our page behind the scenes to make this easier for ourselves.  For the example today, I’m using a simple page with some RichTextBlock controls, but your page can be as complex as you’d like.  Just optimize it for the shape of a sheet of paper.

Additionally, writing this article brought me to the realization that printing from XAML/C# apps in Windows 8 is just hard.  Not only will we be handling all of the steps required to generate print previews and the like, we also have to manually handle all of the pagination required to make our document print appropriately.  This is definitely a situation where the HTML5/JS crew has an advantage over us, and it made me physically angry about how convoluted this process really is.

Making Printing Available From Your App

If you’ve tried to print already, you’ve probably discovered that it’s not possible from your base Windows 8 application.  We have to create a PrintManager object on our page first.  This is not something we can do at the application level, so each page that you might want to print from will have to repeat this code.  (I’m putting this inside my OnNavigatedTo and OnNavigatingFrom events…you can put this logic behind a click if you’d prefer.)

PrintManager manager = PrintManager.GetForCurrentView();
manager.PrintTaskRequested += manager_PrintTaskRequested;
 
 

By adding this code (and the subsequent event method, PrintTastRequested) to our page, it takes our user’s device charm menu from this:

20-XAML-Unprintable

To this:

20-XAML-Printable

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

I’m currently writing this article from a location where I don’t have any printers available to me, but the same print options are available to us when “printing” to OneNote or the Microsoft XPS Document Writer.  Using those is a perfectly acceptable test, and it’s environmentally friendly.  When working on printing for your app, please use one of these options over printing many, many pieces of paper.  It’s just wasteful, and the electronic documents will look exactly the same.

Making Printing Happen in 27 Easy Steps

If you added the two lines of code above and attempted to print, you were probably disappointed.  First, you couldn’t run the app.  We didn’t build the event handler yet.  Second, if you did create the event handler, you got an error the moment you tapped a printer to use. 

First, we have a couple of global variables we’ll be using:

PrintDocument document = null;
IPrintDocumentSource source = null;
List<UIElement> pages = null;
FrameworkElement page1;
protected event EventHandler pagesCreated;
protected const double left = 0.075;
protected const double top = 0.03;
 

Let’s explain what each of these are for now, as we’ll see them in much of the code we have left to write.

  • PrintDocument – this is the actual document object we’ll be adding our content to.
  • IPrintDocumentSource – this is the source of our PrintDocument object.
  • List<UIElement> – this will be where we store the individual pages of our printable document after we have determined the pagination.
  • FrameworkElement – this will hold our first page of our document.
  • protected event – this will be used to determine if we have actually created any pages in this process.
  • left, top – these constants will be used to define a margin in our page, so the content doesn’t run right to the edge of our page.

Let’s first look at my OnNavigatedTo event handler, where I get everything set up:

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    document = new PrintDocument();
    source = document.DocumentSource;
 
    document.Paginate += printDocument_Paginate;
    document.GetPreviewPage += printDocument_GetPreviewPage;
    document.AddPages += printDocument_AddPages;
 
    PrintManager manager = PrintManager.GetForCurrentView();
    manager.PrintTaskRequested += manager_PrintTaskRequested;
 
    pages = new List<UIElement>();
 
    PrepareContent();
}
 
 

First, we instantiate our document and source objects, and then create three new event handlers for our document, Paginate, GetPreviewPage, and AddPages.  Finally, we create our PrintManager (the snippet from earlier), and as two last steps, we create our pages List<> and call a method PrepareContent().  We will revisit PrepareContent a little later.  For now, we have to look at first event handler.

void manager_PrintTaskRequested(PrintManager sender, PrintTaskRequestedEventArgs args)
{
    PrintTask task = null;
    task = args.Request.CreatePrintTask("Simple Print Job", sourceRequested =>
    {
        sourceRequested.SetSource(source);
    });
}
 

When the user selects a printer, the method above will be fired.  Inside, we create a PrintTask object, where we assign it a name, and set its source to our source object that we created earlier.  The name is the actual value that will appear when you user looks in their print queue, like this:

20-XAML-PrinterQueue

Once a printer has been selected, they will see a new window that looks like this:

20-XAML-PrintPreviewMissing

You should notice a couple of things:

  1. We haven’t even specified what to print yet.
  2. The Print Preview box seems to be struggling to show something.

Let’s fix the first problem, which is a lack of content.  Here’s my PrepareContent() method from earlier:

private void PrepareContent()
{
    if (page1 == null)
    {
        page1 = new PageForPrinting();
        StackPanel header = (StackPanel)page1.FindName("header");
        header.Visibility = Windows.UI.Xaml.Visibility.Visible;
    }
 
    PrintContainer.Children.Add(page1);
    PrintContainer.InvalidateMeasure();
    PrintContainer.UpdateLayout();
}
 

There are several important things happening in this method.  First, we’re assigning a new object, PageForPrinting to our page1 element.  PageForPrinting is actually a completely seperate XAML file in our application that is a formatted page for printing.  Second, you’ll see that we’re actually reaching into PageForPrinting, and looking for an element named “header.” (This is entirely optional, but it’s a useful illustration.) This is a StackPanel in our formatted page that is only displayed when the page is printed.  This is a great way to add custom headers and footers to pages, especially when you don’t want them shown when the page is displayed on-screen.  Finally, you can see that we’re adding our page1 to an element named PrintContainer.  We actually want to commit our page to the existing visual tree to force it to go through layout so that all of the containers distribute the content within them.

Here’s what our sample PageForPrinting.xaml file looks like:

<Page
    x:Class="Day20_Printing.PageForPrinting"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:Day20_Printing"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">
 
    <Grid x:Name="printableArea">
 
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition Height="3*"/>
            <RowDefinition Height="3*"/>
            <RowDefinition Height="4*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="6*" />
            <ColumnDefinition Width="4*"/>
        </Grid.ColumnDefinitions>
 
        <StackPanel x:Name="header" Grid.Row="0" Grid.ColumnSpan="2" Height="60"  Visibility="Collapsed">
            <StackPanel Orientation="Horizontal" >
                <Image Source="ms-appx:///Assets/StoreLogo.png" HorizontalAlignment="Left" Stretch="None"/>
                <RichTextBlock Foreground="Black" FontSize="20" TextAlignment="Left" FontFamily="Segoe UI">
                    <Paragraph>Day #20 - Printing</Paragraph>
                </RichTextBlock>
            </StackPanel>
        </StackPanel>
 
        <RichTextBlock Foreground="Black" x:Name="textContent" FontSize="18" Grid.Row="1"  Grid.ColumnSpan="2" OverflowContentTarget="{Binding ElementName=firstLinkedContainer}" 
                       IsTextSelectionEnabled="True" TextAlignment="Left" FontFamily="Segoe UI" VerticalAlignment="Top" HorizontalAlignment="Left">
            <Paragraph FontSize="32">Lorem ipsum dolor sit amet, consectetur</Paragraph>
            <Paragraph></Paragraph>
            <Paragraph >Sed convallis ornare velit et interdum. Donec sapien neque, aliquet consequat convallis at, interdum et enim. Donec iaculis, lectus vel pulvinar cursus, lectus diam interdum ante, a rhoncus tortor quam porta metus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Etiam pulvinar fringilla vestibulum. Pellentesque pharetra nunc in turpis tempus sed faucibus ligula sagittis. Praesent hendrerit est vitae lorem mattis in porttitor urna vestibulum. Phasellus adipiscing aliquam libero ac adipiscing. In a erat sit amet erat sollicitudin bibendum id vitae dui. Vestibulum non consequat nisl. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris elit nisi, blandit et porttitor quis, malesuada nec mi.</Paragraph>
            <Paragraph></Paragraph>
            <Paragraph >Aliquam erat volutpat. In non urna ut libero ultricies fringilla. Proin tellus neque, aliquam lacinia consequat at, vulputate et arcu. Maecenas odio nunc, lobortis sit amet pulvinar sit amet, accumsan et leo. Suspendisse erat lectus, commodo ac auctor eget, rutrum in mi. Suspendisse potenti. Proin ac elit non lacus rutrum mollis. Vivamus venenatis, tellus vel placerat lacinia, arcu ligula dignissim orci, consectetur consectetur eros massa vel nulla. Quisque malesuada iaculis ornare. Nullam tincidunt accumsan egestas. Mauris sit amet scelerisque arcu. Proin euismod sodales magna faucibus commodo. Nam in fringilla orci. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.</Paragraph>
            <Paragraph></Paragraph>
            <Paragraph >Sed eget nunc quis tellus interdum aliquet. Suspendisse rhoncus malesuada nisi a imperdiet. Suspendisse ullamcorper mi sed purus tristique interdum. Mauris lobortis, ante ultrices varius consequat, eros ante hendrerit enim, vulputate convallis dui ligula eget velit. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec eget lectus fermentum nisi consequat dictum. Sed volutpat justo non purus semper vel pretium enim molestie. Nam consectetur, lectus quis feugiat malesuada, neque nunc faucibus velit, nec vehicula risus est id sapien. Vestibulum ut metus massa, ut placerat lacus. Fusce condimentum vehicula tortor, nec vestibulum ligula iaculis ut. Nulla facilisi. Phasellus tincidunt scelerisque erat, ut fermentum urna pretium eu. Donec ut nibh orci. Curabitur sodales metus dictum mauris varius vitae mollis tellus pulvinar. Quisque facilisis ligula sed risus laoreet non lacinia odio luctus. Nam lobortis rhoncus felis vitae ultrices.</Paragraph>
            <Paragraph></Paragraph>
            <Paragraph>Aliquam erat volutpat. In non urna ut libero ultricies fringilla. Proin tellus neque, aliquam lacinia consequat at, vulputate et arcu. Maecenas odio nunc, lobortis sit amet pulvinar sit amet, accumsan et leo. Suspendisse erat lectus, commodo ac auctor eget, rutrum in mi. Suspendisse potenti. Proin ac elit non lacus rutrum mollis. Vivamus venenatis, tellus vel placerat lacinia, arcu ligula dignissim orci, consectetur consectetur eros massa vel nulla. Quisque malesuada iaculis ornare. Nullam tincidunt accumsan egestas. Mauris sit amet scelerisque arcu. Proin euismod sodales magna faucibus commodo. Nam in fringilla orci. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.</Paragraph>
            <Paragraph></Paragraph>
            <Paragraph >Sed eget nunc quis tellus interdum aliquet. Suspendisse rhoncus malesuada nisi a imperdiet. Suspendisse ullamcorper mi sed purus tristique interdum. Mauris lobortis, ante ultrices varius consequat, eros ante hendrerit enim, vulputate convallis dui ligula eget velit. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec eget lectus fermentum nisi consequat dictum. Sed volutpat justo non purus semper vel pretium enim molestie. Nam consectetur, lectus quis feugiat malesuada, neque nunc faucibus velit, nec vehicula risus est id sapien. Vestibulum ut metus massa, ut placerat lacus. Fusce condimentum vehicula tortor, nec vestibulum ligula iaculis ut. Nulla facilisi. Phasellus tincidunt scelerisque erat, ut fermentum urna pretium eu. Donec ut nibh orci. Curabitur sodales metus dictum mauris varius vitae mollis tellus pulvinar. Quisque facilisis ligula sed  risus laoreet non lacinia odio luctus. Nam lobortis rhoncus felis vitae ultrices.</Paragraph>
        </RichTextBlock>
        <RichTextBlockOverflow x:Name="firstLinkedContainer" OverflowContentTarget="{Binding ElementName=continuationPageLinkedContainer}" Grid.Row="2" Grid.Column="0"/>
        <RichTextBlockOverflow x:Name="continuationPageLinkedContainer" Grid.Row="3" Grid.ColumnSpan="2"/>
        <Image Source="ms-appx:///Assets/Logo.png" x:Name="scenarioImage"  HorizontalAlignment="Right" Grid.Row="2" Grid.Column="1" Margin="10"/>
 
        <StackPanel x:Name="footer"  Grid.Row="4" Grid.Column="0" VerticalAlignment="Top" Visibility="Collapsed">
            <Image Source="ms-appx:///Assets/StoreLogo.png" HorizontalAlignment="Left" Stretch="None"/>
            <RichTextBlock Foreground="Black" FontSize="16" TextAlignment="Left" FontFamily="Segoe UI">
                <Paragraph>Copyright © 31 Days of Windows 8.  But please reuse this.</Paragraph>
            </RichTextBlock>
        </StackPanel>
    </Grid>
</Page>

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

We will be using the RichTextBlockOverflow elements link page breaks.  There is nothing changed in the code behind file for this page, but if you were loading this content dynamically, there certainly would be.

At this point, we have handled the user’s choice of printer, and we’ve pulled in a formatted page for printing.  Our next step is to handle the other three event handlers that we’ve created, starting with the Paginate one:

void printDocument_Paginate(object sender, PaginateEventArgs e)
{
    pages.Clear();
    PrintContainer.Children.Clear();
 
    RichTextBlockOverflow lastRTBOOnPage;
    PrintTaskOptions printingOptions = ((PrintTaskOptions)e.PrintTaskOptions);
    PrintPageDescription pageDescription = printingOptions.GetPageDescription(0);
 
    lastRTBOOnPage = AddOnePrintPreviewPage(null, pageDescription);
 
    while (lastRTBOOnPage.HasOverflowContent && lastRTBOOnPage.Visibility == Windows.UI.Xaml.Visibility.Visible)
    {
        lastRTBOOnPage = AddOnePrintPreviewPage(lastRTBOOnPage, pageDescription);
    }
 
    if (pagesCreated != null)
    {
        pagesCreated.Invoke(pages, null);
    }
 
    PrintDocument printDoc = (PrintDocument)sender;
 
    printDoc.SetPreviewPageCount(pages.Count, PreviewPageCountType.Intermediate);
}

 

Each time that this method is called, we want to clear our “pages” list, as well as anything we may have committed to the PrintContainer object that is on our MainPage.  (It’s just a canvas with an Opacity set to zero, so that we can add content to it, but the user doesn’t see it.)

We’re also creating a RichTextBlockOverflow object to keep track of which page we’re currently on.  Finally, we’re calling a method called AddOnePrintPreviewPage.  This method does all of the heavy lifting for us, calculating the size of the page, setting margins, and saving them to the pages List<>.  You’ll also see that we activate the footer in our page as well.

private RichTextBlockOverflow AddOnePrintPreviewPage(RichTextBlockOverflow lastRTBOAdded, PrintPageDescription printPageDescription)
{
    FrameworkElement page;
    RichTextBlockOverflow link;
 
    if (lastRTBOAdded == null)
    {
        page = page1;
        StackPanel footer = (StackPanel)page.FindName("footer");
        footer.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
    }
    else
    {
        page = new ContinuationPage(lastRTBOAdded);
    }
 
    page.Width = printPageDescription.PageSize.Width;
    page.Height = printPageDescription.PageSize.Height;
 
    Grid printableArea = (Grid)page.FindName("printableArea");
 
    double marginWidth = Math.Max(printPageDescription.PageSize.Width - printPageDescription.ImageableRect.Width, printPageDescription.PageSize.Width * left * 2);
    double marginHeight = Math.Max(printPageDescription.PageSize.Height - printPageDescription.ImageableRect.Height, printPageDescription.PageSize.Height * top * 2);
 
    printableArea.Width = page1.Width - marginWidth;
    printableArea.Height = page1.Height - marginHeight;
          
    PrintContainer.Children.Add(page);
    PrintContainer.InvalidateMeasure();
    PrintContainer.UpdateLayout();
 
    // Find the last text container and see if the content is overflowing
    link = (RichTextBlockOverflow)page.FindName("continuationPageLinkedContainer");
 
    // Check if this is the last page
    if (!link.HasOverflowContent && link.Visibility == Windows.UI.Xaml.Visibility.Visible)
    {
        StackPanel footer = (StackPanel)page.FindName("footer");
        footer.Visibility = Windows.UI.Xaml.Visibility.Visible;
    }
 
    // Add the page to the page preview collection
    pages.Add(page);
 
    return link;
}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

If another RichTextBlockOverflow element is found, it is returned to our calling method, and we loop through this process until we’ve added all of the pages to our List<>.

Next, we need to add those pages to our original PrintDocument object, and we do this with the AddPages event we registered at the beginning.  We simply loop through all of the pages that were created in our Paginate method.

void printDocument_AddPages(object sender, AddPagesEventArgs e)
{
    for (int i = 0; i < pages.Count; i++)
    {
        document.AddPage(pages[i]);
    }
 
    PrintDocument printDoc = (PrintDocument)sender;
    printDoc.AddPagesComplete();
}
 

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

 

When that Print Preview box loads on the user’s screen, the GetPreviewPage is fired.  This allows us to provide the first page of the set to the Print Preview window:

void printDocument_GetPreviewPage(object sender, GetPreviewPageEventArgs e)
{
    PrintDocument printDoc = (PrintDocument)sender;
    printDoc.SetPreviewPage(e.PageNumber, pages[e.PageNumber - 1]);
}
 

This is the last step.  Once we provide our preview pages, we’re done.  Your user should now see a menu like this:

20-XAML-PrintPreviewWorking

And if they click Print when using the Microsoft XPS Document Writer, they end up with a file in their Documents folder:

20-XAML-DocumentsFolder

Finally, when we open that file, we can see that our formatted content has been spread over two pages in this new document, just as it would have been on a printer.

 20-XAML-FinalProduct

Summary

In short, this is a really messy, difficult process that seems like it HAS to be simpler than it is.  Sadly, I’ve made this one about as simple as it gets for now.  I think I am going to revisit this problem in the future, and build out a PrintHelper class that just allows you to build a page, formatted with a few simple rules, and call a method to make all of this magic happen.  Very few people will build apps that print if it isn’t made to be easier.

If you’d like to download a working sample of the code shown in this article, click the icon below (I would HIGHLY recommend it for this topic):

downloadXAML

Tomorrow, we’re going to touch on a more useful (and far simpler to implement) subject: capturing data from the camera.  See you then!

downloadTheTools

31 Days of Windows 8 | Day #19: File Picker

This article is Day #19 in a series called 31 Days of Windows 8.  Each of the articles in this series will be published for both HTML5/JS and XAML/C#. You can find additional resources, downloads, and source code on our website.

advertisementsample

Today, we are going to look at another invaluable tool in Windows 8 development, the File Picker.  There are millions of ways to use the File Picker, but when you boil it down, you’re going to need to get files from the user’s computer for something.  This article will show you how to do it.

Updating Your Package.Appxmanifest

As with all of the different mechanisms we’ve used to interact with the user’s system, we need to start with updating our package.appxmanifest file.  Since we are going to be both opening files AND saving files in this article, we need to add both of those Declarations to our file.

For the File Open Picker, you’ll start with a form that looks like this:

19-XAML-FileOpenManifestBroken

You’re expected to make one of two choices:

  • Supports any file type.  By checking this box, you are declaring that you could access any file type from your application.
  • Supported file type.  You can also specify which file types you want to open, so if your app only opens Excel files, for example, you could list “.xls” as your file type.

For this demo, you can just select “Supports any file type.”  The same process can be done for the File Save Picker, which offers you the same choices, like this:

19-XAML-FileSaveManifestBroken

Getting a File from Your User’s Machine

This article is going to build a progressively more complex app for selecting files from a user’s machine.  We’ll start getting just one file, then multiple files, and then multiple files of only a specific file type (.png).  Most of the code for each example is identical, so we’ll focus on what is the “new” code in each step.

For selecting a single file from the user’s hard drive, we are going to start with the FileOpenPicker object.  This is the object that will open the File Picker dialog for the user, help them select one or many files, and return them to us.  It has many options, and we’ll work our way through all of them.  For selecting a single file, of any type, the code starts quite simply:

FileOpenPicker picker = new FileOpenPicker();
picker.FileTypeFilter.Add(".xls");
StorageFile file = await picker.PickSingleFileAsync();

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

 

You’ll notice that we have to set a FileTypeFilter when we create a FileOpenPicker.  You are not allowed to provide a wildcard, or just ignore the filter.  You have to provide at least one, but you can also provide several, which means you can list 8-10 common file types (especially when you’re looking for images on the user’s device, because they could be PNG, JPG, JPEG, GIF, BMP, etc.) When you run this, your user will see an interface that looks like this:

19-XAML-FilePicker

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

This is the File Picker interface, and once the user selects a file, it will be returned to your application as a StorageFile, which we have worked with in several of the previous articles in this series.

Next, we might have a good idea as to where the user should start looking for the files they would want to open in your app.  Maybe it’s their Photos collection, or Music.  We can specify one of 8 locations on their computer:

19-XAML-PickerLocationId

So, to get the File Picker to open in one of these locations, you need to add one line of code to your method, so that it looks like this (you can see I’ve added a few new filters as well):

FileOpenPicker picker = new FileOpenPicker();
picker.FileTypeFilter.Add(".png");
picker.FileTypeFilter.Add(".jpg");
picker.FileTypeFilter.Add(".gif");
picker.FileTypeFilter.Add(".bmp");
picker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
StorageFile file = await picker.PickSingleFileAsync();
 

This time the File Picker will launch directly to the location you’ve specified (in my example, it is the Pictures Library):

19-XAML-FilePicker-Location

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

The last option we can manipulate relates to how the File Picker will display the files.  Simply use the ViewMode property of the FileOpenPicker object, like this:

picker.ViewMode = PickerViewMode.List;
 

Your options are limited to List or Thumbnail, but they look distinctly different.  Thumbnail ONLY shows a square image representing the file.  List shows an icon as well, but also shows some of the metadata for the files.  Here’s a look at the same folder with each view (click to enlarge):

List

19-XAML-List

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Thumbnail

19-XAML-Thumbnail

Retrieving Multiple Files From Your User’s Computer

Sometimes, we want to grab more than one file at a time.  In this case, we can use a different method call on our FileOpenPicker object.  This time, we’ll use the PickMultipleFilesAsync() method.

FileOpenPicker picker = new FileOpenPicker();
picker.FileTypeFilter.Add(".png");
picker.FileTypeFilter.Add(".jpg");
picker.FileTypeFilter.Add(".bmp");
picker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
picker.ViewMode = PickerViewMode.Thumbnail;
IReadOnlyList<StorageFile> files = await picker.PickMultipleFilesAsync();
 

So, more or less, there’s very little difference between selecting one or multiple files from a code perspective.  We do receive a collection of StorageFile objects rather than just one, but that should be expected when we’re enabling multiple files to be selected.

What is different, at least from what I can tell, is HOW the user can select multiple files.

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

19-XAML-MultipleFileSelection

As you can see from the image above (click to enlarge), not only can we select multiple files, but we can also see that there is a “basket” of sorts at the bottom of the File Picker interface.  This shows a list of the files that the user has selected.  What it also enables, however, is a way for a user to select multiple files at once, from completely different folders.  Your users will be able to select a few files from one folder, and a few files from another, and they’ll all be provided to your application at once, without your user having to make multiple trips to those folders.  This is an absolutely awesome feature that should not be overlooked.

In our final section of this article, let’s look at how we can use the File Picker to also save files to a user’s machine.

Saving a File To Your User’s Hard Drive

One of the related operations we may want to use is to save a file to your user’s machine.  This is different from what we talked about on Day #8 of this series, where we were saving files internal to our application.  In this instance, we are saving a permanent file that will continue to live on the user’s machine, even if our app is uninstalled.  If your files are unusable by other apps, this is likely NOT the way you’d want to store your files.  I generally recommend saving files like this for file types that could be opened by a variety of applications.  To do this, it looks very similar to our FileOpenPicker, but it’s now a FileSavePicker.

The other diffference with this situation is that we are writing data to the user’s hard drive.  This means we have all of the other hassles that come with it.  Files can be open already, they can be edited by other apps, or even deleted as we’re working with them.  So we will first launch the File Save Picker to determine where the user wants to save their file (as well as what file type and name they want to give it.  This looks like this:

19-XAML-FileSave

And here’s what the code looks like to make this entire process happen:

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

FileSavePicker saver = new FileSavePicker();
saver.SuggestedStartLocation = PickerLocationId.Desktop;
saver.FileTypeChoices.Add("Text File", new List<string>() { ".txt" });
saver.FileTypeChoices.Add("Microsoft Excel", new List<string>() { ".xls" });
saver.FileTypeChoices.Add("Image", new List<string>() { ".png", ".jpg", ".bmp" });
saver.SuggestedFileName = "PeriodicTableOfTheElements";
StorageFile file = await saver.PickSaveFileAsync();
 
if (file != null)
{
    CachedFileManager.DeferUpdates(file);
    await FileIO.WriteTextAsync(file, "This is a link to the Periodic Table of the Elements.  http://www.ptable.com/  You didn't expect to find all of the contents here, did you?");
    FileUpdateStatus status = await CachedFileManager.CompleteUpdatesAsync(file);
}
 

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

In this case, you can see that the beginning of our code sample doesn’t look that different.  Instead of filters, we can add FileTypeChoices which function as the different formats our user might want to save their file.  The choices I’ve included don’t really make much sense together, but it helps to illustrate that you can specify any file types (or types) in this list.  It’s at your full control.

In addition, once the user has selected a location and name for their file, we’re not done.  If they actually returned those values to use, our next step is to actually write the file to the drive itself.  Inside our if statement, we use the DeferUpdates() method to prevent any additional edits to the file that we’re working with.  They will be “deferred” until after our operation is complete.  Next, we write the contents of our file.  Finally, we commit those changes using the CompleteUpdatesAsync() method.  This is where all of the work is actually completed.

You’ll find that if the file already exists, you’ll also get prompted with a “Replace the existing file?” dialog box.

19-XAML-ReplaceDialog

Ultimately, that’s all it takes to save a simple file to the user’s system.  For more complex operations and file types, you’re going to want to explore the FileIO class.  You can read more about that on MSDN.

Selecting a Folder on the User’s Machine

The last topic I want to cover in this article is also very related to the others: selecting a folder from the user’s machine instead of a specific file.  This is great for allowing the user to choose a default save location for the future, or perhaps a place for you to look for files in the future.  What’s great about this process is that once the user has selected a folder, we can save it as our default folder, and we are granted explicit rights to read and write to/from this folder in the future without having to request future access.  Here’s how we do it.

FolderPicker picker = new FolderPicker();
picker.FileTypeFilter.Add(".xls");
StorageFolder folder = await picker.PickSingleFolderAsync();
if (folder != null)
{
    StorageApplicationPermissions.FutureAccessList.AddOrReplace("DefaultFolder", folder);
}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

As you can see, our first three lines of code look very familiar.  However, if we confirm that an actual folder was returned to us, we can save this to the StorageApplicationPermissions.FutureAccessList as a place we have been granted permission to use.  Saving it this way, and referring to it in the future will limit the number of times you have to ask your user for permission to save a file to their system.

Summary

Today, we looked at several different ways to help our users interact with the files that live on their machine.  We saw how to open single and multiple files, save files back to the hard drive, and even select default save locations for future use.  You’ll likely find yourself using these methods over and over in your apps, so this is an important lesson to learn.

If you would like to download the sample code that was discussed in this article, click the icon below:

downloadXAML

Tomorrow, we’re going to take a look at another common feature you’re likely to run into: Printing.  We’ll look at how to communicate with a user’s printers, as well as how we register with the system to make printing even easier.  See you then!

downloadTheTools

31 Days of Windows 8 | Day #18: File Associations & App Contracts

This article is Day #18 in a series called 31 Days of Windows 8.  Each of the articles in this series will be published for both HTML5/JS and XAML/C#. You can find additional resources, downloads, and source code on our website.

advertisementsample

Today, we are going to look at a couple of cool features in Windows 8: File Associations and App Contracts.  With file associations, we’ll look at both sides of this Windows 8 feature:

  • Registering our app with Windows 8 as an app that opens files of a certain type, like .png, or a custom extension, .31days.
  • Empowering our app to suggest a list of compatible applications when our user tries to open a file that our app doesn’t support, like an .xls file.

Finally, we’ll also discuss some of the other ways our application can register with the user’s system, including things like AutoPlay.

Registering to Open Certain File Types

There’s going to be times that we want to have our user open files of a certain type in our application.  Maybe we are creating an image editor, so no matter what type of image format the user’s files are in, if they’re a common image type, we should be able to open it.  Perhaps we’re building an XML editor that consumes .xml files.  In either case, we want to register our app with the user’s system, so that when they get a prompt like this:

18-XAML-OpenDialog

We want to make sure that our app is in this list when a user opens files appropriate for our application.  To do this, it’s as simple as making some simple changes to our package.appxmanifest file.  Open the file, and choose the declarations tab.  From the “Available Declarations” selection list, choose “File Type Associations,” and click Add.

You will see that it fills the screen with a new form for specifying the file associations you would like to register your app with.  By default, it has a few red Xs that you will need to address.

18-XAML-DeclarationsBroken

For this sample, I am going to pick two different file types.  The first will be a custom extension that files created by my app will use: “31days".  The other will be to register with the “png” extension, so we can try to open images with this application as well.

18-XAML-DeclarationsFixed

The first value, Name, is just a name for the set of extensions you are listing here.  The reason that we can have different sets of extensions is because you might want your app to act differently depending on the file type.  At the bottom of the form, you can specify the entry point for your application for this set of files.  If it’s an XML file, you might want to take them directly to your text editor page.  If it’s an image, perhaps you’d rather take them to an image editing page, or just attach it to an email.  That’s up to you.

Once you’ve done that, you’re basically done.  Run the application once, to install it on your system, and then try to do an “Open With” for the specific file types you used.  The easiest way to test this is to jump to your Desktop, and create a new Text File.  I named mine Blankenburg.31days as the file name.  First, you’ll get the standard warning for renaming files:

18-XAML-FileRenameWarning

Then, you’ll get an icon that looks like all of the default icons that come with a default project.  The only problem is, I don’t have an image that looks like that in my project anymore, so I’m not sure where this one comes from.  Here’s my file sitting on the desktop:

18-XAML-FileNoLogo

That’s not going to work.  We want our logo or icon to appear when we are the default application for a file type.  Just as Excel files have an Excel icon, so too should .31days files have a 31 Days icon.  To do this, jump back into your package.appxmanifest file, and specify a logo in the section where you specified name, like this:

18-XAML-DeclarationsLogo

You can use any icon you’d like, but they do have a square aspect ratio, so I chose my Logo.png file which has the same ratio.  You’ll end up with files that look like this (make sure to run your app again to update your system):

18-XAML-FileLogo

In addition, when we right-click on this file and choose “Open With…” we get this menu now:

 18-XAML-31DaysExtensionDialog

You can see that we’ve found yet another location where our SmallLogo.png file is being used by default, with no place to change it.  Finally, since we also registered our app for “.png” files, let’s take a look at what opening one of those looks like:

18-XAML-PNGAssociation

So, for step one of our article, we’ve demonstrated how to set file associations for your application in Windows 8.  In the next section, we’re going to look at how we prompt the user to open a file from our app to another app.

Opening a File In Another App

Sometimes, your application just can’t open every single file type.  For example, Microsoft Outlook is really good at accepting images, text, and files, but if you’ve ever tried to open an Excel file attached to an email, you’ve probably noticed (and come to expect) that the file gets opened in Excel, not Outlook.  We want to provide that same functionality to our users.  To do this, we are going to explore the Windows.System.Launcher class.

In our first simple example, we’re just going to launch our file (the Excel file from Day #17) without any options or settings.  The system will use the default app to launch our file, without any choice or warning.

Uri uri = new Uri("ms-appx:///Assets/PeriodicTable.xls");
StorageFile sf = await StorageFile.GetFileFromApplicationUriAsync(uri);
await Launcher.LaunchFileAsync(sf);
 

As you can see in this first example, it’s pretty simple.  We call Launcher.LaunchFileAsync(), and pass it a StorageFile object.  Sometimes, however, you might want to warn your user that you are going to be launching another piece of software, so that they get to decide if that’s really what they want to do.  In this case, we can create a LauncherOptions object, and provde that as part of our LaunchFileAsync() call.  Like this:

Uri uri = new Uri("ms-appx:///Assets/PeriodicTable.xls");
StorageFile sf = await StorageFile.GetFileFromApplicationUriAsync(uri);
 
LauncherOptions options = new LauncherOptions();
options.TreatAsUntrusted = true;
 
bool action = await Launcher.LaunchFileAsync(sf, options);
 

When we do this, an alert pops up on the screen letting the user know that another application is being opened.

18-XAML-SwitchingAppsAlert

Finally, there may be a time where you want your user to be able to select the app that they will open a file with.  In this case, we want to bring up the same box that appears when a user selected “Open With…” in the previous section.  To do this, we just set another option: DisplayApplicationPicker.  If you want to modify the position of this box, there’s a little more work, but it’s very similar to what we did on Day #16 with context menus: we need to determine where on the screen to show the box.

If you don’t specify a location, however, it will launch the dialog box centered on the user’s screen.  Personally, I prefer how this looks to having it closer to the control the user clicked, but I understand what the Windows team is trying to do.  Here’s screenshots of the difference (click to enlarge):

Open With Dialog Centered Open With Dialog Positioned
18-XAML-OpenWithCentered 18-XAML-OpenWithPositioned

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }  And here’s the code to make it happen.  (If you don’t want your box to be positioned, which is actually my recommendation, just chop out the pieces that are related to positioning.)

private async void OpenExcelButtonWithOpenWithMenu_Click(object sender, RoutedEventArgs e)
{
    Uri uri = new Uri("ms-appx:///Assets/PeriodicTable.xls");
    StorageFile sf = await StorageFile.GetFileFromApplicationUriAsync(uri);
 
    LauncherOptions options = new LauncherOptions();
    options.DisplayApplicationPicker = true;
    options.UI.InvocationPoint = GetPosition(sender as FrameworkElement);
    options.UI.PreferredPlacement = Placement.Below;
 
    bool action = await Launcher.LaunchFileAsync(sf, options);
}
 
private Point GetPosition(FrameworkElement sender)
{
    GeneralTransform transform = sender.TransformToVisual(null);
    Point location = transform.TransformPoint(new Point());
    location.Y = location.Y + sender.ActualHeight;
 
    return location;
}
 

So there you have it.  Three different ways to launch a file in a separate application from your own.  In the final section of this article, we’re going to look at App Contracts, and how we can make our apps available in other lists for our users.

Using App Contracts for Good

When reading this article, some developers will think that it’s a good idea to register their app for every possible file extension, because it will keep their app and logo in front of their users as often as possible.

PLEASE DON’T DO THAT.

That rule applies to App Contracts as well.  Don’t abuse them, especially if you aren’t actually providing the functionality required.  This will be reviewed when you submit your application to the store, so it will be discovered.

Account Picture Provider

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

You can declare this functionality if you can provide the ability for the user to create a new Account Picture in Windows 8.  You get to the menu on the Start Screen, like this:

18-XAML-AccountPicture

And when your app is registered, the options will look like this, with your app listed:

18-XAML-PersonalizeMenu
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

To do this, just add the Account Picture Provider to your declarations tab in your package.appxmanifest file, like this:

18-XAML-AccountPictureProvider

By default, it will just launch your app, but you can also provide custom entry points, so that the user ends up on the right section of your app automatically.

AutoPlay and Protocols

There are three other App Contracts we can subscribe to, and each of them only really requires another entry in the Declarations.

“AutoPlay Content” recognizes when new media is inserted into the device, like a USB drive or DVD.  If the content type you register for is present, your app will appear in the AutoPlay list that is displayed.  There is an excellent article on MSDN about AutoPlay, and the different values you can use.

“AutoPlay Device” is basically the same idea, but you’re registering for specific types of devices, like cameras, printers, or USB drives.  The same article above applies to this as well.

“Protocol” is a little different.  This allows us to register for URI schemes, like “mailto:” or even use our own custom URI schemes like “31days:.”  It’s another way to register our application with the system to be used at the appropriate times.  Microsoft has an excellent article about protocols as well.

Summary

Today, we looked at some of the extensible points we can access to make our app more prominent and useful to our users.  If they expect to be able to open images with our application, then we should show up in their list of apps when they try to open an image.

If you would like to see the full working solution for the code covered in this article, click the download icon below:

downloadXAML 

Tomorrow, we’re going to look at another powerful mechanism in Windows 8: the File Picker.  This empowers our user to grab files from their hard drive, and provide them to our applications.  See you then!

downloadTheTools