【问题标题】:Issue Related to Scrollbar in MDI Form in C#与 C# 中 MDI 窗体中的滚动条相关的问题
【发布时间】:2012-07-20 21:44:55
【问题描述】:

我有一个 MDI From 和一个子窗体

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

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

【问题讨论】:

    标签: c# .net winforms


    【解决方案1】:

    鼠标滚轮通知消息 (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 上运行良好。

    【讨论】:

      【解决方案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);
      

      }

      【讨论】:

      • 点赞!我花了一天的时间试图找出为什么我的孩子表格神秘地消失了。我忘了读到最后。
      【解决方案3】:

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

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

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

      您可以通过单击 MDI 父窗口来演示这一点(如背景中未被子窗口覆盖的空白区域)。当它获得焦点时,您的滚轮应该会按照您的预期滚动。

      【讨论】:

      • @BoltClock:啊,我一直忙于学习和工作......而且我不允许自己参与(除了研究目的所需的最低要求!)以免我得到被吸进去了,什么也没做!
      • 我尝试过专注于MDI Form,也尝试过专注于mdi的滚动条,但它不起作用??
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-10-12
      • 1970-01-01
      • 2010-11-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多