LaggyAu
LaggyAu

Reputation: 9

Hide ListBox Scrollbar & Synchronize Scrolling

I'm using 3 ListBox Controls and I want to remove their Scrollbars, so they're appearance may looking cleaner.

I have it so when I select an item in any, it selects the same in the rest. Only problem is that I have no idea how to make them scroll together.
E.g., if I scrolled down in the first Listbox, the position of the other two should match the position of the the first one.

I'd also like to know how to remove the Scrollbar, since there is no property for this.

Upvotes: 0

Views: 725

Answers (1)

Jimi
Jimi

Reputation: 32223

Here's an example of a ListBox stripped of its Vertical ScrollBar that can handle Mouse Wheel messages and scroll itself.
The Vertical ScrollBar is removed by default, unless the ScrollAlwaysVisible property is set to true or the custom public VerticalScrollBar property is set to true.

The LisBox is scrolled setting its TopIndex property. There's a Min/Max check in WndProc where WM_MOUSEWHEEL is handled that ensures that the list is not scrolled beyond its limits.
It's kind of a redundant check, but may come in handy if you needs to be perform a more complex calculation to determine the current offset.

When the ListBox is scrolled, it raises the custom public Scroll event. You could create a custom EventArgs class to pass specific values to the Event Handler, if required. Here, I'm just synchronizing all ListBox Controls using the TopIndex property.

Note that the Mouse Wheel scrolls the ListBox by 1 Item, while also pressing SHIFT sets the scroll to 3 Items. Modify this behavior as required.

using System.ComponentModel;
using System.Windows.Forms;

[DesignerCategory("code")]
public class ListBoxEx : ListBox
{
    public event EventHandler<EventArgs> Scroll;

    private const int WS_VSCROLL = 0x200000;
    private const int WM_MOUSEWHEEL = 0x020A;
    private const int MK_SHIFT = 0x0004;
    private bool m_VerticalScrollBar = false;

    public ListBoxEx() { }

    protected override CreateParams CreateParams {
        get {
            CreateParams cp = base.CreateParams;
            if (!ScrollAlwaysVisible && !m_VerticalScrollBar) {
                cp.Style &=~WS_VSCROLL;
            }
            return cp;
        }
    }

    [DefaultValue(false)]
    public bool VerticalScrollBar {
        get => m_VerticalScrollBar;
        set {
            if (value != m_VerticalScrollBar) {
                m_VerticalScrollBar = value;
                RecreateHandle();
            }
        }
    }

    protected override void WndProc(ref Message m)
    {
        base.WndProc(ref m);

        switch (m.Msg) {
            case WM_MOUSEWHEEL:
                var wparm = m.WParam.ToInt64();
                int button = (short)wparm;
                int delta = (int)(wparm >> 16);
                int direction = Math.Sign(delta);
                int steps = button == MK_SHIFT ? 3 : 1;
                TopIndex = Math.Max(Math.Min(Items.Count - 1, TopIndex - (steps * direction)), 0);
                Scroll?.Invoke(this, EventArgs.Empty);
                m.Result = IntPtr.Zero;
                break;
        }
    }
}

Add three instances of this Custom Control to a Form and subscribe to the SelectedIndexChanged event of the first one (for example). E.g.,

private void listBoxEx1_SelectedIndexChanged(object sender, EventArgs e)
{
    var lb = (sender as ListBox);
    if (listBoxEx2.Items.Count > lb.SelectedIndex) {
        listBoxEx2.SelectedIndex = lb.SelectedIndex;
    }
    if (listBoxEx3.Items.Count > lb.SelectedIndex) {
        listBoxEx3.SelectedIndex = lb.SelectedIndex;
    }
}

Now, if you want to sync-scroll the three Controls, subscribe to the custom Scroll event of the first:

private void listBoxEx1_Scroll(object sender, EventArgs e)
{
    var lb = sender as ListBox;
    listBoxEx2.TopIndex = lb.TopIndex;
    listBoxEx3.TopIndex = lb.TopIndex;
}

This is how it works:
The code sample also handles ListBox Controls with different number of Items

ListBox No Vertical ScrollBar


Edit:

How to use:
ListBoxEx is a Custom Control class.
To create this Control:

  • Add a new Class file to the Project, name it ListBoxEx.
  • Overwrite the class definition in that file with the class content you find here.
    Add on top the 2 using directives you find in this code.
  • Build the Project.

Now, in the ToolBox, you can find the new ListBoxEx Control.
To replicate what is shown here:

  • Drop 3 instances of it onto a Form.
  • In the designer, select the first object (listBoxEx1).
  • In the PropertyGrid, switch to the events (⚡) view. Find the Scroll and SelectedIndexChanged events and double-click each. It will create the event handlers for you.
  • Copy the content of the event handler you find here inside the new event handlers just created.

Or, subscribe to the events in code:

  • Copy the listBoxEx1_Scroll and listBoxEx1_SelectedIndexChanged handlers you find here (including their content) and paste them inside the Form that contains the ListBoxEx Controls.

  • Add this to the Form Constructor, after InitializeComponent(), to subscribe to the Scroll and SelectedIndexChanged events of listBoxEx1:

      listBoxEx1.Scroll += this.listBoxEx1_Scroll;
      listBoxEx1.SelectedIndexChanged += this.listBoxEx1_SelectedIndexChanged;
    

Upvotes: 3

Related Questions