I am creating an application where I am sharing a InkManager stroke by first saving the inkmanager stroke to local setting and then reading it as stream and sharing.
Now the HTML content is getting shared but there is no image attached to email. If I comment the code to share html the image is getting attached while sharing through email app. But both are not getting shared at once. How can I achieve this???
Code:
DataPackage requestData = request.Data;
requestData.Properties.Title = this.PageViewModel.JobInformationDetail.JobNumber;
requestData.Properties.Description = this.PageViewModel.JobInformationDetail.CustomerSignatureName;
//requestData.SetText("Sample Text");
RandomAccessStreamReference signatureStream = await GetInkManagerStream();
if (signatureStream != null)
{
requestData.SetBitmap(signatureStream);
}
requestData.SetHtmlFormat(Windows.ApplicationModel.DataTransfer.HtmlFormatHelper.CreateHtmlFormat(this.GetMailDescription()));
Convert InkManager to stream
private async Task<RandomAccessStreamReference> GetInkManagerStream()
{
IRandomAccessStream signatureStream = null;
StorageFile myMerge = await ApplicationData.Current.LocalFolder.CreateFileAsync("sign.png", CreationCollisionOption.OpenIfExists);
IOutputStream signature = await myMerge.OpenAsync(FileAccessMode.ReadWrite);
if (signature != null)
{
await _inkManager.SaveAsync(signature);
signature.Dispose();
}
signatureStream = await myMerge.OpenAsync(FileAccessMode.ReadWrite);
RandomAccessStreamReference streamRef = RandomAccessStreamReference.CreateFromStream(signatureStream);
signatureStream.Dispose();
return streamRef;
}
The problem is caused by awaiting an async method inside your DataRequested event handler. If you're doing this, you need to use DataRequestReferral:
private async void OnDataRequested(DataTransferManager sender, DataRequestedEventArgs args)
{
var deferral = args.Request.GetDeferral();
// the rest of your method, including the awaiting call
deferral.Complete();
}
By doing this you're telling the caller that it needs to wait for an async call to complete before continuing. At the end you signal it to continue by calling deferral.Complete(). Without it the caller continues before you actually set the bitmap, thus the error.
EDIT:
Windows 8 Mail app is not a share target for images shared like this, though. For the Mail app to be available you need to share the image as a storage item:
var file = await ApplicationData.Current.LocalFolder.GetFileAsync(#"sign.png");
var imageItems = new List<IStorageItem>();
imageItems.Add(file);
requestData.SetStorageItems(imageItems);
Now Mail will show up as one of available targets. When you're sharing images, it's best to use both SetBitmap() and SetStorageItems() to get more share targets available since not all apps support both data types.
EDIT 2:
Also the Mail app only picks up one type of shared content, either HTML or StorageItems (if no HTML is provided) as you've already noticed. The only workaround I know of, is to embed the images into the HTML content:
var resourceName = "logo.png";
var html = String.Format("<p>HTML content</p><img src='{0}'/>", resourceName);
requestData.ResourceMap[resourceName] = RandomAccessStreamReference.CreateFromUri(
new Uri("ms-appdata:///local/sign.png"));
requestData.SetHtmlFormat(HtmlFormatHelper.CreateHtmlFormat(html));
You can read more about this approach in a blog post by Mike Taulty.
Related
I have a working solution to load and render a PDF document from a byte array in a Windows Store App. Lately some users have reported out-of-memory errors though. As you can see in the code below there is one stream I am not disposing of. I've commented out the line. If I do dispose of that stream, then the PDF document does not render anymore. It just shows a completely white image. Could anybody explain why and how I could load and render the PDF document and dispose of all disposables?
private static async Task<PdfDocument> LoadDocumentAsync(byte[] bytes)
{
using (var stream = new InMemoryRandomAccessStream())
{
await stream.WriteAsync(bytes.AsBuffer());
stream.Seek(0);
var fileStream = RandomAccessStreamReference.CreateFromStream(stream);
var inputStream = await fileStream.OpenReadAsync();
try
{
return await PdfDocument.LoadFromStreamAsync(inputStream);
}
finally
{
// do not dispose otherwise pdf does not load / render correctly. Not disposing though may cause memory issues.
// inputStream.Dispose();
}
}
}
and the code to render the PDF
private static async Task<ObservableCollection<BitmapImage>> RenderPagesAsync(
PdfDocument document,
PdfPageRenderOptions options)
{
var items = new ObservableCollection<BitmapImage>();
if (document != null && document.PageCount > 0)
{
for (var pageIndex = 0; pageIndex < document.PageCount; pageIndex++)
{
using (var page = document.GetPage((uint)pageIndex))
{
using (var imageStream = new InMemoryRandomAccessStream())
{
await page.RenderToStreamAsync(imageStream, options);
await imageStream.FlushAsync();
var renderStream = RandomAccessStreamReference.CreateFromStream(imageStream);
using (var stream = await renderStream.OpenReadAsync())
{
var bitmapImage = new BitmapImage();
await bitmapImage.SetSourceAsync(stream);
items.Add(bitmapImage);
}
}
}
}
}
return items;
}
As you can see I am using this RandomAccessStreamReference.CreateFromStream method in both of my methods. I've seen other examples that skip that step and use the InMemoryRandomAccessStream directly to load the PDF document or the bitmap image, but I've not managed to get the PDF to render correctly then. The images will just be completely white again. As I mentioned above, this code does actually render the PDF correctly, but does not dispose of all disposables.
Why
I assume LoadFromStreamAsync(IRandomAccessStream) does not parse the whole stream into the PdfDocument object but instead only parses the main PDF dictionaries and holds a reference to the IRandomAccessStream.
This actually is the sane thing to do, why parse the whole PDF into own objects (a possibly very expensive operation resource-wise) if the user eventually only wants to render one page, or even merely wants to query the number of pages...
Later on, when other methods of the returned PdfDocument are called, e.g. GetPage, these methods try to read the additional data from the stream they need for their task, e.g. for rendering. Unfortunately in your case that means after the finally { inputStream.Dispose(); }
How else
You have to postpone the inputStream.Dispose() until all operations on the PdfDocument are finished. That means some hopefully minor architectural changes for your code. Probably moving the LoadDocumentAsync code as a frame into the RenderPagesAsync method or its caller suffices.
I want to read a file from the assets into a stream, I currently use it as the following:
public async void LoadWidthOfUnicodesData()
{
string dataFile = #"Assets\QuranData\Data_Font1.xml";
StorageFolder InstallationFolder = Windows.ApplicationModel.Package.Current.InstalledLocation;
StorageFile file = await InstallationFolder.GetFileAsync(dataFile);
Stream readStream = await file.OpenStreamForReadAsync();
DataContractSerializer ser = new DataContractSerializer(typeof(List<UniCodeWidth>));
WidthOfUnicodes = (List<UniCodeWidth>)ser.ReadObject(readStream);
for (int i = 0; i < WidthOfUnicodes.Count; i++)
{
WidthOfUnicodesDict.Add(WidthOfUnicodes[i].UniCode, WidthOfUnicodes[i].Width);
}
}
The only problem with that, is that part is in my viewmodel, and as it's a non-blocking operation, when the VM initializes with the View as its DataContext, the constructor does what it's supposed to do to fill the view but I always get an exception at using the WidthOfUnicodesDict because it's not yet filled with the data.
What I want to do, is either make the reading to stream a synchronous method (without using async/await) which so far I don't know how to do it on windows store. or somehow make the VM constructor waits till this operation finishes and notifies it's done.
You can fetch a file without async/await semantics like this:
var file = Package.Current.InstalledLocation.GetFileAsync(Folder)
.GetAwaiter()
.GetResult();
I don't know how to get a stream, but you can get an IBuffer or read it as text, using FileIO:
string content = FileIO.ReadTextAsync(file).GetAwaiter().GetResult();
IList lines = FileIO.ReadLinesAsync(file).GetAwaiter().GetResult();
IBuffer buffer = FileIO.ReadBufferAsync(file).GetAwaiter().GetResult();
I've done this before and it doesn't cause any deadlocks.
I am working on a WinRT app that is a local network playable game. I can read the user's DisplayName, and AccountPicture within the app, and display them both locally to the user via xaml. I am using the following code to get the account picture into a BitmapImage for binding within the xaml page:
private async Task<BitmapImage> GetDisplayImage()
{
BitmapImage image = null;
if (CoreApplication.MainView != null)
{
await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(
CoreDispatcherPriority.Normal, async () =>
{
var stream = await Windows.System.UserProfile.UserInformation.GetAccountPicture(Windows.System.UserProfile.AccountPictureKind.LargeImage).OpenReadAsync();
image = new BitmapImage();
image.SetSource(stream);
});
}
return image;
}
I thought that it would be possible to read the stream into a byte array, and ship it when the app connects to others, and then reconstitute the byte array on the other end like so:
public static async Task<byte[]> ToArray(this IRandomAccessStream stream)
{
IBuffer buffer = new Windows.Storage.Streams.Buffer((uint)stream.Size);
await stream.ReadAsync(buffer, buffer.Capacity, InputStreamOptions.ReadAhead);
await stream.FlushAsync();
return buffer.ToArray();
}
However, on the line:
await stream.FlushAsync();
I am receiving an UnauthorizedAccessException:
Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))
It must be possible to share the profile pic with others, because I see it done elsewhere, but I cannot seem to find any information on how to do this. If anyone could point me in the right direction on this (I wonder if there is a different api I need to hook into, or if I need to set something in the App manifest..), I would be most appreciative.
You could directly read the IStorageFile returned by GetAccountPicture:
var file = UserInformation.GetAccountPicture(AccountPictureKind.LargeImage);
var buffer = await FileIO.ReadBufferAsync(file);
var bytes = buffer.ToArray();
I'm having a problem with inclomplete blobs being downloaded from Azure storage. The files that are stored are an images. Almost every file that's downloaded ends up missing several lines on the bottom. I've checked the blobs and they were uploaded correctly.
I'm using the following code for downloading a blob from the Azure service:
private async Task Download(CloudBlobClient client)
{
try
{
_media = await _directory.CreateFileAsync(ResourceName, CreationCollisionOption.FailIfExists);
}
catch (Exception)
{
return;
}
using (var stream = await _media.OpenAsync(FileAccessMode.ReadWrite))
{
var blob = await GetBlob(client);
await blob.DownloadToStreamAsync(stream);
_category.NotifyAzureProgress();
await stream.FlushAsync();
}
}
The method GetBlob() looks like this:
private async Task<CloudBlockBlob> GetBlob(CloudBlobClient client)
{
CloudBlobContainer container = client.GetContainerReference(ContainerName);
await container.CreateIfNotExistsAsync();
var blob = container.GetBlockBlobReference(ResourceName);
return blob;
}
Upload code:
private async Task UploadAsync(CloudBlobClient client)
{
_media = await _directory.GetFileAsync(ResourceName);
using (var stream = await _media.OpenAsync(FileAccessMode.Read))
{
var blob = await GetBlob(client);
await blob.UploadFromStreamAsync(stream);
_category.NotifyAzureProgress();
}
}
Thanks for any help!
Edit: I've realized I've missed out one detail - the downloaded image has correct dimensions, but several lines from the bottom are black - it doesn't has the same pixels as the source image. I've checked the MD5 hashes and while they match, when I download the image through an external app, they don't match when I download them with the code above.
Edit2: after inspecting the properties of CloudBlob and the output stream, I've noticed, that even though the blob gives correct length after download, the stream usually says something a little lower. I've tried downloading throught range, but to no avail
Ok, so I've managed to download the images afterall, by partially using the WinRT Azure library combined with a standard .NET HttpClient.
I used the Azure Lib establish the initial connection and then to get only the Blob reference, because the BlockBlobReference has a method to create Shared Access Signature (and I really didn't want to try to construct it myself). Then I created the HttpClient, made a download URL using the SAS and issued a GET request to the URL, which finally worked and downloaded all the images intact.
I think there might be some weird bug in the official library, since using my download method instead of theirs solved everything.
Code sample:
internal async Task Download(CloudBlobClient client)
{
try
{
_media = await _directory.CreateFileAsync(ResourceName, CreationCollisionOption.FailIfExists);
}
catch (Exception)
{
return;
}
try
{
var blob = await GetBlob(client);
HttpClient httpClient = new HttpClient();
var date = DateTime.UtcNow;
var policy = new SharedAccessBlobPolicy();
policy.Permissions = SharedAccessBlobPermissions.Read;
policy.SharedAccessStartTime = new DateTimeOffset(date);
policy.SharedAccessExpiryTime = new DateTimeOffset(date.AddDays(1));
var signature = blob.GetSharedAccessSignature(policy);
var uriString = string.Format("{0}{1}", blob.Uri.ToString(), signature);
var data = await httpClient.GetByteArrayAsync(uriString);
var buf = new Windows.Storage.Streams.Buffer((uint)data.Length);
await FileIO.WriteBytesAsync(_media, data);
_category.NotifyAzureProgress();
}
catch (Exception e)
{
_media.DeleteAsync();
throw e;
}
}
I am working on a metro app. I used background uploader to upload file but my question is how I get response value after uploading it. I coded like this:
BackgroundUploader uploader = new BackgroundUploader();
uploader.SetRequestHeader("Content-Disposition", "form-data");
uploader.SetRequestHeader("name", "userfile");
uploader.SetRequestHeader("filename", App.ViewModel.DeviceId + ".png");
uploader.SetRequestHeader("Content-Type", "multipart/form-data");
UploadOperation upload = uploader.CreateUpload(uri, file);
await upload.StartAsync();
I came up with the following after noticing there were BytesReceived in my upload progress object.
async private Task<string> GetUploadResponseBody(UploadOperation operation)
{
string responseBody = string.Empty;
using (var response = operation.GetResultStreamAt(0))
{
uint size = (uint)operation.Progress.BytesReceived;
IBuffer buffer = new Windows.Storage.Streams.Buffer(size);
var f = await response.ReadAsync(buffer, size, InputStreamOptions.None);
using (var dr = DataReader.FromBuffer(f))
{
responseBody = dr.ReadString(dr.UnconsumedBufferLength);
}
}
return responseBody;
}
upload.StartAsync().Completed = UploadCompletedHandler;
...
void UploadCompletedHandler(IAsyncOperationWithProgress<TResult, TProgress> asyncInfo,
AsyncStatus asyncStatus)
{
// get a response body from an asyncInfo using the asyncInfo.GetResults() method
}
Follow this resources:
UploadOperation.StartAsync | startAsync Method (Windows)
IAsyncOperationWithProgress Interface (Windows)
AsyncOperationWithProgressCompletedHandler Delegate (Windows)
I've been looking for the same thing for the last few days and no luck. Finally discovered that you can not do this. You can get the "headers" of the response but there is no way of getting the "body" of the response from a BackgroundTransfer getResponseInformation() method.
Currently it's a limitation of the windows API. Hope they'll add it soon.
http://msdn.microsoft.com/en-us/library/windows/apps/windows.networking.backgroundtransfer.responseinformation.aspx#properties
The workaround is you can add your custom header in the response. For this you need to modify your server side script. But if you don't have any control on your server side script then use a proxy script that will do the communication between your app and remote server. For my case I created a proxy script in php that communicates with the remote server and after getting the response I'm adding it into a custom header key.
Then in the app in your complete method use this:
function complete(e){
var mykey = e.getResponseInformation().headers.lookup("mykey");
}
Hope that'll help.