4

I know it sounds complicated to reproduce but please follow me:

You have a ListView with VirtualMode = true.

Select an item, scroll down so that the item selected gets outside the visual area and then try to add another item to the ListView.

You will see that for a split second it behaves abnormally and see somekind of flicker. If you escalate the situation and try to add a lot of items really fast (I add around 20 times per second) you will see that the small problem becomes very big. It's a combination of flickering and invalid items around there.

I have investigated the problem and it seems that the ListView generates a lot of RetrieveVirtualItem events for the selected item (even though it's clearly not visible).

It seems like when I add a new item (increase VirtualListSize) the ListView first tries to focus on the selected item and then go back to the previous location.

Did anybody experience the same problem?

Patrick D'Souza
  • 3,491
  • 2
  • 22
  • 39
Andrei Stanescu
  • 6,353
  • 4
  • 35
  • 64

2 Answers2

5

Here a derived class with workaround this problem.

Use SetVirtualListSize() method instead of the regular VirtualListSize.

public class FlickerFreeListView : ListView
{
    #region Static Functionality

    private static FieldInfo _internalVirtualListSizeField;

    static FlickerFreeListView()
    {
        _internalVirtualListSizeField = typeof(ListView).GetField("virtualListSize", System.Reflection.BindingFlags.NonPublic | BindingFlags.Instance);

        if (_internalVirtualListSizeField == null)
        {
            string msg = "Private field virtualListSize in type System.Windows.Forms.ListView is not found. Workaround is incompatible with installed .NET Framework version, running without workaround.";
            Trace.WriteLine(msg);
        }
    }

    #endregion


    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    private static extern IntPtr SendMessage(HandleRef hWnd, int msg, IntPtr wParam, IntPtr lParam);

    internal IntPtr SendMessage(int msg, IntPtr wparam, IntPtr lparam)
    {
        return SendMessage(new HandleRef(this, this.Handle), msg, wparam, lparam);
    }

    public void SetVirtualListSize(int size)
    {
        // if workaround incompatible with current framework version (usually MONO)
        if (_internalVirtualListSizeField == null)
        {
            VirtualListSize = size;
        }
        else
        {
            if (size < 0)
            {
                throw new ArgumentException("ListViewVirtualListSizeInvalidArgument");
            }

            _internalVirtualListSizeField.SetValue(this, size);
            if ((base.IsHandleCreated && this.VirtualMode) && !base.DesignMode)
            {
                SendMessage(0x102f, new IntPtr(size), new IntPtr(2));
            }
        }
    }
}
DxCK
  • 4,402
  • 7
  • 50
  • 89
  • Thank you for this, but it has been a long time since I had the problem. I can't test this solution anymore. I see it has some upvotes so if anyone tested this and it worked then write a comment here so I can accept this answer. – Andrei Stanescu Apr 29 '13 at 19:49
1

Various controls have a protected DoubleBuffered property. You could try deriving your own DBListView from ListView, and in its constructor set its DoubleBuffered property to true.

Tim Gradwell
  • 2,312
  • 5
  • 24
  • 25