ページ 11

【WINAPI】ボタンをエンターキーで押下させたい

Posted: 2011年1月11日(火) 21:18
by Ciel
お世話になります。

WINAPIで色々とツールを作っているんですが、ボタンを押下させるには、
ボタンをマウスでクリックするか、フォーカスをボタンに移動させてエンターキーを押せば動作すると思ってました。
ですが、エンターキーではボタンは標準では押せないようになってました。

そこで、サブクラス化を使って、専用のウィンドウプロシージャを用意し、
ボタンコントロールのウィンドウプロシージャを、そのプロシージャにすげ替えて,WM_KEYDOWNメッセージが発生したときに、
SendMessage(hwnd,BM_CLICK,NULL,NULL);でメッセージを投げて、ボタンを押下するようにしました。

問題なく動くんですが、たったこれだけのためにサブクラス化までしなくちゃいけないのかなぁと、
ほかに方法があるのではないかと思い、質問させていただきました。

これ以外の方法で何か良い方法はありますでしょうか?

Re: 【WINAPI】ボタンをエンターキーで押下させたい

Posted: 2011年1月11日(火) 23:21
by kimuchi
アクセラレータキーを使う、というのはどうでしょうか?

Re: 【WINAPI】ボタンをエンターキーで押下させたい

Posted: 2011年1月11日(火) 23:45
by Ciel
>>kimuchiさん、ありがとうございます。

アクセラレータキーは初めて知ったんですが、
調べてみると、特定のキーと組み合わせて使う必要があるそうですね。
ctrlとかのボタンを押しながらとなると、ちょっと面倒臭いので、
それなら今一応動いているサブクラス化の方を使いますねぇ。

なので申し訳ないですが、アクセラレータキー以外の方法が良いですね。
もしなければ、以降はスルーで結構ですので。。。

Re: 【WINAPI】ボタンをエンターキーで押下させたい

Posted: 2011年1月11日(火) 23:56
by kimuchi
私のところではエンターキー単体で動作できるのですが・・・
リソースで設定する方法ですがそれでは問題がありますか?
一応コードを載せておきます。

コード:

IDR_ACCELERATOR ACCELERATORS
{
//組み合わせキー なし
    VK_RETURN,         BM_CLICK, VIRTKEY
//組み合わせキー コントロール
    "S",            IDM_SAVE, VIRTKEY, CONTROL
}

Re: 【WINAPI】ボタンをエンターキーで押下させたい

Posted: 2011年1月12日(水) 00:07
by Ciel
あれ?wできましたか?w

すいません。ちょっとやってみますね。

Re: 【WINAPI】ボタンをエンターキーで押下させたい

Posted: 2011年1月12日(水) 00:12
by softya(ソフト屋)
フォーカスが合っているボタンならENTERできるはずですよ。

Re: 【WINAPI】ボタンをエンターキーで押下させたい

Posted: 2011年1月12日(水) 00:21
by Ciel
>>kimuchiさん

これってボタンが複数あるときには不可能じゃないですかね?
一つ一つ違うキーを割り当てないといけない気がするのですがどうでしょう?

>>softyaさん
フォーカスをあわしてやっているんですが、できなかったんです。
通常はできるのでしょうか?

Re: 【WINAPI】ボタンをエンターキーで押下させたい

Posted: 2011年1月12日(水) 00:32
by softya(ソフト屋)
現状のリソースと、どのメッセージで受けているか見せてもらえますか?

【追記】
あと、どうやってフォーカスをボタンに合わせていますか?タブキーでしょうか?

Re: 【WINAPI】ボタンをエンターキーで押下させたい

Posted: 2011年1月12日(水) 00:36
by Ciel
すいません。
ちょっと体調悪いので、続きは明日にします。
申し訳ありません。。。

>>softyaさん
タブキーでもあわせてみましたし、クリックでも合わせてみましたがダメでした。
マウスでクリックしたときはWM_COMMANDメッセージを受け取って、
WPARAMの下位ワードからIDを調べて処理をしています。

追記:00:39
エンターキーを押しただけでは、何も反応無かったので、WM_COMMANDは発生していないと思われます。
おやすみなさい。

Re: 【WINAPI】ボタンをエンターキーで押下させたい

Posted: 2011年1月12日(水) 00:37
by softya(ソフト屋)
風邪が流行ってみるので無理なさら無いように。お大事に。

Re: 【WINAPI】ボタンをエンターキーで押下させたい

Posted: 2011年1月12日(水) 00:38
by kimuchi
今回はCielさんのものに合わせてBM_CLICKを使いましたが、
自分のところではボタンに振ったIDを設定しています。

説明が不足してすみませんでしたorz

Re: 【WINAPI】ボタンをエンターキーで押下させたい

Posted: 2011年1月12日(水) 00:42
by Ciel
>>KIMUCHIさん
それは理解していたので大丈夫です。

BM_CLICKはボタンを押させるためのメッセージです。
これを投げるとWM_COMMANDが発生するので、ボタン押下時の処理をさせることができます。

これが最後です。また明日。

Re: 【WINAPI】ボタンをエンターキーで押下させたい

Posted: 2011年1月12日(水) 01:55
by ISLe
メッセージループにIsDialogMessageをかますとENTERキーでボタンを押すことができます。
フォーカスの移動とかも自動でやってくれますしダイアログでなくても使えます。

Re: 【WINAPI】ボタンをエンターキーで押下させたい

Posted: 2011年1月12日(水) 07:37
by Ciel
>>ISLeさん、ありがとうございます。

IsDIalogMessageの存在はTABキーの移動の制御をしたかったので使おうと思ってたんですが、
ボタンコントロール以外に、エディットコントロールもあり、そいつもIsDIalogMessageで処理されてしまうため、
エディットボックス内のテキスト入力の操作がおかしくなってしまい、断念しました。

if(!IsDialogMessage(親HWND,&msg){
TranslateMessage(&msg);
DispatchMessage(&msg);
}

通常↑のように処理すると思うのですが、これだとエディットボックスの処理も
IdDialogMessageとして処理されてしまうようです。
エディットボックスだけ除外できるような方法があればいいんですけどね。。。

07:38追記:
上記のような理由からTABキーの移動もサブクラス化にて対応しております。

Re: 【WINAPI】ボタンをエンターキーで押下させたい

Posted: 2011年1月12日(水) 19:37
by Ciel
サブクラス化にて使用しているウィンドウプロシージャは下記のコードです。

WM_KEYDOWNメッセージが発生したら、ウィンドウハンドルを調べて、
ボタンコントロールのウィンドウハンドルであれば、そのウィンドウハンドルに
BM_CLICKメッセージを投げて、ボタンを押下させるという仕組みです。

また今回の質問とは関係ないですが、TABキーによるフォーカスの移動は、
下記のようにウィンドウハンドルをチェックして、移動させたいウィンドウハンドルへSetFocusへ移動
させてるという単純なものです。

コード:

ESULT CALLBACK TabProc(HWND hwnd,UINT msg,WPARAM wp,LPARAM lp)
{
	switch(msg){
		case WM_KEYDOWN:
			if(GetAsyncKeyState(VK_TAB)&0x8000){
			
				if(hwnd==edit_hwnd){

					SetFocus(send_hwnd);

				}else if(hwnd==send_hwnd){
					
					SetFocus(cut_hwnd);

				}else if(hwnd==cut_hwnd){

					SetFocus(shutdown_hwnd);
				
				}else if(hwnd==shutdown_hwnd){

					SetFocus(edit_hwnd);
				}
			}else if(GetAsyncKeyState(VK_RETURN)&0X8000){
				if(hwnd==send_hwnd || hwnd==cut_hwnd || hwnd==shutdown_hwnd){
					SendMessage(hwnd,BM_CLICK,NULL,NULL);
				}
			}
			return 0;
	}

	if(hwnd==send_hwnd || hwnd==cut_hwnd || hwnd==shutdown_hwnd){
		return CallWindowProc(childbutton_proc,hwnd,msg,wp,lp);
	}else{
		return CallWindowProc(childedit_proc,hwnd,msg,wp,lp);
	}
}
で、ボタンを押下したときの処理としては、下記のようになっています。
ボタンをクリックしたときは、WM_COMMANDメッセージが発生するので、
WPARAMの下位ワードにある子ウィンドウIDを調べ、そのボタンに応じた処理をそれぞれさせてるという
単純なものです。

コード:



case WM_COMMAND:
			switch(LOWORD(wp)){

				case CHILD_EDIT:
                  //何かの処理
					
				case CHILD_SEND:
                                       //何かの処理
                         }
ですが、ボタンにカーソルを置いて、エンターキーを押してもクリックしたことにはならず、
WM_COMMANDが発生しないため、サブクラス化にて対応し、WM_KEYDOWNのメッセージを受け取り
対応しているという状況です。

何かほかに良い方法はございますでしょうか?
なければそのままスルーで結構です。。。

Re: 【WINAPI】ボタンをエンターキーで押下させたい

Posted: 2011年1月12日(水) 23:19
by chemo
WM_GETDLGCODE でアプリケーション側で処理したいコードを返してはだめでしょうか?

ちなみに WM_GETDLGCODE はコントロール側のウィンドウプロシージャで処理します。
例えばダイアログ側に ENTER キーによる DEFPUSHBUTTON の処理をされたくない場合は
WM_GETDLGCODE のメッセージに対して DLGC_DEFPUSHBUTTON を返せば、
ダイアログ側は何もしなかった………はずです。
(すみませんなにぶん大分昔の知識なので)

Re: 【WINAPI】ボタンをエンターキーで押下させたい

Posted: 2011年1月13日(木) 01:41
by ISLe
未確認ですがSendMessage(hwnd,BM_CLICK,NULL,NULL)をPostMessageにすれば効きそうな気がします。

とは言え、IsDialogMessageをかましておかしくなるならおかしくなるほうを対処するのが良いと思うのです。
標準コントロールを使いつつ標準のUIから外れたことをしようとするとあとからあとから問題が噴出してたくさんの時間を無駄にすることになると思います。

Re: 【WINAPI】ボタンをエンターキーで押下させたい

Posted: 2011年1月13日(木) 07:12
by Ciel
>>chemoさんありがとうございます

WM_GETDLGCODEですかこれは知らなかったですね。
今日帰ったらちょっと調べてみます。

>>ISLeさん、ありがとうございます。

帰ったらちょっとやってみますね。
ISLeさんのおっしゃるとおり、IsDialogMessageにしたほうが仕組みがすごい簡単で後から困らなさそうですよね。
IsDialogMessageを使って対処し、その不具合を解決していく方向で進めてまりいたいと思います。
今日帰ったら、実際のコードと、その不具合をまた報告しにきます。

Re: 【WINAPI】ボタンをエンターキーで押下させたい

Posted: 2011年1月13日(木) 18:48
by Ciel
IsDialogMessageを使用した方法での結果をお伝えします。
下記のようにボタンとエディットボックスコントロールを作成しています。

コード:

CreateWindowA("EDIT",NULL,WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_TABSTOP | ES_AUTOVSCROLL | ES_LEFT | ES_MULTILINE,
		0,570,600,130,hwnd,(HMENU)CHILD_EDIT,hInstance,NULL);
CreateWindowA("BUTTON","終了",WS_CHILD | WS_TABSTOP | WS_VISIBLE | BS_DEFPUSHBUTTON,
		600,662,100,37,hwnd,(HMENU)CHILD_SHUTDOWN,hInstance,NULL);

で、IsDialogMessageで処理してるメッセージループは下記の通りです。

コード:

while(1){
		if(GetMessage(&msg,NULL,0,0)!=-1){
			if(msg.message==WM_QUIT){
				break;
			}
			if(!IsDialogMessage(hwnd,&msg)){//IsDialogMessageで処理されなかったら0が返る
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
			
		}else{
			MSG("メッセージの取得でエラーが発生したので終了します");
			break;
		}
	}
このコードで処理した場合、全てのボタンコントロールにおいて、エンターキーでのボタン押下はサブクラス化などを使わなくても、
動作してくれます。

ですが、タブキーでのフォーカスの移動がエディットボックス→ボタンへ移動する時のみ、タブキーを押しても移動してくれません。
また、ウィンドウスタイルでES_MULTILINEを適用していても、エディットボックス内での複数行の入力が出来なくなってしまいます。
恐らくエディットボックスの全ての処理が、IsDialogMessageで処理されてしまっているのが原因と思われるのですが、
これを回避する方法はございますでしょうか?

分かる方、ご回答よろしくお願い致します。

Re: 【WINAPI】ボタンをエンターキーで押下させたい

Posted: 2011年1月13日(木) 22:00
by ISLe
Ciel さんが書きました:ですが、タブキーでのフォーカスの移動がエディットボックス→ボタンへ移動する時のみ、タブキーを押しても移動してくれません。
また、ウィンドウスタイルでES_MULTILINEを適用していても、エディットボックス内での複数行の入力が出来なくなってしまいます。
メッセージループにIsDialogMessageをかませるだけでは実装が不十分でした。
ウインドウプロシージャの最後でDefWindowProcの代わりにDefDlgProcを呼び出すようにしてください。
またウインドウクラスの登録時、WNDCLASS::cbWndExtraにDLGWINDOWEXTRA(定数)が必要です。
以上でES_MULTILINEスタイルのテキストボックスからフォーカスが移動するようになるはずです。
ただしDefDlgProcの副作用でクライアント領域背景がダイアログカラーで塗り潰されてしまいます。
WM_CTLCOLORDLGメッセージに対して塗り潰しに使う背景ブラシを指定するかWM_ERASEBKGNDメッセージを処理してください。

複数行テキストボックスの改行はデフォルトではCtrl+RETURNです。RETURNキーはデフォルトボタンを押す操作になります。
RETURNキーだけで改行できるようにするにはES_WANTRETURNスタイルを指定してください。

Re: 【WINAPI】ボタンをエンターキーで押下させたい

Posted: 2011年1月13日(木) 22:30
by chemo
自分の説明が悪かったですかね。
特定のコントロールにおいて IsDialogMessage で処理されたくない動作を指定する際に
WM_GETDLGCODE を使用するはずなんですよね。
ただし、WM_GETDLGCODE メッセージハンドラを実装するにはコントロールをサブクラス化
する必要がありますね。

Re: 【WINAPI】ボタンをエンターキーで押下させたい

Posted: 2011年1月13日(木) 23:09
by Ciel
ISLe さんが書きました:
Ciel さんが書きました:ですが、タブキーでのフォーカスの移動がエディットボックス→ボタンへ移動する時のみ、タブキーを押しても移動してくれません。
また、ウィンドウスタイルでES_MULTILINEを適用していても、エディットボックス内での複数行の入力が出来なくなってしまいます。
メッセージループにIsDialogMessageをかませるだけでは実装が不十分でした。
ウインドウプロシージャの最後でDefWindowProcの代わりにDefDlgProcを呼び出すようにしてください。
またウインドウクラスの登録時、WNDCLASS::cbWndExtraにDLGWINDOWEXTRA(定数)が必要です。
以上でES_MULTILINEスタイルのテキストボックスからフォーカスが移動するようになるはずです。
ただしDefDlgProcの副作用でクライアント領域背景がダイアログカラーで塗り潰されてしまいます。
WM_CTLCOLORDLGメッセージに対して塗り潰しに使う背景ブラシを指定するかWM_ERASEBKGNDメッセージを処理してください。

複数行テキストボックスの改行はデフォルトではCtrl+RETURNです。RETURNキーはデフォルトボタンを押す操作になります。
RETURNキーだけで改行できるようにするにはES_WANTRETURNスタイルを指定してください。
>>ISLeさん!本当にありがとうございます!
教えていただいた方法でやってみたところ、タブキーでの移動、テキストボックス内での改行の処理も、ボタンのエンターキーでの押下も
問題なく処理できました!
ここまでは完璧だったんですが、なぜか右上の×で終了しようとすると、なぜか終了してくれませんでした。
今までは右上の×を押すだけで、恐らくDefWindowProcがウィンドウの終了処理をしてくれたんだと思うのですが、
DefDlgProcではウィンドウの終了処理をしてくれないのでしょうか?

右上の×を押すとメッセージ的には、WM_CLOSEは発生しているようなのですが、
どうもDefDlgProcがちゃんと終了処理してくれないようです。
しかも、その後にWM_COMMANDが発生して、WPARAMの下位ワードに2が入っていたため、
あるボタンの処理が実行されてしまうというおかしな状況が発生しました(笑)

なので、WM_CLOSEを直接処理するようにし、そこでDestroyWindowを実行して対処しています。
これぐらいなら、問題ない範囲なのでまあよいのですが、もし原因を知っていたら返答お願いします。
知らなければもうこのままでいくので大丈夫です^^
chemo さんが書きました: 自分の説明が悪かったですかね。
特定のコントロールにおいて IsDialogMessage で処理されたくない動作を指定する際に
WM_GETDLGCODE を使用するはずなんですよね。
ただし、WM_GETDLGCODE メッセージハンドラを実装するにはコントロールをサブクラス化
する必要がありますね。
chemoさん、ありがとうございます。
再度説明していただいて理解しました。
ですが、サブクラス化が必要となると、現状とあまり変わらない状況のままなので、
今回はISLeさんに教えていただいた方法でいこうと思います。
今後のプログラミングの参考にさせていただきます^^

一応、やりたいことはできたので解決にしときます。

Re: 【WINAPI】ボタンをエンターキーで押下させたい

Posted: 2011年1月13日(木) 23:29
by chemo
Ciel さんが書きました: ここまでは完璧だったんですが、なぜか右上の×で終了しようとすると、なぜか終了してくれませんでした。
今までは右上の×を押すだけで、恐らくDefWindowProcがウィンドウの終了処理をしてくれたんだと思うのですが、
DefDlgProcではウィンドウの終了処理をしてくれないのでしょうか?

右上の×を押すとメッセージ的には、WM_CLOSEは発生しているようなのですが、
どうもDefDlgProcがちゃんと終了処理してくれないようです。
しかも、その後にWM_COMMANDが発生して、WPARAMの下位ワードに2が入っていたため、
あるボタンの処理が実行されてしまうというおかしな状況が発生しました(笑)

なので、WM_CLOSEを直接処理するようにし、そこでDestroyWindowを実行して対処しています。
これぐらいなら、問題ない範囲なのでまあよいのですが、もし原因を知っていたら返答お願いします。
知らなければもうこのままでいくので大丈夫です^^
モーダルダイアログですか?
だとすると EndDialog 関数で終了する必要がありますよ。

Re: 【WINAPI】ボタンをエンターキーで押下させたい

Posted: 2011年1月14日(金) 01:40
by ISLe
Ciel さんが書きました:右上の×を押すとメッセージ的には、WM_CLOSEは発生しているようなのですが、
どうもDefDlgProcがちゃんと終了処理してくれないようです。
しかも、その後にWM_COMMANDが発生して、WPARAMの下位ワードに2が入っていたため、
あるボタンの処理が実行されてしまうというおかしな状況が発生しました(笑)
失念してました。
WM_COMMANDのWPARAMの下位ワードが2というのはキャンセルボタンが押されたというイベントです。
ダイアログは既定でOKのボタンIDが1、キャンセルのボタンIDが2となっていて、ウインドウを閉じようとしたのをキャンセルと見なしキャンセルボタンを押したのと同様の流れに持っていくための仕様です。
ユーザー定義のボタンIDは1000番台くらいの大きい数値を使うのが良いと思います。
Ciel さんが書きました: なので、WM_CLOSEを直接処理するようにし、そこでDestroyWindowを実行して対処しています。
これぐらいなら、問題ない範囲なのでまあよいのですが、もし原因を知っていたら返答お願いします。
知らなければもうこのままでいくので大丈夫です^^
その対処方法で良いと思います。
DefDlgProcはOKとキャンセルのボタンがあるつもりで動作するので↑に書いた『キャンセルボタンが押された』イベントに終了処理を書くというのでも良いかもしれません。

Re: 【WINAPI】ボタンをエンターキーで押下させたい

Posted: 2011年1月14日(金) 07:33
by Ciel
chemo さんが書きました: モーダルダイアログですか?
だとすると EndDialog 関数で終了する必要がありますよ。
chemoさんありがとうございます。ちょっと調べておきますね^^
ISLe さんが書きました: 失念してました。
WM_COMMANDのWPARAMの下位ワードが2というのはキャンセルボタンが押されたというイベントです。
ダイアログは既定でOKのボタンIDが1、キャンセルのボタンIDが2となっていて、ウインドウを閉じようとしたのをキャンセルと見なしキャンセルボタンを押したのと同様の流れに持っていくための仕様です。
ユーザー定義のボタンIDは1000番台くらいの大きい数値を使うのが良いと思います。
その対処方法で良いと思います。
DefDlgProcはOKとキャンセルのボタンがあるつもりで動作するので↑に書いた『キャンセルボタンが押された』イベントに終了処理を書くというのでも良いかもしれません。
ISLeさん、ありがとうございます!
そういうことだったんですね!
原因もはっきりわかり、完璧に理解できました!
ボタンIDは大きい数値を使うようにしておきます。

これにて完全解決と致します。
回答していただいた皆さん、本当にありがとうございました^^

Re: 【WINAPI】ボタンをエンターキーで押下させたい

Posted: 2011年1月14日(金) 17:09
by ISLe
CreateDialog系APIで作成したモードレスダイアログはDestroyWindowで破棄します。
本件のようなCreateWindowで作成したモードレスダイアログっぽいウインドウも↑と同じです。

DialogBox系APIで作成したモーダルダイアログはEndDialogで破棄します。

Re: 【WINAPI】ボタンをエンターキーで押下させたい

Posted: 2011年1月14日(金) 18:57
by Ciel
むぅ。私がどちらで破棄しようか迷っていたところまで見抜いて回答してくださるとは・・・!
なんとなくDestroyWindowでいいかなと思ってはいましたが、これで確信に変わりました。

ISLeさん、ありがとうございました!