2013-07-31 11 views
5

Ich arbeite an der Aufnahme von Screenshot von UI-Element (WPF) in verschiedenen Größen und ich bin in der Lage, dies zu erreichen mit RenderTargetBitmap. Aber die UIElement, die eine Adorner Teil hat, kommt nicht, während Kopie. Was soll ich tun, um dies zu erreichen? Jede Referenz oder Code-Ausschnitt?Kopiere UI-Element mit Adorner

Antwort

5

Elemente, die keine direkten Referenzen auf ihre Adorner haben.Adorners verweisen ihr Element durch AdornedElement obwohl, so dass Sie nach Adornern suchen könnten zugewiesen zu Ihrem Element wie folgt:

var layer = AdornerLayer.GetAdornerLayer(element); 
var adorners = layer.GetVisualChildren().Cast<Adorner>().Where(a => a.AdornedElement == element); 

Hier GetVisualChildren ist eine Erweiterung Methode, die definiert ist als:

public static IEnumerable<DependencyObject> GetVisualChildren(this DependencyObject current) { 
    return Enumerable.Range(0, VisualTreeHelper.GetChildrenCount(current)).Select(i => VisualTreeHelper.GetChild(current, i)); 
} 

Die Größe der adorner die Größe des geschmückten Elements zu enthalten scheint (obwohl ich nicht sicher bin, ob dies immer der Fall ist), so, wenn es nur ein Adorner, das ist deine Bildschirmgröße. Wenn es mehr als einen Adorner gibt, müssen Sie das Maximum für jede Grenze (links, oben, rechts, unten) finden, um den Screenshotbereich zu berechnen.

Sie müssen die AdornerDecorator erfassen, die sowohl die geschmückten Elemente als auch die AdornerLayer (layer im obigen Code) enthält. Das würde die visuell Eltern der Schicht sein:

var container = VisualTreeHelper.GetParent(layer) as Visual; 

Sobald Sie den Behälter zur Verfügung haben, können Sie es mit RenderTargetBitmap machen und es auf die Screenshot Region zuzuzuschneiden.

Für den Screenshotbereich benötigen Sie die Elementgrenzen relativ zum Container. Zunächst erhalten die nicht-relativen Grenzen:

var elementBounds = element.RenderTransform.TransformBounds(new Rect(element.RenderSize)); 

Dann diese Grenzen in Bezug auf den Behälter erhalten:

var relativeElementBounds = element.TransformToAncestor(container).TransformBounds(elementBounds); 

Wie ich bereits erwähnt, müssen Sie dies für das Element sowie jeweils tun von seinen Schmeichlern und kombinieren die maximalen Begrenzungen zu einem letzten Rect, das gerade groß genug ist, um alle von ihnen zu enthalten.

Schließlich verwenden CroppedBitmap eine beschnittene Version des RenderTargetBitmap zu bekommen:

var croppedBitmap = new CroppedBitmap(renderTargetBitmap, new Int32Rect(left, top, width, height)); 

CroppedBitmap und RenderTargetBitmap beide von BitmapSource erben, so sollten Sie es speichern die gleiche Art und Weise können.

+0

gut, das wird schmücken, aber ich muss den ganzen Bildschirm aufnehmen. In diesem Fall muss ich einen Schnappschuss von zwei verschiedenen Steuerelementen machen und zwei Bilder eins oben auf wnother mit viel Komplexität integrieren, wie das Finden des Ortes zum vorherigen Adorner-Bild ..... – Mohanavel

+0

@Mohanavel Sie müssen das gesamte Elternelement erfassen und wählen Sie dann die Region aus, die die Steuerelemente belegen. Dies stellt sich als komplizierter heraus, als ich es mir vorgestellt habe, daher habe ich meinen Beitrag detaillierter bearbeitet. – nmclean

4

Sie können die native WPF Printing Namensraum verwenden, um eine XPS-Datei zu drucken und dies wird die adorner im Ergebnis enthalten (I erfolgreich getestet) ...

using System.Windows.Controls; 
private void ExecutePrintCommand(object obj) 
{ 
    PrintDialog printDialog = new PrintDialog(); 
    if (printDialog.ShowDialog() == true) 
    { 
     printDialog.PrintVisual(_mainWindow, "Main Window with Adorner"); 
    } 
} 

Wenn Sie nicht wollen Verwenden Sie den PrintDialog (der tatsächlich ein Dialogfeld öffnet). Sie können die XpsDocumentWriter-Klasse verwenden, um den Prozess programmgesteuert zu steuern. Das ermöglicht Schnipsel hierfür ist ...

 XpsDocumentWriter xpsdw = PrintQueue.CreateXpsDocumentWriter(q); 
    xpsdw.Write(viewer.Document); 

..., die sich von hier extrahiert: Print FixedDocument programmatically Und es gibt mehr Artikel über den Prozess der Feinabstimmung, wenn das Teil Ihrer Anforderungen ist.Beachten Sie, dass es sich bei der XPS-Datei um eine 'Zip'-Datei handelt, die als' xps'-Datei maskiert ist. Sie können sie also entpacken, indem Sie die Erweiterung ändern, um zu sehen, ob der Inhalt nützlich ist.

In zweiter Linie, testete ich auf einem TextBox mit diesem Code ein Fenster mit einem adorner Speichern ...

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(); 
     } 
    } 

... mit guten Ergebnissen. Das heißt, der Adorner erschien in der gespeicherten Bitmap mit seiner roten Umrandung. Dies kann von Ihrem Code abweichen, da ich einen Png-Encoder verwende (aber in einer 'jpg' Datei gespeichert).

Obwohl ich beide Ansätze erfolgreich getestet habe, müssen Sie sie auf Ihrer Hardware überprüfen.

Und schließlich, als allerletzten Ausweg können Sie WPF-Hardware-Rendering-Modus deaktivieren und zu Software-Rendering gesetzt ...

RenderOptions.ProcessRenderMode = RenderMode.SoftwareOnly; 

..., für die ist es eine schöne SO hier fädeln: Software rendering mode - WPF

+2

+1, um zu zeigen, wie die Bitmap tatsächlich gerendert und gespeichert wird. Das Problem mit Adornern ist, dass sie auf einer Vordergrundebene platziert sind und nicht im visuellen Baum des Elements, das sie schmücken. Dies bedeutet, dass sie angezeigt werden, wenn Sie das gesamte Fenster rendern, aber nicht, wenn Sie ein einzelnes geschmücktes Element rendern - so kann das Erfassen eines einzelnen Elements durch Zuschneiden der gesamten Bitmap erreicht werden. – nmclean

0

In meinem Fall alles, was ich brauchte, war rufen die AdornerLayer Klasse wie folgt:

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); 
     } 
    } 

Dies funktioniert, wenn Sie ALLE Adorner in Ihre Arbeitsfläche einschließen möchten.