与C#中的MDI表单中的滚动条相关的问题

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

儿童forms的高度超过MDI儿童..每当儿童forms在MDIforms打开时滚动条显示正确,但是当我尝试使用鼠标滚轮滚动时,它什么都没有?

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

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

MDI客户端窗口的Windows实现中存在一个不幸的缺陷,即您在MDI父窗口中看到的深灰色窗口。 它是显示滚动条的那个,但它不够智能来处理鼠标滚轮通知。 不知道为什么,但是MDI已经老了,并且在老鼠开车前很久就存在了。

Winforms很不错,它可以修复这样的缺陷。 它不能通过替换MDI客户端窗口来完成,它很难烘焙。 需要的是对窗口进行子类化并捕获WM_MOUSEWHEEL消息。 并通过使用WM_VSCROLL消息滚动窗口来添加此缺少的function。 这确实需要一些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 Parent窗体中,实现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); } 

通过关注滚动量(delta)可以进一步改进代码。 但是这个简单的实现在我的机器上运行得很好,ymmv。

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); 

}

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

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

比较一下,如果您的MDI 窗口具有焦点。 然后父级将滚动,显示MDI子窗口的底部,就像移动了附加到父窗口的滚动条一样。

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