Anirban Sarkar Anirban Sarkar - 3 months ago 40
C# Question

Force the display of scroll bars in a ListView?


  • The background: Most of us know the
    SysListView32
    common control and the equivalent wrapper
    ListView
    class provided by the .NET Framework. A little depth into its internals show that the scroll bars it provides for scrolling its contents are NOT controls themselves, but are managed by the
    SysListView32
    control.

  • The goal: Always draw scroll bars even if it has no
    ListViewItems
    to display or has very few such that no scroll bars are needed anyway; sort of like mimicking the
    RichTextBox
    class with its
    ScrollBars
    property set to
    ForcedBoth
    . Or kinda like this
    ListBox
    :

    What I want...







  • The problem(s):


    1. .NET has NO sugar at all for scroll bars within a
      ListView
      .

    2. Win32 documentation does not state when to show/hide and/or enable/disable scrollbars.


  • My workaround(s):


    1. override
      the
      WndProc
      in a derived class and handle its
      WM_HSCROLL
      and
      WM_VSCROLL
      messages as per steps 2 and 3.

    2. Call
      base.WndProc
      to do the actually required processing of the scroll functionality.

    3. Create a method like
      WmScroll
      and do my processing on it immediately
      after
      base.WndProc
      has returned.

    4. This consists of a p/invoke call to
      GetScrollInfo
      . Determine if a scroll bar is actually needed. If it's not then call
      ShowScrollBar
      and
      EnableScrollBar
      with required values to draw visibly disabled scroll bars.


  • Problems with the workaround:


    1. It barely works. The scroll bars are displayed and disabled but are like the ones under Windows Classic Theme.

    2. It hides the collapse buttons of each
      ListViewGroup
      , rendering them useless!




The descriptive image:

This hides the beautiful collapse buttons (and looks awful)!




The long awaited actual question:

How do I force scroll bars to always be
Visible
within a
ListView
irrespective of the number of
ListViewItems
and disable them if they are unnecessary, at the same time avoiding size miscalculation (to display collapse buttons of the
ListViewGroup
s) and theme deterioration?

Answers without code, and answers with code in C#, VB.NET and C++/CLR are welcome. If you post code in any other language supported by .NET, please also leave a link to a code conversion website I may use if the code seems, uh, incomprehensible.

Answer
  • Information:

    • Firstly, I have to admit this is an okay answer and not the best/most efficient one. If you have a different answer from mine, please post it.
    • Secondly, this answer owes some credit to Plutonix's answer, experimenting with which I learned that by default ListView does not have WS_HSCROLL | WS_VSCROLL flags set in its styles.
      • This is why my previous workaround had problem with themes.
      • These Classic scroll bars are ones Windows provides to Controls that do not have these flags set.
      • Changing the CreateParams does not work either. You have to set it manually in the OnHandleCreated method using SetWindowLong.
      • The solution I am posting does not use the above technique. Apparently, calling ShowScrollBar for each window message forces these flags to be set.
  • The Solution:

    • Define your WndProc like the following:

      protected override void WndPoc(ref Message m)
      {
      //custom code before calling base.WndProc
      base.WndProc(ref m);
      //custom after base.WndProc returns
      WmScroll(); //VERY INEFFICIENT, called for each message :(
      }
      
    • Define WmScroll() as follows:

      protected virtual void WmScroll()
      {
      NativeMethods.ShowScrollBar(Handle, SB_BOTH, true);
      	
      //si.fMask = SIF_PAGE | SIF_RANGE <- initialized in .ctor
      	
      NativeMethods.GetScrollInfo(Handle, SB_HORZ, ref si);
      if(si.nMax < si.nPage)
      	NativeMethods.EnableScrollBar(Handle, SB_HORZ, ESB_DISABLE_BOTH);
      else
      	NativeMethods.EnableScrollBar(Handle, SB_HORZ, ESB_ENABLE_BOTH);
      NativeMethods.GetScrollInfo(Handle, SB_VERT, ref si);
      if(si.nMax < si.nPage)
      	NativeMethods.EnableScrollBar(Handle, SB_VERT, ESB_DISABLE_BOTH);
      else
      	NativeMethods.EnableScrollBar(Handle, SB_VERT, ESB_ENABLE_BOTH);
      }
      
    • Output:

It now, looks like:

What I Have

These are with another item added featuring the horizontal scroll and working ListViewGroup collapse button:

Long text left Long text right

  • Imperfections, yes there are:
    1. A call to AutoResizeColumns is required if group collapse changes effective text width, otherwise the vertical scroll bar hides the collapse buttons.
    2. Always leaves extra space after the last item (visible in the first image).

UPDATE: Everything was fine... Then I had an urge to click on the down arrow and chaos was unleashed... Chaos