空腹おやじのログと備忘録

VBA(主にExcel)でいろいろな実験的な事とか、Linuxのコマンドとか設定とかについて忘れないように、あれこれと・・・

【C++】色を選択できるコンボボックス(ドロップダウンリスト)を作ってみた(MFC 未使用)

前置きが長いので、ソースが欲しい方は、リンクで前置きをスキップして下さいw

きっかけ

趣味のような感じで、VBAのコードを書いているのですが、VBEのデフォルトの色が今ひとつなので、VBEThemeColorEditor というツールを使ってカスタマイズした色に変更しています。
自分の好きな色にカスタマイズ出来るので重宝しているのですが、正直、使い勝手があまりよくありません。(個人の感想です。)
そこで、機能拡張版を自分で作ろうと動き始めました。
VBEの設定画面で、各項目ごとに色の割り振りをする機能も含めようと考えたのですが、そのためには、今回作成した「色を選択できるコンボボックス」が必要になったわけです。
でも、コンボボックスは、本来文字列を表示するだけのものなので、自分で作るしかないという結論に・・・

完成イメージ

色が選択できるコンボボックス
色が選択できるコンボボックス

環境

OS:Windows 10 Home 64bit
Microsoft Visual Studio Community 2019

技術情報

Microsoft のサイトの翻訳ですwww

メッセージ

WM_INITDIALOG
ダイアログボックスが表示される直前にダイアログボックスプロシージャに送られます。
ダイアログ ボックス プロシージャは通常、このメッセージを使用してコントロールを初期化したり、
ダイアログ ボックスの外観に影響を与えるその他の初期化タスクを実行したりします。

wParam
デフォルトのキーボードフォーカスを受け取るためのコントロールへのハンドル。ダイアログボックスのプロシージャが TRUE を返した場合にのみ、デフォルトのキーボードフォーカスが割り当てられます。

lParam
追加の初期化データ。このデータは、ダイアログボックスの作成に使用されるCreateDialogIndirectParam、CreateDialogParam、DialogBoxIndirectParam、またはDialogBoxParam関数の呼び出しでlParamパラメータとしてシステムに渡されます。プロパティシートの場合、このパラメータはページの作成に使用されるPROPSHEETPAGE構造体へのポインタです。他のダイアログボックス作成関数が使用されている場合、このパラメータは0です。

戻り値
ダイアログボックスのプロシージャは、キーボードフォーカスを wParam で指定されたコントロールに設定するようにシステムに指示するために TRUE を返します。そうでなければ、システムがデフォルトのキーボードフォーカスを設定しないようにするために FALSE を返すべきです。

docs.microsoft.com

WM_DRAWITEM
今回の肝となる部分です。
オーナーが描いたボタン、コンボボックス、リストボックス、メニューの視覚的側面が変更された場合に、その親ウィンドウに送信されます。

wParam
WM_DRAWITEM メッセージを送信したコントロールの識別子を指定します。メッセージがメニューによって送信された場合、このパラメータは 0 です。

lParam
描画される項目と必要とされる描画の種類に関する情報を含む DRAWITEMSTRUCT 構造体へのポインタ。

戻り値
アプリケーションがこのメッセージを処理した場合は、TRUE を返すべきです。

備考
既定では、DefWindowProc 関数は、所有者が描画したリスト・ボックス項目のフォーカス矩形を描画します。
DRAWITEMSTRUCT 構造体の itemAction メンバは、アプリケーションが実行すべき描画操作を指定します。
このメッセージの処理から戻る前に、アプリケーションは、DRAWITEMSTRUCT構造体のhDCメンバによって識別されるデバイス・コンテキストがデフォルト状態にあることを確認する必要があります。

docs.microsoft.com

WM_COMMAND
ユーザーがメニューからコマンド項目を選択したとき、コントロールが親ウィンドウに通知メッセージを送信したとき、またはアクセラレータのキーストロークが変換されたときに送信されます。
wParam

lParam

戻り値
アプリケーションがこのメッセージを処理した場合は、ゼロを返すべきです。

wParamとlParamのパラメータの使い方

メッセージソース wParam
(High WORD)
wParam
(Low WORD)
lParam
メニュー 0 メニュー識別子(IDM_*) 0
アクセラレータ 1 アクセラレータ識別子(IDM_*) 0
コントロール コントロール定義の通知コード コントロール識別子 コントロールウィンドウへのハンドル

docs.microsoft.com

構造体

DRAWITEMSTRUCT
オーナー・ウィンドウが、オーナーが描画したコントロールまたはメニュー項目の描画方法を決定するために使用する情報を提供します。
所有者が描画したコントロールまたはメニュー項目の所有者ウィンドウは、WM_DRAWITEM メッセージの lParam パラメータとしてこの構造体へのポインタを受け取ります。

typedef struct tagDRAWITEMSTRUCT {
  UINT      CtlType;
  UINT      CtlID;
  UINT      itemID;
  UINT      itemAction;
  UINT      itemState;
  HWND      hwndItem;
  HDC       hDC;
  RECT      rcItem;
  ULONG_PTR itemData;
} DRAWITEMSTRUCT, *PDRAWITEMSTRUCT, *LPDRAWITEMSTRUCT;

CtlType
コントロールのタイプです。このメンバは、以下の値のいずれかになります。

ODT_BUTTON オーナードローボタン
ODT_COMBOBOX オーナードローコンボボックス
ODT_LISTBOX オーナードローリストボックス
ODT_LISTVIEW リストビューコントロール
ODT_MENU オーナードローメニュー
ODT_STATIC オーナードロースタティックコントロール
ODT_TAB タブコントロール

CtlID
コンボボックス、リスト・ボックス、ボタン、または静的コントロールの識別子。
このメンバは、メニュー項目には使用されません。
itemID
メニュー項目のメニュー項目識別子、 またはリストボックスやコンボボックス内の項目のインデックス。
空のリストボックスやコンボボックスの場合、このメンバには -1 を指定することができます。
これにより、アプリケーションは、コントロール内にアイテムがなくても、rcItem メンバで指定された座標にのみフォーカスの矩形を描画することができます。
これは、リストボックスとコンボボックスのどちらにフォーカスがあるかをユーザに示します。itemAction メンバでどのようにビットを設定するかによって、リストボックスやコンボボックスがフォーカスを持っているかのように矩形を描画するかどうかが決まります。
itemAction
要求された描画アクション。このメンバは、1つ以上の値をとることができます。

ODA_DRAWENTIRE コントロール全体を描画する必要があります。
ODA_FOCUS コントロールはキーボードのフォーカスを失ったか、または獲得しました。itemState メンバをチェックして、コントロールがフォーカスを持っているかどうかを判断する必要があります。
ODA_SELECT 選択状態が変更されました。itemState メンバをチェックして、新しい選択状態を決定する必要があります。

itemState
現在の描画アクションが行われた後のアイテムの視覚的な状態。このメンバは、次の表に示す値の組み合わせとすることができる。

ODS_CHECKED メニュー項目にチェックを入れます。このビットはメニューでのみ使用されます。
ODS_COMBOBOXEDIT 描画は、オーナーが描画したコンボボックスの選択フィールド(編集コントロール)で行われます。
ODS_DEFAULT この項目はデフォルトの項目です。
ODS_DISABLED アイテムを無効にして描画します。
ODS_FOCUS アイテムにはキーボードフォーカスがあります。
ODS_GRAYED 項目をグレーにします。このビットはメニューでのみ使用されます。
ODS_HOTLIGHT アイテムがホットトラックされている、つまり、マウスがアイテムの上にあるときにアイテムがハイライトされます。
ODS_INACTIVE 項目は非アクティブであり、メニューに関連付けられたウィンドウは非アクティブです。
ODS_NOACCEL コントロールはキーボードアクセラレータの合図なしで描画されます。
ODS_NOFOCUSRECT コントロールはフォーカスインジケータのキューなしで描画されます。
ODS_SELECTED メニュー項目のステータスが選択されています。

hwndItem
コンボボックス、リスト・ボックス、ボタン、および静的コントロールの場合は、コントロールへのハンドルです。
メニューの場合、このメンバは、項目を含むメニューへのハンドルです。
hDC
このデバイスコンテキストは、コントロール上で描画操作を行う際に使用する必要があります。
rcItem
描画されるコントロールの境界を定義する矩形。
この矩形は、hDC メンバで指定されたデバイスコンテキストにあります。
コンボボックス、リストボックス、ボタンなどのデバイスコンテキストにオーナーウィンドウが描画するものは、システムが自動的にクリップしますが、メニュー項目はクリップしません。
メニュー項目を描画するとき、オーナー・ウィンドウは rcItem メンバで定義された矩形の境界外に描画してはいけません。
itemData
メニュー項目に関連付けられたアプリケーション定義の値。
コントロールの場合、このパラメータはLB_SETITEMDATAまたはCB_SETITEMDATAメッセージによってリストボックスまたはコンボボックスに最後に割り当てられた値を指定します。
リストボックスまたはコンボボックスがLBS_HASSTRINGSまたはCBS_HASSTRINGSスタイルを持っている場合、この値は最初はゼロです。
そうでなければ、この値は、以下のメッセージの1つのlParamパラメータでリストボックスまたはコンボボックスに渡された値が初期値となります。

  • CB_ADDSTRING
  • CB_INSERTSTRING
  • LB_ADDSTRING
  • LB_INSERTSTRING

docs.microsoft.com

ソース

github.com

おまけ

空のプロジェクトを作って、そこからコードを書いて、「さあビルド」となって、下記エラーが発生!!!

LNK2019 未解決の外部シンボル _main が関数 "int __cdecl invoke_main(void)" (?invoke_main@@YAHXZ) で参照されました

「は???」となったわけですよ。
でも、これは以前も経験していて、回避方法は覚えていた。(はずだった・・・)

空のプロジェクトを作ると、プロジェクトのプロパティは、デフォルトでコンソールアプリになっているので、それを直す。(Debug と Release の両方)

f:id:Z1000S:20210128231722j:plain

  • リンカー - システム - サブシステム を、
    コンソール (/SUBSYSTEM:CONSOLE) から、
    Windows (/SUBSYSTEM:WINDOWS) に修正

f:id:Z1000S:20210128231739j:plain
「これで、大丈夫」と再度ビルドしても、まだエラーが消えない。
あれこれイジっていて、気がついたのが、ソリューションプラットフォーム
x86 になっている。これを x64 に直して・・・
無事解決しました。
f:id:Z1000S:20210128231918p:plain