📜 ⬆️ ⬇️

Contract "share" - data transfer in metro applications Windows 8

In Windows 8, metro applications have the ability to transfer data (Share) between applications. In the future, for simplicity, the article will use the term " sharing ".
For security reasons, the data transmission is controlled by the user himself and is called up from the sidebar with the corresponding charm-button Share
image
or the key combination Win + H.

As an example, the possibility of transferring your location from maps or your favorite photo to your email application or twitter.

The implementation of the contract "sharing" can be a very powerful marketing tool. You can provide the opportunity to share your achievements in the game or application with friends, which can help increase the popularity of your application.
')
Here I will use the terms application provider for an application that “shares” data. And the application-receiver for the application which can accept the shared data.

The article turned out quite voluminous and here is a brief summary of the article:

Sharing Data provider
Sharing Receiver data.
Send and receive standard formats.
Sending and receiving text data formats.
Sending and receiving links
Sending and receiving graphics
Sending and receiving files
Sending and receiving non-standard data types
Sending and receiving objects serialized to a string
Standard "non-standard types". Data based on schema.org schemas
Sending and receiving binary data
Delayed data transfer (Data Provider Transfer).
Proper organization of the transfer of large amounts of data (music, video)
Adding links to the sharing panel (Quick links)
We process logical errors.
We improve the sharing of data. Statistics collection
General recommendations for the implementation of sharinga. Avoid errors in implementations.
Recommendations for the application provider.
Recommendations for the application provider.


For a detailed study of the Sharing contract, I recommend downloading two great examples of implementation in MSDN:
Sharing content source app sample
Sharing content target app sample

Sharing Data provider

Implementing an application that supplies data is quite simple Suppose we want to “share” some text.

On the page in the OnNavigatedTo method, add a request handler for sharing data. And unsubscribe from the sharing event in the OnNavigatingFrom method

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


In the handler, we can specify the data that we want to “share”. Suppose we want to publish some text.

  void Share_DataRequested(DataTransferManager sender, DataRequestedEventArgs args) { args.Request.Data.Properties.Title = "Simple notepad"; args.Request.Data.Properties.Description = "Publication of the text"; args.Request.Data.SetText("Hello! World!"); } 


The Title property is mandatory; without its indication, we will get an error when trying to “share” the data. At the same time, the Description property is optional.

Now, if we call data sharing, we will see on the right a panel with a suggestion to choose an application that can receive data.

image

By selecting a mail application, we will receive a form where the text of the letter will already be filled out and having selected the sender we can send a letter. The first two points are from previous activations of the mail application.

Sharing Receiver.

Implementing the data receiver's receiver application is also quite simple. Here we can use the ready-made template in the studio:

image

When you select this template, we will add an entry to the manifest, redefine the method in App.xaml, and add a new page.

We will not consider using this template and “manually” add the necessary elements step by step.

First of all, in the manifest file Package.appxmanifest, you need to add support for sharing contract by the application, and specify the type of data that the application can support:

image

Here we can see that we can also add support for Uri, Bitmap, HTML, StorageItems, RTF.

We will look at supporting these data types and non-standard formats later.

Next, we need to override OnShareTargetActivation in App.xaml and in this method we can send a link from where we can extract the text:

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


Now on the TargetPage page, we can extract the transmitted text. For simplicity, add a TextBlock to the page and set the text property for it.

To do this, override the OnNavigateTo method in the page code and extract the text.

  protected async override void OnNavigatedTo(NavigationEventArgs e) { if (e.Parameter != null) { var shareOperation = (ShareOperation)e.Parameter; txtBlock.Text=await shareOperation.Data.GetTextAsync(); } } 


It's all. Now, if we install the receiver application and then start the sending application and selecting our receiving application during sharing, we can see the following window:

image

Our application received a passed string from another application.

What should pay attention. The application is displayed in the side pop-up panel with a title. This circumstance should be taken into account when planning the design of the receiver page. (Perhaps there will be a different value in the release, but on several different monitors I had a panel width of 645 pixels).

Also, we cannot influence the title - the receiver page will be activated with a title (back arrow, the name of the application and the logo of our application). In this regard, you do not need to work out the navigation on this page. Ideally, this should be one page without any navigation.

Send and receive standard formats.

Quite often we need to transfer and receive slightly more complex data formats rather than strings. For the most commonly requested formats, the appropriate API support has been added. We can transfer as standard formats Text, Rtf, Html, Uri, Bitmap, StorageItems (files). Each of them can be transmitted simultaneously. Those. we can simultaneously send and extract, for example, Text, Html and Bitmap

Sending and receiving text data formats

The first three formats Text, Rtf, Html are transmitted and received in the same way:

Broadcast:

  args.Request.Data.SetText("..."); args.Request.Data.SetRtf("..."); args.Request.Data.SetHtmlFormat("..."); 


Reception:

First of all, to support the “reception” of the Text, HTML, Uri formats, we add the corresponding entry to the manifest.

And the corresponding data acquisition code:

  shareOperation.Data.GetTextAsync(); shareOperation.Data.GetRtfAsync(); shareOperation.Data.GetHtmlFormatAsync(); 


If we support several formats at once, then most likely we will receive only one specific data type and we should check the “content” of the data in the package.

  if(shareOperation.Data.Contains(StandardDataFormats.Text)) { var text = shareOperation.Data.GetTextAsync(); } if(shareOperation.Data.Contains(StandardDataFormats.Html)) { var html = shareOperation.Data.GetHtmlFormatAsync(); } if(shareOperation.Data.Contains(StandardDataFormats.Rtf)) { var rtf = shareOperation.Data.GetRtfAsync(); } 


Sending and receiving links

Transmission and reception of Uri is almost identical:

 args.Request.Data.SetUri(new Uri("http://akhmed.ru/post/2012/07/19/share_contract_win8.aspx")); 


Reception:

 shareOperation.Data.GetUriAsync(); 


Sending and receiving graphics

A bit more interesting is the transmission and reception of the picture.

Can be transferred as pictures from another server

 args.Request.Data.SetBitmap(RandomAccessStreamReference.CreateFromUri(new Uri("http://freshyourmood.com/wp-content/uploads/2012/04/pure-water-aquatecuk.wordpress.com_.jpg"))); 


so and local from the project

 args.Request.Data.SetBitmap(RandomAccessStreamReference.CreateFromUri(new Uri("ms-appx:///apple.jpg"))); 


extracting a picture is also quite a simple operation. First, add the appropriate entry to the manifest of the receiving application: Bitmap.

Next, for example, we will display the text and image on the page in the corresponding xaml code:

  <StackPanel Orientation="Horizontal" VerticalAlignment="Top"> <Image x:Name="img" Width="200" Height="200"></Image> <TextBlock x:Name="txtBlock" Margin="24" Text="start page text" Style="{StaticResource HeaderTextStyle}" /> </StackPanel> 


The code to "extract" data:

  protected async override void OnNavigatedTo(NavigationEventArgs e) { if (e.Parameter != null) { var shareOperation = (ShareOperation) e.Parameter; if (shareOperation.Data.Contains(StandardDataFormats.Text)) { txtBlock.Text = await shareOperation.Data.GetTextAsync(); } if (shareOperation.Data.Contains(StandardDataFormats.Bitmap)) { var bitmapReference = await shareOperation.Data.GetBitmapAsync(); var bitmapImage = new BitmapImage(); bitmapImage.SetSource(await bitmapReference.OpenReadAsync()); img.Source = bitmapImage; } } } 


And we can admire the result:

image

By the same principle, we can transfer images of icons. The syntax looks a little different.

Broadcast:

  args.Request.Data.Properties.Thumbnail = RandomAccessStreamReference.CreateFromUri(new Uri("ms-appx:///apple.jpg")); 


Reception:

  var bitmapReference = shareOperation.Data.Properties.Thumbnail; 


Thus, we can transmit two pictures at once, but for performance reasons, the second approach is used only if the pictures are located locally and they are not large.

Sending and receiving files

Through contracts we can transfer files available in the system. By default, in WinRT we do not have direct access to the file system and we can transfer files received, for example, through the FilePicker contract. In general, working with files deserves a separate article, but using FilePicker is very easy.

For example, while working with an application, we can load several files into our application.

For simplicity, add a button by clicking on which we select the files:

 <Button Content="LoadFiles" Click="LoadFiles_Click" HorizontalAlignment="Left" Margin="268,82,0,0" VerticalAlignment="Top" Width="139"/> 


And save the link to the files selected by the user:

  private IReadOnlyList<StorageFile> files; private async void LoadFiles_Click(object sender, RoutedEventArgs e) { var filePicker = new FileOpenPicker { FileTypeFilter = {"*"} }; files = await filePicker.PickMultipleFilesAsync(); } 


Now, we will indicate to the event of calling the sharing that we want to transfer these files:

  void Share_DataRequested(DataTransferManager sender, DataRequestedEventArgs args) { args.Request.Data.Properties.Title = "file sharing"; if(files!=null) { args.Request.Data.SetStorageItems(files); } } 


The implementation of the “receiver” of files is also quite simple. In the manifest, we need to add support for StorageItems files or tick Supports any file types

Next, for example, we will list the files in the ListBox:

  <ListBox x:Name="listBox"></ListBox> 


And we tie the names of the received files to this list.

  protected async override void OnNavigatedTo(NavigationEventArgs e) { if (e.Parameter != null) { var shareOperation = (ShareOperation)e.Parameter; var files=await shareOperation.Data.GetStorageItemsAsync(); listBox.ItemsSource = files.Select(i=>i.Name); } } 


As a result, we can see about the following result:

image

Sending and receiving non-standard data types.

Having standard formats greatly simplifies work for most scenarios. But still, quite often scripts will be needed when none of the formats are suitable for us. Suppose we have an application that works with a list of products. We have many fields, some of which are optional (Name, price, category, brand, unit of measure, etc.). Now, if we have another application that can also work with this format of products and we want to add the ability to put data in the form that we have another application.

Sending and receiving objects serialized to a string.

We can serialize our data to some format (CSV, XML, JSON) and parse it for receiving. But standard formats will not suit us. Firstly, if we use Text or another format for this, then all applications that support the standard format will open and selecting the mail application, the user will see a letter filled with some incomprehensible text. In addition, we still want to keep the ability to use Text or HTML format in order to be able to send data to another user a list of products without details.

In this case, a special data format can help us.

First of all, we need to come up with a name for the format, for example, Product. (Perhaps, before or after us, someone will come up with a brilliant idea to use a format with the same name and also name its format Product, to avoid conflicts you can use Product_ {GUID} (For example, Product_F6FBDBFB-5703-44F6-ACBE-A2D25EF4D6CE). Here for simplicity, keep the Product).

Suppose both in the receiver and the vendor we have the following class Product:

  public class Product { public string Name { get; set; } public int Count { get; set; } } 


And the serializer:

  public class JsonSerializer { public static string Serialize<T>(T item) { var dataSerializer = new DataContractJsonSerializer(typeof(T)); var ms = new MemoryStream(); dataSerializer.WriteObject(ms,item); ms.Position = 0; return new StreamReader(ms).ReadToEnd(); } public static T Deserialize<T>(string value) { var dataSerializer = new DataContractJsonSerializer(typeof(T)); return (T)dataSerializer.ReadObject(new MemoryStream(Encoding.UTF8.GetBytes(value))); } } 


In the application provider, we can now serialize the product and send specifying the format identifier:

  void Share_DataRequested(DataTransferManager sender, DataRequestedEventArgs args) { args.Request.Data.Properties.Title = "Product sharing"; var product = new Product() { Name = "Bread", Count = 2 }; var productJson = JsonSerializer.Serialize(product); args.Request.Data.SetData("product",productJson); } 


The implementation of the "recipient":

In the recipient application, we must indicate in the manifest that we now support the product format and can use the following code to get the product:

  protected async override void OnNavigatedTo(NavigationEventArgs e) { if (e.Parameter != null) { var shareOperation = (ShareOperation)e.Parameter; if(shareOperation.Data.Contains("product")) { var productJson = await shareOperation.Data.GetTextAsync("product"); var product = JsonSerializer.Deserialize<Product>(productJson); txtBlock.Text = product.Name + " " + product.Count; } } } 


By running the application you can see that our data has been successfully transferred.

image

In this case, we passed and received the usual string in a special form. We can still get the string without specifying the format GetTextAsync ();

Standard "non-standard types". Data based on schema.org schemas

You may want to use not your own private data format, but which one of the commonly accepted formats.

For example, if there are other applications that work with products, then it makes sense to organize the reception and transmission of data with these applications.

It is officially recommended in such cases to use schemas on the schema.org resource.

First of all, we need to decide on the type of entity (books, music, films, products)

By selecting the appropriate entity, we must send or receive a string in JSON according to the schema data.

Suppose we chose the “Product” entity (http://schema.org/Product)

Implementing provider application:

Data transfer is completely analogous to the transfer of the own format discussed above, the only difference is in the JSON format:

  async void Share_DataRequested(DataTransferManager sender, DataRequestedEventArgs args) { args.Request.Data.Properties.Title = "Product sharing"; var productFormat = @"{ ""type"" : ""http://schema.org/Product"", ""properties"" : { ""name"" : ""{0}"", ""description"" : ""{1}"", ""productID"" : ""{2}"" } }"; var productJson = String.Format(productFormat, "Olive oil", "description of product", "8410660101481"); args.Request.Data.SetData("http://schema.org/Product",productJson); } 


Receiver application implementation

To support this schema, we must specify the data format of schema.org/Product in the manifest.

image

Reading data is carried out similarly to reading the line that we considered above:

  protected async override void OnNavigatedTo(NavigationEventArgs e) { if (e.Parameter != null) { shareOperation = (ShareOperation)e.Parameter; var productJson = await shareOperation.Data.GetTextAsync("http://schema.org/Product"); JsonObject productObject = JsonObject.Parse(productJson); JsonObject properties = productObject["properties"].GetObject(); var productId = properties["productID"]; var productName = properties["name"]; var productDescriptions = properties["description"]; } } 


Sending and receiving binary data

In some cases it is easier to transfer binary data instead of a string. (For example, we have djvu editor and we want to transfer raw data).

We can open the file by passing the link to the stream from the open file and reading this stream in the receiver's application.

Consider a more interesting example of transferring data from a stream in memory:

  async void Share_DataRequested(DataTransferManager sender, DataRequestedEventArgs args) { args.Request.Data.Properties.Title = "Product sharing"; var product = new Product() { Name = "Bread", Count = 2 }; var stream = new InMemoryRandomAccessStream(); using(var writer=new DataWriter(stream)) { writer.WriteInt32(product.Name.Length); writer.WriteString(product.Name); writer.WriteInt32(product.Count); await writer.StoreAsync(); await writer.FlushAsync(); writer.DetachStream(); } stream.Seek(0); args.Request.Data.SetData("product", stream); } 


In the receiver application, we can read and parse this stream.

  protected async override void OnNavigatedTo(NavigationEventArgs e) { if (e.Parameter != null) { var shareOperation = (ShareOperation)e.Parameter; if (shareOperation.Data.Contains("product")) { var stream = await shareOperation.Data.GetDataAsync("product") as IRandomAccessStream; var product = new Product(); using(var streamReader=new DataReader(stream)) { await streamReader.LoadAsync((uint) stream.Size); var len = streamReader.ReadInt32(); product.Name = streamReader.ReadString((uint) len); product.Count = streamReader.ReadInt32(); } txtBlock.Text = product.Name + " " + product.Count; } } } 


Delayed data submission (Data Provider Transfer)

In the previous example, there is a significant drawback. We initialize and prepare the data always - even if later the user does not use the application and cancels the sharing operation. Alternatively, we can share not the data but the data provider and the application the receiver can read the data after it has been activated by the user. Rework the last example with a data provider using a data provider.

  async void Share_DataRequested(DataTransferManager sender, DataRequestedEventArgs args) { args.Request.Data.Properties.Title = "Product sharing"; args.Request.Data.SetDataProvider("product",BinaryDataHandler); } private async void BinaryDataHandler(DataProviderRequest request) { try { var product = new Product() { Name = "Bread", Count = 2 }; var stream = new InMemoryRandomAccessStream(); using (var writer = new DataWriter(stream)) { writer.WriteInt32(product.Name.Length); writer.WriteString(product.Name); writer.WriteInt32(product.Count); await writer.StoreAsync(); await writer.FlushAsync(); writer.DetachStream(); } stream.Seek(0); request.SetData(stream); } finally { request.GetDeferral().Complete(); } } 


The main advantage of this approach is that now the sharing dialog will appear faster, the BinaryDataHandler method will only be called AFTER the selection of a specific application. And if the user cancels the sharing operation, the method will not be called at all.

Proper organization of the transfer of large amounts of data (music, video)

In cases where the application delivers a large amount of data, we should not force the user to wait until all data has been transferred.

In those cases when we have already finished receiving data and if we carried out all the necessary actions in the interface (for example, we clicked the Send button on the mail interface or the Save button, where we clearly need confirmation from the user), we can call the method:

 shareOperation.ReportCompleted(); 


Which notifies the system that all data transfer operations have been completed and results in closing the sharing interface of the receiver's application.

In cases where the amount of transmitted data is too large, we should not keep the user waiting.

We can call the method

 shareOperation.ReportStarted(); 


Before retrieving a large data packet, which will cause the transfer process to begin but will hide the sharing interface and allow the user to continue working.

Next we have to call the method

  shareOperation.ReportDataRetreived(); 


Which notifies the system that we have already extracted all the necessary data and the original application can be released (in case the user closed it before).

Upon completion of the transfer, we can call the method

 shareOperation.ReportCompleted(); 


or method

 shareOperation.ReportError("error description"); 


To notify the system of success or error during data transfer.

In the latter case, the user will receive an error notification and may look at the error if he opens the sharing again and follows the link below to check the transfer status.

In the event that the data transfer application uses BackgroundTransferTask, we can notify the system that we use this data transfer method by calling the appropriate method:

  shareOperation.ReportSubmittedBackgroundTask(); 


Adding links to the sharing panel.

Suppose in the application of the receiver after the transfer of data have to do additional operations. For example, when we send text to an email application, the user must also select the addressee to whom he will send the message. In this case, the email application creates a quick link to the last action. Next time choosing this quick link in the “to” field will be filled with the corresponding entry.

Suppose that there is no single list of products in our receiver application. There is a list of lists of products. If the user selected the application and selected the list where he wants to transfer the product, we can provide a quick link to this list.

Consider how you can add a quick link to our receiving application:

  private async void ButtonSaveLink_Click(object sender, RoutedEventArgs e) { var quickLinkInfo = new QuickLink { Id = "homeListId", Title = "Add to Home list", SupportedFileTypes = { "*" }, SupportedDataFormats = { //   StandardDataFormats.Bitmap, "product" } }; try { var iconFile = await Package.Current.InstalledLocation.CreateFileAsync("assets\\Logo.png", CreationCollisionOption.OpenIfExists); quickLinkInfo.Thumbnail = RandomAccessStreamReference.CreateFromFile(iconFile); shareOperation.ReportCompleted(quickLinkInfo); } catch (Exception) { shareOperation.ReportCompleted(); throw; } } 


Relevant code in XAML

 <Button Content="Save" Click="ButtonSaveLink_Click" /> 


Now, if we click the Save button in the receiver application, we will see that the next time we share the quick link with us.

image

Now the next time we can simplify the same type of action, by determining which of the quick links the user clicked.

  shareOperation = (ShareOperation)e.Parameter; if(shareOperation.QuickLinkId=="homeListId") { //handle selected list } 


Why not add this link automatically? Because the user may not take any action after the application is activated.

The sharing API is specifically designed so that adding a link minimizes the receiving application. Accordingly, adding the link should be the last operation that can be done. Those. if we have selected the receiver application and selecting the list, we immediately save and add a quick link. Or by selecting the list, we give the opportunity to change your choice and save to the selected list only after clicking the Save button.

If the link is no longer relevant, we can remove the quick link from our application. (For example, there is no longer a list with this identifier).

  shareOperation.RemoveThisQuickLink(); 


We process logical errors.

If the source application page does not support data sharing when trying to share, the user will see the message “This app can't share.”

. ( ).

, .

For example:

  async void Share_DataRequested(DataTransferManager sender, DataRequestedEventArgs args) { args.Request.Data.Properties.Title = "Product sharing"; args.Request.FailWithDisplayText("please select products for sharing"); } 


«» :

image

.

API . .

, .

.

.

  protected override void OnNavigatedTo(NavigationEventArgs e) { DataTransferManager.GetForCurrentView().DataRequested += Share_DataRequested; DataTransferManager.GetForCurrentView().TargetApplicationChosen += ShareText_TargetApplicationChosen; } protected override void OnNavigatingFrom(NavigatingCancelEventArgs e) { DataTransferManager.GetForCurrentView().DataRequested -= Share_DataRequested; DataTransferManager.GetForCurrentView().TargetApplicationChosen -= ShareText_TargetApplicationChosen; } void ShareText_TargetApplicationChosen(DataTransferManager sender, TargetApplicationChosenEventArgs args) { var targetAppName = args.ApplicationName; //  } 


Similarly, in the receiver application, we can find out from which application the data is delivered to us.

  protected async override void OnNavigatedTo(NavigationEventArgs e) { if (e.Parameter != null) { var shareOperation = (ShareOperation)e.Parameter; //   var sourceAppName = shareOperation.Data.Properties.ApplicationName; //Url      (null    ) var sourceAppMarketUrl = shareOperation.Data.Properties.ApplicationListingUri; //  } } 


General recommendations for the implementation of sharinga. Avoid errors in implementations.

Not all recommendations presented here are official, I wrote a part of the recommendations from myself and should be taken critically.

-.

. . , .

( OnNavigateTo, OnNavigateFrom). .

, , ( ).

. , Text Html , , .

, , .

( , , ). - .

, .

 DataTransferManager.ShowShareUI(); 


-.

. : shareOperation.Data.AvailableFormats

shareOpertaion.Data.Contains();

, , .. , shareOperation.Report…(); .

.

scheme.org always assume that the data will come in the wrong format and conduct the appropriate checks and error handling.

Source: https://habr.com/ru/post/148130/


All Articles