【C#】画面上のどこでも動作するカラーピッカーを作る

【C#】画面上のどこでも動作するカラーピッカーを作る ゲーム・アプリ制作
記事内に広告が含まれています。
スポンサーリンク

はじめに

今回はVisualStudio 2022を使用して、Windowsフォームアプリケーションで画面上の好きな場所の色(カラーコード)を取得できるカラーピッカーを作ってみました。
勉強も兼ねて作成したものなので簡素な作りになっています。

備忘録として作り方を残しておきます。

アプリの概要

今回は以下のような動作のアプリを作ります。

  • 画面上のどこでも、クリックした位置のカラーコードを取得する
  • マウス座標と選択中の色が常に表示される
  • アプリは常に最前面に表示される
  • カラーコードはクリップボードにセットされ、そのまま張り付けて使える

使い方は以下の通りです。

  1. スタートボタンをクリック
  2. カラーコードを取得したい位置でクリック
  3. (1に戻る)

作り方

プロジェクトの作成

言語はC#、テンプレートはWindowsフォームアプリケーション(.NET Framework)で作成します。

画面レイアウト

フォームにコントロールを5つ配置しています。

それぞれの名前は以下の通りです。

番号名前コントロール
startBtnButton
modeLabelLabel
cursorPosLabelLabel
colorBoxLabel
colorCodeTextTextBox

スクリプト

スクリプト全文はこちら。

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

namespace ColorPicker
{
    public partial class Form1 : Form
    {
        // クリック判定(GetKeyState)のために必要
        [System.Runtime.InteropServices.DllImport("user32.dll")]
        private static extern short GetKeyState(int nVirtkey);

        Timer timer;

        public Form1()
        {
            InitializeComponent();

            // ウィンドウを最前面に表示
            this.TopMost = true;

            timer = new Timer();
            timer.Interval = 30; // タイマーの間隔
            timer.Tick += new EventHandler(TimerEventProcessor);
        }

        /// <summary>
        /// タイマーで実行するイベント
        /// </summary>
        private void TimerEventProcessor(object sender, EventArgs e)
        {
            // マウス座標を更新
            cursorPosLabel.Text = $"マウス座標:{Cursor.Position}";

            // 選択中の色を取得・表示
            Color color = GetScreenColor(Cursor.Position);
            colorBox.BackColor = color;

            // カラーコードを表示
            string colorCode = string.Format("#{0:X2}{1:X2}{2:X2}", color.R, color.G, color.B);
            colorCodeText.Text = colorCode;

            // マウスクリックされたら
            if (IsClickDown())
            {
                // タイマーストップ
                timer.Stop();

                // クリップボードにカラーコードをセット
                Clipboard.SetDataObject(colorCode);

                // スタートボタンを押下可能にする
                startBtn.Enabled = true;
                modeLabel.Text = "待機中";
            }
        }

        /// <summary>
        /// クリック判定
        /// </summary>
        private bool IsClickDown()
        {
            return GetKeyState(0x01) < 0;
        }

        /// <summary>
        /// 画面の色を取得
        /// </summary>
        /// <param name="pos">マウス座標</param>
        private Color GetScreenColor(Point pos)
        {
            // Bitmapの作成
            Bitmap bmp = new Bitmap(1, 1, System.Drawing.Imaging.PixelFormat.Format32bppArgb);

            // Graphicsの作成
            Graphics g = Graphics.FromImage(bmp);

            // マウス座標にある画面をコピー
            g.CopyFromScreen(pos.X, pos.Y, 0, 0, new Size(1, 1));

            // ピクセルの色を取得
            Color pixelColor = bmp.GetPixel(0, 0);

            // Graphics解放
            g.Dispose();

            return Color.FromArgb(pixelColor.A, pixelColor.R, pixelColor.G, pixelColor.B);
        }

        /// <summary>
        /// スタートボタンクリック
        /// </summary>
        private void startBtn_Click(object sender, EventArgs e)
        {
            // タイマースタート
            timer.Start();

            // スタートボタンを押下不可にする
            startBtn.Enabled = false;
            modeLabel.Text = "実行中";
        }
    }
}

1つずつ見ていきます。

// クリック判定(GetKeyState)のために必要
[System.Runtime.InteropServices.DllImport("user32.dll")]
private static extern short GetKeyState(int nVirtkey);

Timer timer;

public Form1()
{
    InitializeComponent();

    // ウィンドウを最前面に表示
    this.TopMost = true;

    timer = new Timer();
    timer.Interval = 30; // タイマーの間隔
    timer.Tick += new EventHandler(TimerEventProcessor);
}

一番上に書かれているDLLのインポートは後述のクリック判定(GetKeyState)の時に必要になります。

Form1()内では、画面をクリックしたときにウィンドウが隠れてしまわないようにthis.TopMost = true;で最前面に表示しています。

画面の情報更新頻度を変えたい時はtimer.Intervalの値を変更します。
値が大きいほど更新がゆっくりになります。

/// <summary>
/// タイマーで実行するイベント
/// </summary>
private void TimerEventProcessor(object sender, EventArgs e)
{
    // マウス座標を更新
    cursorPosLabel.Text = $"マウス座標:{Cursor.Position}";

    // 選択中の色を取得・表示
    Color color = GetScreenColor(Cursor.Position);
    colorBox.BackColor = color;

    // カラーコードを表示
    string colorCode = string.Format("#{0:X2}{1:X2}{2:X2}", color.R, color.G, color.B);
    colorCodeText.Text = colorCode;

    // マウスクリックされたら
    if (IsClickDown())
    {
        // タイマーストップ
        timer.Stop();

        // クリップボードにカラーコードをセット
        Clipboard.SetDataObject(colorCode);

        // スタートボタンを押下可能にする
        startBtn.Enabled = true;
        modeLabel.Text = "待機中";
    }
}

主に画面情報の更新を行っている部分です。

左クリックされたらタイマーをストップしてクリップボードにカラーコードをセットします。
タイマーがストップしている間は情報が更新されなくなります。

/// <summary>
/// クリック判定
/// </summary>
private bool IsClickDown()
{
    return GetKeyState(0x01) < 0;
}

左クリックされたかどうかを判定します。
0x01がマウスの左クリックに対応している仮想キーコードです。

/// <summary>
/// 画面の色を取得
/// </summary>
/// <param name="pos">マウス座標</param>
private Color GetScreenColor(Point pos)
{
    // Bitmapの作成
    Bitmap bmp = new Bitmap(1, 1, System.Drawing.Imaging.PixelFormat.Format32bppArgb);

    // Graphicsの作成
    Graphics g = Graphics.FromImage(bmp);

    // マウス座標にある画面をコピー
    g.CopyFromScreen(pos.X, pos.Y, 0, 0, new Size(1, 1));

    // ピクセルの色を取得
    Color pixelColor = bmp.GetPixel(0, 0);

    // Graphics解放
    g.Dispose();

    return Color.FromArgb(pixelColor.A, pixelColor.R, pixelColor.G, pixelColor.B);
}

CopyFromScreenで画面の指定した範囲をキャプチャし、作成したBitmapから色を取得します。

/// <summary>
/// スタートボタンクリック
/// </summary>
private void startBtn_Click(object sender, EventArgs e)
{
    // タイマースタート
    timer.Start();

    // スタートボタンを押下不可にする
    startBtn.Enabled = false;
    modeLabel.Text = "実行中";
}

スタートボタンをクリックしてタイマーをスタートします。

CopyFromScreenのずれを修正

コードが出来たので実行してみると、片方のモニター上ではきちんとカーソル位置の色が表示されましたが、もう片方のモニターではずれた位置の色が表示されてしまいました。
おそらく解像度が異なるモニターを使用しているのが原因だと思われます。

Microsoft Learnに書かれていた以下の対処法を試してみると、無事修正できました。

①マニフェストファイル(app.manifest)を追加

app.manifestで以下の記述を探し、Windows10部分のコメントアウトを解除

<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
  <application>
    <!-- Windows 10 compatibility ↓この行のコメントを外す -->
    <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
  </application>
</compatibility>

App.config<configuration>内に以下の記述を追加

<System.Windows.Forms.ApplicationConfigurationSection>
  <add key="DpiAwareness" value="PerMonitorV2" />
</System.Windows.Forms.ApplicationConfigurationSection>

④アプリケーションのエントリポイントでEnableVisualStylesを呼びだす
※私の場合は最初からProgram.csMain()に書かれていました。

static void Main()
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    Application.Run(new Form1());
}

動作確認

最後に、カラーコードで色を調べられる原色大辞典というサイトを使ってアプリの動作確認をしてみます。
画像の水色の部分をクリックして取得したカラーコードを入力してみると、取得した色と同じ色が表示されました。

参考

高 DPI サポート – Windows Forms .NET Framework | Microsoft Learn

コメント

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