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

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

今まで調べてきた 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 XP テーマを実装しました。

おわりに

今回のタイトルバーの変更や自作コントロールなどを含めたWindows Forms用コントロール(OTZcontrols)を BOOTH で販売しています。興味があればサンプルアプリもありますのでご確認頂ければと思います。

コメント

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