【C#/WinForms】タイトルバーをカスタマイズする方法のまとめ

C#
この記事は約10分で読めます。

今まで調べてきた Windows Forms でタイトルバーのカスタマイズ方法のまとめです。標準タイトルバーを削除して自作する方向けの内容になります。

タイトルバーをダークモードにする方法

まずはタイトルバーをダークモードにする方法です。

サンプルコード

using System.Runtime.InteropServices;

namespace WinFormsAppSample.TitleBar {
    public partial class Form4 : Form {

        [DllImport("dwmapi.dll")]
        public static extern int DwmSetWindowAttribute(IntPtr hwnd, int attribute, int[] attrValue, int attrSize);

        public Form4() {
            InitializeComponent();
            const int DWMWA_USE_IMMERSIVE_DARK_MODE = 20;
            DwmSetWindowAttribute(this.Handle, DWMWA_USE_IMMERSIVE_DARK_MODE, new[] { 1 }, sizeof(int));
        }
    }
}

画面イメージ

タイトルバーをダークモードにしたいだけの場合はこれで十分ですね。

タイトルバーだけ削除する方法

手っ取り早く、タイトルバーだけ削除する方法です。おそらく、ブラウザの Chrome も同じ方法で実装してるんじゃないかと思います。クライアント領域を変更してタイトルバーを削除します。この方法だとウィンドウの影とかは消えません。

ただ、Windows 10 の場合、ウィンドウの上枠線が消えてしまいます。また、マウスカーソルでのウィンドウ操作も上以外は可能です。とにかくお手軽にタイトルバーを削除したい方にお勧めですね。サンプルコードは DPI も考慮済みです。

サンプルコード

namespace WinFormsAppSample.TitleBar {
    public partial class Form1 : Form {

        [DllImport("user32.dll")]
        public static extern int GetSystemMetricsForDpi(int nIndex, int dpi);

        [StructLayout(LayoutKind.Sequential)]
        public struct NCCALCSIZE_PARAMS {
            public RECT rcNewWindow;
            public RECT rcOldWindow;
            public RECT rcClient;
            public IntPtr lppos;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct RECT {
            public int Left;
            public int Top;
            public int Right;
            public int Bottom;
        }

        public Form1() {
            InitializeComponent();
        }

        protected override void WndProc(ref Message m) {
            const int WM_NCCALCSIZE = 0x83;
            if (m.Msg == WM_NCCALCSIZE) {
                if (!this.DesignMode && m.WParam != IntPtr.Zero) {
                    NCCALCSIZE_PARAMS nc = Marshal.PtrToStructure<NCCALCSIZE_PARAMS>(m.LParam);
                    int dpi = this.DeviceDpi;
                    Size frame = GetFrameBorderSize(dpi);
                    int padding = GetPaddingBorder(dpi);
                    nc.rcNewWindow.Right -= (frame.Width + padding);
                    nc.rcNewWindow.Left += (frame.Width + padding);
                    nc.rcNewWindow.Bottom -= (frame.Height + padding);
                    Marshal.StructureToPtr(nc, m.LParam, false);
                    m.Result = IntPtr.Zero;
                    return;
                }
            }
            base.WndProc(ref m);
        }

        public static Size GetFrameBorderSize(int dpi) {
            const int SM_CXFRAME = 0x20;
            const int SM_CYFRAME = 0x21;
            int width = GetSystemMetricsForDpi(SM_CXFRAME, dpi);
            int height = GetSystemMetricsForDpi(SM_CYFRAME, dpi);

            return new(width, height);
        }

        public static int GetPaddingBorder(int dpi) {
            const int SM_CXPADDEDBORDER = 92;
            return GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi);
        }
    }
}

画面イメージ

Windows 10 で実行した結果です。ウィンドウの上枠線が消えて表示されますが、ウィンドウの影などのエフェクトはそのままです。ウィンドウ上のマウスカーソルの操作を独自に実装する必要があります。

タイトルバーとウィンドウの枠線も削除する方法

次は、枠線なども全て削除する方法です。 Form.FormBorderStyle = FormBorderStyle.None か下記コードでタイトルバーと枠線を削除します。おそらく、 VS Code などの Electron アプリなんかはこちらの方法を採用していると思います。

namespace WinFormsAppSample.TitleBar {
    public partial class Form2 : Form {

        public Form2() {
            InitializeComponent();
        }

        protected override void WndProc(ref Message m) {
            const int WM_NCCALCSIZE = 0x83;
            if (m.Msg == WM_NCCALCSIZE) {
                if (!this.DesignMode && m.WParam != IntPtr.Zero) {
                    m.Result = IntPtr.Zero;
                    return;
                }
            }
            base.WndProc(ref m);
        }
    }
}

画面イメージ

タイトルバーも枠線もウィンドウの影も全て削除されます。さらにマウスカーソルによる操作もできなくなります。何から何まで実装したい人にはお勧めですね。

ウィンドウに影を付ける方法

タイトルバーも枠線も削除するウィンドウの影が消えてしまします。できれば Windows 標準と同じにしたいためウィンドウに影を付けます。影を付けるには DwmExtendFrameIntoClientArea を利用します。やっぱり影はあった方が良いですね。

サンプルコード

namespace WinFormsAppSample.TitleBar {
    public partial class Form3 : Form {

        [DllImport("dwmapi.dll")]
        public static extern int DwmExtendFrameIntoClientArea(IntPtr hwnd, ref MARGINS margins);

        [StructLayout(LayoutKind.Sequential)]
        public struct MARGINS {
            public int leftWidth;
            public int rightWidth;
            public int topHeight;
            public int bottomHeight;
        }

        public Form3() {
            InitializeComponent();
        }

        protected override void WndProc(ref Message m) {
            const int WM_NCCALCSIZE = 0x83;
            const int WM_ACTIVATE = 0x06;

            if (m.Msg == WM_ACTIVATE) {
                base.WndProc(ref m);
                MARGINS margins= new MARGINS();
                margins.topHeight = 1;
                DwmExtendFrameIntoClientArea(this.Handle, ref margins);
                m.Result = IntPtr.Zero;
                return;
            } else if (m.Msg == WM_NCCALCSIZE) {
                if (!this.DesignMode && m.WParam != IntPtr.Zero) {
                    m.Result = IntPtr.Zero;
                    return;
                }
            }
            base.WndProc(ref m);
        }
    }
}

画面イメージ

ウィンドウの上 1px に白線があると思います。実はこれがタイトルバーです。この白線は透明度を持った色であれば上書きできます。また、一度設定した白線は margins.topHeight = 0 で解除できます。白線を削除すると影も削除されます。

2つの方法の違い

この2つの方法の違いですが、同じように見えて、実はマウスカーソルがサイズ変更になるまでの位置が異なります。例えば下記は上が Chromeで下が VS Code になります。Chrome のようにタイトルバーだけ削除の場合は若干サイズ変更にゆとりがあります。Windows の標準動作はこちらですね。一方、VS Code の場合はウィンドウの上にマウスカーソルがこないとサイズ変更になりません。どっちがいいかは好みですね。

おわりに

今回のタイトルバーの変更や自作コントロールなどを含めたWindows Forms用コントロール(OTZcontrols)を BOOTH で販売しています。

コメント

タイトルとURLをコピーしました