(X) Hide this
    • Login
    • Join
      • Generate New Image
        By clicking 'Register' you accept the terms of use .

Windows Store apps in HTML and XAML: Win as one with Share contracts

(1 votes)
Andrea Boschin
>
Andrea Boschin
Joined Nov 17, 2009
Articles:   91
Comments:   9
More Articles
0 comments   /   posted on Jun 17, 2013
Categories:   Windows 8

Tweet

One of the pillars of the Windows Store apps guidelines is "win as one" that remembers much more the Three Musketeers by Dumas than something related the software development. Instead, it is an important concept brought into the scene by Windows 8, that is really simple to understand. The fact is that, before windows 8, every application had always tried to embed so many functions that are not strictly related with the domain they are done to manage. As an example you can think at a photo album application. Togheter with the main functions of storing images and organizing them in albums, usualy these apps have also functions to pick files from file system, shoot a photo with the camera or to share the images with any destination, like email, twitter or facebook accounts. These are not directly related with the image management but are for sure functions that enrich an application.

In Windows 8, for the first time it is proposed the concept of sharing that ask applications to collaborate, leaving each app to do the best in its functions, and delegate to other support functions. So, a photo management software should rely on other apps to deliver mail messages, post to twitter or facebook and get images from any source to add them to an album. Since, in Windows 8, sharing is based on a contract, side by side with the benefit of using the best app for each function, this leaves the user free to plug other sources or destinations that are not originally predicted.

Exploring the sharing contract: the share source

As you can easily understand, there are always two point of view that apply to each kind of sharing. Someone can be share source when it sends something or target when it is receiving the content sent from another app. This is an important concept because it is the first question to answer when you have to implement a sharing contract: am I a source or a target?

Once you have decided, you have another question to answer. It is related to the content you need to share and is about the format sent or received. Sharing content in the right format is important because it affects directly the peer app you can engage. Both when sharing as a source or as a target you have explicitly to assert the content you are treating. This lets the operating system to know which apps to show in the sharing pane. It matches the apps using the type of content the source is sharing right now, comparing it with the type of contents that potential targets have declared to be able to receive.

Differently from what you probably expect, when you share as a source you have to handle an event, raised by  the operating system. This event states that the user have activated the sharing pane using the known charm. In answer to this event you have to prepare the content and return it to the operating system that will send it to the target application. Differently from being a sharing target, implementing the sharing source does not requires you to add any declaration to the manifest. If you do not handle the DataRequested event the operating system knows you are not a sharing source and will show an appropriate message to the user. Differently, when the event is attached and an answer is returned the operating system will shows the available target apps. Both in Javascript and in C# this operation is made almost in the same way if you do not take in consideration the language casing:

   1: // Javascript
   2:  
   3: (function () {
   4:  
   5:     "use strict";
   6:  
   7:     var page = WinJS.UI.Pages.define("/sharesourcepage.html", 
   8:     {
   9:         ready: function (element, options) 
  10:         {
  11:             var dataTransferManager = Windows.ApplicationModel.DataTransfer.DataTransferManager.getForCurrentView();
  12:             dataTransferManager.addEventListener("datarequested", dataRequested);
  13:         },
  14:         unload: function () 
  15:         {
  16:             var dataTransferManager = Windows.ApplicationModel.DataTransfer.DataTransferManager.getForCurrentView();
  17:             dataTransferManager.removeEventListener("datarequested", dataRequested);
  18:         }
  19:     });
  20:  
  21:     function dataRequested(e) 
  22:     {
  23:         // handle here the sharing request
  24:     }
  25: })();

Here is the same done with C#

   1: // C#
   2:  
   3: public sealed partial class ShareSourcePage : LayoutAwarePage
   4: {
   5:     protected override void OnNavigatedTo(NavigationEventArgs e)
   6:     {
   7:         DataTransferManager.GetForCurrentView().DataRequested += ShareSourcePage_DataRequested;
   8:         base.OnNavigatedTo(e);
   9:     }
  10:  
  11:     protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
  12:     {
  13:         DataTransferManager.GetForCurrentView().DataRequested -= ShareSourcePage_DataRequested;
  14:         base.OnNavigatingFrom(e);
  15:     }
  16:  
  17:     private void ShareSourcePage_DataRequested(DataTransferManager sender, DataRequestedEventArgs args)
  18:     {
  19:         // handle here a share request...
  20:     }
  21: }

The important thing to note is that the event is attached when the user enters the page and detached when he goes away. This is required to avoid to handle the sharing multiple times for the same page or worst for different pages. Once the user request a sharing the page gets the event and have to user the passed argument to return the packed content. This operation is done equally in C# and Javascript:

   1: function dataRequested(e) 
   2: {
   3:     var request = e.request;
   4:     var contentToShare = document.getElementById("inputContentTextBox").value;
   5:  
   6:     if (contentToShare != null && contentToShare != '')
   7:     {
   8:         request.data.properties.title = "This content is shared";
   9:         request.data.properties.description = "Shortly describes the shared content";
  10:         request.data.setText("Hi there, I'm a shared content!");
  11:     }
  12:     else
  13:         request.failWithDisplayText("Nothing to share at the moment.");
  14: }

And a slightly  more complex example in C#

   1: private void ShareSourcePage_DataRequested(DataTransferManager sender, DataRequestedEventArgs args)
   2: {
   3:     Earthquake quake = this.DataContext as Earthquake;
   4:  
   5:     if (quake != null)
   6:     {
   7:         args.Request.Data.Properties.Title = "Earthquake alert: " + quake.Place;
   8:         args.Request.Data.Properties.Description = "Earthquake happened in '" + quake.Place + "'";
   9:         
  10:         string html = string.Format(CultureInfo.InvariantCulture, 
  11:             @"<html>
  12:                 <body>
  13:                     <h2>{2:#.0} - {3} ({4})</h2>
  14:                     <div>
  15:                         <img src=""http://maps.googleapis.com/maps/api/staticmap?center={0},{1}&zoom=6&size=300x300&maptype=roadmap&markers=color:red%7Clabel:G%7C{0},{1}&sensor=false"" width=""300"" height=""300"">
  16:                     </div>
  17:                 </body>
  18:             </html>", quake.Latitude, quake.Longitude, quake.Magnitude, quake.Place, quake.Region);
  19:  
  20:         string htmlFormat = HtmlFormatHelper.CreateHtmlFormat(html);
  21:         args.Request.Data.SetHtmlFormat(htmlFormat);
  22:     }
  23:     else
  24:         args.Request.FailWithDisplayText("Earthquake has not been loaded");
  25: }

Since in Javascript I've shown the basic text sharing options, using the "setText" method, in C# I've shared a fragment of HTML with the "SetHTMLFormat" method. These examples, made different per language can be easily turned in the other language because there is not any sensible difference in the toolset between Javascript anc C#. The C# example only shows an alternative way to share that gives you much more control over the generated content, creating a map pointed to a geographic coordinate, but is it perfectly available also to Javascript. Exploring the "Set*" methods gives you an idea of what you are able to share.

  • SetBitmap to share images
  • SetData to share arbitrary byte arrays as a stream
  • SetDataProvider to set a delegate called when the peer has been connected
  • SetHtmlFormat for HTML (with also embedded images)
  • SetRtf for Rich text from a RichTextBox
  • SetStorageItems for files peeked up by the local storage
  • SetText for plain text
  • SetUri for links

Each one of these items also communicate to the O.S. the type of the content shared and is the starting point to connect to a compatible target.

And the share target

Being a sharing target require to be slightly more formal. This is because in this scenario the application passively undergoes to the sharing started by another app and the operating system have to know if it may accept the type of content without starting it. It would be a problem if every time someone starts a share each application will be awaked to answer to the question "is this content feasible to you?" Instead they designed the feature to require a precise description of the content able to handle in the package manifest. So the first you have to do it to open the manifest's editor and diligently compile the form:

image

This editor as usual is not so friendly. The data format textbox should be compiled by hand and it is not immediately clear which kind of value you have to enter. In the brief description above the field it is reported a short list of values. In the previous figure I've set to support HTML and Text content.

When a share is started and the user matches the source with your target application, it is activated and it gets an event that must be handled to correcly handle the share. This is slightly different in Javascript or C#. Let start with the simplest one: In C# you have to override the OnShareTargetActivated method from the Application class. This is done into the App.xaml.cs as you can see in this box:

   1: sealed partial class App : Application
   2: {
   3:     public App()
   4:     {
   5:         this.InitializeComponent();
   6:     }
   7:  
   8:     protected override void OnShareTargetActivated(ShareTargetActivatedEventArgs args)
   9:     {
  10:         var rootFrame = new Frame();
  11:         rootFrame.Navigate(typeof(MainPage), args.ShareOperation);
  12:         Window.Current.Content = rootFrame;
  13:         Window.Current.Activate();
  14:     }
  15: }

This code simply handles the incoming share operation and activates a page that is tailored to manage the shared content. In the target page, you get the share operation and you are able to get the shared content this way:

   1: protected override async void OnNavigatedTo(NavigationEventArgs e)
   2: {
   3:    var shareOperation = (ShareOperation)e.Parameter;
   4:  
   5:     await Task.Factory.StartNew(async () =>
   6:     {
   7:         var title = this.shareOperation.Data.Properties.Title;
   8:  
   9:         string format;
  10:         string content;
  11:  
  12:         if (this.shareOperation.Data.Contains(StandardDataFormats.Text))
  13:         {
  14:             format = StandardDataFormats.Text;
  15:             content = await this.shareOperation.Data.GetTextAsync();
  16:         }    
  17:         else if (this.shareOperation.Data.Contains(StandardDataFormats.Html))
  18:         {
  19:             format = StandardDataFormats.Html;
  20:             content = await this.shareOperation.Data.GetHtmlFormatAsync();
  21:         }
  22:  
  23:         await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
  24:         {
  25:             /// do what you want with content...
  26:         });
  27:     });
  28: }

The important thing to take note is that the content processing is made asynchronously. This is suggested by the guidelines because the sharing pane should be released ad soon as possible. So the code starts a thread and immediately return to the caller. Then, after read the content it returns on the UI Thread using the Dispatcher of the page.

In Javascript there is mainly a difference in the naming of the events, and in the place the content handling happes. Infact you have to remember that a Javascript application does not have an App instance but uses a default.html page to embed a frame. So, into the app manifest you have to specify an additional parameter that is the landing page for the sharing operation:

image

In the page you handle the "activated" event and into the event arguments you are able to discover if it started by a sharing operation:

   1: function activatedHandler(eventObject) 
   2: {
   3:     if (eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.shareTarget) 
   4:     {
   5:          eventObject.setPromise(WinJS.UI.processAll());
   6:          shareOperation = eventObject.detail.shareOperation;
   7:          WinJS.Application.queueEvent({ type: "shareready" });
   8:      }
   9:  }

As for C# the operation should be completed asynchronously. So the page enqueue and event that received the sharing notification and process it:

   1: function shareReady(eventArgs) 
   2: {
   3:     var title = shareOperation.data.properties.title;
   4:  
   5:     if (shareOperation.data.contains(Windows.ApplicationModel.DataTransfer.StandardDataFormats.text)) 
   6:     {
   7:         shareOperation.data.getTextAsync().done(
   8:             function (text) 
   9:             {
  10:                 // handle text here
  11:             });
  12:     }
  13:     else if (shareOperation.data.contains(Windows.ApplicationModel.DataTransfer.StandardDataFormats.html)) 
  14:     {
  15:         shareOperation.data.getHtmlFormatAsync().done(
  16:             function (htmlFormat) 
  17:             {
  18:                 // handle HTML here
  19:             });
  20:     }
  21: }

As you see it is similar to C# in the problems it handles but really different in the implementation.

A great opportunity to sare time and improve ux,.

Often we have spent lot of work implementing functions that I know being very well implemented by other software. My feel in these cases is to be wasting my time just because I need to understant the logic behind some pieces of software that are currently not really useful . My answer is often to search for some open source library to embed in my project, but this is not always available nor simple to understand. Being able to share functions exposed by some one else, virtually at non cost, is simply great.


Subscribe

Comments

No comments

Add Comment

Login to comment:
  *      *       

From this series