.NET WPF: FrameworkElement als Designer-Objekt

Hallo,

da ich die Problematik hatte, dass ich Content aus unserer WPF-Anwendung in L&L als Custom-Designer-Objekt einbinden wollte (wir benutzen WPF-Charts eines anderen Herstellers), schrieb ich angehängten Konverter, der das System.Drawing.Graphics-Objekt aus dem “DrawDesignerObject”-Callback befüllen kann.

Ich bin sicher, dass das auch jemand anders gut gebrauchen kann.

Viele Grüße
Alex

P.S.: diese Klasse arbeitet mit unsafe-Code, also muss das entsprechend in der enthaltenden Assembly erlaubt werden!


using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace WpfApplication3
{
public static class WpfToWinForms
{
public static void DrawFrameworkElementOnGraphics (System.Drawing.Graphics graphics,
System.Drawing.Rectangle clipRectangle, FrameworkElement element)
{
bool limitedResolution;
System.Drawing.Image img = Convert (element, clipRectangle.Width, clipRectangle.Height,
true, out limitedResolution);
if (img != null)
{
graphics.DrawImage (img, clipRectangle.X, clipRectangle.Y, clipRectangle.Width, clipRectangle.Height);
}
}

    /// <summary>
    /// Renders the specified FrameworkElement to a System.Drawing.Image.
    /// </summary>
    /// <param name="controlToRender">The control which has to be rendered. 
    /// Be careful: if this is visible and rendered, its layout may change when allowRearrange is 'true'.</param>
    /// <param name="allowRearrange">If false, the FrameworkElement will not be rearranged. It will then get
    /// exactly the layout which it already has on the screen (and might be blurry!). Use this if you provided
    /// an element/instance which is already arranged on the screen to prevent that layout to be damaged.
    /// In most other cases this value should be true!</param>
    /// <param name="ResolutionWasLimited">If true is returned, the resulting image does not have the resolution
    /// which was specified because the dimensions are too large (but aspect-ratio is reserved).</param>
    public static System.Drawing.Image Convert (FrameworkElement controlToRender, int width, int height,
        bool allowRearrange, out bool ResolutionWasLimited)
    {
        ResolutionWasLimited = false;

        // The number of pixels which are allowed for the result-image 
        // Note: on my machine there was an OutOfMemoryException with values higher than ~34,8 MPixel (~138,9 MB)
        int RESOLUTION_LIMIT = 20000000; // Still leads to good results
        if (width * height > RESOLUTION_LIMIT)
        {
            double ratio = (double) width / (double) height;
            double tempHeight = Math.Sqrt ((double) RESOLUTION_LIMIT / ratio);
            width = (int) Math.Round (ratio * tempHeight);
            height = (int) Math.Round (tempHeight);

            ResolutionWasLimited = true;
        }

        Size size = new Size ((double) width, (double) height);
        RenderTargetBitmap renderTargetBmp;
        try
        {
            if (allowRearrange)
            {
                // Rearrange the element and apply a scale so that the element will fit completely into the given size
                controlToRender.Measure (new Size (double.PositiveInfinity, double.PositiveInfinity));
                Size originalWpfSize = controlToRender.DesiredSize;
                double factor = Math.Min ((double) width / originalWpfSize.Width, (double) height / originalWpfSize.Height);

                controlToRender.LayoutTransform = new ScaleTransform (factor, factor);
                controlToRender.Arrange (new Rect (
                    ((double) width - originalWpfSize.Width * factor) / 2d, ((double) height - originalWpfSize.Height * factor) / 2d,
                    originalWpfSize.Width * factor, originalWpfSize.Height * factor));

                renderTargetBmp = new RenderTargetBitmap (width, height, 96, 96, PixelFormats.Pbgra32);
                renderTargetBmp.Render (controlToRender);
            }
            else
            {
                // Render into an image
                Size originalWpfSize = new Size (controlToRender.ActualWidth, controlToRender.ActualHeight);
                RenderTargetBitmap intermediateRenderTargetBmp = new RenderTargetBitmap (
                    (int) originalWpfSize.Width, (int) originalWpfSize.Height, 96, 96, PixelFormats.Default);
                intermediateRenderTargetBmp.Render (controlToRender);

                Image wpfImage = new Image ();
                wpfImage.Source = intermediateRenderTargetBmp;
                wpfImage.Stretch = Stretch.Uniform;
                wpfImage.Measure (size);
                wpfImage.Arrange (new Rect (new Point (0d, 0d), size));

                // The image will stretch into the specified size
                renderTargetBmp = new RenderTargetBitmap (width, height, 96, 96, PixelFormats.Default);
                renderTargetBmp.Render (wpfImage);
            }
        }
        catch
        {
            double res = ((double) width * height) / 1000000d;
            double mem = res * 4;
            Console.WriteLine ("!!!!!!!! Too less memory with resolution of " + res.ToString () +
                " MPixel (" + mem.ToString () + " MB)");
            return null;
        }

        // Conversion via bit-copy (needs the assembly to allow unsafe code)
        byte[] bits = new byte[4 * width * height];
        int stride = 4 * width;
        renderTargetBmp.CopyPixels (bits, stride, 0);

        System.Drawing.Image result;
        unsafe
        {
            fixed (byte* pBits = bits)
            {
                IntPtr ptr = new IntPtr (pBits);
                result = new System.Drawing.Bitmap (width, height, stride,
                    System.Drawing.Imaging.PixelFormat.Format32bppPArgb, ptr);
            }
        }

        return result;
    }
}

}

“Alexander Ziegler” <mail03@ziegler-we…> schrieb im Newsbeitrag
news:338181092008161327@combit.net…

Hallo,

da ich die Problematik hatte, dass ich Content aus unserer WPF-Anwendung
in L&L als Custom-Designer-Objekt einbinden wollte (wir benutzen
WPF-Charts eines anderen Herstellers), schrieb ich angehängten Konverter,
der das System.Drawing.Graphics-Objekt aus dem
“DrawDesignerObject”-Callback befüllen kann.

Ich bin sicher, dass das auch jemand anders gut gebrauchen kann.

Cool, klasse :-). Die Idee, WPF-Elemente in den Report zu geben ist echt
inspirierend :-)). Das Wochenende ist gerettet…

Ich glaube sogar, dass Du an der Resolution-Schraube noch weiter drehen
kannst (und so auch Performance gewinnen kannst); im Endeffekt erzeugst Du
im Moment ein 96dpi-Bild, allerdings ein riesiges. Die Koordinaten, die im
Event ankommen sind per Default 1/100 mm (vorsicht, das ist einstellbar
(“Units”)). Wenn Du da ein schoenes Bild reinmalen willst, muss das erst auf
Inch umgerechnet werden und dann mit der gewuenschten Aufloesung (fuer den
Druck vielleicht 300 dpi) skaliert werden.

Bsp: User-Objekt aus dem Designererweiterungsbeispiel hat Hoehe/Breite von
3856x6835. Das entspricht 1,518 x 2,691 inch. Bei 300 dpi wuerde also -
denke ich - ein Bild der Groesse 455 x 807 und 300dpi Aufloesung ausreichen,
um das “verlustfrei” auszugeben. Dein Code wuerde im Moment vmtl. ca. das
8-fache machen, das noch in beiden Dimensionen, d.h. Du kannst den
Speicherverbrauch auf 1/64 senken (wenn ich mich nicht verrechnet habe) :-).
Das sollte dann auch deutlich schneller werden.

G.

Hallo,

ich hatte schon mal versucht, mittels der, im Paint-Ereignis, übergebenen dpi-Zahl die Darstellung zu optimieren, aber diese scheint nicht ganz zuverlässig zu sein, vielleicht mache ich auch etwas falsch.

Jedenfalls habe ich die Konverter-Klasse hier nochmals angehängt, wobei ich einen Bug korrigiert habe (bei jedem Nauzeichnen hat die Größe des gerenderten Bildes getoggelt). Auch habe ist ein neuer Parameter dazugekommen: “lowQuality”, der aus DefineElementsEventArgs.IsDesignMode (z.B. aus dem “DefineVariables”-Ereignis) genommen werden kann, und bewirkt, dass die Darstellung wesentlich schneller vonstatten geht, allerdings in niedrigerer effektiver Auflösung.


using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace MyNamespace
{
public static class WpfToWinForms
{
///


/// Draws the specified framework-element on a System.Drawing.Graphics-object.
///

/// Get from OnPaint.
/// Get from OnPaint.
/// The framework-element to render.
/// If false, the FrameworkElement will not be rearranged. It will then get
/// exactly the layout which it already has on the screen (and might be blurry!). Use this if you provided
/// an element/instance which is already arranged on the screen to prevent that layout to be damaged.
/// In most other cases this value should be true!
/// If true, rendering will be quite fast, but the effective resolution will be low.
public static void DrawFrameworkElementOnGraphics (System.Drawing.Graphics graphics,
System.Drawing.Rectangle clipRectangle, FrameworkElement element, bool allowRearrange, bool lowQuality)
{
bool limitedResolution;
System.Drawing.Image img = Convert (element, clipRectangle.Width, clipRectangle.Height,
allowRearrange, lowQuality, out limitedResolution);
if (img != null)
{
graphics.DrawImage (img, clipRectangle.X, clipRectangle.Y, clipRectangle.Width, clipRectangle.Height);
}
}

    /// <summary>
    /// Renders the specified FrameworkElement to a System.Drawing.Image.
    /// </summary>
    /// <param name="controlToRender">The control which has to be rendered. 
    /// Be careful: if this is visible and rendered, its layout may change when allowRearrange is 'true'.</param>
    /// <param name="allowRearrange">If false, the FrameworkElement will not be rearranged. It will then get
    /// exactly the layout which it already has on the screen (and might be blurry!). Use this if you provided
    /// an element/instance which is already arranged on the screen to prevent that layout to be damaged.
    /// In most other cases this value should be true!</param>
    /// <param name="forceLowRes">If true, the image will be low resolution. In this case 'resolutionWasLimited' always
    /// returns true!</param>
    /// <param name="resolutionWasLimited">If true is returned, the resulting image does not have the resolution
    /// which was specified because the dimensions are too large (but aspect-ratio is reserved).</param>
    public static System.Drawing.Image Convert (FrameworkElement controlToRender, int width, int height,
        bool allowRearrange, bool forceLowRes, out bool resolutionWasLimited)
    {
        resolutionWasLimited = false;

        // The number of pixels which are allowed for the result-image 
        // Note: on my machine there was an OutOfMemoryException with values higher than ~34,8 MPixel (~138,9 MB)
        int RESOLUTION_LIMIT = forceLowRes ? 100000 : 20000000;
        if (width * height > RESOLUTION_LIMIT)
        {
            double ratio = (double) width / (double) height;
            double tempHeight = Math.Sqrt ((double) RESOLUTION_LIMIT / ratio);
            width = (int) Math.Round (ratio * tempHeight);
            height = (int) Math.Round (tempHeight);

            resolutionWasLimited = true;
        }

        Size size = new Size ((double) width, (double) height);
        RenderTargetBitmap renderTargetBmp;
        try
        {
            if (allowRearrange)
            {
                // Rearrange the element and apply a scale so that the element will fit completely into the given size
                controlToRender.LayoutTransform = new ScaleTransform (1d, 1d);
                controlToRender.Measure (new Size (double.PositiveInfinity, double.PositiveInfinity));
                Size originalWpfSize = controlToRender.DesiredSize;
                double factor = Math.Min ((double) width / originalWpfSize.Width, (double) height / originalWpfSize.Height);

                controlToRender.LayoutTransform = new ScaleTransform (factor, factor);
                controlToRender.Arrange (new Rect (
                    ((double) width - originalWpfSize.Width * factor) / 2d, ((double) height - originalWpfSize.Height * factor) / 2d,
                    originalWpfSize.Width * factor, originalWpfSize.Height * factor));

                renderTargetBmp = new RenderTargetBitmap (width, height, 96, 96, PixelFormats.Pbgra32);
                renderTargetBmp.Render (controlToRender);
            }
            else
            {
                // Render into an image
                Size originalWpfSize = new Size (controlToRender.ActualWidth, controlToRender.ActualHeight);
                RenderTargetBitmap intermediateRenderTargetBmp = new RenderTargetBitmap (
                    (int) originalWpfSize.Width, (int) originalWpfSize.Height, 96, 96, PixelFormats.Default);
                intermediateRenderTargetBmp.Render (controlToRender);

                Image wpfImage = new Image ();
                wpfImage.Source = intermediateRenderTargetBmp;
                wpfImage.Stretch = Stretch.Uniform;
                wpfImage.Measure (size);
                wpfImage.Arrange (new Rect (new Point (0d, 0d), size));

                // The image will stretch into the specified size
                renderTargetBmp = new RenderTargetBitmap (width, height, 96, 96, PixelFormats.Default);
                renderTargetBmp.Render (wpfImage);
            }
        }
        catch
        {
            double res = ((double) width * height) / 1000000d;
            double mem = res * 4;
            Console.WriteLine ("!!!!!!!! Too less memory with resolution of " + res.ToString () +
                " MPixel (" + mem.ToString () + " MB)");
            return null;
        }

        // Conversion via bit-copy (needs the assembly to allow unsafe code)
        byte[] bits = new byte[4 * width * height];
        int stride = 4 * width;
        renderTargetBmp.CopyPixels (bits, stride, 0);

        System.Drawing.Image result;
        unsafe
        {
            fixed (byte* pBits = bits)
            {
                IntPtr ptr = new IntPtr (pBits);
                result = new System.Drawing.Bitmap (width, height, stride,
                    System.Drawing.Imaging.PixelFormat.Format32bppPArgb, ptr);
            }
        }

        return result;
    }
}

}