How to build efficient KPI list for WinForms - c#

I need to build a Windows control that displays KPI indicators. Something similar to the one displayed at the following picture.
alt text http://www.novolocus.com/wp-content/uploads/2008/03/normal.JPG
There are also the following requirements:
List needs to refresh from a background thread every ~15 seconds
It needs to handle 100+ indicators
My initial idea was to use FlowLayoutPanel with a combination of a label and picturebox (for each row). I managed to create exactly the same thing but as list grows and refreshes itselft, UI becomes unresponsive and memory footprint is increasing.
I tried the same approach with GridView and some 3rd party components but every time the result was similar UI would eventually freeze completely because it could not handle that many items refreshing.
So, how would you approach this problem, what would you do create this control, which objects would you choose to build UI and how would you refresh it. (If there is a 3rd party control that looks good that might also work for me).

A layout like that should not lead to an increasing memory footprint, or a slowdown.
Your description sounds more like a resource leak in your control (not Disposing graphic objects). Make sure that you know that the IDisposable interface implies the need of using(){} for Brush, Graphics and Image objects.
A short sample:
using (Graphics g = Graphics.FromImage(picture))
using (Brush fill = new SolidBrush(Color.Yellow))
{
g.FillRectangle(fill, x0, y0, x1, y1);
}

Related

What is the fastest way to draw thousands of lines in WinForms application

I have WinForms application. I made an user control, which draws a map from coordinates of ca 10k lines. Actualy, not all lines are straight ones, but when the map is zoomed out fully - Bezier curves are irrelevant and are replaced with straight lines.
When the map is zoomed, I have smaller number of lines and curves, so the drawing is fast enough (below 15ms). But when it's zoomed out fully - I need to draw all lines (because all fit into viewport). This is painfully slow. On my very fast machine it takes about 1000ms, so on slower machines it would be an overkill.
Is there a simple way to speed up the drawing?
I use Graphics object for drawing and I set Graphics.Scale property to my map fit into my control.
Does this slow things down?
I use Graphics.TranslateTransform() to ensure the whole map is visible.
Both scale and translate is set only once in OnPaint() event handler.
Then there is a loop which draws ca 10k lines. And I just see them drawing on the screen.
Maybe WPF container would help?
Well, I could probably simplify the map to merge some lines, but I wonder if it's worth the effort. It would complicate the code greatly, would introduce much more calculations, use extra memory and I don't know if at the end of the day it would be considerably faster.
BTW, I tested that processing of all lines (converting from one structure to another with some aditional calculations) takes ca 10ms on my machine. So - the drawing alone costs 100x more time.
EDIT:
Now here's the new problem. I've turned double buffering on with:
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer, true);
Here's my messy OnPaint() handler:
protected override void OnPaint(PaintEventArgs e) {
base.OnPaint(e);
if (Splines == null) return;
var pens = new[] {
new Pen(TrackColor),
new Pen(TrackColor),
new Pen(RoadColor),
new Pen(RiverColor),
new Pen(CrossColor)
};
var b = Splines.Bounds;
Graphics g = e.Graphics;
g.PageScale = _CurrentScale;
g.TranslateTransform(-b.Left, -b.Top);
int i = 0;
foreach (var s in Splines) {
g.DrawLine(pens[s.T], s.A, s.D);
if (++i > 100) break;
//if (s.L) g.DrawLine(pens[s.T], s.A, s.D);
//else g.DrawBezier(pens[s.T], s.A, s.B, s.C, s.D);
}
foreach (var p in pens) p.Dispose();
}
Take my word the code works, if I only remove OptimizedDoubleBuffer from styles. When double buffering is on the handler executes properly, each DrawLine is executed with correct params. But the graphics is not displayed. CPU usage during resizing is next to zero. Like all DrawLine calls were ignored. What's happening here?
In a related post I've seen recently but can't find, the OP claimed to have seen a large speed-up when switching his control to use double-buffering. Apparently there's a substantial hit for drawing stuff to the screen.
Another thing you could try is decimating the point lists in the lines you draw when zoomed out. Instead of doing the decimation each frame, you could do it only once each time the zoom is changed.
Try double buffering as a possible solution or try to reduce the number of lines. Only testing will give you an answer for your application.
Winforms Double Buffering
Double buffering with Panel
The feasibility of this really depends on if you're using anti-aliasing, if the thing can rotate, if the thickness has to be very accurate, etc.
However you can always draw all the lines into a bitmap, then simply redraw the bitmap unless the map data itself has actually changed. Of course then you get into having different bitmaps for different zoom levels, hiding and showing them, multiple bitmaps in a grid for the high details etc.
It's definitely not ideal, but if you really do need to draw thousands of lines on a 20ms refresh though.. it might be your only real option.
Or you could use lower level of drawing, outside GDI+. one such example is SlimDX. This wrapper allows you to create a directX device write from your windows controls and forms. Once DirectX is in action, the speed can increase up to several times.
2ndly, when drawing on win panel even with DoubleBuffered enabled, you always have to Invalidate the panel which asks the Environment to call the OnPaint event which actual draws using the system provided Graphics object. This invalidation usually requires a timer with fire rate more than 30 to five you a feeling of smooth playback. Now, when the load increases, the subsequent timer event is delayed since everything is happening under a single thread. And the timer must Yield the thread for around 25ms after every fire (windows OS limitation). Cross Thread access ia not allowed, using which a System.Threading.Timer could have prevent this jitter.
See this link for an example where I have tried to transfer my existing GDI code to DirectX. The code uses a lot of graphics attributes which i have incorporated in the wrapper which can draw on both GDI and DirectX.
https://drive.google.com/file/d/1DsoQl62x2YeZIKFxf252OTH4HCyEorsO/view?usp=drivesdk

Improving DrawingContext performance rendering Geometry (Polygons and Polylines)

this is my first question, however I'm a long time lurker. I'll split up this into two parts, one part explaining what I'm doing and why I think this is the way to go, the second one being the actual question that I can't solve for myself.
What am I doing?
I'm currently developing a framework for rendering 2-dimensional features meant to be displayed in real-time. You can think of an application like Google Maps in your browser, however the framework is meant to render all kinds of geographical data (not just axis-aligned raster data, like those Google Tiles).
The framework is to be integrated into our (the company's) newest product which is a WPF application for the desktop and laptop.
Therefore I chose WPF for actually rendering geometry only; Visibility and Occlusion Culling are done by myself as well as input handling (mouse picking), moving the camera, etc..
Being a real-time application, it need to achieve at least 30 FPS. The framework performs adequate when rendering images: I can draw several thousand bitmaps per frame without a problem, however polyonal data turns out to be a major problem.
The actual question
I'm rendering my fair amount of polyline and polygon data using WPF, specifically using DrawingContext and StreamGeometry. My understanding so far is that this is the way to go for if I need performance. However I am not able to achieve the results that I expected from this.
This is how I fill the StreamGeometry with actual data:
using (StreamGeometryContext ctx = Geometry.Open())
{
foreach (var segment in segments)
{
var first = ToWpf(segment[0]);
ctx.BeginFigure(first, false, false);
// Skip the first point, obviously
List<Point> points = segment.Skip(1).Select(ToWpf).ToList();
ctx.PolyLineTo(points, true, false);
}
}
Geometry.Freeze();
And this is how I draw my geometry:
_dc.PushTransform(_mercatorToView);
_dc.DrawGeometry(null, _pen, polyline);
_dc.Pop();
As a test, I loaded ESRI shapes from OpenStreetMap into my application to test its performance, however I'm not satisfied at all:
My test data consists of ~3500 line segments with a total of ~20k lines.
Mapping each segment to its own StreamGeometry performed extremely bad, but I kinda expected that already: Rendering takes about 14 seconds.
I've then tried packing more segments into the same StreamGeometry, using multiple figures:
80 StreamGeometry, Rendering takes about 50ms.
However I can't get any better results than this. Increasing the amount of lines to around 100k makes my application nearly unusable: Rendering takes more than 100ms.
What else can I do besides freezing both the geometry as well the pen when rendering vector data?
I'm at the point where I'd rather make use of DirectX myself than to rely on WPF for me do to it because something seems to be going terribly wrong.
Edit
To further clarify what I am doing: The application visualizes geographic data in real-time, very much like an application like Google Maps in the browser: However it is supposed to visualize much, much more data. As you may know, Google Maps allows both zooming and panning, which requires > 25 FPS for it to appear as a fluent animation; anything less does not feel fluent.
*
Sorry but I shouldn't upload a video of this before the actual product is released. You may however envision something like Google Maps, however with tons of vector data (polygons and polylines).
*
There are two solutions, one of which is very often stated:
Cache heavy drawings in a bitmap
The implementation seems kinda easy, however I see some problems with this approach: In order to properly implement panning, I need to avoid drawing the heavy stuff each frame, and therefore I am left with the choice of either not updating the cached bitmap while panning the camera, or creating a bitmap which covers an even bigger region than the viewport, so that I only need to update the cached bitmap every so often.
The second "problem" is related to zooming. However it's more of a visual artifact than a real problem: Since the cached bitmap can't properly be updated at 30 FPS, I need to avoid that when zooming as well. I may very well scale the bitmap while zooming, only creating a new bitmap when the zoom ends, however the width of the polylines would not have a constant thickness, although they should.
This approach does seem to be used by MapInfo, however I can't say I'm too fond of it. It does seem to be the easiest to implement though.
Split geometry up into different drawing visuals
This approach seems to deal with the problem differently. I'm not sure if this approach works at all: It depends on whether or not I correctly understood how WPF is supposed to work in this area.
Instead of using one DrawingVisual for all stuff that needs to be drawn, I should use several, so that not every one needs to be RenderOpened(). I could simply change parameters, for example the matrix in the sample above, in order to reflect both camera panning and moving.
However I see some problems with this approach as well: Panning the camera will inevitably bring new geometry into the viewport, hence I would need to perform something similar than in the first approach, actually render stuff which is currently not visible, but may become visible due to the camera shifting; Drawing everything is out of the question as it may take ridiculous amounts of times for a rather small amount of data.
Problem related to both approaches
One big problem which neither of these approach can solve is that even if the overall frame-rate is stable, occasional hickups, either when updating the cached bitmaps (okay, this doesn't apply if the cached bitmap is only updated when the camera is no longer panned) or calling RenderOpen to draw the visible chunk of geometry, seem to be inevitable.
My thoughts so far
Since these are the only two solutions I ever see to this problem (I've done my fair share of googling for more than a year), I guess the only solution so far is to accept frame-rate hickups on even the most powerful GPUs (which should be able to rasterize hundreds of millions of primitives per second), a delayed updating of the viewport (in the case where bitmaps are only updated when the viewport is no longer moved) or to not use WPF at all and resort to DirectX directly.
I'm very glad for the help, however I can't say I'm impressed by WPFs rendering performance so far.
To improve 2D WPF rendering performance you could have a look at the RenderTargetBitmap (for WPF >= 3.5) or the BitmapCache class (for WPF >= 4).
Those classes are used for Cached Composition
From MSDN:
By using the new BitmapCache and BitmapCacheBrush classes, you can cache a complex part of the visual tree as a bitmap and greatly improve rendering time. The bitmap remains responsive to user input, such as mouse clicks, and you can paint it onto other elements just like any brush.

How to prevent PictureBox internally refreshes itself?

I have a very fast loop which renders animation in a Bitmap buffer and adds filter to it (by using LockBits/UnlockBits to access to the raw data and Marshaling changes to it.) in an independent thread.
I wanted to figure out a way to display the render on the Form, real-time, so I created a PictureBox and linked its Image to the bitmap I created. Everytime immediately after the bitmap is unlocked, I refreshed the PictureBox (using delegate, to do cross-threading) so that the rendering is updated properly.
It's totally fine and works very fast, but one big problem came out as I tried dragging the form to the border of the screen, to see if any bug would appear, and oops, the app collapse..saying 'the bitmap is being locked' This happens when either there's other window moving above the PictureBox or the PictureBox is dragged partially out of the screen. I suspice it because PictureBox can refresh itself when redraw is neccessary, and it does when the bitmap is still being locked. So...any way to sovle this problem? Or anyother ways to render the my animation better?
One of possible solutions could be is create your custom MyPictureBox : PictureBox (say) class which override OnPaintBackground, like this:
protected override OnPaintBackground(...)
{
// nothing, an empty method
}
But I'm not very sure that this will work, you should to check this by yourself.
What I would do, personally, considering your comment:
I have a very fast loop which renders animation in a Bitmap buffer and
adds filter to it (by using LockBits/UnlockBits to access to the raw
data and Marshaling changes to it.) in an independent thread
just forget about PictureBox, cause I found it, personally, too generic and non suitable for high performance rendering. Just write a simple class that handles the drawing of specified bitmap on specified surface.
Imo, this is a best choice.
You can't do that.
Instead, you should copy the image (on the background thread) and put the copy in the PictureBox.
For better performance, you can swap between two images to avoid creating too many images.

Winforms: SuspendLayout/ResumeLayout is not enough?

I have a library of a few "custom controls". Essentially we have our own buttons, rounder corner panels, and a few groupboxes with some custom paint. Despite the "math" in the OnPaint methods, the controls are pretty standard. Most of the time, all we do is draw the rounded corners and add gradient to the background. We use GDI+ for all that.
These controls are ok (and very nice looking according to our customers), however and despite the DoubleBuffer, you can see some redrawing, especially when there are 20++ buttons (for example) on the same form. On form load you see the buttons drawing… which is annoying.
I'm pretty sure that our buttons are not the fastest thing on earth but my question is: if double buffer is "on", shouldn't all that redraw happen in background and the Windows subsystem should show the results "instantly" ?
On the other hand, if there's "complex" foreach loop that will create labels, add them to a panel (double buffered) and change their properties, if we suspendlayout of the panel before the loop and resume layout of the panel when the loop is over, shouldn't all these controls (labels and buttons) appear "almost instantly"? This doesn't happen like that, you can see the panel being filled.
Any idea why this is not happening? I know it's hard to evaluate without sample code but that's hard to replicate too. I could make a video with a camera, but trust me on this one, it's not fast :)
We've seen this problem too.
One way we've seen to "fix" it is to completely suspend drawing of the control until we're ready to go. To accomplish this, we send the WM_SETREDRAW message to the control:
// Note that WM_SetRedraw = 0XB
// Suspend drawing.
UnsafeSharedNativeMethods.SendMessage(handle, WindowMessages.WM_SETREDRAW, IntPtr.Zero, IntPtr.Zero);
...
// Resume drawing.
UnsafeSharedNativeMethods.SendMessage(handle, WindowMessages.WM_SETREDRAW, new IntPtr(1), IntPtr.Zero);
One of the things you should look at is whether you have set BackColor=Transparent on any of the child controls of your panels. The BackColor=Transparent will significantly degrade rendering performance especially if parent panels are using gradients.
Windows Forms does not use real transparency, rather it is uses "fake" one. Each child control paint call generates paint call on parent so parent can paint its background over which the child control paints its content so it appears transparent.
So if you have 50 child controls that will generate additional 50 paint calls on parent control for background painting. And since gradients are generally slower you will see performance degradation.
Hope this helps.
I'll approach your problem from a performance angle.
foreach loop that will create labels,
add them to a panel (double buffered)
and change their properties
If that's the order things are done, there's room for improvement. First create all your labels, change their properties, and when they are all ready, add them to the panel: Panel.Controls.AddRange(Control[])
Most of the time, all we do is draw
the rounded corners and add gradient
to the background
Are you doing the same thing over and over again? How are your gradients generated? Writing an image can't be that slow. I once had to create a 1680x1050 gradient in-memory, and it was really fast, like, too fast for Stopwatch, so drawing a gradient can't be so hard.
My advice would be to try and cache some stuff. Open Paint, draw your corners and save to disk, or generate an image in-memory just once. Then load (and resize) as needed. Same for the gradient.
Even if different buttons have different colors, but the same motif, you can create a bitmap with Paint or whatever and at runtime load it and multiply the Color values by another Color.
EDIT:
if we suspendlayout of the panel before the
loop and resume layout of the panel when the loop is over
That's not what SuspendLayout and ResumeLayout are for. They suspend the layout logic, that is, the automatic positioning of the controls. Most relevant with FlowLayoutPanel and TableLayoutPanel.
As for doublebuffering, I'm not sure it applies to custom draw code (haven't tried). I guess you should implement your own.
Doublebuffering in a nutshell:
It's very simple, a couple lines of code. On the paint event, render to a bitmap instead of rendering to the Graphics object, and then draw that bitmap to the Graphics object.
In addition to the DoubleBuffered property, also try adding this to your control's constructor:
SetStyle(ControlStyles.OptimizedDoubleBuffer |
ControlStyles.AllPaintingInWmPaint, true);
And if that ends up not being enough (which I'm gonna go out on a limb and say it isn't), consider having a look at my answer to this question and suspend/resume the redraw of the panel or Form. This would let your layout operations complete, then do all of the drawing once that's done.
You may want to look at the answer to my question, How do I suspend painting for a control and its children? for a better Suspend/Resume.
It sounds like what you are looking for is a "composited" display, where the entire application is drawn all at once, almost like one big bitmap. This is what happens with WPF applications, except the "chrome" surrounding the application (things like the title bar, resize handles and scrollbars).
Note that normally, unless you've messed with some of the window styles, each Windows Form control is responsible for painting itself. That is, every control gets a crack at the WM_ PAINT, WM_ NCPAINT, WM_ERASEBKGND, etc painting related messages and handles these message independently. What this means for you is that double buffering only applies to the single control you are dealing with. To get somewhat close to a clean, composited effect, you need to concern yourself not just with your custom controls that you are drawing, but also the container controls on which they are placed. For example, if you have a Form that contains a GroupBox which in turn contains a number of custom drawn buttons, each of these controls should have there DoubleBuffered property set to True. Note that this property is protected, so this means you either end up inheriting for the various controls (just to set the double buffering property) or you use reflection to set the protected property. Also, not all Windows Form controls respect the DoubleBuffered property, as internally some of them are just wrappers around the native "common" controls.
There is a way to set a composited flag if you are targeting Windows XP (and presumably later). There is the WS_ EX_ COMPOSITED window style. I have used it before to mix results. It doesn't work well with WPF/WinForm hybrid applications and also does not play well with the DataGridView control. If you go this route, be sure you do lots of testing on different machines because I've seen strange results. In the end, I abandoned used of this approach.
Maybe first draw on a control-only 'visible' (private) buffer and then render it:
In your control
BufferedGraphicsContext gfxManager;
BufferedGraphics gfxBuffer;
Graphics gfx;
A function to install graphics
private void InstallGFX(bool forceInstall)
{
if (forceInstall || gfxManager == null)
{
gfxManager = BufferedGraphicsManager.Current;
gfxBuffer = gfxManager.Allocate(this.CreateGraphics(), new Rectangle(0, 0, Width, Height));
gfx = gfxBuffer.Graphics;
}
}
In its paint method
protected override void OnPaint(PaintEventArgs e)
{
InstallGFX(false);
// .. use GFX to draw
gfxBuffer.Render(e.Graphics);
}
In its resize method
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
InstallGFX(true); // To reallocate drawing space of new size
}
The code above has been somewhat tested.
I had the same problem with a tablelayoutpanel when switching usercontrols that I wanted displayed.
I completely got rid of the flicker by creating a class that inherited the table, then enabled doublebuffering.
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
namespace myNameSpace.Forms.UserControls
{
public class TableLayoutPanelNoFlicker : TableLayoutPanel
{
public TableLayoutPanelNoFlicker()
{
this.DoubleBuffered = true;
}
}
}
I've had a lot of similar issues in the past, and the way I resolved it was to use a third-party UI suite (that is, DevExpress) rather than the standard Microsoft controls.
I started out using the Microsoft standard controls, but I found that I was constantly debugging issues which were caused by their controls. The problem is made worse by the fact that Microsoft generally does not fix any of the issues which are identified and they do very little to provide suitable workarounds.
I switched to DevExpress, and I have nothing but good things to say. The product is solid, they provide great support and documentation and yes they actually listen to their customers. Any time I had a question or an issue, I got a friendly response within 24 hours. In a couple of cases, I did find a bug and in both instances, they implemented a fix for the next service release.
I have seen bad winforms flicker on forms where the controls referred to a missing font.
This is probably not common, but it's worth looking into if you've tried everything else.

WPF - ScreenSaver graphics performance improvements

I took this WPF-VS2008 ScreenSaver template and started to make a new screen saver. I have some experience with winForms-platform (GDI+) screen savers, so i am little bit lost with WPF.
Background-element for my screen saver is Canvas.
A DispatcherTimer tick is set to 33 msec, which is ~ 30 FPS.
Background-color is just one huge LinearGradientBrush.
On the screen I have (per available screen, on my local computer i have 2) n-Ellipses drawn with randomly-calculated (Initialization) Background colors + Alpha channel. They are all in Canvas's Children collection.
I'm moving those Ellipses around the screen with some logic (every DispatcherTimer tick). I make a move per-ellipse, and then just call Canvas.SetLeft(...) and Canvas.SetTop(...) for each Ellipse.
If N (number of Ellipses) is higher > 70-80, i begin to notice graphics slow-downs.
Now, i wonder, if there is anything i could do to improve the graphic-smoothness when choosing higher N-values ? Can I "freeze" "something" before moving my Ellipses and "un-freeze" "something" when i'm finished ? Or is there any other trick i could do?
Not that i would be too picky about mentioned performance drop downs - becouse when N==50, everything works smooth as it should. Even if Ellipses are ALL in the SAME place (loads of transparency stuff), there are no problems at all.
Have you tried rendering in the CompositionTarget.Rendering event, rather than in a timer? I've gotten impressive performance in a 3D screen saver when using the Rendering event and doing my own double buffering. (See http://stuff.seans.com/2008/08/21/simple-water-animation-in-wpf/ , http://stuff.seans.com/2008/08/24/raindrop-animation-in-wpf/ , and http://stuff.seans.com/2008/09/01/writing-a-screen-saver-in-wpf/ )
You will improve performance if you call the Freeze method on objects that inherit from Freezable - brushes for example.
The reason is that Freezable supports extra change notifications that have to be handled by the graphics system, when you call Freeze the object can no longer change and so there are no more change notifications.
For an example of this notification system, if you create a brush, use it to paint a rectangle (for example) and then change the brush the on-screen rectangle will change color.
It is not possible to unfreeze something once it has been frozen (although a copy of the object is unfozen by default). Double buffering is also enabled by default in WPF so you cannot gain here.
Once way to improve performance if not already done is to use geometry objects such as Ellipse Geometry rather than shapes if you do not need to the all of the events as these are lighter weight.
I also have found this MSDN Article Optimizing Performance: 2D Graphics and Imaging that suggests a CachingHint may help along with some other tips.
Finally ensure that you are using the latest service pack one as it has many performance improvements outlined here

Categories

Resources