using System;
using System.IO;
using System.IO.Packaging;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Xps;
using System.Windows.Xps.Packaging;
namespace StreamToMe.Flow.Windows.Utils
{
public static class WebBrowserHelper
{
public static BitmapSource MakeBitmapSource(this UIElement control)
{
// The control we have been passed is quite possibly the child of
// another element and we have no idea of what its left/top properties
// may be. So the first thing we have to do is take a visual copy of it.
// This will ensure we have a relative 0,0 origin.
Rectangle rect = PaintControl(control);
// Create an in memory xps package to write to
using (var stream = new MemoryStream())
{
Package package = Package.Open(stream,
FileMode.CreateNew, FileAccess.ReadWrite);
// A name (any name) is required when referencing the package.
const string inMemoryPackageName = "memorystream://out.xps";
var packageUri = new Uri(inMemoryPackageName);
// A package, managing the memory stream, is held as a reference.
PackageStore.AddPackage(packageUri, package);
// We must keep the document open until we have finished with
// the resulting visual, otherwise it wont be able to access
// its resources.
using (var doc = new XpsDocument(
package, CompressionOption.Maximum, inMemoryPackageName))
{
// Print the control using Xps printing.
Visual capture = PrintToVisual(doc, rect);
// We want to render the resulting visual into a RenderTarget
// so that we can create an image from it.
RenderTargetBitmap renderTarget =
RenderVisual(capture, 700, 1024);
// Tidy up
PackageStore.RemovePackage(packageUri);
// Voila! The most complicated method of creating an image
// from a control you've ever seen!
return renderTarget;
}
}
}
///
/// Paints a control onto a rectangle. Gets around problems where
/// the control maybe a child of another element or have a funny
/// offset.
///
///
///
private static Rectangle PaintControl(UIElement control)
{
// Fill a rectangle with the illustration.
var rect = new Rectangle
{
Fill = new VisualBrush(control) { TileMode = TileMode.None, Stretch = Stretch.Uniform },
Width = (control.RenderSize.Width),
Height = (control.RenderSize.Height)
};
// Force the rectangle to re-size
var szRect = new Size(rect.Width, rect.Height);
rect.Measure(szRect);
rect.Arrange(new Rect(szRect));
rect.UpdateLayout();
return rect;
}
///
/// Prints any UIElement to an xps document and gets the resulting Visual.
/// This is the only fool proof way to copy the contents of a UIElement into
/// a visual. Other methods may work well...but not with WindowsFormsHosts.
///
///
///
///
private static Visual PrintToVisual(XpsDocument doc, Visual element)
{
// Write the element to an XpsDocument
XpsDocumentWriter writer = XpsDocument.CreateXpsDocumentWriter(doc);
writer.Write(element);
// Get the visual that was 'printed'
var Sequence = doc.GetFixedDocumentSequence();
if (Sequence != null)
{
Visual capture = Sequence.DocumentPaginator.GetPage(0).Visual;
doc.Close();
return capture;
}
return null;
}
///
/// Render a Visual to a render target of a fixed size. The visual is
/// scaled uniformly to fit inside the specified size.
///
///
///
///
///
private static RenderTargetBitmap RenderVisual(Visual visual,
double height, double width)
{
// Default dpi settings
const double dpiX = 96;
const double dpiY = 96;
// We can only render UIElements...ContentPrensenter to
// the rescue!
var presenter = new ContentPresenter { Content = visual };
// Ensure the final visual is of the known size by creating a viewbox
// and adding the visual as its child.
var viewbox = new Viewbox
{
MaxWidth = width,
MaxHeight = height,
Stretch = Stretch.Uniform,
Child = presenter
};
// Force the viewbox to re-size otherwise we wont see anything.
var sFinal = new Size(viewbox.MaxWidth, viewbox.MaxHeight);
viewbox.Measure(sFinal);
viewbox.Arrange(new Rect(sFinal));
viewbox.UpdateLayout();
// Render the final visual to a render target
var renderTarget = new RenderTargetBitmap(
(int)width, (int)height, dpiX, dpiY, PixelFormats.Pbgra32);
renderTarget.Render(viewbox);
// Return the render taget with the visual rendered on it.
return renderTarget;
}
}
}