31 Days of Windows 8 | Day #7: Share Contract

HTMLCallout

This article is Day #7 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

day5-charms-side Over the past two days we’ve jumped into this new Windows 8 feature: contracts. We introduced this notion of contracts by exploring how to incorporate Settings into our applications. Then we explored extending our applications presence to users with Search. Today we’re going to take the next step and explore one of the features that gets both Clark and I really excited, Sharing.

Prior to Windows 8, building “social” into apps was hard.  Not only did you have to learn the APIs for the platform your app was targeting, but you also had to learn the APIs for Facebook, Twitter, and however many other social networks you wanted to include.

That’s practically overwhelming, if not nearly impossible to do effectively.

With Windows 8, we only have to worry about building our app.  In fact, putting buttons in your app that say things like “Share on Social Network X” are actually discouraged in this ecosystem.  Why provide a user with a Twitter button when they don’t use Twitter?  Same goes for Google+, Facebook, Flickr, GitHub, or any other socially based platform.

With the Sharing Contract, the user gets to be in full control.  You make your content shareable, and THEY decide where and how it gets shared.  It’s an empowering feeling that is truly one of the highlights of Windows 8 for me.

Sharing Defined

Sharing happens two ways, he who shares ( the source app )  and he who receives ( the target app ). This happens through a broker.

Block diagram showng the components involved in sharing

Image via: http://msdn.microsoft.com/en-us/library/windows/apps/hh758314.aspx 

What types of files can you share? There are 6 different types of content you can share with the broker:

  • Unformatted Plain Text
  • Link
  • Formatted Content / HTML
  • Files
  • Single Image
  • Custom Data Format

Data is shared through an object called the DataPackage.  In this article, we’re going to divide Share Source and Share Target into two separate sections.

Share Source

We are going to start with the simpler of the two scenarios, Share Source.  In this scenario, we are going to show how you can share each of the 7 types of content listed above.  First, we need to create a new Blank App Template, so start there.  In the sample code for this article, you’ll notice that I have created a separate page for each type of data.  You certainly don’t need to do that in your app, but keep in mind that you can only share one type of data at a time from any one page.  Let’s start with Unformatted Text.

Sharing Plain, Unformatted Text

This will be the lengthiest example for Share Source because most of the code is exactly the same for each example.  I’m only going to highlight all of it here, and then I’ll focus on the stuff that is different in the successive examples.

First, we need a reference to the DataTransferManager.  You will also need to add the Windows.ApplicationModel.DataTransfer namespace to your page.  In the code below, you’ll see that I instantiate the reference to the DataTransferManager, and then create an event handler (in the OnNavigatedTo() method) for when data is requested to be shared from this page with the DataRequested() event handler.

DataTransferManager dtm;
 
protected override void OnNavigatedTo(NavigationEventArgs e)
{
    base.OnNavigatedTo(e);
    dtm = DataTransferManager.GetForCurrentView();
    dtm.DataRequested += dtm_DataRequested;
}
 
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
    base.OnNavigatingFrom(e);
    dtm.DataRequested -= dtm_DataRequested;
}
 

You should also notice that we set up the OnNavigatingFrom method to remove the DataRequested event when we leave the page.  This is a best practice that you should incorporate in every one of your pages.  Make sure you cancel any event handlers that you register.

Finally, we need to actually share some text, and we do this through our dtm_DataRequested() method:

void dtm_DataRequested(DataTransferManager sender, DataRequestedEventArgs args)
{
    string textSource = "Testing the Share Source functionality in Windows 8. #31daysofwin8";
    string textTitle = "31 Days of Windows 8!";
    string textDescription = "This just explains what we're sharing.";  //This is an optional value.
 
    DataPackage data = args.Request.Data;
    data.Properties.Title = textTitle;
    data.Properties.Description = textDescription;
    data.SetText(textSource);
}

 

As you can see, we simply create a DataPackage object and set its properties accordingly.  Using the values I’ve provided in code, here’s the prompt the user will see (click to enlarge):

.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; }

7-XAML-UnformattedTextPrompt

Once they choose an application (in this case the Mail app), they’ll see the data populate their chosen App (click to enlarge):

7-XAML-UnformattedTextResult

For the rest of the data types, I will show you the contents of the dtm_DataRequested() method, but that everything else basically stays the same.

Sharing Links

The only difference between a link and unformatted text is that you must specify a valid Uri object to the DataPackage. Here’s the code:

void dtm_DataRequested(DataTransferManager sender, DataRequestedEventArgs args)
{
    Uri linkSource = new Uri("http://31daysofwindows8.com");
    string linkTitle = "31 Days of Windows 8!";
    string linkDescription = "This just explains what we're sharing.";  //This is an optional value.
 
    DataPackage data = args.Request.Data;
    data.Properties.Title = linkTitle;
    data.Properties.Description = linkDescription;
    data.SetUri(linkSource);
}
 

Where the real differences are show up in the UI.  Here’s the initial prompt for sharing:

7-XAML-LinkPrompt

You’ll notice that they promoted the People app above the line, and suggested that I post it to Twitter.  If I actually open the People app, I still have the ability to choose Facebook instead, so it’s not entirely clear to me how they choose Twitter over Facebook here.  Here’s what the mail app does with this data when it’s opened:

7-XAML-LinkResult

What’s cool about this implementation is a few features:

  • The mail app actually jumped out to the web and grabbed a couple of images that it thought would be useful thumbnails.  I’ll have to talk to Clark about why his image is the default. 🙂
  • The Mail app also went out and grabbed the actual <title> from the website and used that in the listing.  Very helpful.
  • In the unformatted text example, the Description property was not used by the Mail app.  In this case, it was embedded with the link.

Let’s take a look at sharing Formatted Content like HTML now.

Sharing Formatted Content / HTML

Here’s a look at our code implementation:

void dtm_DataRequested(DataTransferManager sender, DataRequestedEventArgs args)
{
    string HTMLSource = "<strong>This is bold text,</strong> and this is not.  <a href='http://31daysofwindows8.com'>Check out the 31 Days of Windows 8!</a>";
    string HTMLTitle = "31 Days of Windows 8!";
    string HTMLDescription = "This just explains what we're sharing.";  //This is an optional value.
 
    DataPackage data = args.Request.Data;
    data.Properties.Title = HTMLTitle;
    data.Properties.Description = HTMLDescription;
    data.SetHtmlFormat(HtmlFormatHelper.CreateHtmlFormat(HTMLSource));
}

We had to treat this content slightly differently.  You’ll notice that when we set our content for our DataPackage this time, we had to call the HtmlFormatHelper.  This takes our otherwise ordinary string of characters, and converts it to active, working HTML markup that can be parsed and used.  The initial sharing prompt looks exactly the same as it has, but you can see that our formatted HTML appears in the body of our email message.

7-XAML-HTMLPrompt

7-XAML-HTMLResult

A great application of this would be for creating HTML-formatted email messages.  Creating messages like that are extremely difficult for those folks that don’t already know HTML.  Imagine a simple app that allowed them to create their HTML interface visually, and then they could share it to their Mail client and send it out.  Someone build that.

Sharing Files

One file or many files are treated the same way in this situation, but because we are reading files from storage, there’s also a little extra code that needs to be written.  If you have additional files, you’ll simply add additional logic to grab the additional files asynchronously, and add them to the List<IStorageItem> collection.  We will asynchronously grab the file(s), and when the operation is complete, we will share it.

async void dtm_DataRequested(DataTransferManager sender, DataRequestedEventArgs args)
{
    string FileTitle = "31 Days of Windows 8!";
    string FileDescription = "This just explains what we're sharing.";  //This is an optional value.
 
    DataPackage data = args.Request.Data;
    data.Properties.Title = FileTitle;
    data.Properties.Description = FileDescription;
 
    DataRequestDeferral waiter = args.Request.GetDeferral();
 
    try
    {
        StorageFile textFile = await Package.Current.InstalledLocation.GetFileAsync("FileForSharing.txt");
        List<IStorageItem> files = new List<IStorageItem>();
        files.Add(textFile);
        data.SetStorageItems(files);
    }
    finally
    {
        waiter.Complete();
    }
}

Note that the entire method needed to be decorated with the “async” keyword in order to make this happen.  You will see this over and over in Windows 8 development as the way to handle asynchronous events.  It just makes life so much easier.  If it’s not entirely clear, I created a simple .txt file and added it to my project.  This is the file that I grab and share.  Again, the sharing prompt looks identical to every other instance we’ve had so far, but here’s what it looks like opened in the Mail app:

7-XAML-FileResult

The interesting thing to note in this situation is that previously the Mail app used our Title property as the subject of the email message.  In this example, it doesn’t.  I’m sure that the product team has a list of reasons why this is, but I’d vote for consistency on this one.

Sharing Images

When sharing an image file, you might want to treat it differently than just a basic file, but you also might not.  In this example, we’re actually going to share our image file two different ways, so that the Target apps can use the data that they are interested in.  Apps that are looking for files will be available, as will apps that are just specifically looking for Images.  You can use this handy trick when you’ve got multiple types of data to share as well, not just when the same thing could be shared two different ways.

async void dtm_DataRequested(DataTransferManager sender, DataRequestedEventArgs args)
{
    string FileTitle = "31 Days of Windows 8!";
    string FileDescription = "This just explains what we're sharing.";  //This is an optional value.
 
    DataPackage data = args.Request.Data;
    data.Properties.Title = FileTitle;
    data.Properties.Description = FileDescription;
 
    DataRequestDeferral waiter = args.Request.GetDeferral();
 
    try
    {
        StorageFile image = await Package.Current.InstalledLocation.GetFileAsync("Assets.png");
        data.Properties.Thumbnail = RandomAccessStreamReference.CreateFromFile(image);
        data.SetBitmap(RandomAccessStreamReference.CreateFromFile(image));
 
        List<IStorageItem> files = new List<IStorageItem>();
        files.Add(image);
        data.SetStorageItems(files);
    }
    finally
    {
        waiter.Complete();
    }
}
 

.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 see that we’ve called both the SetBitmap method, as well as the SetStorageItems method.  In either case, the Share prompt looks identical, but the list of apps available are definitely different in each case.  For example, the Mail app doesn’t accept raw images, but it does accept images as StorageItems.  Give this sample code a try.  I was also surprised to see that the People app doesn’t accept either type of sharing.  We’ll talk later in this article about being a Share Target, and how we can accept different kinds of data.

Sharing a Custom Data Format

There are going to be many times where you want to share some kind of custom data.  99% of the time, it’s going to fit a standard schema, like those found on http://schema.org.  You’re certainly not locked into a pre-determined schema, but make sure you understand the guidance Microsoft offers for creating your own custom data formats.

The short story on sharing custom data is that you’re going to specify a DataPackageFormat, but it can be absolutely anything you want.  For most purposes, you’re only likely to share custom data formats between two of your own apps, but standardizing on a standard schema certainly increases your odds of being able to share with other apps as well.  Ultimately, you will specify a string that represents your data schema, and if any other app is a Target for that same specific string of characters, they will show up in the list of potential Targets.

void dtm_DataRequested(DataTransferManager sender, DataRequestedEventArgs args)
{
    string customData = @"{
        ""type"" : ""http://schema.org/Person"",
        ""properties"" :
        {
        ""image"" : ""http://jeffblankenburg.com/images/200x200.png"",
        ""name"" : ""Jeff Blankenburg"",
        ""affiliation"" : ""Microsoft"",
        ""birthDate"" : ""07/22/1976"",
        ""jobTitle"" : ""Senior Technical Evangelist"",
        ""nationality"" : ""United States of America"",
        ""gender"" : ""Male""
        }
    }";
    string linkTitle = "31 Days of Windows 8!";
    string linkDescription = "This just explains what we're sharing.";
 
    DataPackage data = args.Request.Data;
    data.Properties.Title = linkTitle;
    data.Properties.Description = linkDescription;
    data.SetData("http://schema.org/Person", customData);
}

.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 my example above, I am using the schema from http://schema.org/Person, and since none of the other apps on my machine recognize that schema, I get a Share prompt that looks like this:

7-XAML-CustomDataPrompt

As you’ve seen throughout this section, our Title and Description continue to show up in the same place, but this time, there aren’t any apps available to share with.  In the next section, we’re going to look at how we make our app capable of receiving these data types.

Share Target

When I first sat down to write this article, I made Share Target last because I thought that was going the be the more difficult (and lengthy) of the two.  As it turns out, I’m wrong.  Much of the work for being a Share Target falls in your Package.appmanifest file.

Open yours to the Declarations section, and you’re likely to find a barren wasteland.  What we need to do is add the Share Target declaration, by choosing it from the dropdown and clicking the Add button.

7-XAML-AppManifest

Once you add the Share Target, it’s going to light up with a bunch of red X symbols.

7-XAML-AppManifestBroken

Adding a Share Target declaration isn’t enough.  You also need to specify what type of Target your app is going to be.  Thankfully, we can choose any/all of the formats we’ve already covered in this article.  The next screenshot shows my Share Target declarations completely filled out, making my app a Target for nearly everything.

7-XAML-AppManifestComplete

Now, as we get into the code, there’s WAY more going on here than you’ll probably need for your application.  I’ve built a sample application that shares and consumes every possible data type, and as such, it’s got many more lines of code than you will.

First, we need to open our App.xaml.cs file and add a new method to handle when the Share Target is activated.  It is called, not surprisingly, OnShareTargetActivated.

protected override void OnShareTargetActivated(ShareTargetActivatedEventArgs args)
{
    var rootFrame = new Frame();
    rootFrame.Navigate(typeof(MainPage), args.ShareOperation);
    Window.Current.Content = rootFrame;
    Window.Current.Activate();
}

.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; }

All this really indicates to the app is which page you want to have loaded when your application is targeted for sharing.  This will likely be a separate page for you (not MainPage), but for my example, that’s exactly what I’ve used.

Now for the big chunk of logic. I’ve added comments so that you can find the pieces you’re specifically interested in.  Here’s the entire OnNavigatedTo method in my MainPage.xaml file.  Remember, this logic will likely go in a seperate “SearchTarget” page in your application that has the sole responsibility of catching shared data.

ShareOperation share;
string title;
string description;
string text;
string customData;
string customDataFormat = "http://schema.org/People/";
string formattedText;
Uri uri;
IReadOnlyList<IStorageItem> storageItems;
IRandomAccessStreamReference bitmapImage;
IRandomAccessStreamReference thumbImage;
        
 
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
    share = e.Parameter as ShareOperation;
 
    await Task.Factory.StartNew(async () =>
    {
        title = share.Data.Properties.Title;
        description = share.Data.Properties.Description;
        thumbImage = share.Data.Properties.Thumbnail;
 
        //IF THERE WAS FORMATTED TEXT SHARED
        if (share.Data.Contains(StandardDataFormats.Html))
        {
            formattedText = await share.Data.GetHtmlFormatAsync();
        }
        //IF THERE WAS A URI SHARED
        if (share.Data.Contains(StandardDataFormats.Uri))
        {
            uri = await share.Data.GetUriAsync();
        }
        //IF THERE WAS UNFORMATTED TEXT SHARED
        if (share.Data.Contains(StandardDataFormats.Text))
        {
            text = await share.Data.GetTextAsync();
        }
        //IF THERE WERE FILES SHARED
        if (share.Data.Contains(StandardDataFormats.StorageItems))
        {
            storageItems = await share.Data.GetStorageItemsAsync();
        }
        //IF THERE WAS CUSTOM DATA SHARED
        if (share.Data.Contains(customDataFormat))
        {
            customData = await share.Data.GetTextAsync(customDataFormat);
        }
        //IF THERE WERE IMAGES SHARED.
        if (share.Data.Contains(StandardDataFormats.Bitmap))
        {
            bitmapImage = await share.Data.GetBitmapAsync();
        }
 
        //MOVING BACK TO THE UI THREAD, THIS IS WHERE WE POPULATE OUR INTERFACE.
        await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
            {
                TitleBox.Text = title;
                DescriptionBox.Text = description;
 
                if (text != null)
                    UnformattedTextBox.Text = text;
                if (uri != null)
                {
                    UriButton.Content = uri.ToString();
                    UriButton.NavigateUri = uri;
                }
 
                if (formattedText != null)
                    HTMLTextBox.NavigateToString(HtmlFormatHelper.GetStaticFragment(formattedText));
 
                if (bitmapImage != null)
                {
                    IRandomAccessStreamWithContentType bitmapStream = await this.bitmapImage.OpenReadAsync();
                    BitmapImage bi = new BitmapImage();
                    bi.SetSource(bitmapStream);
                    WholeImage.Source = bi;
 
                    bitmapStream = await this.thumbImage.OpenReadAsync();
                    bi = new BitmapImage();
                    bi.SetSource(bitmapStream);
                    ThumbImage.Source = bi;
                }
 
                if (customData != null)
                {
                    StringBuilder receivedStrings = new StringBuilder();
                    JsonObject customObject = JsonObject.Parse(customData);
                    if (customObject.ContainsKey("type"))
                    {
                        if (customObject["type"].GetString() == "http://schema.org/Person")
                        {
                            receivedStrings.AppendLine("Type: " + customObject["type"].Stringify());
                            JsonObject properties = customObject["properties"].GetObject();
                            receivedStrings.AppendLine("Image: " + properties["image"].Stringify());
                            receivedStrings.AppendLine("Name: " + properties["name"].Stringify());
                            receivedStrings.AppendLine("Affiliation: " + properties["affiliation"].Stringify());
                            receivedStrings.AppendLine("Birth Date: " + properties["birthDate"].Stringify());
                            receivedStrings.AppendLine("Job Title: " + properties["jobTitle"].Stringify());
                            receivedStrings.AppendLine("Nationality: " + properties["Nationality"].Stringify());
                            receivedStrings.AppendLine("Gender: " + properties["gender"].Stringify());
                        }
                        CustomDataBox.Text = receivedStrings.ToString();
                    }
                }
            });
        });
 
}

 

As you can see, each example is slightly different, depending on the type of data.

.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; }

Summary

Today, we took took a very extensive look into adding Sharing into your application. The Search contract provides an amazing new paradigm for applications to interact with one another.  There’s something incredibly refreshing about knowing that applications that have no idea mine exists can still communicate with one another.  I hope you’ll take the opportunity to share and consume the data that makes sense for your application.

You can download the entire sample solution from this article here:

downloadXAML

Tomorrow, we’re going to focus on storing data, both locally on the user’s device, as well as roaming that data in the cloud.  See you then!

downloadTheTools

13 thoughts on “31 Days of Windows 8 | Day #7: Share Contract

  1. I was wondering if its possible to include Image and text at the same time to share on twitter. I defined both:
    DataPackage data = args.Request.Data;
    data.Properties.Title = text;
    data.SetBitmap(RandomAccessStreamReference.CreateFromUri(new Uri(uri)));

    But only the image shows up using MetroTweet with an empty message body. Using “SetText()” doesn’t help eather.

    Any suggestions?
    Thanks!

  2. On the Share Target, is it possible to only accept specific URIs, i.e. links to a certain domain, or links to files with a specific file type?

  3. I implement the code (sharing images) and i have an error :
    The name package does not exist in the current context.
    Any suggestions?
    Thank you.

  4. Jeff, thank you so much for the great articles.

    Its so much easier to build an app from scratch, just adding what you need rather than having to wade through the code in the MS templates and try to work out what you don’t need.

    I have the example on this page running as expected, however we do not seem to have a method to display the received files (storage items) after we send them.

    Any particular reason?

  5. hi jeff,

    great articles, love it!

    one question: is it possible to send an email to a specific recipient through share contract?

    thanks for your answer

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s