DOBON.NET DOBON.NETプログラミング掲示板過去ログ

メモ帳を起動して、PostMessageで文字列を表示

環境/言語:[XP、.NET Framework2.0]
分類:[.NET]

いつも参考にさせていただいております。
私の検索方法が悪い為、過去レスを見つけられず、質問させていただきました。

VB.NET2005で作成したアプリから、メモ帳を起動して、文字列を表示させたくて、NETを検索して、下記のソースを作成しましたが、下記のソースですと、エラーも返らず、文字も表示されません><

<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
Private Shared Function PostMessage( _
ByVal hWnd As IntPtr, _
ByVal Msg As UInteger, _
ByVal wParam As IntPtr, _
ByVal lParam As IntPtr) As Boolean
End Function

<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
Private Shared Function FindWindowEx(ByVal parentHandle As IntPtr, _
ByVal childAfter As IntPtr, _
ByVal lclassName As String, _
ByVal windowTitle As String) As IntPtr
End Function

<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
Private Shared Function FindWindow( _
ByVal lpClassName As String, _
ByVal lpWindowName As String) As IntPtr
End Function

Const WM_CHAR = &H102

Privete Sub SetNotepad()
Dim lpszParentClass As String = "Notepad"
Dim lpszParentWindow As String = "無題 - メモ帳"
Dim lpszClass As String = "Edit"
Dim ParenthWnd As New IntPtr(0)
Dim hWnd As New IntPtr(0)
Dim procID As Integer
Dim newProc As Diagnostics.Process

newProc = Diagnostics.Process.Start("Notepad.exe")
procID = newProc.Id

ParenthWnd = FindWindow(lpszParentClass, lpszParentWindow)

If ParenthWnd.Equals(IntPtr.Zero) Then
 Debug.WriteLine("メモ帳を起動してください!")
Else
hWnd = FindWindowEx(ParenthWnd, hWnd, lpszClass, "")
If hWnd.Equals(IntPtr.Zero) Then
Debug.WriteLine("メモ帳を起動してください!")
Else
Debug.WriteLine("Notepad Window: " & ParenthWnd.ToString())
Debug.WriteLine("Edit Control: " & hWnd.ToString())
End If
End If

Dim strMY As String = "123"
For intI As Integer = 1 To Len(strMY)
PostMessage(hWnd, WM_CHAR, Mid(strMY, intI, 1), 0)
Next
End Sub

2日程考えたのですが、解決方法が思い浮かばず悩んでおります。
ご教授いただけると嬉しいです。
■No21332に返信(beniさんの記事)
> いつも参考にさせていただいております。
> 私の検索方法が悪い為、過去レスを見つけられず、質問させていただきました。
>
> VB.NET2005で作成したアプリから、メモ帳を起動して、文字列を表示させたくて、NETを検索して、下記のソースを作成しましたが、下記のソースですと、エラーも返らず、文字も表示されません><
>
> <DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
> Private Shared Function PostMessage( _
> ByVal hWnd As IntPtr, _
> ByVal Msg As UInteger, _
> ByVal wParam As IntPtr, _
> ByVal lParam As IntPtr) As Boolean
> End Function
>
> <DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
> Private Shared Function FindWindowEx(ByVal parentHandle As IntPtr, _
> ByVal childAfter As IntPtr, _
> ByVal lclassName As String, _
> ByVal windowTitle As String) As IntPtr
> End Function
>
> <DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
> Private Shared Function FindWindow( _
> ByVal lpClassName As String, _
> ByVal lpWindowName As String) As IntPtr
> End Function
>
> Const WM_CHAR = &H102
>
> Privete Sub SetNotepad()
> Dim lpszParentClass As String = "Notepad"
> Dim lpszParentWindow As String = "無題 - メモ帳"
> Dim lpszClass As String = "Edit"
> Dim ParenthWnd As New IntPtr(0)
> Dim hWnd As New IntPtr(0)
> Dim procID As Integer
> Dim newProc As Diagnostics.Process
>
> newProc = Diagnostics.Process.Start("Notepad.exe")
> procID = newProc.Id
>
> ParenthWnd = FindWindow(lpszParentClass, lpszParentWindow)
>
> If ParenthWnd.Equals(IntPtr.Zero) Then
>  Debug.WriteLine("メモ帳を起動してください!")
> Else
> hWnd = FindWindowEx(ParenthWnd, hWnd, lpszClass, "")
> If hWnd.Equals(IntPtr.Zero) Then
> Debug.WriteLine("メモ帳を起動してください!")
> Else
> Debug.WriteLine("Notepad Window: " & ParenthWnd.ToString())
> Debug.WriteLine("Edit Control: " & hWnd.ToString())
> End If
> End If
>
> Dim strMY As String = "123"
> For intI As Integer = 1 To Len(strMY)
> PostMessage(hWnd, WM_CHAR, Mid(strMY, intI, 1), 0)
> Next
> End Sub
>
> 2日程考えたのですが、解決方法が思い浮かばず悩んでおります。
> ご教授いただけると嬉しいです。

その要求は無理です。
ぽんさま、早々のアドバイスをありがとうございます。
■No21336に返信(ぽんさんの記事)
> その要求は無理です。

要求は無理ということは、私の記述が間違っているのでしょうか?
それとも、方法そのものが間違っているのでしょうか?
文字列をファイルに保存して、そのファイルを表示する方法は使用できないので、文字列を直接メモ帳に表示しないといけない為、NETを検索してSendMessage/PostMessageだと出来そうな感じでしたので試してみようかと思い、結果行き詰ってしまいました^^;

C#だと出来ました・・・という記事は他にあったのですが、VB.NETからそれを利用する方法も理解できずに悩んでおります。

よろしくお願いします。
取り敢えず、Process.Start してから WaitForInputIdle でメモ帳に入力可能になるまで待ってみるのはどうでしょう。
Hongliangさま、早々のアドバイスをありがとうございます。

■No21338に返信(Hongliangさんの記事)
> 取り敢えず、Process.Start してから WaitForInputIdle でメモ帳に入力可能になるまで待ってみるのはどうでしょう。

いただいたアドバイスを元に
newProc = Diagnostics.Process.Start("Notepad.exe")
procID = newProc.Id
の下に
newProc.WaitForInputIdle()
の一行を追加して実行してみましたが動きませんでした。
記述が間違っているのでしょうか?

申し訳ありません、よろしくお願いいたします。
あー、VB ならではの問題をスルーしてました。

> For intI As Integer = 1 To Len(strMY)
> PostMessage(hWnd, WM_CHAR, Mid(strMY, intI, 1), 0)
> Next

Mid(strMY, intI, 1) の返値は String 型です。
PostMessage の引数が IntPtr で定義されているため、暗黙に Long に変換されてから更に IntPtr に変換されて渡されます。
// 数字以外の文字列を PostMessage の第三引数に与えれば例外が出るはずです。
WM_CHAR の WPARAM に渡すのは飽くまで文字コードで無ければなりません。文字列から文字の文字コードに変換するには AscW 関数を使用します。
更に言うと、WM_CHAR の型が指定されていないとか(As UInteger)、PostMessage の第三・第四引数が IntPtr を渡していなかったり(New IntPtr に Integer を渡すだけです。また IntPtr.Zero も使用できます)、型にルーズだとバグの元ですよ。
Hongliangさま、早々のアドバイスをありがとうございます。
■No21341に返信(Hongliangさんの記事)
> あー、VB ならではの問題をスルーしてました。
>
> Mid(strMY, intI, 1) の返値は String 型です。
> PostMessage の引数が IntPtr で定義されているため、暗黙に Long に変換されてから更に IntPtr に変換されて渡されます。
> // 数字以外の文字列を PostMessage の第三引数に与えれば例外が出るはずです。
> WM_CHAR の WPARAM に渡すのは飽くまで文字コードで無ければなりません。文字列から文字の文字コードに変換するには AscW 関数を使用します。
> 更に言うと、WM_CHAR の型が指定されていないとか(As UInteger)、PostMessage の第三・第四引数が IntPtr を渡していなかったり(New IntPtr に Integer を渡すだけです。また IntPtr.Zero も使用できます)、型にルーズだとバグの元ですよ。

ご指摘いただきました通り、型変換に対してルーズでした。
IntPtrへの考えが足りませんでした。
> Const WM_CHAR = &H102→ Const WM_CHAR As UInteger = &H102
> PostMessage(hWnd, WM_CHAR, Mid(strMY, intI, 1), 0)
→PostMessage(hWnd, WM_CHAR, AscW(Mid(strMY, intI, 1)), 0)
に変換いたしましたら、望んでいたとおりに動作いたしました。

お忙しい折、ご教授くださりありがとうございました。
解決済み!
2008/01/26(Sat) 15:48:04 編集(投稿者)
2008/01/26(Sat) 15:44:05 編集(投稿者)

はじめまして、引っ込んだ(略)と申します。

 # 本掲示板に初めて投稿させていただきます。よろしくお願いいたします。
 # もし名前が不適切な場合は改名いたします。
 
  SendMessage/PostMessageは私も未知の領域なのですが、
 手元の文献で、関連する記述を見つけることができました。
 (FindWindow、SetForegroundWindowAPI呼び出しと、SendKeysクラスを使用して、
  メモ帳にキーストロークを送信するサンプルが掲載されています。)

  私のPCで試してみたところ、
 メモ帳を開いてキーストロークを送信することができました。
 ※もし既にご存知でしたら、私の投稿は無視してください。

  参考文献
   日経BPソフトプレス発行「プログラミングVisualBasic.NET Vol.2活用編」、pp. 113 - 115.

[追記]SendMessage/PostMessageのほうが処理が高速ですね。。。(汗 [ここまで追記]

 <以下コード> ↓念のための補足:参考文献のサンプルの単純コピペではありません。
Imports System.Runtime.InteropServices

Public Class SendKeysEx

<DllImport("user32")> _
Private Shared Function FindWindow(ByVal lpClassName As String, ByVal lpWindowName As String) As Integer
End Function
<DllImport("user32")> _
Private Shared Function SetForegroundWindow(ByVal hwnd As Integer) As Integer
End Function

Private Shared CrLf As String = Environment.NewLine

Public Shared Sub SendWait(ByVal appTitle As String, ByVal msg As String)
' 別のアプリケーションを探す
Dim hwnd As Integer = FindWindow(Nothing, appTitle)
' 見つからなかった場合は終了
If hwnd <= 0 Then
MessageBox.Show("アプリケーション(Windowタイトル)「" + appTitle + "」が見つかりません。" + CrLf + "キーストロークを送信できませんでした。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error)
Return
End If
'アプリケーションをアクティブにする
SetForegroundWindow(hwnd)

'キーストロークを送信して、送信先アプリケーションでキー処理が終わるのを待つ
SendKeys.SendWait(msg)

End Sub

End Class

Public Class Program
<STAThread()> _
Public Shared Sub Main()
Application.EnableVisualStyles()

Dim pr As Process = Process.Start("notepad.exe")
pr.WaitForInputIdle()
' ↓半角の「~」は改行文字、半角の「+」はSHIFTキーをあらわします。
SendKeysEx.SendWait("無題 - メモ帳", "何らかの文字列を送信~aaa~+a+A祝!送信達成記念~")

End Sub
End Class

以上です。
引っ込んだ(略)さま、アドバイスをありがとうございます。

■No21347に返信(引っ込んだ(略)さんの記事)
>   SendMessage/PostMessageは私も未知の領域なのですが、
>  手元の文献で、関連する記述を見つけることができました。
>  (FindWindow、SetForegroundWindowAPI呼び出しと、SendKeysクラスを使用して、
>   メモ帳にキーストロークを送信するサンプルが掲載されています。)
中略
> 以上です。

実はSendKeysは最初に試してみたのですが、私の記述と知識が足りず、メモ帳が複数起動し且つメッセージが複数(メモ帳の)ウィンドウに分割して表示されてしまいました。
おまけに文字列も2回繰替えされてしまうというとても恥ずかしい結果になってしまいました^^;

引っ込んだ(略)さまのアドバイスをそのまま実行させていただいた所、望んでいた動作が出来ました。
ですが、デバッグをステップ実行で行うとソースコードを記載したウィンドウに文字が送信されてしまったのですが、これは私のやり方が間違っているのでしょうか?

申し訳ありません、よろしくお願いいたします。
SendKeys はアクティブになっているウィンドウに対してキー入力が送信されます。ステップ実行中は通常デバッガがアクティブでしょうから、デバッガに送信されたわけです。
このように SendKeys は任意のウィンドウに対しては送信できないため、確実性に劣っています。お勧めできません。
Hongliangさま、早々のアドバイスをありがとうございます。
休日は私的事情でお返事が遅くなってしまいます。
お返事が遅くなりましたこと、お詫びいたします。

■No21350に返信(Hongliangさんの記事)
> SendKeys はアクティブになっているウィンドウに対してキー入力が送信されます。ステップ実行中は通常デバッガがアクティブでしょうから、デバッガに送信されたわけです。
> このように SendKeys は任意のウィンドウに対しては送信できないため、確実性に劣っています。お勧めできません。

そうだったのですね。
SendKeyに対する疑問が解決いたしました^^
お忙しい折、ご教授くださりありがとうございました。
解決済み!

DOBON.NET | プログラミング道 | プログラミング掲示板