WPF用户控件的服务器端渲染

14

我正在使用C#/.Net 4.5编写一个服务器端控制台应用程序,获取一些数据并创建静态图像以保存供Web服务器显示。主要使用以下方法:http://lordzoltan.blogspot.com/2010/09/using-wpf-to-render-bitmaps.html,但是我在Arrange()之后添加了mainContainer.UpdateLayout();以更新数据绑定并在呈现的图像中可见,还在其前面添加了Measure()以进行良好的排版...啊,我不会再多说了。

这是执行渲染的方法:

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

该方法的目标参数将是我制作的一个WPF / XAML UserControl实例 - 目前非常简单,只是一个网格以及一些文本数据绑定到我分配给DataContext的ViewModel对象。

保存在磁盘上的图像看起来很好,但OxyPlot Plot对象完全是白色的。

现在,在Visual Studio 2013的设计器中,我可以看到它。我添加了一个设计时DataContext,它与我在运行时使用的对象相同(这是我正在进行的一个尝试 - ViewModel尚未达到最终形式,只是在解决问题时具有一些默认数据)。在设计器中,我可以看到OxyPlot绘制的图表。

是否需要特殊处理才能使我的渲染也包含此OxyPlot图表? 这基本上是本次练习的重点,因此如果能够真正显示它,那将非常棒!

非常感谢任何见解和建议!


考虑到支持的平台种类繁多,我怀疑问题出在OxyPlot WPF控件的实现方式上。也许它是一个嵌入在WPF控件中的WinForms控件,这可能会有问题。我不知道这是否有帮助。 - Phillip Scott Givens
当然,你说的可能是对的 - 有可能没有办法在不使用完整的WPF环境下实现这个控件。如果是这样的话,我会去寻找另一个图表组件。但是,如果我能够弄清楚设计师做了什么我没做的事情,那么我就有希望触发它自己绘制。 - Rune Jacobsen
@swiszcz:你能解释一下为什么这里提供的答案对你不起作用吗? - Dirk Vollmar
2个回答

5
如果您在运行时正确地绑定数据,那么它应该可以正常工作。

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

感谢您的回答,对我晚回复感到抱歉。显然,您的示例可以正常工作。但是,我有两个例子(不幸的是,它们都是专有代码),在这些例子中它并不能正常工作...希望我能在不久的将来抽出一些时间来制作一个最小可重现的示例,以找出它为什么会在某些情况下无法正常工作。 - Maciek Świszczowski
非常欢迎。听到这个很抱歉……有机会一定要发布一个 MCVE,然后给我发个消息,我很乐意再看一次。干杯。 - jsanalytics

3
我不知道关于OxyPlat的任何信息,但我知道大多数图表通常使用硬件API进行渲染。当在预期环境之外工作时(即客户端显示可见的桌面窗口),硬件加速通常容易出错。
在应用程序初始化时,请尝试禁用硬件加速:
RenderOptions.ProcessRenderMode = RenderMode.SoftwareOnly;

另一个可能的调整是在渲染位图之前调用DoEvents()。可能需要将priority设置为SystemIdle。这将确保您的DataContext已成功绑定。

public static void DoEvents(
    DispatcherPriority priority = DispatcherPriority.Background)
{
    Dispatcher.CurrentDispatcher.Invoke(new Action(delegate { }), priority);
}

我们的应用程序非常沉重,因此另外删除硬件支持并不是最好的选择。对于您的回答晚了抱歉! - Maciek Świszczowski

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接