In WPF, Silverlight and Windows Phone it is possible to render a visual object into a bitmap using the RenderTargetBitmap. This functionality, that I find pretty basic, was not available for Windows Store applications. Fortunately, Windows 8.1 provides that functionality for Windows Store applications too, through the same RenderTargetBitmap class.
There are some limitations though:
- it should be used in the code behind (not declared in XAML) because you have to call RenderAsync
- collapsed visual objects are not rendered (only visible ones)
- in rare circumstances the content can be lost due to the interaction with lower level systems; in this case a specific exception is triggered
- the rendered target bitmap does not automatically scale when the current DPI settings change
- the maximum rendered size of a XAML visual tree is restricted by the maximum dimensions of a DirectX texture
Here is a demo Windows Store application that has several controls and a button that when pressed a screenshot of the area shown in red (it’s a grid) is taken. The bitmap is saved on disk, but also displayed as the source for the image control shown in the preview area.
<Page> <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" Name="mainGrid"> <TextBlock Text="Windows Store App Screenshot Demo" Style="{StaticResource HeaderTextBlockStyle}" Grid.Column="0" Grid.ColumnSpan="5" Grid.Row="0" Margin="0,30,0,30" TextAlignment="Center" /> <Grid Name="controlsGrid" Grid.Column="1" Grid.ColumnSpan="2" Grid.Row="1" Margin="5,5,5,50"> </Grid> </Grid> </Page>
The handler for the Click button even looks like this:
async void btnScreenshot_Click(object sender, RoutedEventArgs e) { var bitmap = await SaveScreenshotAsync(controlsGrid); imagePreview.Source = bitmap; }
SaveScreenshotAsync is an async method that takes the reference to the FrameworkElement to be rendered to a bitmap (in this case the constrolsGrid) and returns a Task<RenderedTargetBitmap> that can be awaited on. As soon as we have the bitmap we set it as the source for the image control (imagePreview).
async Task<RenderTargetBitmap> SaveScreenshotAsync(FrameworkElement uielement) { var file = await PickSaveImageAsync(); return await SaveToFileAsync(uielement, file); } async Task<RenderTargetBitmap> PickSaveImageAsync() { var filePicker = new FileSavePicker(); filePicker.FileTypeChoices.Add("Bitmap", new List() { ".bmp" }); filePicker.FileTypeChoices.Add("JPEG format", new List() { ".jpg" }); filePicker.FileTypeChoices.Add("Compuserve format", new List() { ".gif" }); filePicker.FileTypeChoices.Add("Portable Network Graphics", new List() { ".png" }); filePicker.FileTypeChoices.Add("Tagged Image File Format", new List() { ".tif" }); filePicker.DefaultFileExtension = ".jpg"; filePicker.SuggestedFileName = "screenshot"; filePicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary; filePicker.SettingsIdentifier = "picture picker"; filePicker.CommitButtonText = "Save picture"; return await filePicker.PickSaveFileAsync(); }
SaveScreenshotAsync is an async method that takes the FrameworkElement to be rendered to a bitmap and returns a Task<RenderedTargetBitmap> that can be awaited on. This method first prompts the user to select a destination file for the rendered bitmap. When the file is available it calls SaveToFileAsync to rendered the bitmap and write it to the file.
async Task<RenderTargetBitmap> SaveToFileAsync(FrameworkElement uielement, StorageFile file) { if (file != null) { CachedFileManager.DeferUpdates(file); Guid encoderId = GetBitmapEncoder(file.FileType); try { using (var stream = await file.OpenAsync(FileAccessMode.ReadWrite)) { return await CaptureToStreamAsync(uielement, stream, encoderId); } } catch(Exception ex) { DisplayMessage(ex.Message); } var status = await CachedFileManager.CompleteUpdatesAsync(file); } return null; } Guid GetBitmapEncoder(string fileType) { Guid encoderId = BitmapEncoder.JpegEncoderId; switch (fileType) { case ".bmp": encoderId = BitmapEncoder.BmpEncoderId; break; case ".gif": encoderId = BitmapEncoder.GifEncoderId; break; case ".png": encoderId = BitmapEncoder.PngEncoderId; break; case ".tif": encoderId = BitmapEncoder.TiffEncoderId; break; } return encoderId; } async void DisplayMessage(string error) { var dialog = new MessageDialog(error); await dialog.ShowAsync(); }
SaveToFileAsync is an async method that takes the FrameworkElement to be rendered to a bitmap and the StorageFile when the bitmap is to be saved and returns a Task<RenderedTargetBitmap> that can be awaited on. The file is opened asynchronous for read-write access and the returned IRandomAccessStream is passed further together with the framework element and the bitmap encoder id (that specifies how the bitmap should be encoded, i.e. BMP, JPEG, PNG, GIF, etc.) to CaptureToStreamAsync.
async Task<RenderTargetBitmap> CaptureToStreamAsync(FrameworkElement uielement, IRandomAccessStream stream, Guid encoderId) { try { var renderTargetBitmap = new RenderTargetBitmap(); await renderTargetBitmap.RenderAsync(uielement); var pixels = await renderTargetBitmap.GetPixelsAsync(); var logicalDpi = DisplayInformation.GetForCurrentView().LogicalDpi; var encoder = await BitmapEncoder.CreateAsync(encoderId, stream); encoder.SetPixelData( BitmapPixelFormat.Bgra8, BitmapAlphaMode.Ignore, (uint)renderTargetBitmap.PixelWidth, (uint)renderTargetBitmap.PixelHeight, logicalDpi, logicalDpi, pixels.ToArray()); await encoder.FlushAsync(); return renderTargetBitmap; } catch (Exception ex) { DisplayMessage(ex.Message); } return null; }
CaptureToStreamAsync creates a new RenderTargetBitmap object and calls RenderAsync to render the visual tree of the framework element to a bitmap. After the bitmap is rendered it retries the image as a buffer of byes in the BGRA8 format. It then asynchronously creates a BitmapEncoder for the IRandomAccessStream stream that it received as an argument, it calls SetPixelData to set the pixels data (notice the BitmapPixelFormat.Bgra8 parameter that matches the pixels format returned by GetPixelsAsync) and later asynchronously flushes all the image data, basically writing it to the file. It then returns that RenderTargetBitmap object that it created, which is used eventually as the source for the image control.
Here is how the saved JPEG image (also seen in the preview screenshot above) looks likes:
You can check the source code of the attached WinRT Screenshot demo (3260 downloads ) . It requires Visual Studio 2013 and Windows 8.1.
This is not working for me at all.
Honestly, I don’t know why folks like classes so much. I have yet to see many working examples of any kind that use classes.
I miss the good old days that did not involve classes.
I think classes complicated things too much.
They are great in theory but they make simple tasks take days and days to fix. 🙁
I get 114 errors, starting with:
Cannot implicitly convert type Windows.Storage.StorageFile to Windows.UI.Xaml.Media.Imaging.RenderTargetBitmap on this line:
return await filePicker.PickSaveFileAsync();
inside of PickSaveImageAsync()
Thanks anyway..
Roark
Thank you so much for posting this blog.It Really helped me. It works perfectly. 🙂
Roark,
I think the author has a typo in the code. The line:
async Task PickSaveImageAsync()
should be:
async Task PickSaveImageAsync()
Give that a try. As for the rest of your errors. For each object type that is underlined in red, Use the “Ctrl” + “.” facility to find and add the missing import library.
Hi, I tried to run the app, text are missing, any thoughts?
await encoder.FlushAsync() for me never returns.
Thank you. It works Great!
Hi,
I am trying to take screenshot of entire page with App Bar when it is open but it doesnt include the AppBar in the screenshot. So I tried to just pass Page.TopAppBar to take screenshot but it fails with below error:
A first chance exception of type ‘System.ArgumentException’ occurred in mscorlib.dll
Additional information: Value does not fall within the expected range.
Is there anyway I can take SS of the app bars?
After 2 years, posts like this still helping newbies like me. works both in Windows 8.1 and Windows 10. Thanks.
I have listview, i can’t render Render all item in listview 🙁 . help me!
Thanks a lot for sharing this! Helped me a lot.