Sharing - Programming Windows Store Apps with C# (2014)

Programming Windows Store Apps with C# (2014)

Chapter 7. Sharing

In this chapter, we’re going to look at one of the key tenets of the Windows Store app user experience: sharing.

One of the key problems with iPad is that information is stored in each app’s private silo. It’s hard to share information between apps, and really the only tool that you have is the copy-and-paste feature. With Window Store apps, information sharing between apps is front and center. Apps can register themselves as a share target, or they can share information by acting as a share source.

Those of you who have been around the block a few times will see similarities in this feature with Dynamic Data Exchange, or DDE. DDE was a feature introduced in very early versions of Windows that was designed to work in a very loose and decoupled way. The idea was that an application, say Excel, could tell Windows that it had some text information to share. You could then choose an application that understood text, such as Word. Windows would then marshal the data from one to the other using some extra magic atop the standard system clipboard.

Sharing in Windows Store apps works in pretty much that exact way. An app can indicate that it shares data, and you’ll receive a message asking for said data. You can provide a combination of text, HTML, URIs, bitmaps, files (storage items), and RTF data, or you can define custom formats. The OS will then find apps that are interested in receiving shared data (i.e., those that register as having a search contract) and present to the user a list of compatible apps. When the user chooses an app, it’s activated and asked to provide a UI. The target app then acts on the shared data in some form (e.g., sending an email).

In this chapter, we’re going to look at how we share data first. Then we’re going to look at how we can implement a share contract and become a target for shared data.

In that first part we’ll look at the fundamentals of pushing data out to other apps. This will include creating a deferral when handling requests that take a long time to fulfill (for example, in situations where we don’t have the data in a state where it can be shared because we need to process or augment it).

In the second part we’ll look at how to draw data in from other apps. This will also include some debugging tips that can be generally helpful.

Sharing Data

The basic sharing data functions are tremendously easy to implement. Throughout the WinRT APIs, whenever Microsoft really wants you to include a feature in the Windows 8/Windows RT experience the APIs related to those features are always very straightforward. This is no exception.

In this section, we’re going to look at the basics of sharing data first. We’re then going to add more sophistication, particularly in dealing with share operations that take a long time and need to feed back information to the user.

Basic Sharing

To tell Windows that we’re able to share content, we need to use the Windows.ApplicationModel.DataTransfer.DataTransferManager class. This class doesn’t understand anything about our view-model, so we need to close that loop.

Hooking the DataTransferManager into the view-model

So that we know what we’re trying to call into, we’ll build the handling method in IViewModel and ViewModel now. The intention will be that view-models that wish to partake in sharing will override a new ShareDataRequest method on the view-model.

Here’s the change to IViewModel. The two parameters to that method are in the Windows.ApplicationModel.DataTransfer namespace.

// Add method to IViewModel...

public interface IViewModel : INotifyPropertyChanged

{

// property to indicate whether the model is busy working...

bool IsBusy

{

get;

}

// called when the view is activated...

void Activated();

// called when the view-model might have some data to share...

void ShareDataRequested(DataTransferManager sender,

DataRequestedEventArgs args);

}

The implementation for the method will be a no-op call for now. (A no-op call means “no operation”—that is, it doesn’t do anything. It’s typically included to get things compiling, or as a placeholder for future work.) Here’s the code:

// Add method to ViewModel...

public virtual void ShareDataRequested(DataTransferManager sender,

DataRequestedEventArgs args)

{

// no-op by default...

}

To call that method, we need to be able to dereference our view-model from a Window instance.

When our app boots, we have a single window, which can be referenced via Window.Current. The App class that’s created as part of the Visual Studio project creates a Frame instance and tells it to navigate to the Page that we want to show. The frame is then attached to the window (via the Window instance’s Content property) and everything springs into life.

In Chapter 5 we created a FrameworkElementExtender class specifically to allow us to show and hide the app bars on a page. One of the functions we added there walks the parents of a FrameworkElement instance to find one of type Page. Once it finds that, it casts it and returns it.

In the case we’re about to look at, we have to walk down the tree as well as up. When we receive our message indicating that sharing needs to happen, the only thing we’ll know about is the Window. We need to walk down the tree, examining any available Content properties. Hopefully we’ll then hit the Frame whose Content property will be set to the Page. Once we have a Page, we can optimistically cast its DataContext property to IViewModel.

To implement this functionality, you’ll have to add the new GetViewModel methods, and replace GetParentPage with GetRelatedPage in FrameworkElementExtender. Here’s the code:

// Add methods to FrameworkElementExtender...

internal static class FrameworkElementExtender

{

internal static IViewModel GetViewModel(this Window window)

{

if (window.Content is FrameworkElement)

return ((FrameworkElement)window.Content).GetViewModel();

else

return null;

}

internal static IViewModel GetViewModel(this FrameworkElement element)

{

// walk up...

var page = element.GetRelatedPage();

if (page != null)

return page.DataContext as IViewModel;

else

return null;

}

internal static Page GetRelatedPage(this FrameworkElement element)

{

// up...

DependencyObject walk = element;

while (walk != null)

{

if (walk is Page)

return (Page)walk;

if (walk is FrameworkElement)

walk = ((FrameworkElement)walk).Parent;

else

break;

}

// down...

walk = element;

while (walk != null)

{

if(walk is Page)

return (Page)walk;

if (walk is ContentControl)

walk = ((ContentControl)walk).Content as FrameworkElement;

else

break;

}

// nothing...

return null;

}

// other methods omitted...

}

The helper methods that we just added are generally helpful anyway, but they’ll be of specific use as we go through the rest of this chapter.

Finally, we need to close the loop and register with the DataTransferManager. To do this, find the OnLaunched method in App and add a call to subscribe to the DataRequested event. The event handler will dereference the view-model (if any) and then call through to the IViewModelinterface’s ShareDataRequested method. Here’s the code—although I’ve omitted a chunk of OnLaunched for brevity:

protected override async void OnLaunched(LaunchActivatedEventArgs args)

{

// code omitted...

// Place the frame in the current Window and ensure that it's active

Window.Current.Content = rootFrame;

Window.Current.Activate();

// register for data transfer...

var manager = DataTransferManager.GetForCurrentView();

manager.DataRequested += manager_DataRequested;

}

static void manager_DataRequested(DataTransferManager sender,

DataRequestedEventArgs args)

{

// find the view model and dereference...

if (Window.Current != null)

{

var viewModel = Window.Current.GetViewModel();

if (viewModel != null)

viewModel.ShareDataRequested(sender, args);

}

}

At this point, we can go ahead and test that everything up to this point is working. Set a breakpoint in the manager_DataRequested handler method. Start the app and summon the charms from the right side of the screen. Select Share, and the breakpoint should hit.

Now that we know we can hook into the sharing subsystem, let’s actually share some data.

Sharing basic data

In Chapter 5 we built a mechanism for selecting items on the Reports page. This is what we’ll use for sharing. Although that view-model supports selecting multiple items, we’ll assume that the first item in that set is the one the user wants to share. That’s a convenient shortcut here, but it would create a confusing experience in a production app, where it would be better to be able to share the entirety of the selected items in one operation.

Once we have an item to share, all we have to do is populate the DataRequest item that we get passed through in the request. To populate it, we have to set some metadata (a subject and description, basically) and then provide the data. You can supply as many formats as you like; Windows and the target apps will work out how best to interpret what’s shared.

The simplest data to share is text and URI information, so we’ll do that first.

In the ReportsPageViewModel class, override the ShareDataRequested method and add this code:

// Add method to ReportsPageViewModel...

public async override void ShareDataRequested(DataTransferManager

sender, DataRequestedEventArgs args)

{

// do we have a selection?

if (!(this.HasSelectedItems))

return;

// share the first item...

var report = this.SelectedItems.First();

// set the basics...

var data = args.Request.Data;

data.Properties.Title = string.Format("StreetFoo report '{0}'",

report.Title);

data.Properties.Description = string.Format("Sharing problem report

#{0}", report.NativeId);

// set the text...

data.SetText(string.Format("{0}: {1}", report.Title,

report.Description));

// set the URI...

data.SetUri(new Uri(report.PublicUrl));

}

We have to build that PublicUrl method before we can run it. All this does is format a special URL with which the user can review the report live on the StreetFoo server. Here’s the code:

// Add property to ReportViewItem...

public string PublicUrl

{

get

{

return string.Format

("https://streetfoo.apphb.com/PublicReport.aspx?api={0}&id={1}",

ServiceProxy.ApiKey, this.NativeId);

}

}

You can now run that. Navigate to the Reports page, select an item, and then access the Share feature through the charms. You’ll see something similar to Figure 7-1.

Selecting an app to share with

Figure 7-1. Selecting an app to share with

If you go ahead and select an app, you can see how the shared data flows into the other app. It’s worth trying a few apps to get a feel for how they pick and choose from the available data. For example, Figure 7-2 shows the Mail app, which ignores the text value but takes the metadata and the URL.

A problem report shared with the Mail app

Figure 7-2. A problem report shared with the Mail app

Sharing images

Now that we’ve got down the basics of sharing, the “edges” of this API are in the types of data that you can share, and in reporting progress back to the user. In this and the following two sections, we’ll look at the other data types, and then we’ll move on to reporting progress.

NOTE

We’re not going to look at custom data types in this book, as that’s an unusual niche requirement.

After text and URLs, the next most common thing you’ll want to share is images. One problem we’ll have to negotiate is that (as of the time of writing at least) none of the built-in apps receive image data. I’ll present a workaround for this when we get to it.

Sharing images is a matter of giving our DataPackage object a RandomAccessStreamReference instance. As its name implies, this is a helpful class that wraps a stream so that you can pass it around either within your own app or—as we’re about to do—out into other apps. You can create RandomAccessStreamReference from a file, a stream you already have, or a URI.

We’ve already seen that we can use the special ms-appx and ms-appdata protocols to access resources that are part of the deployment and stored in the app’s private data. Incidentally, you can reference http and https protocol URLs too, in which case WinRT will undertake some of the heavy lifting involved with setting up the network requests.

Anyway, we already have a URL for the image stored in the ImageUrl. All we have to do is provide that URL through to the share operation. One wrinkle we have to deal with is that we’re supposed to provide a thumbnail image when we share. For simplicity here, I’ve just given it the main image for the thumbnail.

NOTE

As of the time of writing, some share targets will fail if you specify an image without a thumbnail. You can also find an example of how to scale images in Chapter 12.

Here’s the modified version of ShareDataRequested:

// Modify method in ReportsPageViewModel...

public async override void ShareDataRequested(DataTransferManager

sender, DataRequestedEventArgs args)

{

// do we have a selection?

if (!(this.HasSelectedItems))

return;

// share the first item...

var report = this.SelectedItems.First();

// set the basics...

var data = args.Request.Data;

data.Properties.Title = string.Format("StreetFoo report '{0}'",

report.Title);

data.Properties.Description = string.Format("Sharing problem report

#{0}", report.NativeId);

// set the text...

data.SetText(string.Format("{0}: {1}", report.Title,

report.Description));

// set the URI...

data.SetUri(new Uri(report.PublicUrl));

// do we have an image?

if (report.HasImage)

{

var reference = RandomAccessStreamReference.CreateFromUri

(new Uri(report.ImageUrl));

data.Properties.Thumbnail = reference;

data.SetBitmap(reference);

}

}

As I mentioned, the challenge is now sharing the image. You may have an app on your device that acts as a target for images—the version on which I based this book’s work didn’t have a built-in app that supports being an image target. The one I’m using in Figure 7-3 is from the MSDN, “Sharing content target app sample.” This dumps out information on shared data, and you’ll be able to find it by searching the MSDN.

Successfully sharing an image

Figure 7-3. Successfully sharing an image

Sharing other types of data

The other basic types of data are very easy to share. I won’t take you through these in detail, but I will call them out.

§ You can share one or more filesystem objects via the SetStorageItem method. This takes an array of IStorageItem instances. (Oddly, this means you can share entire folders as StorageFolder implements IStorageItem.)

§ You can share HTML data by providing a string to SetStorageHtml.

§ Finally, you can share RTF data by providing a string to SetStorageRtf.

As mentioned, we’re not going to look at custom data in this book, as this is a niche requirement.

Pull Requests/Deferrals

Sometimes you’ll need time to prepare the data to share, in which case you need to set up a pull request. You can’t just use async/await on the handler method that responds to the share request, as Windows doesn’t understand you’re doing a long-running operation and will just cancel you. You have to be more explicit.

In this section we’re going to create a share operation that takes a reasonably long time. Specifically, we’re going to download a static image off the StreetFoo server. We’ll do this on demand, in the background.

NOTE

To keep the StreetFoo client “clean,” I’m proposing that we create a new project for doing this. In the download for this chapter, you’ll find a solution called SharingScratch and a project PullRequestScratch that contains this work.

Pull requests are pretty easy to implement. All you have to do is tell the DataRequest object that you want to register a handler for a specific data type, and then within that handler ask for a DataProviderDeferral instance. In fact, the only work we have to do in this method relates to actually getting the image down from the server.

On the StreetFoo server, there are static versions of the graffiti sample files that we’ve seen so far. To download it, we create an HttpWebRequest (as we did when we set up ServiceProxy way back in Chapter 2), and then we’ll copy the response into a InMemoryRandomAccessStream.InMemoryRandomAccessStream is a WinRT class, whereas the Stream object that we get back from the HttpWebResponse is a .NET class. Because RandomAccessStreamReference doesn’t understand Stream instances (because that too is a WinRT class, not a .NET class), we have to use an extension method to gain a façade. This happens quite a lot when we’re building Windows Store apps; although sometimes mapping between WinRT and .NET happens automatically through a project, on occasion we have to use a façade. In this case, we’ll ask for a Streamfaçade in the WinRT InMemoryRandomAccessStream instance.

Here’s the code, which I’ve placed into my MainPage class in the PullRequestScratch project:

// Add methods to MainPage...

protected override void OnNavigatedTo(NavigationEventArgs e)

{

base.OnNavigatedTo(e);

// subscribe...

var manager = DataTransferManager.GetForCurrentView();

manager.DataRequested += manager_DataRequested;

}

void manager_DataRequested(DataTransferManager sender,

DataRequestedEventArgs args)

{

// do the basics...

var data = args.Request.Data;

data.Properties.Title = "Deferred image";

data.Properties.Description = "I'll have to be downloaded first!";

// get a deferral...

data.SetDataProvider(StandardDataFormats.Bitmap, async (request) =>

{

var deferral = request.GetDeferral();

try

{

// download...

var httpRequest = HttpWebRequest.CreateHttp

("http://streetfoo.apphb.com/images/graffiti00.jpg");

var response = await httpRequest.GetResponseAsync();

using (var inStream = response.GetResponseStream())

{

// copy the stream... but we'll need to obtain a facade

// to map between WinRT and .NET...

var outStream = new InMemoryRandomAccessStream();

inStream.CopyTo(outStream.AsStream());

// send that...

var reference = RandomAccessStreamReference.

CreateFromStream(outStream);

request.SetData(reference);

}

}

finally

{

deferral.Complete();

}

});

}

If you run that and access the share charm, it will work as per the sharing operation where we shared the image that we already had on disk. However, you should notice that the share target will load and after a time the image will appear. When we looked at this before from the main StreetFoo app, the image appeared immediately.

NOTE

This concept of a deferral comes up a few times in WinRT. You’ll see it in other chapters.

Acting as a Share Target

Now that we’ve looked at how we can share data, we’ll turn our attention to how we can become a target for sharing.

This section is going to segue into work that we’re going to do in Chapter 12 with regards to creating new reports. At the moment we’ve been relying on preexisting problem reports on the StreetFoo server. In Chapter 12 we’re going to create reports on the device and upload them using background processes. (The background process itself will be covered in Chapter 15.)

In a production app, one way in which we might want to create reports is by receiving shared information from other apps (particularly images). Chapter 12 looks at using the camera to do this directly. In this chapter, we’ll see in theory how we can receive both text data and photo data. In a real application, you’d likely want to create reports from both sources. However, given the constraints of having to put this book in a fixed order—and where I’ve happened to put this chapter before the camera chapter—you’ll have to use your imagination as to how to ultimately create reports from shared images.

Setting up as a share target involves rigging your app with a share contract and then picking out the pieces of data that you’re interested in. There are also some edge functions in terms of supporting long-running operations.

Sharing Text

Visual Studio will do most of the heavy lifting for you in terms of implementing a share contract into your project. If you open up the Add New Item dialog, search for the Share Target Contract item. Give it a name (e.g., ShareTargetPage), and click OK.

This will do three things: it will create the new page in your project, override the OnShareTargetActivated method in your App class, and alter your manifest declarations to include a Share Target declaration. Figure 7-4 illustrates the manifest change.

The Share Target contract/declaration

Figure 7-4. The Share Target contract/declaration

NOTE

You’ll see some inconsistency here in that in some areas these are called contracts and in others they are referred to as declarations. They’re both the same thing—the naming is down to the fact that in the manifest you’re “adding declarations of contracts.”

By default we receive notification of text and URI formats. We’re not interested in URI, so you can remove this from the declaration.

What we’ll do to prove this works is run the app. If we share some text from another app, our icon will appear in the available apps list, and if we click our app we’ll spring into life.

However, sharing text from the built-in apps is a little counterintuitive. In the version of Windows 8 on which this book is based, the Mail app doesn’t act as a share source. IE does act as a share source, but will only share text data if you select a block of text on the page. Given those facts, using IE but making sure that we have a selection on the page seems to be the path of least resistance. (Don’t forget you need to use the Windows Store app version of IE, as legacy desktop apps do not support share operations.)

NOTE

If you are trying to troubleshoot share operations, using the Share Target Sample from MSDN—the one that we looked at before—can be a big help, as this dumps all of the information that’s been shared.

Thus, if you go into IE, select some text, and then initiate a share, our app will appear in the list. Figure 7-5 illustrates.

StreetFoo as a share target

Figure 7-5. StreetFoo as a share target

NOTE

You should note, though, that as of the time of writing, there was an issue whereby if an app was already in the background, the share target UI would appear and then disappear instantly. If the app isn’t in the background, the share target UI will appear and remain on the screen as expected.

If you go ahead and share that data, you’ll get something like Figure 7-6. This shows the properties going across but none of the data. But as we’ll see in the next section, we can use the opportunity of trying to share the text to learn more about how to debug interactions with Windows Store apps when things go wrong.

Viewing share data properties in StreetFoo

Figure 7-6. Viewing share data properties in StreetFoo

Sharing Text (and Troubleshooting)

One of the problems with working with Windows Store apps is that because the system is designed to drive down user intimidation, you’re not supposed to bamboozle users with errors. For example, if the app crashes and quits, you’re not allowed (according to the rules) to show an error message. The idea is that the user will know that something went wrong by virtue of arriving at the Start screen.

NOTE

This seems like poor UX design, but this is in fact what the iPad does and is something I’ve never heard iPad users complain about.

What I want to show you in this section is a quick trick to dump out debugging information when you do hit problems.

We’ll do this by breaking the share operation that we were given by Visual Studio. Well, I say “breaking,” but what we’re actually going to do is migrate the ShareTargetPage class over to the MVVM pattern of our other pages. In doing this, we’re going to hit a problem that we have to fix, the symptom of which is to crash the app during the share operation. We’ll then see how to view diagnostic information, and then fix the problem.

Migrating ShareTargetPage to MVVM

As is the way with all of the Visual Studio templates, the provided implementation of ShareTargetPage uses the not-really-a-view-model DefaultViewModel “bucket” that’s included by default in LayoutAwarePage. (Recall that in Chapter 2 one of the first things we did was to build a proper MVVM implementation to replace this bucket.)

If you look in the default code, you’ll find lots of references to DefaultViewModel. Here’s an example:

public async void Activate(ShareTargetActivatedEventArgs args)

{

this._shareOperation = args.ShareOperation;

// Communicate metadata about the shared content through the

// view model

var shareProperties = this._shareOperation.Data.Properties;

var thumbnailImage = new BitmapImage();

this.DefaultViewModel["Title"] = shareProperties.Title;

this.DefaultViewModel["Description"] = shareProperties.Description;

this.DefaultViewModel["Image"] = thumbnailImage;

this.DefaultViewModel["Sharing"] = false;

this.DefaultViewModel["ShowImage"] = false;

this.DefaultViewModel["Comment"] = String.Empty;

this.DefaultViewModel["SupportsComment"] = true;

Window.Current.Content = this;

Window.Current.Activate();

// Update the shared content's thumbnail image in the background

if (shareProperties.Thumbnail != null)

{

var stream = await shareProperties.Thumbnail.OpenReadAsync();

thumbnailImage.SetSource(stream);

this.DefaultViewModel["ShowImage"] = true;

}

}

NOTE

Note the async void declarations on that method. That’s not recommended. async should in most cases return a Task; otherwise, you don’t have any way of controlling the method’s lifetime or responding to the method’s lifetime changes.

What we need to do is create a new ShareTargetPageViewModel and IShareTargetPageViewModel so that we can deprecate the DefaultViewModel bucket approach.

We’ve done this a few times (see Chapter 2 for a basic run-through), so I’ll go through this part quickly. What we need is properties for the basic items on the view (title and description labels, a comments, field, and a Share button), and some properties that the default page implementation needs to do its magic. These properties, all Booleans, are Sharing (indicates that a share operation is in progress), ShowImage (indicates that we have image data, which isn’t strictly needed, but I’m proposing leaving it in for consistency with the standard implementation), andSupportsComment (indicates that we want to capture a comment). We’ll also need a command to indicate that the user wants to go ahead with the sharing. Ultimately from this we’re going to share out a text value and an image value, so we’ll create additional properties for SharedTextand SharedImage.

Here’s the interface code:

public interface IShareTargetPageViewModel : IViewModel

{

string Title { get; }

string Description { get; }

string Comment { get; set; }

string SharedText { get; }

BitmapImage SharedImage { get; }

bool ShowImage { get; }

bool SupportsComment { get; }

bool Sharing { get; }

ICommand ShareCommand { get; }

void SetupShareData(ShareOperation operation);

}

Now we can look at the view-model implementation.

You should be aware that a significant “gotcha” with regards to sharing operations is that the event that tells your app that you need to share (ShareTargetActivated) will pass over a ShareOperation. You must store this value in a field in your view-model; otherwise, the sharing operation will fail. What will happen if you don’t do this is that the share operation will appear and disappear immediately. This is due to WinRT’s COM-based nature. When you store a reference to a WinRT object in a .NET field, the COM reference count for that object is incremented. This “locks” your reference to the object. Without this locking, when the sharing mechanism releases its hold on the object, it’s assumed that the sharing operation finishes, and the whole thing closes. (If you’re not that familiar with how COM reference tracking works, don’t worry about it too much, as the places where it has an impact are few and far between. This is the only place where it’s important in this entire book, for example.)

For now, by way of a standard command handler, we’re just going to put a MessageDialog on the screen. Here’s the implementation for ShareTargetPageViewModel:

public class ShareTargetPageViewModel : ViewModel,

IShareTargetPageViewModel

{

private ShareOperation ShareOperation { get; set; }

public string Title { get { return this.GetValue<string>(); }

private set { this.SetValue(value); } }

public string Description { get { return this.GetValue<string>(); }

private set { this.SetValue(value); } }

public string Comment { get { return this.GetValue<string>(); }

set { this.SetValue(value); } }

public string SharedText { get { return this.GetValue<string>(); }

private set { this.SetValue(value); } }

public BitmapImage SharedImage {

get { return this.GetValue<BitmapImage>(); }

private set { this.SetValue(value); } }

public bool ShowImage { get { return this.GetValue<bool>(); }

private set { this.SetValue(value); } }

public bool SupportsComment { get { return this.GetValue<bool>(); }

private set { this.SetValue(value); } }

public bool Sharing { get { return this.GetValue<bool>(); }

private set { this.SetValue(value); } }

public ICommand ShareCommand { get; private set; }

public ShareTargetPageViewModel(IViewModelHost host)

: base(host)

{

this.ShowImage = false;

this.Sharing = false;

this.SupportsComment = true;

this.ShareCommand = new DelegateCommand(async (args) => await

HandleShareCommandAsync());

}

private async Task HandleShareCommandAsync()

{

await this.Host.ShowAlertAsync("Shared.");

}

public void SetupShareData(ShareOperation share)

{

// store the share operation - we need to do this to hold a

// reference or otherwise the sharing subsystem will assume

// that we've finished...

this.ShareOperation = share;

// get the properties out...

var data = share.Data;

var props = data.Properties;

this.Title = props.Title;

this.Description = props.Description;

// we'll add code to read shared data later...

}

}

The final thing we need to do is add a mapping for the interface and implementation to StreetFooRuntime.Start. I won’t show this here for brevity.

NOTE

Whenever Windows tries to invoke our app for a sharing operation, it will use the deployed version in our ~/Debug/AppX folder. Thus, whenever we build, we also need to deploy by navigating to Build→Deploy.

Tracking debug information

Once we’ve migrated the page over, if we run the share operation it will fail. (Remember, to run the share operation select some text in the Window Store app version of IE and use the share charm.) Figure 7-7 shows us what we can expect to see.

Cryptic sharing failure

Figure 7-7. Cryptic sharing failure

The problem here is that because of the way that Windows manages lifetimes for Windows Store apps, we have to get Visual Studio to attach a debugger to our app before it actually runs. This is easy enough to do, as Visual Studio has a feature for handling exactly this.

However, what I want to do is go through some of the diagnostic bits and pieces that we can take advantage of when building Windows Store apps.

Debugging share operations

This technique applies to both share operations and other places in which Windows may start your app to do something, such as searching (Chapter 8) and background tasks (Chapter 14).

In Visual Studio, open up the properties for the project and select the Debug tab. Select the “Do not launch, but debug my code when it starts” option. Figure 7-8 illustrates.

Setting the project to debug when code starts

Figure 7-8. Setting the project to debug when code starts

Should you be working without a debugger, we have to be able to write error information somewhere so that we can actually see what’s happening. Showing a MessageDialog is no good—on the one hand it’s against the rules, and on the other hand its asynchronous nature will conflict with the startup process of the share operation and we’ll hang. Luckily, since the very first versions of Windows there’s been a shared debug message queue, which we can access.

The Windows event log is where this sort of crash information is supposed to be stored. In fact, in the standard Application log we do see a report of the error. Here’s an example:

Faulting application name: StreetFoo.Client.UI.exe, version: 1.0.0.0,

time stamp: 0x4ffa029d

Faulting module name: Windows.UI.Xaml.dll, version: 6.2.8400.0, time stamp:

0x4fb6fcf4

Exception code: 0xc000027b

Fault offset: 0x0052967b

Faulting process id: 0x75c

Faulting application start time: 0x01cd5d54e800c69e

Faulting application path:

C:\BookCode\Chapter08\StreetFoo.Client\StreetFoo.Client.UI\bin\Debug\AppX

\StreetFoo.Client.UI.exe

Faulting module path: C:\Windows\System32\Windows.UI.Xaml.dll

Report Id: 25e22ff1-c948-11e1-9b91-e4ce8f131b41

Faulting package full name:

569e8a16-efb8-4992-ada5-7407fecb3dee_1.0.0.0_neutral__fv7d1kf84c3t4

Faulting package-relative application ID: App

This doesn’t tell us anything that we don’t already know—we know that we crashed and there’s little additional information here.

Interestingly, in addition to that error we’ll also get a report written to disk by the Windows Error Reporting subsystem. This turns out to be equally unhelpful, but worth calling out to mention that error summaries will go back to Microsoft via this standard reporting mechanism. Microsoft will use this information as part of its regular telemetry, but if you publish your app through the Store, Microsoft will share this data with you.

While we’re in the event logs, if you look under Application and Services Logs/Microsoft/Windows you’ll find subsystem-specific error logs. Some of them pertain to Windows Store apps. The Apps log does in particular. This contains more information, but still none that’s of any help. Here’s an example:

Activation of the app 569e8a16-efb8-4992-ada5-7407fecb3dee_fv7d1kf84c3t4!

App for the Windows.ShareTarget contract failed with error:

The remote procedure call failed..

The main reason this information is unhelpful is that it’s all created from the WinRT side, not the .NET side. .NET’s exception handling is generally “stronger” than WinRT’s error handling, so if at all possible we want to track .NET’s exception handling.

It turns out this is really easy—we just use Debug.WriteLine.

When the share target contract was added to the project, Visual Studio added an override to the App class, specifically OnShareTargetActivated. All we have to do is modify this so that it writes the error information through to Debug.WriteLine, like this:

// Modify method in App...

protected override void OnShareTargetActivated(Windows.ApplicationModel.

Activation.ShareTargetActivatedEventArgs args)

{

try

{

var shareTargetPage = new StreetFoo.Client.UI.ShareTargetPage();

shareTargetPage.Activate(args);

}

catch (Exception ex)

{

Debug.WriteLine(ex);

throw ex;

}

}

You’ll be familiar with Debug.WriteLine as a .NET developer. When the debugger is attached, output is routed through to Visual Studio’s Output window. What you might be less familiar with is that this also keys into a standard Win32 API called OutputDebugString. Windows maintains a piece of globally shared memory for debugging text. We can use an application called DbgView to show the output.

NOTE

Debug.WriteLine is pretty limited. If you want a more fully featured logging system for your app, have a look at MetroLog.

DbgView can be downloaded from Microsoft’s website; go to the Microsoft Download Center and search for DbgView. When it’s downloaded, make sure you run it as administrator, and select Capture – Capture Global Win32 from the menu.

Now if you run the share operation again, it will still fail, but you will be able to see why. Figure 7-9 illustrates.

DbgView reporting exception information

Figure 7-9. DbgView reporting exception information

So that’s great! We can now actually see the problem. We just need to fix it.

Fixing the runtime startup problem

The problem that we have here relates to the fact that our app is dependent on the ViewModelFactory being initialized whenever we need to obtain an instance of a view-model. At the moment, we configure this in the OnLaunched handler in App by calling StreetFooRuntime.Start. When our app is started from the share operation, OnLaunched is not called and hence Start is not called. The fix, then, is to make sure that Start is called when sharing starts.

An additional thing we have to deal with is that we need to automatically log on the remembered user when we start a share operation. Luckily, we can do this using the RestorePersistentLogonAsync method that we built in Chapter 4.

Reporting back to the user that there was an error is slightly more complex, however. The Windows Store app experience as it relates to displaying error messages is asynchronous in nature. The share UI will disappear, and a few seconds later toast will be used to tell the user that a problem occurred. What we want to do is put a message on screen immediately, front and center, telling the user that he or she needs to log in.

To fix this, we’ll create a new page called NotLoggedOnPage. This will display a simple message asking the user to go into the app via the Start screen and log in. (This isn’t a great user experience; I’ve taken a shortcut, as this operation is not central to this discussion. What you should do in a production app is get the user to log on through the NotLoggedOnPage UI.)

Create your new NotLoggedOnPage and add a couple of TextBlock instances to display a message. Figure 7-10 gives an example of what such a thing looks like when running. I haven’t bothered adding a view-model for this page, as it’s not interactive. If you did want to build this out properly, you could use the existing LogonPageViewModel class and simply wrap a new UI around it. Remember, though, that you need to hold a reference to the provided ShareOperation in a field; otherwise, the share UI will disappear. (See the previous discussion on this.)

The “Not logged in” message

Figure 7-10. The “Not logged in” message

Now we can turn our attention to the code. Remember, our goal here is to call StreetFooRuntime.Start. The easiest place to do this, and to add in the capability to show the logon prompt, is in OnShareTargetActivated itself. Here’s the code—note that I’ve changed the signature of this method to indicate that it is now async:

// Modify method in App...

protected override async void OnShareTargetActivated

(Windows.ApplicationModel.Activation.ShareTargetActivatedEventArgs args)

{

try

{

// start...

await StreetFooRuntime.Start("Client");

// logon?

var logon = ViewModelFactory.Current.GetHandler

<ILogonPageViewModel>(new NullViewModelHost());

if (await logon.RestorePersistentLogonAsync())

{

var shareTargetPage = new ShareTargetPage();

shareTargetPage.Activate(args);

}

else

{

var notLoggedOnPage = new NotLoggedOnPage();

notLoggedOnPage.Activate(args);

}

}

catch (Exception ex)

{

Debug.WriteLine(ex);

throw ex;

}

}

Now if you retry the share operation (remember that you’ll need to deploy it from within Visual Studio, close it, and then try to share from within IE), you’ll either get the old ShareTargetPage or the new NotLoggedOnPage. However, we still haven’t reached the point where the shared text is being displayed, so let’s look at that now.

Handling shared text

Sharing the text is very straightforward. In the page that Visual Studio built, you’ll find a Grid control that’s positioned in the middle of the form. All we have to do is add a TextBlock control bound to the SharedText property that we stubbed out earlier:

Here’s what the markup in ShareTargetPage looks like before we make our changes:

<Grid Grid.Row="1" Grid.ColumnSpan="2">

<!-- TODO: Add application scenario-specific sharing UI -->

</Grid>

<TextBox Grid.Row="1" Grid.ColumnSpan="2" Margin="0,0,0,27"

Text="{Binding Comment}"

Visibility="{Binding SupportsComment, Converter={StaticResource

BooleanToVisibilityConverter}}"

IsEnabled="{Binding Sharing, Converter={StaticResource

BooleanNegationConverter}}"/>

<!-- Standard share target footer -->

In the version of the template I used to write this book, there seemed to be a bug in that the “comments” form overwrote whatever content was added. I replaced this grid with a StackPanel control, and then brought up the editing code into that new StackPanel. Here’s my markup, but you may well find it easier to refer to the code download to get a handle on exactly what I’ve done. The highlighted parts are new or changed items—I’ve moved the TextBox from lower down in the page.

<StackPanel Grid.Row="1" Grid.ColumnSpan="2">

<TextBlock Text="{Binding SharedText}" Style="{StaticResource

BodyTextStyle}"

Margin="0,0,0,15"></TextBlock>

<Image Source="{Binding SharedImage}" Margin="0,0,0,15"

Visibility="{Binding ShowImage, Converter={StaticResource

BooleanToVisibilityConverter}}"></Image>

<TextBox Margin="0,0,0,27" Text="{Binding Comment}"

Visibility="{Binding SupportsComment, Converter={StaticResource

BooleanToVisibilityConverter}}"

IsEnabled="{Binding Sharing, Converter={StaticResource

BooleanNegationConverter}}"/>

</StackPanel>

To extract the text from the share operation, we need to go back to our view-model. (We’ll cover the image sharing in a later section.)

When we want to extract data from the view-model, we use async calls. This means that we need to use async/await on the method that sets up the sharing operation. Previously I presented this method as synchronous. We’re now going to change it, but the reason I’ve left it so that we have to change it is to help you get a feel for the “fiddlyness” of working with async/await. This happens a lot in Window Store app development—you build something one way, and then you discover that you have to rattle through a whole bunch of calls to put in async functionality.

In this case, we need to change the name and the return type of the SetupShareData method, change its return type, map that through to the interface, and change the operation of the caller.

Here’s the first set of changes to SetupShareData. This also includes a call to GetTextDataAsync. Note that we need to check whether or not the shared data package contains text data. The call to GetTextDataAsync will fail if we try to load a format that is not within the bucket.

// Modify and rename SetupShareData in ShareTargetPageViewModel...

public async Task SetupShareDataAsync(ShareOperation share)

{

// store the share operation - we need to do this to hold a

// reference or otherwise the sharing subsystem will assume

// that we've finished...

this.ShareOperation = share;

// get the properties out...

var data = share.Data;

var props = data.Properties;

this.Title = props.Title;

this.Description = props.Description;

// now the data...

if(data.Contains(StandardDataFormats.Text))

this.SharedText = await data.GetTextAsync();

}

The interface will need changing, which I won’t present, but I will present the change to the caller. Here’s the modification to the Activate method on the ShareTargetPage itself. Note that we’ve had to add the async modifier to the method.

public async void Activate(ShareTargetActivatedEventArgs args)

{

// give it to the view-model...

await this.Model.SetupShareDataAsync(args.ShareOperation);

// show...

Window.Current.Content = this;

Window.Current.Activate();

}

So that’s it! We’ve managed to create a share target based on a property MVVM implementation and handle the situation where the app is both logged in and not logged in. Let’s now look at long-running operations.

Long-Running Operations

Although a lot of share operations are likely to be quite quick to execute—after all, the likelihood is that the data will be local to the device and in a state where it can be shared—from time to time, we may have to handle data that takes a long time to process. We can use a bunch of methods in ShareOperation to tell Windows what’s going on if it will take a long time to get the data to a point where it can be shared. (Note that this process is for situations where the target is taking a long time. If the source is taking a long time, then we create a deferral. Recall that we looked at that aspect earlier in the chapter.)

Here are the methods that you can use:

§ ReportStarted is used to tell Windows that you have started the operation. Windows will take this as an indication that the user interface is now finished with. The UX angle on this is that the UI doesn’t need the modality of the share operation if the share operation has been kicked off.

§ Similarly, ReportDataRetrieved is used to tell Windows that you’ve grabbed the data that you need from the source app. For this, imagine a situation where you grab a whole load of files but then you need to upload them. The source app is not needed for the period of time that the upload happens, so Windows can swap it out of memory if needed. You can retrieve all the data and call ReportDataRetrieved before the user confirms the share operation and initiates ReportShared. (Windows will obviously work out the user experience flow here.) In fact, that’s what we’ll do in a moment.

§ ReportSubmittedBackgroundTask is used to tell Windows that you have deferred processing of the shared data to a background task. We’ll talk more about background tasks in Chapter 15, although we won’t be discussing this particular point.

§ ReportError is used to tell Windows that something went wrong with the share operation.

§ ReportCompleted is used to tell Windows that you have finished the share operation.

Although our operation isn’t going to take a long time, I will illustrate how to do this properly.

We should handle the ReportDataRetrieved scenario first. In SetupShareDataAsync we have all the data we need; hence, it’s valid to call that method. Here’s the change:

// Modify method in SearchTargetPageViewModel...

public async Task SetupShareDataAsync(ShareOperation share)

{

// store the share operation - we need to do this to hold a

// reference or otherwise the sharing subsystem will assume

// that we've finished...

this.ShareOperation = share;

// get the properties out...

var data = share.Data;

var props = data.Properties;

this.Title = props.Title;

this.Description = props.Description;

// now the data...

this.SharedText = await data.GetTextAsync();

// tell the OS that we have the data...

share.ReportDataRetrieved();

}

Calling the other three methods is just an issue of handling the ReportStarted, ReportError, and ReportCompleted calls. Here, I’ve used Debug.WriteLine to render some error information to the system debug view as we did before. Here’s the change toHandleShareCommandAsync:

private async Task HandleShareCommandAsync()

{

try

{

// tell the OS that we've started...

this.ShareOperation.ReportStarted();

// placeholder message...

await this.Host.ShowAlertAsync("Shared.");

}

catch (Exception ex)

{

Debug.WriteLine("Sharing failed: " + ex.ToString());

this.ShareOperation.ReportError("The sharing operation failed.");

}

finally

{

this.ShareOperation.ReportCompleted();

}

}

If you run that and share some text, you’ll see the result shown in Figure 7-11. The long-running reporting stuff that we just did won’t have a discernable effect on the presentation.

The final share operation

Figure 7-11. The final share operation

With that done, let’s look at the process of sharing images.

Sharing Images

As I mentioned at the top of the chapter, the requirement to work with images comes up very often when you’re building LOB apps for mobile work, which is why I wanted to cover this topic in this chapter. What we’re going to do here is look at capturing images that are shared by the built-in Photos app, and images from other sources.

Perhaps counter intuitively, the built-in Photos app actually shares files—or more specifically, it shares IStorageItem instances, all of which happen to be files. Other apps that share images are likely to actually share image data directly. Thus, we need to build our code such that we can handle either eventuality.

Way back when we first built ISharePageViewModel, we added a SharedImage property of type BitmapImage. All we need to do is set that, as well as the ShowImage property that we inherited from the legacy implementation provided by Visual Studio.

Configuring the manifest

Back when we created the share target contract in the project, we configured it such that it was looking for text data only. We now need to configure it to listen for storage files and bitmaps. Double-click the Package.appxmanifest file and modify it to include those two elements in the Share Target declaration. When you specify the storage files element, you’ll need to specify the file types. Figure 7-12 illustrates.

Configuring the additional data formats in the manifest

Figure 7-12. Configuring the additional data formats in the manifest

If you deploy that and try to share an image from the Photos app, StreetFoo will appear in the candidate list. Next we need to read in the data.

Reading image data

We can test for which type of data we are given by querying the Contains method with StandardDataFormats.StorageFiles or StandardDataFormats.Bitmap.

If we receive files over the sharing link, we’ll ignore multiple files and just work with the first one in the set. (In production code this would be confusing, so avoid this shortcut—you’ll need to handle multiple files properly and/or display some UI to the user to explain what was happening.) When we have a file, we can use RandomAccessStreamReference as we’ve done before, and then use that to initialize a new BitmapImage. If we get an image, we’ll actually be handed an IRandomAccessStreamReference interface—and we can use that to initialize a real Imageinstance.

We’ve already changed the XAML to show an image bound to the SharedImage property on the view-model, so all we have to do is modify SetupShareDataAsync to initialize the image. Here’s the code:

public async Task SetupShareDataAsync(ShareOperation share)

{

// store the share operation - we need to do this to hold a

// reference or otherwise the sharing subsystem will assume

// that we've finished...

this.ShareOperation = share;

// get the properties out...

var data = share.Data;

var props = data.Properties;

this.Title = props.Title;

this.Description = props.Description;

// now the text...

if(data.Contains(StandardDataFormats.Text))

this.SharedText = await data.GetTextAsync();

// do we have an image? if so, load it...

if (data.Contains(StandardDataFormats.StorageItems) || data.Contains

(StandardDataFormats.Bitmap))

{

IRandomAccessStreamReference reference = null;

// load the first one...

if (data.Contains(StandardDataFormats.StorageItems))

{

var file = (IStorageFile)(await data.GetStorageItemsAsync())

.FirstOrDefault();

reference = RandomAccessStreamReference.

CreateFromFile(file);

}

else

reference = await data.GetBitmapAsync();

// load it into an image...

var image = new BitmapImage();

using(var stream = await reference.OpenReadAsync())

image.SetSource(stream);

// set...

this.SharedImage = image;

this.ShowImage = true;

}

// tell the OS that we have the data...

share.ReportDataRetrieved();

}

Run the code and share an image from the Photos app. You’ll see something like Figure 7-13.

Testing sharing an image via GetBitmapAsync

To make it easier for you to find a share source that works with bitmap data as opposed to storage files, in the code download for this chapter I’ve created a project called ImageShareScratch. You’ll find it within the SharingScratch solution.

Run this project and you’ll be able to pick an image using the FileOpenPicker. It will automatically start a share operation (which is against the Windows Store app rules, incidentally, but it’s a good illustration). Share with the StreetFoo client to check that the image loading works from this side too.

Quick Links

The one feature of sharing that we haven’t looked at is quick links. The idea here is that you set up “shortcuts” for common sharing operations on a per-app basis. For example, you might configure a quick link to email to quickly address an email to the same person.

I’ve skipped this topic in this chapter mostly because of space, but also because it’s not a central function that we would need for the StreetFoo client. It’s relatively easy to do—you create a new QuickLink object and pass it into the ReportCompleted method in ShareOperation. The next time you run that same sort of share operation, the quick link will appear in the share charm UI.

A photo shared from the Photos app

Figure 7-13. A photo shared from the Photos app