ListView的onScroll事件

13

我正在编写一个简单的C#应用程序,我需要在Listview上使用onScroll事件。因此,我创建了一个继承原始ListView的ListviewEx类。我找到了如何从WinAPI中检测滚动消息,并修改了WndProc方法。

现在我有了这个WndProc:

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

    if (m.Msg == WM_VSCROLL) 
    { 
        onScroll(this, new EventArgs()); 
    } 
}

但问题是,我不知道如何检测有关滚动的信息。此数据应该在WParam中,但在C#中没有像C ++中的LOWORD宏,我需要使用switch来检测诸如SB_BOTTOM、SB_ENDSCROLL、SB_PAGEUP等参数。

是否有任何方法可以在C#中替换LOWORD宏?

或者其他方法如何检测滚动所需的参数?

4个回答

16
谢谢你的回答,它确实帮助了我 :) 现在我有我想要的东西...
这是代码:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using Microsoft.Win32;
using System.Reflection;

namespace ControlsEx
{
    public class ListViewEx : ListView
    {
        // Windows messages
        private const int WM_PAINT      = 0x000F;
        private const int WM_HSCROLL    = 0x0114;
        private const int WM_VSCROLL    = 0x0115;
        private const int WM_MOUSEWHEEL = 0x020A;
        private const int WM_KEYDOWN    = 0x0100;
        private const int WM_LBUTTONUP  = 0x0202;                 

        // ScrollBar types
        private const int SB_HORZ = 0;
        private const int SB_VERT = 1;

        // ScrollBar interfaces
        private const int SIF_TRACKPOS  = 0x10;
        private const int SIF_RANGE     = 0x01;
        private const int SIF_POS       = 0x04;
        private const int SIF_PAGE      = 0x02;
        private const int SIF_ALL       = SIF_RANGE | SIF_PAGE | SIF_POS | SIF_TRACKPOS;

        // ListView messages
        private const uint LVM_SCROLL       = 0x1014;
        private const int  LVM_FIRST        = 0x1000;                   
        private const int  LVM_SETGROUPINFO = (LVM_FIRST + 147);  

        public enum ScrollBarCommands : int
        {
            SB_LINEUP = 0,
            SB_LINELEFT = 0,
            SB_LINEDOWN = 1,
            SB_LINERIGHT = 1,
            SB_PAGEUP = 2,
            SB_PAGELEFT = 2,
            SB_PAGEDOWN = 3,
            SB_PAGERIGHT = 3,
            SB_THUMBPOSITION = 4,
            SB_THUMBTRACK = 5,
            SB_TOP = 6,
            SB_LEFT = 6,
            SB_BOTTOM = 7,
            SB_RIGHT = 7,
            SB_ENDSCROLL = 8
        }

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

            switch(m.Msg)
            {
                case WM_VSCROLL:
                    ScrollEventArgs sargs = new ScrollEventArgs(ScrollEventType.EndScroll, GetScrollPos(this.Handle, SB_VERT));
                    onScroll(this, sargs);
                    break;

                case WM_MOUSEWHEEL:
                    ScrollEventArgs sarg = new ScrollEventArgs(ScrollEventType.EndScroll, GetScrollPos(this.Handle, SB_VERT));
                    onScroll(this, sarg);
                    break;

                case WM_KEYDOWN:
                    switch (m.WParam.ToInt32())
                    {
                        case (int)Keys.Down:
                            onScroll(this, new ScrollEventArgs(ScrollEventType.SmallDecrement, GetScrollPos(this.Handle, SB_VERT)));
                            break;
                        case (int)Keys.Up:
                            onScroll(this, new ScrollEventArgs(ScrollEventType.SmallIncrement, GetScrollPos(this.Handle, SB_VERT)));
                            break;
                        case (int)Keys.PageDown:
                            onScroll(this, new ScrollEventArgs(ScrollEventType.LargeDecrement, GetScrollPos(this.Handle, SB_VERT)));
                            break;
                        case (int)Keys.PageUp:
                            onScroll(this, new ScrollEventArgs(ScrollEventType.LargeIncrement, GetScrollPos(this.Handle, SB_VERT)));
                            break;
                        case (int)Keys.Home:
                            onScroll(this, new ScrollEventArgs(ScrollEventType.First, GetScrollPos(this.Handle, SB_VERT)));
                            break;
                        case (int)Keys.End:
                            onScroll(this, new ScrollEventArgs(ScrollEventType.Last, GetScrollPos(this.Handle, SB_VERT)));
                            break;
                    }   
                    break;
            }

        }

        public int ScrollPosition 
        {
            get
            {
                return GetScrollPos(this.Handle, SB_VERT);
            }
            set
            {
                int prevPos;
                int scrollVal;

                if (ShowGroups == true)
                {
                    prevPos = GetScrollPos(this.Handle, SB_VERT);
                    scrollVal = -(prevPos - value);
                }
                else
                {
                  // TODO: Add setScrollPosition if ShowGroups == false
                }

                SendMessage(this.Handle, LVM_SCROLL, (IntPtr)0, (IntPtr)scrollVal);
            }
        }

        public event ScrollEventHandler onScroll;

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool GetScrollInfo(IntPtr hwnd, int fnBar, ref SCROLLINFO lpsi);

        [DllImport("user32.dll")]
        public static extern int SendMessage(
              int hWnd,      // handle to destination window
              uint Msg,       // message
              long wParam,  // first message parameter
              long lParam   // second message parameter
              );

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

        [DllImport("user32.dll")]
        static extern int SendMessage(IntPtr hWnd, uint wMsg,
                                       IntPtr wParam, IntPtr lParam);

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

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


        [StructLayout(LayoutKind.Sequential)]
        struct SCROLLINFO
        {
            public uint cbSize;
            public uint fMask;
            public int nMin;
            public int nMax;
            public uint nPage;
            public int nPos;
            public int nTrackPos;
        }
    }
}

1
很好,我可能需要借用这段代码。 :) 我注意到键盘按下事件转换为“ScrollEventArgs”的方式是反过来的,并且也没有使用“LargeIncrement”和“LargeDecrement”。因此,“Keys.Up”应对应于“SmallDecrement”,“PageUp”=>“LargeDecrement”,“Down”=>“SmallIncrement”,“PageDown”=>“LargeIncrement”。 - Jonas
我也要借用这段代码 :) 感谢您的贡献。但是,在 WM_MOUSEWHEEL 和 WM_KEYDOWN 情况下(调用 onScroll 方法的地方),它会出现“NullReferenceException”错误,原因我不知道。 - platypus

12

您可以定义WParam常量如下:

private const int WM_HSCROLL = 0x114;
private const int WM_VSCROLL = 0x115;

private const int SB_HORZ = 0;
private const int SB_VERT = 1;

private const int SB_LINELEFT = 0;
private const int SB_LINERIGHT = 1;
private const int SB_PAGELEFT = 2;
private const int SB_PAGERIGHT = 3;
private const int SB_THUMBPOSITION = 4;
private const int SB_THUMBTRACK = 5;
private const int SB_LEFT = 6;
private const int SB_RIGHT = 7;
private const int SB_ENDSCROLL = 8;

private const int SIF_TRACKPOS = 0x10;
private const int SIF_RANGE = 0x1;
private const int SIF_POS = 0x4;
private const int SIF_PAGE = 0x2;
private const int SIF_ALL = SIF_RANGE | SIF_PAGE | SIF_POS | SIF_TRACKPOS;

检查WParam的实际代码可能是这样的:

if (m.Msg == WM_VSCROLL)
{

        ScrollInfoStruct si = new ScrollInfoStruct();
        si.fMask = SIF_ALL;
        si.cbSize = (uint)Marshal.SizeOf(si);
        GetScrollInfo(msg.HWnd, SB_VERT, ref si);
        if (msg.WParam.ToInt32() == SB_ENDSCROLL)
        {
            ScrollEventArgs sargs = new ScrollEventArgs(ScrollEventType.EndScroll, si.nPos);
            onScroll(this, sargs);
        }
}

pinvoke.net是一个很好的网站,可以获取使用Windows 32 API中常量值,而无需自己检查头文件。

查看此示例


在 "if (msg.WParam.ToInt32() == SB_ENDSCROLL)" 中,msg变量是什么?我没有看到msg的定义。 - anhtv13

11

Martijn提供的答案是可行的,但不能捕获所有滚动。只有当用户直接操纵滚动条时才会发送WM_VSCROLL消息。如果用户使用鼠标滚轮滚动或使用UpArrow / DownArrow / PageUp / PageDown键进行滚动,则不会发送WM_VSCROLL

您可以通过侦听LVN_BEGINSCROLL通知消息来捕获由滚动条和鼠标滚轮引起的滚动。

捕获使用键时发生的滚动更难。例如,在响应PageUp键时,不会向控件发送任何消息。在这种情况下,最好的做法是侦听KeyPress事件,然后在事件之前和之后检查滚动条位置是否有变化。

当然,对于您的目的而言,这可能完全无关紧要。WM_VSCROLL消息可能已经足够了。但是,如果您想捕获所有可能的滚动,请查看ObjectListView中的代码,该代码已经具有捕获所有这些可能性的Scroll事件。


太对了,我在我的回答中漏掉了那个。 - Martijn Laarman

0

@Klinki

感谢您的出色贡献 我认为您忘记处理水平滚动了

Case WM_HSCROLL
    Dim sargs As New ScrollEventArgs(ScrollEventType.EndScroll, GetScrollPos(Me.Handle, SB_HORZ))
            RaiseEvent OnScroll(Me, sargs)
            Exit Select

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接