如何生成 WPF 图像并在网页中渲染它
WPF 图像生成
引言
以下代码示例将向您展示如何使用 WPF 生成基本图像并在网页中渲染它。它将涵盖在此解决方案开发过程中遇到的主要问题。
背景
在某个项目工作中,我们需要生成包含实时数据的图像,以便在向客户群发送电子邮件时,从数据库中数据的快照渲染图像。该图像嵌入到电子邮件中,即使在一天后打开它,它仍然是实时、相关且特定于阅读者的。此解决方案的优点在于,您可以根据电子邮件打开的时间来编码图像生成,因此如果您的客户一周后打开它,您可以提供当前的优惠或特价。
使用代码
下面是一个基本的 aspx 网页,将托管 WPF 应用程序的二进制输出。您需要创建一个 STA 线程,以便与 WPF 项目进行通信。
public partial class _Default : System.Web.UI.Page
{
private Byte[] _bufferMain;
public Byte[] BufferMain
{
get { return _bufferMain; }
set { _bufferMain = value; }
}
public void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
DisplayDialog();
Response.Clear();
Response.ContentType = @"image/jpeg";
Response.BufferOutput = true;
Response.BinaryWrite(BufferMain);
Response.Flush();
}
}
[STAThread]
public void DisplayDialog()
{
try
{
Thread worker = new Thread(new ThreadStart(DisplayDialogInternal));
worker.SetApartmentState(ApartmentState.STA);
worker.Name = "DisplayDialog";
worker.Start();
worker.Join();
}
catch (Exception exp)
{
Response.Write(exp.ToString());
}
}
public void DisplayDialogInternal()
{
try
{
string fileName = @"C:\TestWeb.jpg";
RenderBookingGridImage image = new RenderBookingGridImage();
//en-code image to transport
BufferMain = image.RenderImage("TESTVALUE");
Stream st = new MemoryStream(BufferMain);
//de-code view
JpegBitmapDecoder jpeg = new JpegBitmapDecoder(st, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.None);
using (Stream stm = File.Create(fileName))
{
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
encoder.Frames.Add(jpeg.Frames[0]);
encoder.Save(stm);
}
}
catch (Exception exp)
{
///ExceptionUtil.DisplayError(exp);
}
}
}
图像将通过 WPF 类生成,但在开发过程中遇到了一些非常重要的问题。在使用画布控件时,必须先测量和安排画布,然后才能使用它来渲染图像。如果未这样做,您可能会得到一个空白画布。
下面是需要在 WPF 项目中渲染图像的代码。
public Byte[] RenderImage(string passedInVar)
{
//ADDING CONTROLS VIA CODE >>>>>
TextBlock tb = new TextBlock();
tb.Width = (double)400;
tb.Height = (double)200;
tb.TextAlignment = TextAlignment.Center;
tb.Text = "Text added via code...";
tb.FontSize = (Double)30;
tb.Foreground = Brushes.Blue;
InnerCanvas.Children.Add(tb);
//When adding children you might need to update the layout for controls...
InnerCanvas.UpdateLayout();
//NB : You have to force the canvas to reload for it to
//re-render correctly when calling in from another source
Canvas canvas = (Canvas)this.FindName("InnerCanvas");
canvas.Measure(new Size((int)canvas.Width, (int)canvas.Height));
canvas.Arrange(new Rect(new Size((int)canvas.Width, (int)canvas.Height)));
int Height = ((int)(InnerCanvas.ActualHeight));
int Width = ((int)(InnerCanvas.ActualWidth));
RenderTargetBitmap rtb = new RenderTargetBitmap(Width, Height, 96, 96, PixelFormats.Pbgra32);
rtb.Render(InnerCanvas);
JpegBitmapEncoder jpg = new JpegBitmapEncoder();
jpg.Frames.Add(BitmapFrame.Create(rtb));
Byte[] tmpArry;
using (MemoryStream ms = new MemoryStream())
{
jpg.Save(ms);
tmpArry = ms.ToArray();
}
///NB : You need to clean up the thread manually
///as they will still reside in memory if they are not flagged
///for termination....Thread count will go through the roof on
///the server if you dont invoke the following calls.
if (jpg.Dispatcher.Thread.IsAlive)
{
jpg.Dispatcher.InvokeShutdown();
}
if (rtb.Dispatcher.Thread.IsAlive)
{
rtb.Dispatcher.InvokeShutdown();
}
jpg = null;
rtb = null;
return tmpArry;
}
关注点
由于某种原因,WPF 中的线程处理对我们的服务器造成了真正的混乱,因为如果您以非传统方式调用 WPF 应用程序,它会留下在服务器上运行的线程。因此,在打开某些控件时请务必小心。
确保在使用任何使用调度器的对象时调用以下内容。
.Dispatcher.InvokeShutdown()