Introduction

This article provides a surprisingly efficient way of displaying real-time data changes in an Excel-style grid. When choosing to display data in a grid pattern, your natural first-choice would be to use a DataGrid control bound to a DataSet. However, I thought I'd try using a ListView control for the same purpose.

The aim was to provide a lightweight control that requires very little memory and processing power and has very small latency under heavy load. The simple definition of latency is the time delay between the moment something is initiated and the moment its first effect begins. For software this is usually measured as the time between the raw data arriving and the results being displayed on the screen.

Background

Q. Why does it matter how much processing power is being used?

A. Developing software for the financial sector isn't for the faint-hearted. Huge sums of money can be made or lost in a very short space of time and for the developer, this means that the software has to be reliable, robust and above all, very fast. One of the hallmarks of well-written financial software is very small latency.

The main cause of problems is due to the sheer volume of data that needs to be processed and analysed as quickly as possible; a corporate profit warning, an unexpected change in interest rates or a terrorist attack will all send a shockwave around the financial markets of the world and result in an overwhelming amount of data being generated. This is known in the trade as a 'Fast-Market'.

The data is usually received via data-streams from financial exchanges like Dow-Jones, Liffe and Eurex and this is an obvious case for multi-threaded processing being used to boost performance. However, the data also needs to be displayed in an informative way and this requires the use of a GUI - a notoriously single-threaded process. In poorly designed systems this can lead to a fatal bottleneck with the actual rendering of the data taking up far too much of the available processing power. The result will be out-of-date information being displayed to the user as current data is backed-up awaiting display.

If more processing power is being used by the GUI, it will mean that there's less processor available for the data analysis algorithms that are busy crunching data in the background.

The Example Application

The example application is a bit contrived but demonstrates a typical scenario for which a real-time data grid is required. A random data feeder is used to simulate market data arriving on multiple threads. The speed at which the data arrives can be controlled by using a sliding scale from 0 (No-Data) to 10 (Fast-Market). There are also a couple of buttons that will provide a sudden burst of data that will flood the grid with update events all at once, something that is quite common in the financial markets whenever key data is released.

The remaining controls are all concerned with the appearance of the grid itself and are meant to illustrate the effect on performance that the different painting effects have.

Design Overview

This article assumes a working knowledge of .NET programming with C# and a familiarity with the standard windows ListView control. The control of interest is unimaginatively named ActiveGrid and is little more than an owner-drawn ListView control with the View property hardwired to Details.

The ListView control, along with its Items and SubItems, have all been sub-classed so that some of the key functionality can be either intercepted or overridden. A more obvious candidate would be the DataGrid control but for my money, the ListView has always looked more appealing than a DataGrid from an aesthetic point-of-view and this was my main reason for using it. It comes with some very useful built-in functionality and it would be a shame to lose this.

Main Classes

System.Windows.Forms Base Class

Derived Class

ListView

ActiveGrid

ListViewItem

ActiveRow

ColumnHeader

ActiveColumnHeader

ListView.ColumnHeaderCollection

ActiveGrid.ActiveColumnHeaderCollection

ListView.ListViewItemCollection

ActiveGrid.ActiveRowCollection

ListViewItem.ListViewSubItem

ActiveRow.ActiveCell

ListViewItem.ListViewSubItemCollection

ActiveRow.ActiveCellCollection

ActiveColumnHeader Class

This class is responsible for the presentation of content in both the column header and all of the cells belonging to the column. In addition to all of the existing properties of the System.Windows.Forms.ColumnHeader, a new category of properties has been created to handle the appearance of the cells:

// Category: Cell Appearance
//// Horizontal alignment of the text in all cells belonging to this column
Boolean CellHorizontalAlignment;
// Vertical alignment of the text in all cells belonging to this column
StringAlignment CellVerticalAlignment;
// Format specifier for the text in all cells belonging to this column
String CellFormat;
// Show or hide the value of zero
Boolean DisplayZeroValues;
// Allows the rows to be sorted based on the contents of the cells belonging to this column
SortOrderEnum SortOrder;

ActiveGrid Class

This class is the main control that hosts the collections of both columns and rows. The System.Windows.Forms.ListView class from which it is derived can be displayed in five different view-types but only the Details setting makes any sense if you want to display a grid style view. Consequently, this property is hardwired in the derived class. Additional key properties are provided in two new categories:

// Category: Appearance Alternating Row
//// Draw alternating backgrounds
Boolean UseAlternateRowColors;
// Background color of alternate rows in the list
Color AlternatingBackColor;
// Draw backgrounds using a gradient
Boolean UseGradient;
// Direction of the background gradient
LinearGradientMode LinearGradientMode;
// Start Color of the alternating background gradient
Color AlternatingGradientStartColor;
// End Color of the alternating background gradient
Color AlternatingGradientEndColor;
//// Category: Flash Behavior
//// Flash the cell when its contents are changed
Boolean AllowFlashing;
// Draw gradient backgrounds for flashed cells
Boolean UseFlashGradient;
// Fade-out effect for flashed cells
Boolean UseFlashFadeOut;
// Length of time in milliseconds that a cell remains in the flashed state
Int32 FlashDuration;
// Colour of the text when a cell is in the flashed state
Color FlashForeColor;
// Colour of the background when a cell is in the flashed state
Color FlashBackColor;
// Font used when a cell is in the flashed state
Font FlashFont;
// Start Colour of the background gradient when a cell is in the flashed state
Color FlashGradientStartColor;
// End Colour of the background gradient when a cell is in the flashed state
Color FlashGradientEndColor;
// Direction of the Background gradient for flashed cells
LinearGradientMode FlashLinearGradientMode;

ActiveRow.ActiveCell Class

This class is where most of the new functionality is to be found and mainly concerns the presentation of the cell contents. There are three text regions in every cell:

The centre text-string is aligned according to the CellHorizontalAlignment setting in the column header. Each of the three text regions has its own user-defined colour, font and string. All aspects of the cell's appearance can be modified at run-time immediately prior to re-painting it. This enables individual cells to be color-coded according to the type of change being made. For example, if the value is increasing it can be drawn in blue, whereas if the value is decreasing it can be drawn in red.

Other Functionality

Columns can be moved at run-time

Rows can be sorted by column by clicking on the column header

The row header behaves like a LinkLabel

Conclusion

What is presented here is an alternative approach to the standard DataGrid that could easily be used under the right circumstances. It's very efficient and can be easily extended to suit your own business requirements.

It also emphasises the relationship between GUI complexity and processor requirements. There is an ever increasing tendency to make software that is as good-looking as possible but unfortunately all of this 'sex-appeal' comes at a price. As a general rule, the more plain and boring your user interface, the better your performance will be. This is important to bear in mind if the processing power could be better used elsewhere. Of course, this can always be offset by better hardware. A top-of-the-range processor and graphics card will serve you well in this example, which is all very well if your budget can run to it. If it can't, you'll just have to rely on good, old-fashioned coding that gets the job done as efficiently as possible.

History

29 September 2007: Release 1.0.0.0 of ActiveGrid

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

Comments and Discussions

Great starting point. Looks like has beed a while since initial version. Are you still reading this thread?

I just noticed that occasionally when I am closing my Form hosting your grid causes exception in InvalidateCell()
. Here is part of stack.

System.InvalidOperationException: Invoke or BeginInvoke cannot be called on a control until the window handle has been created.
at System.Windows.Forms.Control.WaitForWaitHandle(WaitHandle waitHandle)
at System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous)
at System.Windows.Forms.Control.Invoke(Delegate method, Object[] args)
InvalidateCell(GridCell cell)

Thank you very much for the article. I am currently working on a real-time control using a DataGridView component. I have been struggling for quite some time to increase its performance and I never managed. From your article I saw how you use double buffering on visual controls, which increased the performance significantly. I will post a small article exemplifying how to do a real-time DataGridView linking to your example.

Nice article.
I'm developing similar UI to display realtime data using datagrid.Right now using IList collection to bind the data.Actually I'm binding data to bindingsource and problem with my existing approach is that inorder to reflects the changes in the UI on every updates , client needs to call ResetBidnings() {I think this will redraw the entire grid}.Is there anyway to refresh the changed row only.

Great work!
I found that it exactly fit into a requirement I met in a project of mine.
But there's a little bug.
The interface of ActiveRow suggests, that You can add subitems viaActiveRow.SubItems.Add(anyString).

But unfortunately it doesn't work.
I found, that I have to create a ActiveCell each time.

It appears to only be optimized when you use the .DecimalValue property. I thought it would work with just the .Text value, but that causes multiple draws to be made, so it's actually slower than a normal listview if you do that. Great control if you just need to insert decimal values, or you can download and modify the decimalValue to be a stringValue - just don't use .Text.

Great control, just wanted to point this out for others in case they try doing what I did.

Thank you very much for the wonderful tool. We really appreciate the job that you did. I only found one minor issue related to right to left (Arabic version). I hope if you can look at in the next version of the control.
Thanks again really its so helpful component.

Thank you for sharing your work. It works fine. But how can you think that a ListView is more efficient than a DataGridView ? I tried both and I think that DataGridView is very quick and offers more fonctionnalities that ListView. More of this, the ListView refreshes itself entirely when a single cell has changed, which is not the case of the DataGridView.
What do you think about this ?

You're quite right about the relative performance benefits of the default ListView and DataGrid controls but the trick here is that the list view is in 'OwnerDraw' mode. This gives you control over how the refresh functionality behaves and enables you to draw each cell manually, only refreshing the areas that have changed. Hence the optimized performance.

The DataGrid is still my preferred weapon of choice for most grid-based requirements as the data-binding functionality is first rate, but this ListView provides a fast, non-databound alternative.

I'd be very interested to see the same functionality implemented in a DataGrid if anyone out there has the time....

Thak you for sharing your work. It works fine. But how can you think that a ListView is more efficient than a DataGridView ? I tried both and I think that DataGridView is very quick and offers more fonctionnalities that ListView. More of this, the ListView refreshes itself entirely when a single cell has changed, which is not the case of the DataGridView.
What do you think about this ?

Good concept, but your effort falls down in the execution. Let me explain. I suspect you, like many others who fall into this trap, are a talented and eager software developer. But developing software and making a product people will use, are two very different things.

Your article, Jason, presents a great concept. I had to do something like it once and I suspect you have done a much better job of it. But your article says nothing about how to use the product. The reader must load your project into Visual Studio and spend a significant time deconstructing what you have done. Let's say that takes anywhere from 30 minutes to a few hours, depending on the reader. Let us further assume very conservatively you have only 100 readers (probably you will have many more!). That means 50 to 300 person-hours will be invested to figure out your product. And that time is not spent producing any work. After digesting the project, the reader must then put in the time to actually use your product.

If instead, *you* had put in another few hours to explain how to use your product, that would not have to be done over and over again by each and every reader. Moreover, you have the expert knowledge; any reader comes in as a product-novice and is bound to miss some of the subtleties. That will, in turn, lead to more debugging time down the road; assuming 0 to 5 hours of debugging per reader, that potentially adds up to another 500 person-hours to use your tool.

Do you see where I am going with this? It is not my intention to single you out or to belittle your work; on the contrary, I applaud you for your efforts. But it is my intention that you and other contributors who read this, might save all the rest of us from duplicating efforts; then you have a true building block.

I appreciate your comments, and I can kind of see where you're coming from, but you've completely misjudged the spirit in which this artice was submitted. You seem to be under the impression that I was trying to showcase a shrink-wrapped product that could be used straight out of the box with minimal effort. That is definately not the case. If it was a product I would have included a copyright statement and provided a user guide and warranty.

Instead, it is presented as nothing more than an academic exercise in taking an existing .NET control and expanding it to introduce some useful functionality that may not be obvious at first glance. It encompasses many different programming disciplines and to document them all would have taken up much more of my time than I am prepared to give. The whole point of it is to be deconstructed and taken apart to see how it works - and any developer worth their salt would do this anyway instead of blindly cutting and pasting. There's no free rides available here.

As for the amount of time that I have caused everybody to waste with my lack of explanation then I recommend that you think about the time saved if you had to start this project from scratch. All the trials that proved to be errors, the blind alleys that you go down, the proof-of-concepts, the optimizations and the debugging. That's worth about a week of anybodies time in my book.

In short, I apologise if you were duped into expecting something that just wasn't intended. However, I'm still looking for feedback into how the control could be improved and its performance increased. After all, that's the reason why I posted the article in the first place.

I'll be honest here and admit that I never thought about the performance of the control during a resize - too busy focusing on other aspects - but I'll definately look into ways of improving this oversight.