SendInputで指定したウィンドウに仮想キーを送るには?

解決


takap  2004-12-10 05:05:42  No: 55618  IP: [192.*.*.*]

SendInputを使ってタスクトレイのアイコンから指定したウィンドウに
キーを送るプログラムで、行き詰っています。

・タスクトレイのアイコンをクリックすると、メモ帳のウィンドウハンドルを
  取得し、そこに「F1」のキーを送って、ヘルプを表示させたいのですが、
  キーを送るタイミングが悪いのか、SetForegroundWindow()の使い方が悪いのかで
  ヘルプが表示される場合と、表示されない場合があります。

・また、GetWindowThreadProcessId()、AttachThreadInput()をSetForegrondWindow()
  に併用してメモ帳のウィンドウを最前面に置いてみたりしましたが、
  うまくいきませんでした。

大変行き詰っておりますので、どうかご教授の程宜しくお願い致します。

コードは以下の通りです。

  case WM_TRAYICONMESSAGE:
    // 通知領域のアイコンに対して操作が行われた場合
    switch(lParam)
    {
    case WM_LBUTTONDOWN:

      HWND hWndID;
      INPUT inputKey[2];
      int cnt;

      hWndID = ::FindWindow("Notepad", NULL);
      for(cnt=0; cnt<2; cnt++){
        inputKey[cnt].type = INPUT_KEYBOARD;
        inputKey[cnt].ki.wVk = VK_F1;
        inputKey[cnt].ki.wScan = MapVirtualKey(VK_F1, 0);
        if(cnt == 0){
          inputKey[cnt].ki.dwFlags = 0;
        }
        else{
          inputKey[cnt].ki.dwFlags = KEYEVENTF_KEYUP;
        }
        inputKey[cnt].ki.dwExtraInfo = 0;
        inputKey[cnt].ki.time = 0;

        ::SetForegroundWindow(hWndID);

        SendInput(2, inputKey, sizeof(INPUT));
      }
      break;
  default:  // タスクバーが再作成されたらアイコンを再登録
    if(message == msgTaskBarRestart)
      AddNotifyIcon();
    break;

編集    削除
なーめ  2004-12-10 05:57:13  No: 55619  IP: [192.*.*.*]

WM_HELP を PostMessage() するのでは?
あとSleep() をいれてみると状況が変わることがありそう。
(相手アプリが動く間を与える意味で)
Spy++ で Notepad が受け取るメッセージを確認することもお忘れなく。

編集    削除
takap  2004-12-10 18:21:09  No: 55620  IP: [192.*.*.*]

なーめ様
ありがとうございます。

>WM_HELP を PostMessage() するのでは?
私の書き方がまずかったです。
今回、私が作ろうとしているのは、F1〜F12のキーをiniファイルの設定によって判断し、
別のウィンドウに送るというもので、ヘルプを表示させたいという訳ではないのです。

>あとSleep() をいれてみると状況が変わることがありそう。
確かに、一度試してみようと思います。
有難う御座います。

編集    削除
takap  2004-12-10 20:13:49  No: 55621  IP: [192.*.*.*]

なーめ様有難う御座います。

SetForegrounWindow()の後に、
Sleep(30)を入れてみたら、問題なく、想定どおりの動作を
してくれました。

やはりタイミングの問題であったみたいです。

しかし、Windows2000の環境ではSleep()を入れなくても
問題なく動作しています。
開発マシーンはWindowsXPなのですが、何か環境が影響しているのでしょうか?
それとも、DLL等のバージョンが異なっているのでしょうか?
ちなみに、INPUT構造体・SendInputを使用するために、
StdAfx.hの頭に次の記述をしています。

#define _WIN32_WINNT 0x0501
#define WINVER 0x0501

そこは疑問が残ります。。。

編集    削除
なーめ  2004-12-10 20:35:39  No: 55622  IP: [192.*.*.*]

最近、フリーセルが流行っているじゃない。
それでね、フリーセルのステージデータを
読み取ってプログラムで解を求めてやろうと思ったわけですよ。
フリーセルの内部を解析するのはリバースエンジニアリングなんで、
この手は使いません。
表に見えている、画面をキャプチャしてカードの並びを読み取る
ようにしたのですが、カードの重なっている部分にAがあると
色の区別はつくけどクローバとスペード、ハートとダイヤの
区別がつかないのです。で、あなたのようにフリーセルにメッセージ
を(この場合WM_RBUTTONDOWN/UP) 送って不明なカードだけめくって
キャプチャして、全配置を読み取っています。
指定番号のステージを読み取れるように、
メニューを開いて、番号入力まで、メッセージを送って実行できるように
してみました。
キー押下に関しては WM_KEYDOWN/UP だけでなく WM_CHAR も送らなければ
ならない場合がありました。
そのプログラムから抜粋しますね。
Win2K/VC++6.0/MFC

相手側アプリ(ここではフリーセル)の挙動はSpy++で調べました。
Win2K/Xpでは問題なく動作しています。
Win98 ではある程度繰り返すとフリーセル側がフリーズしてしまいます。
メッセージの受け取り方が違うと思われますが、原因は未確認です。
Win98 側でも Spy++ で調べなおさなければならないですね。

まあ、「やっつけ」程度で作成したプログラムで、
お作法なんて、どっかへ飛んでる部分もありますが、
参考まで。

//  WM_CHAR で使用する。lParam のコレクション(wParam=VK_0..VK9用)。
const DWORD g_dwNumKeyCodes[10] =
{
  0x00520001,0x004F0001,0x00500001,0x00510001,0x004B0001,
  0x004C0001,0x004D0001,0x00470001,0x00480001,0x00490001,
};

BOOL CCaptureDib::FreeCellSelectStage(int nID)
{
  BOOL bDone = FALSE;
  int wx,wy,nCurID;
  CWnd * pAppWnd,* pDlg,* pCtl;
  char szID[16];
  sprintf( szID, "%d", nID );

  //  オリジナルアプリを検索。
  if(( pAppWnd = FindFreeCellWindow( nCurID )) == NULL )
  {
    //  ここでオリジナルアプリを起動してもよい。
    return( FALSE );
  }
  if( nCurID == nID ) return( TRUE );
  CheckIconic( pAppWnd, TRUE );
  CheckTargetSize( pAppWnd, wx, wy );
  //  [ゲーム(G)][選択(S)] で発生するコマンドを送出。
  TRACE("MESSAGE SEND=[ゲーム(G)][選択(S)]\n");
  pAppWnd->PostMessage( WM_COMMAND, 0x00010067, 0x00000000 );

  //  確認ダイアログが立ち上がるまで待つ。
  Sleep(100);
  //  確認ダイアログを検索。
  if(( pDlg = FindFreeCellDialog( "フリーセル\tはい(&Y)\tいいえ(&N)\tこのゲームを終了しますか?" )) == NULL )
  {
    TRACE("フリーセル確認ダイアログが見つからない.\n");
    return( FALSE );
  }
  //  確認ダイアログに IDYES コマンドを送出。
  TRACE("MESSAGE SEND=WM_COMMAND/IDYES\n");
  pDlg->PostMessage( WM_COMMAND, 0x00000006, 0x002002EB );

  //  ゲーム番号の指定ダイアログが立ち上がるまで待つ。
  Sleep(100);
  //  ゲーム番号の指定ダイアログを検索。
  if(( pDlg = FindFreeCellDialog( "ゲーム番号の指定\tゲーム番号を選んでください。\t(1 から 32000 まで)\tOK" )) == NULL )
  {
    TRACE("ゲーム番号の指定ダイアログが見つからない.\n");
    return( FALSE );
  }

  //  ダイアログのエディットコントロールを取得する。
  //  0x00CB は Spy++ を用いて調査した。
  if(( pCtl = pDlg->GetDlgItem( 0x00CB )) == NULL )
  {
    TRACE("ゲーム番号の指定ダイアログにエディットコントロールがない。\n");
    return( FALSE );
  }

  //  エディットコントロールに数字を入力
  char * p;
  for( p = szID; *p; p ++ )
  {
    pCtl->PostMessage( WM_CHAR,  0x00000000+p[0], g_dwNumKeyCodes[p[0]-0x30] );
    Sleep(10);
  }

  //  ゲーム番号の指定ダイアログに<ENTER押下>を送出。
  TRACE("MESSAGE SEND=WM_KEYDOWN/VK_RETURN\n");
  pDlg->PostMessage( WM_KEYDOWN, VK_RETURN, 0xC11C0001 );

  //  ダイアログが閉じられるのを待つ。  
  Sleep(100);
  //  ステージが変更されたことを確認する。
  CWnd * pWndCheck;
  if(( pWndCheck = FindFreeCellWindow( nCurID )) == NULL )
  {
    TRACE( "FreeCell が落ちた?\n");
    return( FALSE );
  }
  //  指定ステージになっていることを確認。
  if( nCurID == nID )
  {
    bDone = TRUE;
  }
  //
  m_pWnd->SetActiveWindow();  //  自アプリへ戻す。
  CheckIconic( pAppWnd, FALSE );
  return( bDone );
}

//  所定のコントロールを所有したダイアログを検索。
//  
CWnd * CCaptureDib::FindFreeCellDialog( CString cs )
{
  CWnd * pDlg;
  if( pDlg = m_pWnd->GetDesktopWindow())
  {
    CString csT,csW;
    int pos = cs.Find( 0x09 );
    if( pos < 0 )
    {
      csT = cs;
      cs = "";
    }
    else
    {
      csT = cs.Left( pos );
      cs.Delete( 0,pos );
    }
    TRACE("対象: %s\n", csT );
    for( pDlg = pDlg->GetTopWindow(); pDlg; pDlg = pDlg->GetNextWindow( GW_HWNDNEXT ))
    {
      pDlg->GetWindowText( csW );
      TRACE("Window: %s\n", csW );
      if( csW == csT )
      {
        TRACE("Window: %s - コントロールを検索\n", csW );
        CWnd * pCtl;
        for( pCtl = pDlg->GetTopWindow(); pCtl; pCtl = pCtl->GetNextWindow( GW_HWNDNEXT ))
        {
          pCtl->GetWindowText( csW );
          TRACE("Window-Control: %s\n", csW );
          if(( pos = cs.Find( csW )) > 0 )
          {
            cs.Delete( pos,csW.GetLength());
          }
        }
        cs.Replace( "\t","" );
        TRACE("見つからなかった項目: %s\n", cs );
        if( cs == "" )
        {
          return( pDlg );
        }
      }
    }
  }
  return( NULL );
}

編集    削除
なーめ  2004-12-10 20:45:27  No: 55623  IP: [192.*.*.*]

>> しかし、Windows2000の環境ではSleep()を入れなくても
>> 問題なく動作しています。
結構マシンの早さや、マルチタスクの環境の違いで、
挙動が変わりますし、同じ相手アプリでも実はビルドが
違っていたりするかもしれません。
Win2k でWM_KEYDOWN/KEYUPが非対称でも
いい場合があるのに対して、WinME (前回誤記 Win98->WinME)
では対称でないとまずいらしいといったことがありますし。
やはり、OSごとに対称アプリの通常の操作を Spy++ で
調べることが肝要かと思います。

編集    削除
なーめ  2004-12-10 20:55:55  No: 55624  IP: [192.*.*.*]

あ、↑のプログラム、ステージセレクトの部分ね。
カードをめくる部分ではなかったです。

>> しかし、Windows2000の環境ではSleep()を入れなくても
私のプログラムは win2kで開発したものです。
Sleep(100)がはいっていますよね。
これは必要でいれたものです。
つまり、あなたの場合入れなくても動いたというのは
あなたの Win2kが非常に軽い状態にあると考えられるわけです。
私のマシンは IIS や SQL Server などいろいろ常駐していますので。

編集    削除
takap  2004-12-11 00:07:34  No: 55625  IP: [192.*.*.*]

なーめさん本当に有難う御座いました。
非常に参考になりました。
めでたく解決です。

今また、別の問題が起こっておりますが、
そちらも宜しければ御教授お願いいたします。

編集    削除