问题
I am working on taking screenshot of UI Element (WPF) in various size and i'm able to achieve this using "RenderTargetBitmap. But the UIElement
which has an Adorner
part is not coming while take copy. What should i do to achieve this. Any reference or code snippet?
回答1:
As far as I know, elements don't have direct references to their adorners. Adorners do reference their element through AdornedElement though, so you could search for adorners assigned to your element like this:
var layer = AdornerLayer.GetAdornerLayer(element);
var adorners = layer.GetVisualChildren().Cast<Adorner>().Where(a => a.AdornedElement == element);
Here GetVisualChildren
is an extension method which is defined as:
public static IEnumerable<DependencyObject> GetVisualChildren(this DependencyObject current) {
return Enumerable.Range(0, VisualTreeHelper.GetChildrenCount(current)).Select(i => VisualTreeHelper.GetChild(current, i));
}
The size of the adorner seems to include the size of the adorned element (although I'm not sure if this is always the case), so if there is only one adorner, that is your screenshot size. If there is more than one adorner, you would need to find the maximum for each bound (left, top, right, bottom) to calculate the screenshot region.
You will need to capture the AdornerDecorator
which contains both the elements being adorned and the AdornerLayer
(layer
in the above code). That would be the visual parent of the layer:
var container = VisualTreeHelper.GetParent(layer) as Visual;
Once you have the container, you can render it with RenderTargetBitmap
and crop it to the screenshot region.
For the screenshot region, you need the element bounds relative to the container. First, get the non-relative bounds:
var elementBounds = element.RenderTransform.TransformBounds(new Rect(element.RenderSize));
Then get those bounds relative to the container:
var relativeElementBounds = element.TransformToAncestor(container).TransformBounds(elementBounds);
As I mentioned above, you will need to do this for the element as well as each of its adorners and combine the maximum bounds into one final Rect which is just large enough to contain all of them.
Finally, use CroppedBitmap to get a cropped version of the RenderTargetBitmap
:
var croppedBitmap = new CroppedBitmap(renderTargetBitmap, new Int32Rect(left, top, width, height));
CroppedBitmap
and RenderTargetBitmap
both inherit from BitmapSource
, so you should be able to save it the same way.
回答2:
You can use the native WPF Printing name space to print to an XPS file and this will include the adorner in the result (I tested it successfully)...
using System.Windows.Controls;
private void ExecutePrintCommand(object obj)
{
PrintDialog printDialog = new PrintDialog();
if (printDialog.ShowDialog() == true)
{
printDialog.PrintVisual(_mainWindow, "Main Window with Adorner");
}
}
If you did not want to use the PrintDialog (which actually opens a dialog box). You can use the XpsDocumentWriter class to programmatically control the process. The enabling snippet for this is...
XpsDocumentWriter xpsdw = PrintQueue.CreateXpsDocumentWriter(q);
xpsdw.Write(viewer.Document);
...which was extracted from here: Print FixedDocument programmatically And there are more articles about fine-tuning the process if that is part of your requirements. NOTE that the XPS file is actually a 'zip' file masquerading as an 'xps' file, so you can unzip it by changing the extension to see if the contents are of any use.
Secondarily, I tested saving a window with an adorner on a TextBox with this code...
private void SaveWithAdorner()
{
RenderTargetBitmap rtb = RenderVisaulToBitmap(_mainWindow, 500, 300);
MemoryStream file = new MemoryStream();
BitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(rtb));
encoder.Save(file);
using (FileStream fstream = File.OpenWrite("Myimage.jpg"))
{
file.WriteTo(fstream);
fstream.Flush();
fstream.Close();
}
}
...with good results. I.e., the adorner appeared in the saved bitmap with its red border. This might differ from your code because I use a Png encoder (but saved to a 'jpg' file).
Although I have tested both approaches successfully, you'll need to check them on your hardware.
And lastly, as a last-ditch resort, you can deactivate WPF's hardware rendering mode and set it to software rendering...
RenderOptions.ProcessRenderMode = RenderMode.SoftwareOnly;
...for which there's a nice SO thread here: Software rendering mode - WPF
回答3:
In my case all I needed was call the AdornerLayer class like so:
public void GetScreenshotWithAdorner(Canvas canvas, string filename)
{
AdornerLayer adornerlayer = AdornerLayer.GetAdornerLayer(canvas);
RenderTargetBitmap rtb = new RenderTargetBitmap(
(int)canvas.ActualWidth,
(int)canvas.ActualHeight,
96, //dip X
96, //dpi Y
PixelFormats.Pbgra32);
rtb.Render(canvas); //renders the canvas screen first...
rtb.Render(adornerlayer); //... then it renders the adorner layer
SaveRTBAsPNG(rtb, filename);
}
private void SaveRTBAsPNG(RenderTargetBitmap bmp, string filename)
{
PngBitmapEncoder pngImage = new PngBitmapEncoder();
pngImage.Frames.Add(BitmapFrame.Create(bmp));
using (var filestream = System.IO.File.Create(filename))
{
pngImage.Save(filestream);
}
}
This works if you want to include ALL adorners in your canvas.
来源:https://stackoverflow.com/questions/17969651/copy-ui-element-with-adorner