[C#][Windows Formsアプリ][ColorDialog] CustomColors完全ガイド:BGR 形式と保存・復元の実装

スポンサーリンク

はじめに

本記事では、Windows Forms の ColorDialog における CustomColors プロパティの使い方を解説します。CustomColors は「カスタム色パレット(ユーザー定義色)」を保持・復元するための int[] で、ダイアログの「色の作成」枠に表示される候補を事前に用意したり、前回選んだ色を次回に引き継いだりできます。キーポイントは次の3点です。

CustomColorsint[](0x00BBGGRR 形式、BGR の順)であること
・ダイアログ表示前にセット、ShowDialog() 後に取得して保存する流れ
・配列の永続化(ファイルや設定)でユーザー体験を向上

説明

ColorDialog の「カスタム色」領域に表示される色は、CustomColors プロパティで入出力できます。型は int[] で、各要素は 0x00BBGGRR(BGR の並び)として色をエンコードします。これは Color.ToArgb()0xAARRGGBB)とは並びが異なるため注意が必要です。

基本の使い方は次のとおりです。

1) 表示前に dlg.CustomColors に配列をセット(なければ省略可)
2) dlg.ShowDialog(owner) を実行
3) 戻り値が DialogResult.OK のとき、dlg.CustomColors を取得して保存(次回用)

カスタム色の枠数は UI 上は一般に 16 枠です。プロパティ自体は int[] を受け取りますが、見た目に合わせて 16 個まで用意すると分かりやすくなります(足りなければ空欄、超過分は表示されません)。

BGR 変換の目安Color c から int にするには (c.B << 16) | (c.G << 8) | c.R。逆変換は r = x & 0xFFg = (x >> 8) & 0xFFb = (x >> 16) & 0xFF です(アルファは常に 255 として扱います)。

サンプルコード

以下は、選んだ色をプレビューに反映し、CustomColors をローカルファイルに保存・復元するサンプルです。初回は空、2 回目以降は前回のカスタム色が「色の作成」領域に並びます。AllowFullOpenFullOpen を組み合わせ、起動直後からカスタム領域を開いた状態にしています。

using System;
using System.Drawing;
using System.IO;
using System.Collections.Generic;
using System.Windows.Forms;

namespace ColorDialogCustomColorsSample
{
    static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new MainForm());
        }
    }

    public class MainForm : Form
    {
        private readonly Button btnPick;
        private readonly Button btnReset;
        private readonly Panel preview;
        private readonly Label lblInfo;
        private readonly Label lblStatus;

        // カスタム色(0x00BBGGRR の配列)
        private int[] _customColors = Array.Empty<int>();

        public MainForm()
        {
            Text = "ColorDialog.CustomColors の基本";
            Width = 640;
            Height = 360;
            StartPosition = FormStartPosition.CenterScreen;

            btnPick = new Button
            {
                Text = "色を選ぶ...",
                AutoSize = true,
                Location = new Point(20, 20)
            };
            btnPick.Click += OnPickColor;

            btnReset = new Button
            {
                Text = "カスタム色をリセット",
                AutoSize = true,
                Location = new Point(120, 20)
            };
            btnReset.Click += OnReset;

            preview = new Panel
            {
                BorderStyle = BorderStyle.FixedSingle,
                BackColor = Color.White,
                Location = new Point(20, 70),
                Size = new Size(580, 120)
            };

            lblInfo = new Label
            {
                AutoSize = true,
                Location = new Point(20, 200),
                Text = "RGB と HEX をここに表示します"
            };

            lblStatus = new Label
            {
                AutoSize = true,
                Location = new Point(20, 230),
                ForeColor = Color.DimGray,
                Text = "カスタム色はユーザーごとに保存されます。"
            };

            Controls.AddRange(new Control[] { btnPick, btnReset, preview, lblInfo, lblStatus });

            // 起動時にカスタム色を読み込み
            _customColors = LoadCustomColors();
            lblStatus.Text = $"読み込み済み: {_customColors.Length} 件";
        }

        private void OnPickColor(object sender, EventArgs e)
        {
            using (var dlg = new ColorDialog())
            {
                dlg.Color = preview.BackColor; // 初期色
                dlg.AllowFullOpen = true;      // カスタム色の使用を許可
                dlg.FullOpen = true;           // 起動時にカスタム領域を展開

                // 既存のカスタム色を表示用に渡す
                dlg.CustomColors = _customColors;

                var result = dlg.ShowDialog(this);
                if (result == DialogResult.OK)
                {
                    var c = dlg.Color;
                    preview.BackColor = c;

                    // 表示用に HEX を作成
                    string hex = $"#{c.R:X2}{c.G:X2}{c.B:X2}";
                    lblInfo.Text = $"RGB: {c.R}, {c.G}, {c.B}   HEX: {hex}";

                    // ダイアログが保持する最新のカスタム色を保存
                    _customColors = dlg.CustomColors ?? Array.Empty<int>();
                    SaveCustomColors(_customColors);
                    lblStatus.Text = $"カスタム色を {_customColors.Length} 件保存しました。";
                }
            }
        }

        private void OnReset(object sender, EventArgs e)
        {
            _customColors = Array.Empty<int>();
            SaveCustomColors(_customColors);
            lblStatus.Text = "カスタム色をクリアしました。";
        }

        // --- 永続化ヘルパ ---
        private static string StorePath
        {
            get
            {
                var dir = Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
                    "ColorDialogCustomColorsSample");
                Directory.CreateDirectory(dir);
                return Path.Combine(dir, "customcolors.txt");
            }
        }

        private static void SaveCustomColors(int[] values)
        {
            try
            {
                var csv = string.Join(",", values ?? Array.Empty<int>());
                File.WriteAllText(StorePath, csv);
            }
            catch (Exception ex)
            {
                MessageBox.Show("カスタム色の保存に失敗しました: " + ex.Message);
            }
        }

        private static int[] LoadCustomColors()
        {
            try
            {
                if (!File.Exists(StorePath)) return Array.Empty<int>();
                var csv = File.ReadAllText(StorePath);
                if (string.IsNullOrWhiteSpace(csv)) return Array.Empty<int>();

                var parts = csv.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                var list = new List<int>();
                foreach (var p in parts)
                {
                    if (int.TryParse(p.Trim(), out int val))
                        list.Add(val);
                }
                return list.ToArray();
            }
            catch
            {
                return Array.Empty<int>();
            }
        }

        // --- 参考:BGR と Color の相互変換 ---
        public static int ToBgr(Color c)
        {
            return (c.B << 16) | (c.G << 8) | c.R; // 0x00BBGGRR
        }

        public static Color FromBgr(int bgr)
        {
            int r = bgr & 0xFF;
            int g = (bgr >> 8) & 0xFF;
            int b = (bgr >> 16) & 0xFF;
            return Color.FromArgb(255, r, g, b);
        }
    }
}

コード解説(重要ポイント)

BGR 形式CustomColors0x00BBGGRRColor.ToArgb()0xAARRGGBB)の値をそのまま渡すと色が狂います。必要ならサンプルの ToBgr/FromBgr を使って変換します。

配列の渡し方:表示前に dlg.CustomColors にセット、OK 後に dlg.CustomColors を読み直して保存します。null のときは空配列扱いで問題ありません。

永続化:本サンプルは簡単のため CSV 文字列でローカル保存しています。実運用では Properties.Settings や JSON 保存に置き換えると管理しやすいです。

アルファ値ColorDialog は透明度を扱いません(常に A=255)。

セーフティ:ファイルの読み書きは try-catch でエラーに備えています。例外が出ても初期状態で続行できるようにしています。

つまづきポイント

RGB/ARGB と BGR の取り違え0x00BBGGRR である点を忘れると、期待と違う色になります。
型の誤りCustomColorsColor[] ではなく int[]
配列の更新を保存しないShowDialog() のたびに dlg.CustomColors を読み直して保存しないと、次回に反映されません。
枠数の想定:UI 上は一般に 16 枠。配列の長さが少ないと空き枠が表示されます。

まとめ

CustomColors はユーザーの色選択履歴や推奨色パレットをダイアログに反映できる便利なフックです。BGR 形式(0x00BBGGRR)という点を押さえ、表示前セット→OK 後取得→保存というリズムを作るだけで、色選択の体験が一段上がります。まずは既存アプリに保存・復元の 3 ステップを組み込んでみてください。

Please follow and like us:

コメント

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