未来の自分へ知識貯金

ゆーきのエンジニアブログ

約4分 C#Visual Studioプログラミング

【C#】画面上に枠を表示する

【C#】画面上に枠を表示する

デスクトップの好きな場所に、こんな青い枠だけを表示するコードを紹介します。

私がこれを作ったのは、画面キャプチャ自動化ツールで「今どの領域を対象にしているか」をユーザーに見せるためです。キャプチャ範囲・OCR の監視領域・ガイド表示など、「画面上の場所を指し示したい」場面は自動化ツールを作っているとよく出てきます。

一見特殊な描画 API が必要そうに見えますが、実は Windows フォームの標準機能だけで作れます。

仕組み:枠の正体は「中身が透明なフォーム」

種明かしをすると、この枠は**普通のフォーム(ウィンドウ)**です。次の合わせ技で「枠だけ」に見せています。

設定役割
FormBorderStyle = Noneタイトルバー・境界線を消して、ただの四角にする
BackColor = Color.Blueフォーム全体を枠の色で塗る
TransparencyKey = Color.Whiteフォーム上の白い部分を透明にする
内側に白い PictureBox を配置中央部分が透明になり、外周だけが枠として残る
TopMost = true常に最前面に表示

ポイントは TransparencyKey です。フォーム上の「指定した色」のピクセルが本当に透明になり、下のウィンドウが見えるだけでなく、その部分へのクリックも下のウィンドウに素通しになります。つまり枠の内側は操作の邪魔をしません。

フォーム全体を青にして、内側だけ白い PictureBox で覆えば、「青い外周リングだけが残る」というわけです。

コード

デザイナを使わず、コンストラクタで全部設定する形にまとめました。このファイル1枚で動きます。

using System;
using System.Drawing;
using System.Windows.Forms;

namespace SCSupportTool
{
    public class DialogRegionView : Form
    {
        // マウスのクリック位置を記憶(ドラッグ移動用)
        private Point mousePoint;

        private PictureBox pictureBox1 = new PictureBox();

        public DialogRegionView()
        {
            // --- 「枠だけのウィンドウ」にするための設定 ---
            this.FormBorderStyle = FormBorderStyle.None; // タイトルバーなし
            this.BackColor = Color.Blue;                 // 枠の色
            this.TransparencyKey = Color.White;          // 白を透明にする
            this.TopMost = true;                         // 常に最前面
            this.ShowInTaskbar = false;                  // タスクバーに出さない
            this.StartPosition = FormStartPosition.Manual;
            this.Padding = new Padding(3);               // ← 枠の太さ(3px)

            // 内側を白(=透明になる色)で覆う
            this.pictureBox1.BackColor = Color.White;
            this.pictureBox1.Dock = DockStyle.Fill;
            this.Controls.Add(this.pictureBox1);

            // イベント登録
            this.KeyDown += DialogRegionView_KeyDown;
            this.pictureBox1.MouseDown += pictureBox1_MouseDown;
            this.pictureBox1.MouseMove += pictureBox1_MouseMove;
        }

        // ESCキーで閉じる
        private void DialogRegionView_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Escape)
            {
                this.DialogResult = DialogResult.Cancel;
                this.Close();
            }
        }

        // 枠を掴んで移動できるように
        private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
        {
            if ((e.Button & MouseButtons.Left) == MouseButtons.Left)
            {
                // 位置を記憶する
                mousePoint = new Point(e.X, e.Y);
            }
        }

        private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
        {
            if ((e.Button & MouseButtons.Left) == MouseButtons.Left)
            {
                this.Left += e.X - mousePoint.X;
                this.Top += e.Y - mousePoint.Y;
            }
        }

        // 表示する位置とサイズを外から指定する
        public void SetRegion(Rectangle region)
        {
            this.Location = region.Location;
            this.Size = region.Size;
        }
    }
}

補足を2つ。

  • 枠の太さは Padding で決まりますnew Padding(3) なら 3px。内側の PictureBoxDock = Fill で「Padding を除いた全面」に張り付くため、Padding のぶんだけ地の色(青)が残って枠になります
  • ドラッグ移動は「押した瞬間の位置を覚えて、動いた差分だけフォームを動かす」という定番の実装です。枠線(青い部分)ではなく内側は透明でつかめないので、実際は PictureBox 上……ではなく枠部分をつかみたい場合、MouseDown/MouseMove をフォーム自身にも同様に登録しておくと確実です

使い方

呼び出し側はこれだけです。位置とサイズを Rectangle で渡して表示します。

DialogRegionView view = new DialogRegionView();
view.SetRegion(new Rectangle(100, 100, 800, 450));
view.Show();  // ShowDialog ではなく Show(表示したまま他の操作をさせる)

私のツールでは複数の監視領域を扱っていたので、領域の番号(enum)ごとに表示/非表示を切り替えられるようにしていました。

public enum eRegion
{
    Sample1,
    Sample2,
    Sample3,

    _Count,
}

private DialogRegionView[] dlgRegionViews = new DialogRegionView[(int)eRegion._Count];

// 指定した領域の枠を表示する
private void ShowRegion(eRegion iRegion)
{
    Rectangle region = Program.App.Param.Regions[(int)iRegion];

    if (this.dlgRegionViews[(int)iRegion] == null)
    {
        this.dlgRegionViews[(int)iRegion] = new DialogRegionView();
        DialogRegionView view = this.dlgRegionViews[(int)iRegion];

        view.SetRegion(region);
        view.Show();
    }
}

// 指定した領域の枠を消す
private void InvisibleRegion(eRegion iRegion)
{
    if (this.dlgRegionViews[(int)iRegion] != null)
    {
        this.dlgRegionViews[(int)iRegion].Close();
        this.dlgRegionViews[(int)iRegion] = null;
    }
}

Show()(モードレス表示)を使っているのがポイントです。ShowDialog() だと枠を出している間ほかの操作ができなくなってしまいます。

ハマりポイント

  • 透明にしたはずの部分が白いままTransparencyKey と内側の色が完全に同じ色である必要があります。1bit でも違うと透明になりません。Color.White 同士のように、同じ定数を指定するのが確実です
  • 枠の内側がクリックできてしまう/できないTransparencyKey で透明になった部分はクリックが下のウィンドウに素通しになります(今回はこれが望みの動作)。逆に「枠の内側でもクリックを受け取りたい」場合はこの方式は使えないので、Opacity を下げた半透明フォームを使う方向になります
  • ESC で閉じられないKeyDown はフォーカスのあるコントロールに飛ぶため、PictureBox にフォーカスがあるとフォームの KeyDown が呼ばれないことがあります。フォームの KeyPreview = true を設定しておくと、フォームが先にキーを受け取れて確実です
  • 高DPI環境(拡大率125%など)で位置がずれる → 座標を扱う他の処理と同様、DPI 非対応のアプリだと SetRegion に渡した座標が実際の画面位置とずれます。対処は画面キャプチャ記事のハマりポイントと同じで、app.manifest で DPI-Aware を宣言します

関連記事:組み合わせてキャプチャツールに

この枠表示は、以前紹介した「範囲選択」「画面キャプチャ」と組み合わせて使っていました。マウスで選んだ領域に枠を出し続けて、その範囲をキャプチャする、という流れです。

まとめ

  • デスクトップ上の枠表示は、中身を透明にしたフォームで作れる(特殊な API 不要)
  • 肝は FormBorderStyle = None × TransparencyKey × 内側を同色で塗る、の合わせ技
  • 透明部分はクリックも素通しになるので、操作の邪魔をしないガイド表示に向く
  • 枠の太さは Padding、色は BackColor で自由に変えられる
  • TransparencyKey完全一致の色でないと効かない点に注意

「画面上のここ!」を指し示す UI が欲しくなったら、ぜひ試してみてください。

この記事をシェアする

ゆーき
ゆーき

プログラマー経験10年以上。好きなプログラム言語はC#。使える言語はC++, Python他。好きな開発環境はVisual Studio。

プロフィールを見る →