3
  • 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 ListViewGroups) 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.

Community
  • 1
  • 1
Anirban Sarkar
  • 796
  • 6
  • 14
  • 1
    There is a bunch of ListView related scrollbar manipulation shown [in this Q+A](http://stackoverflow.com/q/24716049/1070452) including a Pinvoke to `ShowScrollBar` – Ňɏssa Pøngjǣrdenlarp Aug 20 '16 at 19:46
  • @Plutonix Nice link, but I don't want to resize my columns based on whether a scroll bar appears. I don't want to hide a scroll bar either. I just want them to be present throughout the `ListView`'s lifetime. – Anirban Sarkar Aug 21 '16 at 03:29
  • Given that the system appears to want not to do this and that list views work perfectly in their standard form I wonder what your motivation is. – David Heffernan Aug 21 '16 at 07:20
  • @DavidHeffernan Apart from it looking cool, mostly learning - if other common controls support this why not this one. Also I like to experiment on test subjects (in this case `ListView`) until I hit a wall saying "It can't be done!" or "This is EVIL!". ;) In both these cases I'd just like to know why I shouldn't be doing something that otherwise I might try to do. – Anirban Sarkar Aug 21 '16 at 12:07
  • @DavidHeffernan The motivation in this case: I don't want my `ListView` to display partial items. With horizontal scrolling enabled, an integral number of items are shown if I set the size accordingly. This only works with either the scroll bar displayed, or not displayed but not both (default is displaying them as needed). I don't want to disable horizontal scrolling, nor can I dynamically change the size of the `ListView` (it'd be just way too awkward.) So, this is my only option: disable and enable the scroll bars as needed and have them visible throughout the lifetime of the `ListView`. – Anirban Sarkar Aug 21 '16 at 12:28
  • You question is much closer to the one I linked than you think. Each time you add or remove an item, the normal LV behavior is to check if Scrollbars are needed. So, you will need to do something very ,much like the link and monitor changes to the size and Reissue the call to turn on the VScroll from time to time. – Ňɏssa Pøngjǣrdenlarp Aug 21 '16 at 14:36
  • @Plutonix It is really close, but I need more than that. I needed to show them always. Also, I need them updated whenever it was shown, `Items` are added or removed, __AND__ _when the_ `ListViewGroup` _is expanded or collapsed._ – Anirban Sarkar Aug 22 '16 at 10:19

1 Answers1

0
  • 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

  • Imperfection, yes there is:
    • A call to AutoResizeColumns is required if group collapse changes effective text width, otherwise the vertical scroll bar hides the collapse buttons.
Community
  • 1
  • 1
Anirban Sarkar
  • 796
  • 6
  • 14