简体   繁体   中英

Is there a way to auto scroll a listbox in C# WinForms

I an writing a program will display a list of number in serveral list boxes on a big monitor, my question is is there a way to auto scroll the list box to show all data in the boxes?

Normally, I would do:

listBox.SelectedIndex = listBox.Items.Count - 1;
listBox.SelectedIndex = -1;

but you can also try

int nItems = (int)(listBox.Height / listBox.ItemHeight);
listBox.TopIndex = listBox.Items.Count - nItems;

Hope this helps :)

To directly control the scrolling without selecting items, you need to use the Win32 SetScrollPos method from User32.dll . Here is a extended class which gives you basic support:

public class ScrollableListView : ListView
{
    private const int WM_VSCROLL = 0x115;

    private enum ScrollBar : int { Horizontal = 0x0, Vertical = 0x1 }

    public void SetScroll(int x, int y)
    {
        this.SetScroll(ScrollBar.Horizontal, x);
        this.SetScroll(ScrollBar.Vertical, y);
    }

    public void SetScrollX(int position)
    {
        this.SetScroll(ScrollBar.Horizontal, position);
    }

    public void SetScrollY(int position)
    {
        this.SetScroll(ScrollBar.Vertical, position);
    }

    [DllImport("User32.Dll", EntryPoint = "PostMessageA")]
    private static extern bool PostMessage(IntPtr hWnd, uint msg, int wParam, int lParam);

    [DllImport("user32.dll")]
    private static extern int SetScrollPos(IntPtr hWnd, int nBar, int nPos, bool bRedraw);

    private void SetScroll(ScrollBar bar, int position)
    {
        if (!this.IsDisposed)
        {
            ScrollableListView.SetScrollPos((IntPtr)this.Handle, (int)bar, position, true);
            ScrollableListView.PostMessage((IntPtr)this.Handle, ScrollableListView.WM_VSCROLL, 4 + 0x10000 * position, 0);
        }
    }
}

You can then set the X or Y scroll quickly and easily. This should work for other controls too.


If you want to make the control automatically scroll down and up, you would need to set up a recurring timer, with an interval of around 20 milliseconds. Keep track of the scroll position and direction, and increment or decrement it accordingly, sending the position to the control using these methods.


UPDATE:

I had a few problems with the SetScrollPos method posted above, primarily around the scrollbar moving, but the contents not. It's probably just a minor oversight, but in the meantime, here is a somewhat "outside the box" MarqueeListView solution..

First up, the enum representing which scrollbar to use. I used display names instead of the Win32 names for these ( SB_HORIZ and SB_VERT ) just to make things a little clearer.

public enum ScrollBarDirection : int { Horizontal = 0x0, Vertical = 0x1 }

Another enum for the scroll command codes themselves - I've trimmed out everything apart from Up ( SB_LINEUP ), Down ( SB_LINEDOWN ) and EndScroll ( SB_ENDSCROLL ). EndScroll is needed after the scroll message to inform the control to update.

public enum ScrollCommand : int { Up = 0x0, Down = 0x1, EndScroll = 0x8 }

Then finally the class itself. It basically starts by scrolling down, every 20ms (by default - note this is changeable via the MarqueeSpeed property). It then gets the scroll position, and compares it to last time. Once the scroll bar stops moving, it reverses the direction. This is to get around problems I was having with the GetScrollInfo method.

public class MarqueeListView : ListView
{
    protected const int WM_VSCROLL = 0x115;

    private ScrollCommand scrollCommand;
    private int scrollPositionOld;
    private Timer timer;

    public MarqueeListView()
        : base()
    {
        this.MarqueeSpeed = 20;

        this.scrollPositionOld = int.MinValue;
        this.scrollCommand = ScrollCommand.Down;

        this.timer = new Timer() { Interval = this.MarqueeSpeed };
        this.timer.Tick += (sender, e) =>
        {
            int scrollPosition = MarqueeListView.GetScrollPos((IntPtr)this.Handle, (int)ScrollBarDirection.Vertical);
            if (scrollPosition == this.scrollPositionOld)
            {
                if (this.scrollCommand == ScrollCommand.Down)
                {
                    this.scrollCommand = ScrollCommand.Up;
                }
                else
                {
                    this.scrollCommand = ScrollCommand.Down;
                }
            }
            this.scrollPositionOld = scrollPosition;

            MarqueeListView.SendMessage((IntPtr)this.Handle, MarqueeListView.WM_VSCROLL, (IntPtr)this.scrollCommand, IntPtr.Zero);
            MarqueeListView.SendMessage((IntPtr)this.Handle, MarqueeListView.WM_VSCROLL, (IntPtr)ScrollCommand.EndScroll, IntPtr.Zero);
        };
        this.timer.Start();
    }

    public int MarqueeSpeed
    {
        get
        {
            return this.timer.Interval;
        }
        set
        {
            this.timer.Interval = value;
        }
    }

    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern int GetScrollPos(IntPtr hWnd, int nBar);

    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    protected static extern int SendMessage(IntPtr hWnd, int wMsg, IntPtr wParam, IntPtr lParam);
}

Finally, here's a quick Main method to test it:

    private static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);

        Form form = new Form() { StartPosition = FormStartPosition.CenterScreen, Width = 1280, Height = 720 };
        MarqueeListView list = new MarqueeListView() { View = View.Tile, Dock = DockStyle.Fill };
        for (int i = 0; i < 1000; i++) { list.Items.Add(Guid.NewGuid().ToString()); }
        form.Controls.Add(list);

        Application.Run(form);
    }

Please bear in mind, this isn't necessarily the "proper" or best way of doing things, but I figured a different approach might give you some ideas!

I was hoping to use SetScrollPos which would give a much better, smoother effect. You can then easily include acceleration and deceleration - possibly optionally slowing to a halt on mouse over, then accelerating away on mouse out etc. It's just not playing ball at present though - I've got a working scroll update method in an old project somewhere, so I'll update this if I get it working again.

Hope that helps!

Or just insert your items at the top by using: lbLog.Items.Insert(0,"LogItem");

Even if it was a long time ago, others will certainly have the same problem. Since selecting the last item did not lead to automatic scrolling for me, here is a solution that works for me:

ListBox1.ScrollIntoView(ListBox1.Items[ListBox1.Items.Count - 1]);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM