マウスホイールのイベントとCtrlキーの組み合わせ検出で誤動作

解決


ウォレス  2009-01-30 23:10:57  No: 33264

標記の件、
マウスホイールのイベント時に、ctrlキーが押下かどうかで処理を分けようとしています。
(Delphi IDEとかでもshift、ctrl、何も無し、ではエディタのマウスホイールスクロール動作が異なるように。)

ところがRadioGroupを置くと誤動作するようになります。
(ctrlキーを押していてもctrlキー無しと判定されるようになる)

対処法をご存じないでしょうか?

以下再現コードです。
RadioGroupあり、なしで動作が変わります。
(RadioGroupのItemIndexは-1以外でないと再現できません。)

procedure TForm1.FormMouseWheelDown(Sender: TObject; Shift: TShiftState;
  MousePos: TPoint; var Handled: Boolean);
begin

  if ssCtrl in shift then
    Label1.Caption := 'Ctrl + down'

  else
    Label1.Caption := ' down';

end;

procedure TForm1.FormMouseWheelUp(Sender: TObject; Shift: TShiftState;
  MousePos: TPoint; var Handled: Boolean);
begin
  if ssCtrl in shift then
    Label1.Caption := 'Ctrl + up'

  else
    Label1.Caption := ' up';

end;


どやさ  2009-01-30 23:46:07  No: 33265

フォームにパネルをはりつけて
その上にラジオボタンを貼り付ける
というのはどうでしょうか?


monaa  2009-01-30 23:52:17  No: 33266

原因はわかりませんがたぶん仕様ですよね。
GetAsyncKeyState(VK_CONTROL)とかGetKeyboardStateで調べればいいのでは?
VCLの問題なのかは調べてません。


monaa  2009-01-30 23:53:28  No: 33267

ちょっと試した感じでは、
RadioGroup特融ではなくRadioButton,CheckButton,Editで同じ動作を確認しました。


ウォレス  2009-01-31 00:31:00  No: 33268

早速の回答ありがとうございます。

どやささん、Panelの上にあるかどうかは関係ないみたいです。
monaaさん、
GetKeyboardStateでできました。ありがとございます。
http://www.swissdelphicenter.ch/torry/showcode.php?id=1002

var
  State: TKeyboardState;
begin
  GetKeyboardState(State);

  if ((State[VK_CONTROL] and 128) <> 0) then
  ・・・・


ofZ  2009-01-31 02:19:49  No: 33269

当方、Delphi5です。

バグなのか仕様なのかわかりませんが、
FormMouseWheelDownとFormMouseWheelUp で、以下の一行を追加してみてください。
Handled := True;

これで、'Ctrl + up'、'Ctrl + down' が表示されるでしょう。

で、どんなメッセージの流れになっているのか追ってみると・・・
(説明が通じるのかわかりませんが)

1)フォーカスのあるコントロールでWM_MOUSEWHEEL を受け取ります
  (TWinControl.WMMouseWheel)

2)受け取ったコントロールは、まず自分が乗っているフォームに
  WM_MOUSEWHEELを投げます(TWinControl.MouseWheelHandler)。
  このとき、CM_MOUSEWHEELを投げるために、ShiftState の変換を行います。
  TCMMouseWheel(Message).ShiftState := KeysToShiftState(Message.Keys);
  これで、Message.WParamが変更されます。

3)フォームは、フォーカスのあるコントロールにCM_MOUSEWHEELを送信します
  (TCustomForm.MouseWheelHandler)。
  WParam,LParamは、WM_MOUSEWHEELで受け取った値をそのまま使用。

4)CM_MOUSEWHEELを受け取ったコントロールは、OnMouseWheel〜イベントを
  発生させたりして、処理されたか判定(イベント引数のHandledね)
  Handled = True にならない場合、Parent に、CM_MOUSEWHEELを送ります。

5)ここでParentが無くなるか、Handled=Trueになるまで処理します。
  フォームのOnMouseWheel〜が発生するのは、ココ
  ここでは、正常にShiftStateが入ってきています。

6)以上が、フォーカスのあるコントロールでWM_MOUSEWHEELを受け取った時の、VCLの処理。
  ここまででHandled = True にしていない場合、TWinControl.DefaultHandler から、
  CallWindowProcを呼んで、OS側に処理をおまかせします。

7)OSは、メッセージ処理が終わっていないと判断して、(たぶん)Parentに
  向けてWM_MOUSEWHEELを投げます。
  (もしかしたら、最上位Parentのフォームにいきなり投げるのかも)

8)Parentは、1)からの処理を、同じように行いますが、ここで注意すべきなのが、
  2)で、WParamを書き換えている点。
  書き換えられたWParamを、CM_MOUSEWHEELを投げるために、再度書き換えを
  行うため、ShiftStateがおかしくなります。
  おかしくなったShiftStateで、OnMouseWheel〜イベントが発生するから、
  ShiftStateが正しくないのは当然。
  2回目(以降)のイベントで、誤った結果を表示し、それしか見えないから
  正しく入ってきていないと思ってしまう。

  TWinControl.WMMouseWheel が、以下のようになっていたら、大丈夫と思う。

  TCMMouseWheel(Message).ShiftState := KeysToShiftState(Message.Keys);
  MouseWheelHandler(TMessage(Message));
  if Message.Result = 0 then inherited;

  ↓

  TCMMouseWheel(Message).ShiftState := KeysToShiftState(Message.Keys);
  MouseWheelHandler(TMessage(Message));
  if Message.Result = 0 then 
  begin
    //逆変換(ShiftStateToKey)の関数は自作するしかないのかな?
    TCMMouseWheel(Message).ShiftState := ShiftStateToKey(Message.Keys);
    inherited;
  end;


ウォレス  2009-02-02 19:23:40  No: 33270

ofzさん、詳しく調べていただき有難うございました。

>で、どんなメッセージの流れになっているのか追ってみると・・・
>(説明が通じるのかわかりませんが)

・・・すみません。実はほとんど付いて行けません(汗

ただ、「Handled をtrueにするだけ」の方が副作用?が少なそうでした。
これまた理由は良く判りませんが
GetKeyboardState  で判定すると1スクロールで複数回(3回程度)イベントが発生します。
これはまたこれで、すごく困りました・・・

Handled をtrueにする方法だと1スクロール1イベントにきちんとなります。


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加