A place for writing about how different little programming problems were solved, in particular relating to C++ using .NET.

The idea behind this particular blog is to document solutions to particular problems I have come across while programming. I feel that by documenting these here, it is serving two purposes. First it is an easy place for me to check and find out what I did, without having to keep searching through old files in my code trying to remember exactly where I'd written the code, sometimes over several functions, or even different files. Second, it is a place for others to find solutions, and hopefully help them to reach a solution much sooner than I achieved; sometimes hours or even days have spent trying to find out how to fix a problem.

Thursday, 24 December 2009

ListView Flicker in Virtual Mode with OwnerDraw

The Problem

Within a ListView if you have set the Virtual Mode, and also have OwnerDraw selected, the result is very unpleasant. It appears that due to the number of times that the virtual items are retrieved and asked to redraw, the control starts to flicker badly. This is apparent when doing something as simple as moving the mouse over the ListView. As soon as you start to scroll the listview, it is hideous and does not look at all professional.

The solution to this problem is actually very simple, but took a lot of tracking down, dismissing one solution that required overriding the WndProc function.

The Solution

The solution is unbelievably simple, and makes you wonder why Microsoft have not made it a setting you can change in the ListView properties. It all revolves around the following simple function call.
SetStyle( ControlStyles::OptimizedDoubleBuffer, true );
What this does is pretty self explanatory, and just about removes all the flicker and actually improves the performance of the ListView itself. Here lies another issue, in that this function can't be called from within your own code, as it's an inaccessible method of an ancestor.

There is a way around this, where instead of putting a ListView on your dialog, you create your own class, inherited from the ListView, as in the following example.
public class MyOptimisedListView : ListView
 {
     public MyOptimisedListView()
         : ListView()
     {
         SetStyle( ControlStyles.OptimizedDoubleBuffer, true );
     }
 }
There is a bad issue with doing this though, because this means you will not longer have the ListView control showing within the View Designer. This means any changes you make would be largely blind, and only really useful if you have one very simple case you're working with.

A better solution is to create a Custom Control of your own, which needs to be added to a Managed DLL. This is actually very simple to do within Visual Studio 2008. Go to the Project menu and click the "Add New Item" option, which brings up a dialog for adding the new item. Select "User control" from within the UI category, giving it a name, such as MyListView. This will bring up the View Designer showing a dialog box, which is of little importance to us. What you want to do is look at the View Code window, and scrolling down you should find the following code:
public ref class MyListView : public System::Windows::Forms::Form
 {
 public:
     MyListView(void)
     {
         InitializeComponent();
         //
         //TODO: Add the constructor code here
         //
     }
To complete the job, you want to change that bit of code so that it is as follows:
public ref class MyListView : public System::Windows::Forms::ListView
 {
 public:
     MyListView(void)
     {
         InitializeComponent();
         //
         //TODO: Add the constructor code here
         //

         SetStyle( ControlStyles::OptimizedDoubleBuffer, true );
     }
This now gives us a control that we will be able to add a dialog box, but in order to do that we need to get it into the ToolBox. This is achieved by right clicking within the ToolBox window, and selecting the "Choose Items ...." option. This brings up a new dialog, which may take a while to appear. Click the browse button and navigate to the compiled version of the DLL in which you've added this custom control. Once you click okay, this custom control will be added to the list of selectable controls. Make sure the CheckBox is selected for the control, and then click the OK button. The control is then added to the bottom of the "All Windows Forms" section of the ToolBox. You can then place this control on any dialog you create, and interact with it the same way you can with the standard ListView control.

The flicker in your ListView will then stop, making your application more professional. In the end I'm not actually using this custom control due to a different limitation on the number of items that the VirtualListSize allows. In the particular dialog I sometimes had datasets that required more than 100 million items. If you set the VirtualListSize to be larger than that value, the control will display nothing, with none of the relevant events being called.

No comments: