Render a WPF Control On Reporting Services Report
I struck an issue this week where I need to display some ranged data in nice concise and easy to understand way on screen and on a report. I tried graphing the data in Excel and a bar graph almost works but some of the data works back from 100% which won’t graph properly.
So today I set out to see if it was possible to render out a WPF control to an in memory byte[] on an extended “View” version of our Contract business object and bind an image to that property. After a lot of mucking about I finally got this.
Yep that’s a WPF button on an RS Report bound to an in memory object rendered on screen in the Winforms report viewer control.
Codewise it’s not that tricky really, if you follow the class below you can see how I render out the byte[] for the property in the constructor. We’re using VS2010 so you need to have your objects Serializable to use them in reporting services.
[Serializable] public class GrainPurchaseContractReportView : GrainPurchaseContract { public GrainPurchaseContractReportView(GrainPurchaseContract src) { // copy base class feilds here GenerateTestStandardGraph(); } public GrainPurchaseContractReportView(SerializationInfo info, StreamingContext context) : base(info, context) { _bmp = (Byte[])info.GetValue("StandardsGraph", typeof(Byte[])); } private Byte[] _bmp; public Byte[] StandardsGraph { get { return _bmp; } } private void GenerateTestStandardGraph() { BitmapEncoder encoder = new BmpBitmapEncoder(); Button btn = new Button() { Content = "WPF", Height=40, Width=200 }; double dpi = 96; RenderTargetBitmap rtb = new RenderTargetBitmap( (Int32)(btn.Width * dpi / 96), (Int32)(btn.Height * dpi / 96), dpi, dpi, PixelFormats.Default); // Get the size of canvas Size size = new Size(btn.Width, btn.Height); // force control to Update btn.Measure(size); btn.Arrange(new Rect(size)); rtb.Render(btn); using (MemoryStream ms = new MemoryStream()) { encoder.Frames.Add(BitmapFrame.Create(rtb)); encoder.Save(ms); _bmp = new byte[ms.Length]; ms.Seek(0, SeekOrigin.Begin); ms.Read(_bmp, 0, _bmp.Length); } } public override void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("StandardsGraph", StandardsGraph, StandardsGraph.GetType()); base.GetObjectData(info, context); } }
The last part is to place an image on the report and use the “Database” source type and point it to your property.
Of course rendering a button on the report is not that handy, next I’ve got to build a user control that will render the actual data from our object.