WPF vs. GDI+
The problem
In one of our projects in Kvinivel we need to draw a huge table of results of some physical experiments. The table can be 100×100 and can be 1000×1000. The application is usually Windows desktop application built with WPF. And as usual, we’ve tried to use some 3rd party grid. And as you can imagine, we’ve got unacceptable performance even with visualization. The most problematic thing is that the user needs to zoom out the table to see every cell as a single pixel, in this case, visualization gives us nothing.
Solution #1
One of the first ideas was to create our own control derived from FrameworkElement
or UIElement
and implement a drawing of the table inside the overridden OnRender
method. As we expected, this should give us maximum performance. And keeping in mind that WPF is based on DirectX, and we will get performance as in 3D games I’ve started the implementation of proof of concept with following OnRender
:
protected override void OnRender(DrawingContext dc) { Debug.WriteLine("OnRender..."); for (int i = 0; i < rows; i++) { for (int j = 0; j < columns; j++) { int x = width * i; int y = height * j; // Draw cell rect dc.DrawRectangle( Brushes.Green, pen, new Rect(x, y, width, height)); // Draw some text in cell dc.DrawText( new FormattedText(string.Format("{0},{1}", i, j), CultureInfo.InvariantCulture, FlowDirection.LeftToRight, typeface, 10, Brushes.Black), new Point(x, y)); } } Debug.WriteLine("OnRender finish"); }
But the performance was still unacceptable, even for a table of 100×100 cells. UI was refreshed in about 10 seconds. And when I measured the time elapsed by OnRender
I have got a strange result of 800 ms. UI stuck for 10 seconds, but OnRender takes only 800ms. I’ve got these results because WPF never draws everything immediately inside OnRender
. With dc.Draw***
you just told infrastructure to draw something. Then WPF draws all the required things at some other moment. So a real drawing of a 100×100 table requires about 10 seconds.
Solution #2
After failing with the first solution, I’ve tried to get a DirectDraw surface and draw everything by myself. And it is not so easy with WPF. I have not found any built functionality for this. In blogs, I found that the only way to use DirectDraw is to call it through COM interop. Some nightmare, as for me!
After that, I’ve decided to check GDI+ (System.Drawing namespace) and try to draw the table with System.Drawing.Bitmap
and then just draw a bitmap with WPF:
protected override void OnRender(DrawingContext dc) { Debug.WriteLine("OnRender..."); using (var bmp = new System.Drawing.Bitmap( columns * width, rows * height)) { using (var g = System.Drawing.Graphics.FromImage(bmp)) { for (int i = 0; i < rows; i++) { for (int j = 0; j < columns; j++) { int x = width * i; int y = height * j; g.FillRectangle( System.Drawing.Brushes.Green, x, y, width, height); g.DrawRectangle( System.Drawing.Pens.DarkGray, x, y, width, height); g.DrawString( string.Format("{0},{1}", i, j), font, System.Drawing.Brushes.Black, new System.Drawing.PointF(x, y)); } } } // Create Image from bitmap and draw it var options = BitmapSizeOptions.FromEmptyOptions(); var img = Imaging.CreateBitmapSourceFromHBitmap( bmp.GetHbitmap(), IntPtr.Zero, Int32Rect.Empty, options); dc.DrawImage(img, new Rect(0, 0, bmp.Width, bmp.Height)); } Debug.WriteLine("OnRender finish"); }
When I started the app, I decided that something is going wrong. UI was refreshed in less than one second. More than 10 times faster than with WPF drawing!
Conclusion
WPF gives us complex layouts and device independence and other sweet things. But old GDI+ sometimes gives much more — performance and simplicity!