-2

我有一个 MDI From 和一个子表单

子窗体的高度高于 MDI 子窗体。每当子窗体以 MDI 窗体打开时滚动条显示正确,但是当我尝试使用鼠标滚轮滚动它时,它什么也没有?

如何使用鼠标滚轮向下和向上滚动?

4

3 回答 3

3

鼠标滚轮通知消息 (WM_MOUSEWHEEL) 是一个不寻常的消息,它会“冒泡”。只要没有窗口处理它,消息就会发送到窗口的父级。重复直到一个窗口处理它或没有更多的父级。

MDI 客户端窗口的 Windows 实现中存在一个不幸的缺陷,即您在 MDI 父窗口中看到的深灰色窗口。它是显示滚动条的那个,但它不够聪明,无法处理鼠标滚轮通知。不知道为什么,但 MDI 是石器时代的,早在老鼠得到轮子之前就已经存在了。

Winforms 很好,它允许修复这样的缺陷。不能通过更换MDI客户端窗口来完成,它是硬生生的。所需的是对窗口进行子分类并捕获 WM_MOUSEWHEEL 消息。并通过使用 WM_VSCROLL 消息使窗口滚动来添加此缺少的功能。这确实需要一点 pinvoke 的魔力,Winforms 也不容易获得对 MDI 客户端窗口的引用。在您的项目中添加一个新类并粘贴以下代码:

using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;

class MdiScroller : NativeWindow {
    public static void Install(Form mdiParent) {
        if (!mdiParent.IsMdiContainer) throw new ArgumentException("Not an MDI application");
        if (!mdiParent.IsHandleCreated) throw new InvalidOperationException("Create me in the Load event please");
        foreach (Control ctl in mdiParent.Controls) {
            if (ctl is MdiClient) {
                var hooker = new MdiScroller();
                hooker.AssignHandle(ctl.Handle);
                break;
            }
        }
    }

    protected override void WndProc(ref Message m) {
        if (m.Msg == WM_DESTROY) this.ReleaseHandle();
        if (m.Msg == WM_MOUSEWHEEL) {
            short delta = (short)((int)(long)m.WParam >> 16);
            SendMessage(this.Handle, WM_VSCROLL, (IntPtr)(delta < 0 ? SB_LINEUP : SB_LINEDOWN), IntPtr.Zero);
            m.Result = IntPtr.Zero;
        }
        base.WndProc(ref m);
    }

    // PInvoke:
    private const int WM_DESTROY = 0x002;
    private const int WM_MOUSEWHEEL = 0x20a;
    private const int WM_VSCROLL = 0x115;
    private const int SB_LINEDOWN = 0;
    private const int SB_LINEUP = 1;

    [DllImport("user32.dll")]
    private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);

}

在 MDI 父窗体中,实现 Load 事件处理程序或重写 OnLoad() 以激活此代码。像这样:

    protected override void OnLoad(EventArgs e) {
        MdiScroller.Install(this);
        base.OnLoad(e);
    }

或者:

    private void Form1_Load(object sender, EventArgs e) {
        MdiScroller.Install(this);
    }

通过注意滚动量(增量)可以进一步改进代码。但是这个简单的实现已经在我的机器 ymmv 上运行良好。

于 2012-07-22T14:51:36.950 回答
2

Hans Passant 的回答很棒,但是漏掉了一点:如果父窗体没有垂直滚动条,子窗体在滚动时会消失……

修复很简单——你只需要检查是否有垂直滚动条;我复制了 Passant 的MdiScroller课程并添加了一些内容:

using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;

class MdiScroller : NativeWindow
{
    public static void Install(Form mdiParent)
    {
        if (!mdiParent.IsMdiContainer) throw new ArgumentException("Not an MDI application");
        if (!mdiParent.IsHandleCreated) throw new InvalidOperationException("Create me in the Load event please");
        foreach (Control ctl in mdiParent.Controls)
        {
            if (ctl is MdiClient)
            {
                var hooker = new MdiScroller();
                hooker.AssignHandle(ctl.Handle);
                break;
            }
        }
    }

protected override void WndProc(ref Message m)
{
    if (m.Msg == WM_DESTROY) this.ReleaseHandle();
    if (m.Msg == WM_MOUSEWHEEL)
    {
        short delta = (short)((int)(long)m.WParam >> 16);
        var scrollbars = GetVisibleScrollbars();

        // ** ADDED **
        if (scrollbars == ScrollBars.Horizontal || scrollbars == ScrollBars.None) { return;    }

        SendMessage(this.Handle, WM_VSCROLL, (IntPtr)(delta < 0 ? SB_LINEUP : SB_LINEDOWN), IntPtr.Zero);
        m.Result = IntPtr.Zero;
    }
    base.WndProc(ref m);
}

// ** ADDED **
private ScrollBars GetVisibleScrollbars()
{
    int wndStyle = GetWindowLong(this.Handle, GWL_STYLE);
    bool hsVisible = (wndStyle & WS_HSCROLL) != 0;
    bool vsVisible = (wndStyle & WS_VSCROLL) != 0;

    if (hsVisible)
    {
        return vsVisible ? ScrollBars.Both : ScrollBars.Horizontal;
    }
    else
    {
        return vsVisible ? ScrollBars.Vertical : ScrollBars.None;
    }
}

// PInvoke:
private const int WM_DESTROY = 0x002;
private const int WM_MOUSEWHEEL = 0x20a;
private const int WM_VSCROLL = 0x115;
private const int SB_LINEDOWN = 0;
private const int SB_LINEUP = 1;

// ** ADDED **
public const int GWL_STYLE = -16;
public const int WS_VSCROLL = 0x00200000;
public const int WS_HSCROLL = 0x00100000;

[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);

// ** ADDED **
[DllImport("user32.dll", SetLastError = true)]
public static extern int GetWindowLong(IntPtr hWnd, int nIndex);

}

于 2014-11-18T13:38:52.753 回答
0

滚动鼠标滚轮在 Windows 中的工作方式是具有输入焦点的窗口是滚动的窗口。

这意味着如果您的 MDI窗口具有焦点,它将是接收滚动消息的窗口。如果它没有滚动条,那么它将忽略这些消息,因为它没有可滚动的内容。

将其与您的 MDI窗口是否具有焦点进行比较。然后父窗口将滚动,露出 MDI 子窗口的底部,就像您移动了附加到父窗口的滚动条一样。

您可以通过单击 MDI 父窗口来演示这一点(如背景中未被子窗口覆盖的空白区域)。当它接收到焦点时,您的滚轮应该使它像您期望的那样滚动。

于 2012-07-22T10:50:18.237 回答