MDIFromと子フォームがあります
子フォームの高さはMDI子よりも高くなります。子フォームがMDIフォームで開くと、スクロールバーが正しく表示されますが、マウスホイールを使用してスクロールしようとすると、何も表示されません。
マウスホイールを使用して上下にスクロールするにはどうすればよいですか?
マウス ホイールの通知メッセージ (WM_MOUSEWHEEL) は異常なメッセージで、「バブル」します。ウィンドウがそれを処理しない限り、メッセージはウィンドウの親に送信されます。ウィンドウがそれを処理するか、親がなくなるまで繰り返します。
MDI クライアント ウィンドウの Windows 実装には、残念ながら、MDI 親ウィンドウに表示される濃い灰色のウィンドウという欠陥があります。これはスクロールバーを表示するものですが、マウス ホイールの通知を処理するほどスマートではありません。理由は定かではありませんが、MDI は石器時代から存在し、マウスが車輪を得るずっと前から存在していました。
Winforms は素晴らしいです。このような欠陥を修正できます。MDI クライアント ウィンドウを置き換えることではできません。ハード ベークされています。必要なのは、ウィンドウをサブクラス化し、WM_MOUSEWHEEL メッセージをキャッチすることです。そして、WM_VSCROLL メッセージでウィンドウをスクロールさせることで、この不足している機能を追加します。これにはちょっとしたピンボーク マジックが必要です。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 で既にうまく機能しています。
Hans Passant の答えは素晴らしいですが、1 つの点が欠けています: 親フォームに垂直スクロールバーがない場合、スクロールすると子フォームが消えます...
修正は簡単です。垂直スクロールバーがあるかどうかを確認するだけです。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 親ウィンドウ (子ウィンドウで覆われていない背景の空白のようなもの) をクリックすることで実証できます。フォーカスを受け取ると、スクロール ホイールによって期待どおりにスクロールする必要があります。