Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Server-side rendering of WPF UserControl

I am writing a server side console app in C#/.Net 4.5 that gets some data and creates static chart images that are saved to be displayed by a web server.

I am mostly using the method described here: http://lordzoltan.blogspot.com/2010/09/using-wpf-to-render-bitmaps.html

However, I added a mainContainer.UpdateLayout(); after the Arrange() so that the databindings would update and be visible in the rendered image, as well as a Measure() before it for good... ah, I'm not gonna go there.

Here is the method that does the rendering:

void RenderAndSave(UIElement target, string filename, int width, int height)
{
    var mainContainer = new Grid
    {
        HorizontalAlignment = HorizontalAlignment.Stretch,
        VerticalAlignment = VerticalAlignment.Stretch
    };

    mainContainer.Children.Add(target);

    mainContainer.Measure(new Size(width, height));
    mainContainer.Arrange(new Rect(0, 0, width, height));
    mainContainer.UpdateLayout();

    var encoder = new PngBitmapEncoder();
    var render = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);

    render.Render(mainContainer);
    encoder.Frames.Add(BitmapFrame.Create(render));
    using (var s = File.Open(filename, FileMode.Create))
    {
        encoder.Save(s);
    }
}

The target parameter to the method will be an instance of a WPF/XAML UserControl I made - fairly simple at this point, just a grid with some text databinding to a ViewModel object that I assigned to the DataContext.

The saved image on disk looks good EXCEPT for the OxyPlot Plot object - it is entirely white.

Now, when I am in the designer in Visual Studio 2013, I can see it. I have added a design-time DataContext which is the same object that I use at runtime (this is a spike I am doing - the viewmodel is not in its final form yet, just having a bunch of default data while I work out the kinks). In the designer I see the chart as OxyPlot paints it.

Is there anything special I need to do in order to get my rendering to also contain this OxyPlot chart? It is more or less the point of the exercise so it would be awesome to actually get it to show up!

Thanks in advance for any insights and suggestions!

like image 353
Rune Jacobsen Avatar asked Jul 11 '15 09:07

Rune Jacobsen


People also ask

What is UserControl WPF?

User controls, in WPF represented by the UserControl class, is the concept of grouping markup and code into a reusable container, so that the same interface, with the same functionality, can be used in several different places and even across several applications.

How does WPF render?

WPF uses vector graphics as its rendering data format. Vector graphics—which include Scalable Vector Graphics (SVG), Windows metafiles (. wmf), and TrueType fonts—store rendering data and transmit it as a list of instructions that describe how to recreate an image using graphics primitives.

Does WPF use OpenGl?

in WPF sample, WinForms control is used to encapsulate OCC viewer since WPF does not provide the necessary interface to embed OpenGl view.

Is WPF frontend or backend?

WPF, stands for Windows Presentation Foundation is a development framework and a sub-system of . NET Framework. WPF is used to build Windows client applications that run on Windows operating system. WPF uses XAML as its frontend language and C# as its backend languages.


2 Answers

If you're correctly binding data at runtime as well, then it should work.

enter image description here

 [STAThread]
 static void Main(string[] args)
 {
     string filename = "wpfimg.png";

     RenderAndSave(new UserControl1(), filename, 300, 300);

     PictureBox pb = new PictureBox();
     pb.Width = 350;
     pb.Height = 350;
     pb.Image = System.Drawing.Image.FromFile(filename);

     Form f = new Form();
     f.Width = 375;
     f.Height = 375;
     f.Controls.Add(pb);
     f.ShowDialog();
 }

XAML:

<UserControl x:Class="WpfApp92.UserControl1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:oxy="http://oxyplot.org/wpf"
             xmlns:local="clr-namespace:WpfApp92"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">

    <Grid>
        <oxy:PlotView Model="{Binding Model}"/>
    </Grid>
</UserControl>

CS:

public partial class UserControl1 : UserControl
{
    public PlotModel Model { get; set; }

    public UserControl1()
    {
        InitializeComponent();

        Model = new PlotModel();
        Model.LegendBorderThickness = 0;
        Model.LegendOrientation = LegendOrientation.Horizontal;
        Model.LegendPlacement = LegendPlacement.Outside;
        Model.LegendPosition = LegendPosition.BottomCenter;
        Model.Title = "Simple model";
        var categoryAxis1 = new CategoryAxis();
        categoryAxis1.MinorStep = 1;
        categoryAxis1.ActualLabels.Add("Category A");
        categoryAxis1.ActualLabels.Add("Category B");
        categoryAxis1.ActualLabels.Add("Category C");
        categoryAxis1.ActualLabels.Add("Category D");
        Model.Axes.Add(categoryAxis1);
        var linearAxis1 = new LinearAxis();
        linearAxis1.AbsoluteMinimum = 0;
        linearAxis1.MaximumPadding = 0.06;
        linearAxis1.MinimumPadding = 0;
        Model.Axes.Add(linearAxis1);
        var columnSeries1 = new ColumnSeries();
        columnSeries1.StrokeThickness = 1;
        columnSeries1.Title = "Series 1";
        columnSeries1.Items.Add(new ColumnItem(25, -1));
        columnSeries1.Items.Add(new ColumnItem(137, -1));
        columnSeries1.Items.Add(new ColumnItem(18, -1));
        columnSeries1.Items.Add(new ColumnItem(40, -1));
        Model.Series.Add(columnSeries1);
        var columnSeries2 = new ColumnSeries();
        columnSeries2.StrokeThickness = 1;
        columnSeries2.Title = "Series 2";
        columnSeries2.Items.Add(new ColumnItem(12, -1));
        columnSeries2.Items.Add(new ColumnItem(14, -1));
        columnSeries2.Items.Add(new ColumnItem(120, -1));
        columnSeries2.Items.Add(new ColumnItem(26, -1));
        Model.Series.Add(columnSeries2);

        DataContext = this;
    }
}
like image 188
jsanalytics Avatar answered Sep 29 '22 08:09

jsanalytics


I don't know anything about this OxyPlat, but I do know that most charts are often rendered using hardware APIs. Hardware acceleration is usually error-prone when working outside the expected environment (i.e. a client showing a visible Desktop window).

On application initialization, try disabling hardware acceleration:

RenderOptions.ProcessRenderMode = RenderMode.SoftwareOnly;

Another possible tweak is to call DoEvents() before you render your bitmap. Possibly with priority set to SystemIdle. This will make sure your DataContext has been successfully bound.

public static void DoEvents(
    DispatcherPriority priority = DispatcherPriority.Background)
{
    Dispatcher.CurrentDispatcher.Invoke(new Action(delegate { }), priority);
}
like image 42
l33t Avatar answered Sep 29 '22 09:09

l33t