środa, 11 kwietnia 2012

Notatki o Windows 8 Consumer Preview - odc.9

W tym odcinku skupię się głównie na różnicach jakie zaobserwowałem w przypadku wymiany danych poprzez kontrakt sharing oraz mechanizm kopiuj-i-wklej.

Jeśli chodzi o kontrakt sharing dostajemy więcej informacji o definiowaniu własnych formatów, doprecyzowano sposoby obsługi dłużej trwającego przekazywania danych – za pomocą metody deferral (model push) i za pomocą delegata (model pull, najbardziej wygodny dla długo trwających operacji, także gdy dane trzeba jakoś specjalnie przed rozpoczęciem przekazywania przygotować np. skonwertować). W różnych miejscach można zauważyć pewne zmiany w API (m.in bardziej rozbudowane przykłady, nowe wymagania co do użycia, asynchroniczne metody do odczytu tekstu czy innego formatu danych). Ciekawe jest kopiowanie HTML zawierającego obrazek, wtedy oprócz samego markupu załącza się w specjalnym słowniku pliki, do których są odwołania.

Uwagę przykuwa funkcjonalność copy & paste jako sposób na przekazywanie danych pomiędzy aplikacjami Metro i desktopowymi. Pod względem implementacji w maksymalnym stopniu współdzieli API razem z kontraktem sharing (namespace, struktury i formaty danych). Silverlight oferuje tylko obsługę prostego tekstu, tu mamy “na dzień dobry” obsługę kilku popularnych formatów tj. tekst prosty i bogaty, linki, HTML, bitmapy, pliki. Można też definiować własne formaty. Silverlight pozwala pobierać zawartość schowka i zapisywać do niego, w aplikacjach Metro w Windows 8 możemy także otrzymywać notyfikacje o zmianie zawartości schowka. Funkcjonalność copy & paste została zaimplementowana w wielu kontrolkach.

Otrzymujemy wskazówki, kiedy lepiej używać kontraktu sharing, kiedy kopiuj i wklej, a kiedy oba mechanizmy w równym stopniu nadają się do zastosowania.

JS

In most cases, the user launches the Share charm through a simple touch gesture or mouse click. However, as a developer, you also have the option of launching the Share charm programmatically. This option is ideal for situations in which it isn't obvious what a user wants to share, like a high score in a game or a postal address on a webpage.

One advantage the clipboard has over sharing is that it's the only way to move data from Metro style apps to the desktop, and vice versa. For example, consider a user who's editing an image using a legacy app. Because the app isn't a Metro style app, the user is in the desktop. Later, the user decides to use the feature of a Metro style app to further enhance the picture. In this situation, using the Share charm doesn't make sense. Instead, using the familiar copy and paste commands of the clipboard is a more natural and logical solution.

Just about every app should support copy and paste operations to some degree. In many cases, you can support copy and paste actions with little additional coding. For example, copy and paste shares much of the same API as the share feature. So, if you are already writing code to support share actions, you should be able to extend that code to support copy and paste. If you're using Microsoft Visual Studio to develop your app, you'll find that most of the controls that you can use automatically support copy and paste without any additional work on your part.

Letting other apps use your app as a save location for files makes sense when the user is working with files and folders. Its less intuitive when it comes to smaller sets of data, such as a URL or a text selection. In those cases, the Share charm or the copy and paste commands are better options. That said, there are plenty of situations in which it makes sense to support both the Share charm and saving. For example, consider a user who wants to store a file using a cloud-storage service, such as SkyDrive. In this situation, both the Share charm and saving are equally valid options.

Adding a share

Hh758314.SharingOverview(en-us,WIN.10).png

First, when the user selects your app, Windows will display it using the Snap view. You can also specify a specific form or HTML file that opens when the app is activated through the Share charm. That way, you can create a customized experience specifically for sharing.

While it's likely that one format makes more sense than the others, by supplying the same data in other formats you can help to ensure that users can share by using the target app of their choice. For example, if a user wants to share formatted text, such as from a webpage, you might also want to supply a plain-text version of the content, because not every target app supports formatted text.

Windows 8 supports custom formats, for such situations. These custom formats can either be based on standard data schemas, such as those found on http://www.schema.org, or a format that you define that's unique to your app.

Using schema-based formats

There are lots of times where the data that you want to share (either as a source or as a target app) is more specific than the standard formats provide. For example, a movie-focused app might want to share information about movies, and want details such as the title, rating, director, and so on. A book-focused app might want to share information about books, including author, title, and publication date. If your app falls into a category like this, we recommend that you support one of the many schemas listed on http://www.schema.org.

If you want to share data using one of these schemas, here's how you do it:

  1. Identify the item (book, movie, and so on) that the user wants to share.
  2. Collect the information related to the item and package it as a JSON object.
  3. Use setData to add the content to a DataPackage. When you do, you have to include a format ID. For now, use Windows-8-Preview-<schema> as the ID. For example, Windows-8-Preview-Book.

    Note The format ID is going to change before the final release of Windows 8.

var book = { "type" : "http://schema.org/Book", "properties" : { "image" : "http://sourceurl.com/catcher-in-the-rye-book-cover.jpg", "name" : "The Catcher in the Rye", "bookFormat" : "http://schema.org/Paperback", "author" : "http://sourceurl.com/author/jd_salinger.html", "numberOfPages" : 224, "publisher" : "Little, Brown, and Company", "datePublished" : "1991-05-01", "inLanguage" : "English", "isbn" : "0316769487" }};book = JSON.stringify(book);function shareCustomData() { var dataTransferManager = Windows.ApplicationModel.DataTransfer.DataTransferManager.getForCurrentView(); dataTransferManager.addEventListener("datarequested", function (e) { var request = e.request; request.data.setData("Windows-8-Preview-Book", book); });}

If you want to receive data that uses one of these schemas, here are the steps you need to follow:

  1. Edit your package manifest to declare that your app is a share target. See our Quickstart to learn how to do this.
  2. In your package manifest, add a data format that identifies the schema that your app supports. Use Windows-8-Preview-<schema> as the data format. For example, Windows-8-Preview-Book.
  3. Use getDataAsync to get the content from the DataPackageView object that you receive during a share operation.

You must publish the format. That way, developers who want to use the format know how they need to package the content.

There's always a chance that something unexpected could happen. To help you handle these situations, the DataRequest object supports a FailWithDisplayText method. Use this method to display a text message to the user if something happens that prevents your app from sharing their content.

How to share HTML

function shareHtml() { var htmlExample = "<p>Here is our store logo: <img src='images/logo.png'>.</p>"; var fileExample = "images\\logo.png"; var dataTransferManager = Windows.ApplicationModel.DataTransfer.DataTransferManager.getForCurrentView(); dataTransferManager.addEventListener("datarequested", function (e) { var streamRef; Windows.ApplicationModel.Package.current.installedLocation.getFileAsync(fileExample).then(function (file) { try { streamRef = new Windows.Storage.Streams.RandomAccessStreamReference.createFromFile(file); } catch (ex) { document.getElementById("output").innerText = "Fail."; } }); var htmlFormat = Windows.ApplicationModel.DataTransfer.HtmlFormatHelper.createHtmlFormat(htmlExample); var request = e.request; request.data.properties.title = "Share Html Example"; request.data.properties.description = "A demonstration that shows how to share."; request.data.setHtmlFormat(htmlFormat); request.data.resourceMap[fileExample] = streamRef; });}

The createHtmlFormat function wraps the HTML format with the headers and other information the system needs to share the content.To add the HTML, use the setHtmlFormat function.To add the image file, use the resourceMap function.

How to share an image

We recommend that you always add a thumbnail image any time you're sharing an image. We also recommend that you convert the image to a bitmap (if it isn't already). This helps to ensure that the user can choose from the largest number of apps when they share the image. To add the image as a bitmap, use the setBitmap method.

function shareImage() { var shareImage; var shareThumbnail; Windows.ApplicationModel.Package.current.installedLocation.getFileAsync("images\\smalllogo.png").then(function (file) { shareThumbnail = file; }); var dataTransferManager = Windows.ApplicationModel.DataTransfer.DataTransferManager.getForCurrentView(); dataTransferManager.addEventListener("datarequested", function (e) { var request = e.request; request.data.properties.title = "Share Image Example"; request.data.properties.description = "A demonstration that shows how to share an image."; var thumbnailStreamReference = Windows.Storage.Streams.RandomAccessStreamReference.createFromFile(shareThumbnail); request.data.properties.thumbnail = thumbnailStreamReference; var deferral = request.getDeferral(); Windows.ApplicationModel.Package.current.installedLocation.getFileAsync("images\\logo.png").then(function (file) { shareImage = file; var shareImageReference = Windows.Storage.Streams.RandomAccessStreamReference.createFromFile(shareImage); request.data.setBitmap(shareImageReference); deferral.complete(); }); });}

By the way, if your app takes a long time (greater than 200 milliseconds) to get the image ready, you might want to use a delegate function to share it.

How to share files

By the way, if your app takes a long time (more than 200 milliseconds) to get the files ready, you might want to use a delegate function to share it. If you decide to use a delegate function, you need to set the FileTypes property on the DataPackage before adding the files themselves.

After you call setStorageItems, remember that you can't set any new properties on the individual items. That's why you should make sure your files are completely ready for sharing before you add them to the DataPackage.

How to delay sharing

Windows 8 Consumer Preview supports two ways of giving your app more time to get data ready for sharing: deferrals and delegates. A deferral means that your app tells the system to wait until the data is ready, which means that the user also has to wait. That's not a big deal, as long as the user doesn't have to wait too long. Using a delegate enables your app to support pull operations. Instead of pushing the data to another app, your app waits until the app pulls the data. This type of sharing takes a little more work to code, but lets the sharing happen behind the scenes—the user can move to their next activity.

How to support pull operations

If your app needs to do some additional work before the files are ready - for example, to convert the files from one format to another -then you should take a look at pull operations. That topic shows you how to use a delegate function so that target apps can pull the content being shared from your app, instead of requiring your app push the content.

To support pull operations, you first create a function that packages the data that the user wants to share. Then, instead of supplying the data to another app, you supply a function. When the target app tries to get the data, your app calls your function. The advantage here is that your app can share the data behind the scenes, allowing the user to continue using your app for other activities.

var imageFile; var picker = new Windows.Storage.Pickers.FileOpenPicker(); picker.fileTypeFilter.replaceAll([".jpg", ".bmp", ".gif", ".png", ".wmv"]);picker.pickSingleFileAsync().then(function (file) { logText("PickImage: Picker returned a file"); imageFile = file;});function onDeferredImageRequested(request) { try { if (imageFile) { // This is to make sure deferral works even in synchronous case var deferral = request.getDeferral(); var imageStreamRef = Windows.Storage.Streams.RandomAccessStreamReference.createFromFile(imageFile); request.setData(imageStreamRef); deferral.complete(); } } catch (exc) { // Error handling goes here. }}function shareFiles() { var dataTransferManager = Windows.ApplicationModel.DataTransfer.DataTransferManager.getForCurrentView(); dataTransferManager.addEventListener("datarequested", function (e) { var request = e.request; request.data.properties.title = "Share Files Example"; request.data.properties.description = "A demonstration that shows how to share files."; request.data.properties.fileTypes.replaceAll([".jpg", ".bmp", ".gif", ".png", ".wmv"]); request.data.setDataProvider(Windows.ApplicationModel.DataTransfer.StandardDataFormats.bitmap, onDeferredImageRequested); });}

Notice that function OnDeferredImageRequested uses setData to add the content, instead of a format-specific function, like setBitmap or setStorageItems. Any time you use a delegate function, you must use setData to supply the content. If you're using a delegate function to share files, you need to specify what file types the DataPackage contains.

One other thing: using setStorageItems adds copies of the files to the DataPackage. This means that if you add properties to the original file after you call this method, those new properties won't be included. That's why we recommend you don't add the files until you're completely ready.

Receiving shared content

if (eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.shareTarget) { // Code to handle activation goes here.}

The data that the user wants to share is contained in a ShareOperation object.

shareOperation = eventObject.detail.shareOperation;

if (shareOperation.data.contains(Windows.ApplicationModel.DataTransfer.StandardDataFormats.text)) { shareOperation.data.getTextAsync().then(function (text) { if (text !== null) { // To output the text using this example, // you need a div tag with an id of "output" in your HTML file. document.getElementById("output").innerText = text; } });}

After your app finishes processing the content that the user wants to share, call reportCompleted. You must call this method so that Windows 8 knows that your app is no longer needed.

shareOperation.reportCompleted();

If you're going to test the code in this section, we recommend that you hold off on adding the code that calls reportCompleted, as the system will close your app after it's called. When moving your code into production, make sure you add the call to reportCompleted.

Report extended share status (for lengthy operations)

After you call reportStarted, don't expect any more user interaction with your app. As a result, you shouldn't call it unless you're app is at a point where it can be dismissed by the user.

If something goes wrong, you can also send an error message to the system. The user will see the message when they check on the status of the share. At this point, your app shut down—the user will have re-share the content to use your app again.

Finally, when your app has processed the shared content, you should call reportCompleted to let the system know. After your app knows the share is complete, your app is dismissed.

Return a Quicklink object if sharing was successful

function reportCompleted(shareOperationObject) { var quickLink = new Windows.ApplicationModel.DataTransfer.ShareTarget.QuickLink(); quickLink.id = "123456789"; quickLink.title = id("quickLinkTitle").value; Windows.ApplicationModel.Package.current.installedLocation.getFileAsync("images\\user.png").then(function (iconFile) { quickLink.thumbnail = Windows.Storage.Streams.RandomAccessStreamReference.createFromFile(iconFile); }); var dataFormats = Windows.ApplicationModel.DataTransfer.StandardDataFormats; quickLink.supportedFileTypes.replaceAll(["*"]); quickLink.supportedDataFormats.replaceAll([dataFormats.text, dataFormats.uri, dataFormats.bitmap, dataFormats.storageItems, customFormatName]); shareOperation.reportCompleted(quickLink);}

Remember that your app is responsible for storing the ID of the QuickLink and the corresponding user data. When the user taps the QuickLink, you can get its ID through the ShareOperation.quickLink property.

How to receive a link

shareOperation.data.getUriAsync().then(function (uri) { if (uri !== null) { // To output the link using this example, // you need a div tag with an id of "output" in your HTML file. document.getElementById("output").innerText = uri.absoluteUri; }});

How to receive HTML

shareOperation.data.getHtmlFormatAsync().then(function (html) { document.getElementById("test").innerText = "Test"; if (html !== null) { var htmlFragment = Windows.ApplicationModel.DataTransfer.HtmlFormatHelper.getStaticFragment(html); var htmlDiv = document.createElement("div"); htmlDiv.innerHTML = htmlFragment; document.body.appendChild(htmlDiv); } });

How to receive an image

Often, apps that share images include thumbnail versions of those images. The following code checks to see if a thumbnail exists. If so, it appends it as an element to an HTML document.

if (shareOperation.data.properties.thumbnail) { shareOperation.data.properties.thumbnail.openReadAsync().then(function (thumbnailStream) { var thumbnailBlob = MSApp.createBlobFromRandomAccessStream(thumbnailStream.contentType, thumbnailStream); var thumbnailUrl = URL.createObjectURL(thumbnailBlob, false); // To display the thumbnail, you need an element with id of "thumbnail" // in your HTML page. document.getElementById("thumbnail").src = thumbnailUrl; });}

shareOperation.data.getBitmapAsync().then(function (streamRef) { streamRef.openReadAsync().then(function (bitmapStream) { if (bitmapstream) { var blob = MSApp.createBlobFromRandomAccessStream(bitmapStream.contentType, bitmapstream); var imageUrl = URL.createObjectURL(blob, false); // To display the image, you need an element with id of "imageholder" // in your HTML page. document.getElementById("imageholder").src = imageUrl; } }); });

How to receive files

Processing files can take time. It is important that you don't force the user to wait until your app is finished loading and processing data. Instead, call the reportStarted method. This method lets the system know that your app has started to process the content being shared. The system will keep your app alive until you call reportCompleted—even if the user dismisses your app to return to the source app.

shareOperation.data.getStorageItemsAsync().then(function (storageItems) { var fileList = ""; for (var i = 0; i < storageItems.size; i++) { fileList += storageItems.getAt(i).name; if (i < storageItems.size - 1) { fileList += ", "; } } filesDiv = document.createElement("div"); filesDiv.innerText = fileList; document.body.appendChild(filesDiv);});

How to create a QuickLink

First, a QuickLink doesn't actually store any data. Instead, it contains an identifier that, when selected, is sent to your app. For QuickLinks to work, your app needs to store the data somewhere—such as in the cloud, or on the user's computer—along with its associated ID.

Step 1: Create a QuickLink object. The QuickLink object is part of the ShareOperation class.

Step 2: Add an ID to the QuickLink.

Step 3: Add a title and an icon to the QuickLink.

Step 4: Specify the supported file types

Step 5: Send the QuickLink to the system.

function reportCompleted(shareOperationObject) { var quickLink = new Windows.ApplicationModel.DataTransfer.ShareTarget.QuickLink(); quickLink.id = "123456789"; quickLink.title = id("quickLinkTitle").value; Windows.ApplicationModel.Package.current.installedLocation.getFileAsync("images\\user.png").then(function (iconFile) { quickLink.thumbnail = Windows.Storage.Streams.RandomAccessStreamReference.createFromFile(iconFile); }); var dataFormats = Windows.ApplicationModel.DataTransfer.StandardDataFormats; quickLink.supportedFileTypes.replaceAll(["*"]); quickLink.supportedDataFormats.replaceAll([dataFormats.text, dataFormats.uri, dataFormats.bitmap, dataFormats.storageItems, customFormatName]); shareOperation.reportCompleted(quickLink);}

Call this function after your app finishes a share operation, instead of calling ShareOperation.reportCompleted directly.

Guidelines for creating custom data formats

Choosing a data type

One of the most important decisions you’ll make when defining a custom format is the WinRT data type used for transferring it between source and target applications. The DataPackage interface supports several data types for a custom format:

If you choose string as the data type for your format, you can retrieve it on the target side using the GetTextAsync(formatId) form of the GetTextAsync function. This function performs data-type checking for you. Otherwise, you have to use GetDataAsync, which means you must protect against potential data-type mismatches. For example, if a source application provides a single URI, and the target attempts to retrieve it as a collection of URIs, a mismatch occurs.

Populating the DataPackage

System.Uri[] uris = new System.Uri[2]; uris[0] = new System.Uri("http://www.msn.com"); uris[1] = new System.Uri("http://www.microsoft.com"); DataPackage dp = new DataPackage(); dp.SetData("UriCollection", uris);

Retrieving the data

if (dpView.Contains("UriCollection")){ System.Uri[] retUris = await dpView.GetDataAsync("UriCollection") as System.Uri[]; if (retUris != null) { // Retrieved Uri collection from DataPackageView }}

Custom format example: WebFileItems

Adding custom formats to your app

Stick to the intended purpose of the format. Don't use it in unintended ways.

Copying and pasting data

From a developer perspective, both sharing and clipboard operations use the same Windows namespace, Windows.ApplicationModel.DataTransfer. Both also require that your app collect the data that a user selects. (We often refer to this as packaging, because you use the DataPackage class to accomplish this.)

As a developer, remember that while sharing and clipboard operations are very similar, they do have key differences. With sharing, the focus is on the destination: the app or service that the user selects. With the clipboard, the focus is more on the data. Most importantly, clipboard operations are the only way to exchange data between a desktop app and a Metro style app.

Many of the controls that you can use to create your Metro style app already support clipboard operations.

In Windows 8, there are two categories of formats: standard and custom.

Currently, standard formats include:

  • Text
  • HTML
  • URI
  • Bitmaps
  • StorageItems
  • RTF

To support custom formats, we strongly encourage you to use one of the many schemas specified at schema.org. Using commonly known data formats, such as these schemas, helps to ensure that the app that receives the clipboard data knows what to do with it.

Supporting clipboard operations usually has two parts: copy (or cut), and paste.

var dataPackage = new Windows.ApplicationModel.DataTransfer.DataPackage(); dataPackage.requestedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.copy; //.move

At this point, you can add the data that a user has selected to the DataPackage object.

dataPackage.setText("Hello World!");

To see examples of how to add other formats to a DataPackage, see the clipboard sample in the code gallery. And remember, you can add more than one format to a DataPackage.

The last thing you need to do is add the DataPackage to the clipboard.

Windows.ApplicationModel.DataTransfer.Clipboard.setContent(dataPackage);

Paste

To get the contents of the clipboard, call the static Clipboard.getContent method. This method returns a DataPackageView that contains the content. This object is almost identical to a DataPackage object, except that its contents are read-only. With that object, you can use either the AvailableFormats or the contains method to identify what formats are available.

var dataPackageView = Windows.ApplicationModel.DataTransfer.Clipboard.getContent(); if (dataPackageView.contains(Windows.ApplicationModel.DataTransfer.StandardDataFormats.text)) { dataPackageView.getTextAsync().then(function (text) { // To output the text from this example, you need an HTML element // with an id of "output". document.getElementById("output").innerText = "Clipboard now contains: " + text; });}

Tracking changes to the clipboard

In addition to copy and paste commands, you might also find it helpful to add an event handler so that your app knows when the content of the clipboard changes. You can do this by handling the clipboard's ContentChanged event.

Windows.ApplicationModel.DataTransfer.Clipboard.addEventListener("contentchanged", function (event) { var dataPackageView = Windows.ApplicationModel.DataTransfer.Clipboard.getContent(); if (dataPackageView.contains(Windows.ApplicationModel.DataTransfer.StandardDataFormats.text)) { dataPackageView.getTextAsync().then(function (text) { // To output the text from this example, you need an HTML element // with an id of "output". document.getElementById("output").innerText = "Clipboard now contains: " + text; }); }});

Guidelines and checklist for clipboard commands

Although Windows 8 Consumer Preview supports other ways for apps to exchange information - such as through sharing - copy and paste commands remain an expected part of the Windows experience. Your app should support them whenever possible.

In general, you should support copy and paste for any editable content that a user can explicitly select—such as a subset of a document or an image. You should also consider supporting copy and paste commands for content that users might want to use somewhere else. For example:

  • Images in a photo gallery application
  • Computation results in a calculator
  • Restaurant address in a restaurant-search application

A few guidelines:

  • Provide support for paste only on editable regions and canvases in your application.
  • Consider supporting sharing if you are also supporting copy.
  • Don't provide support for copying content that can’t be selected—either explicitly, or through a context menu.

Accessing copy and paste commands in your app

Before implementing support for copy and paste commands, consider how users access copy and paste commands. In general, users access copy and paste commands by one of three methods: a context menu, the app bar, or keyboard shortcuts.

Use a context menu:

  • For items that users can select only through tap-and-hold gestures—such as hyperlinks or embedded images. For example, let's say your app displays an address to the user, and you want the user to be able to copy that address. A great user experience would be to create a Copy Address command that users can access when they either right-click or tap-and-hold the address.

Example of accessing copy command from context menu

  • For text selection (both editable and read-only).
  • For paste operations where the target is well defined, such as a cursor location or a table cell.

If the preceding guidelines don't apply to your app, then you can likely use the app bar. Some examples include:

  • When your app supports the selection of multiple items.
  • When the user can select a portion of an image.
  • When the target of a paste command is clear - such as pasting a screen shot on a canvas.

We strongly encourage you to always support keyboard shortcuts.

C++

Using JavaScript Object Notation (JSON)

1 komentarz: