ExcelVBA 外部アプリ→起動から入力可能になるまで待機する処理
解決済
回答 3
投稿
- 評価
- クリップ 0
- VIEW 4,946
いつもお世話になっております。
ExcelVBAにおきまして、外部アプリケーション(Oracle系データベースソフト)の起動からログインまでを自動で処理させたいと思っております。
この文の最後に記述しているコードでは、
アイドル状態になるまで待機するという処理を組んでいるのですが、
起動後、入力フォームが表示されるまで読み込みによるラグがあり、
入力可能になる前にデータが送信されてしまいます。
現在、Sleepにより数秒間の待機時間を設けていますが、
PCのスペックによって読み込み時間が上下するため、ほぼ使い物になりません。
IEなどの場合は、入力可能まで待機させるようなAPIが用意されているようですが、
他の外部アプリでは、そういったAPIは存在しないのでしょうか?
また、存在しない場合、何か別の方法で実現させることは可能でしょうか?
ご教示の程、どうか宜しくお願いいたします。
Option Explicit
Private Declare Sub Sleep Lib "kernel32" (ByVal ms As Long)
' OpenProcess 関数
Private Declare Function OpenProcess Lib "KERNEL32.DLL" ( _
ByVal dwDesiredAccess As Long, _
ByVal bInheritHandle As Long, _
ByVal dwProcessId As Long _
) As Long
' CloseHandle 関数
Private Declare Function CloseHandle Lib "KERNEL32.DLL" ( _
ByVal hObject As Long _
) As Long
' WaitForInputIdle 関数
Private Declare Function WaitForInputIdle Lib "USER32.DLL" ( _
ByVal hProcess As Long, _
ByVal dwMilliseconds As Long _
) As Long
' 定数の定義
Private Const SYNCHRONIZE As Long = &H100000
Private Const INFINITE As Long = &HFFFF
' -------------------------------------------------------------------------------
' プロセスがアイドル状態になるまで指定したミリ秒数待機します。
'
' @Param lProcessId アイドル状態になるまで待機するプロセスの ID。
' @Param [lMilliseconds] アイドル状態になるまで待機する時間。(省略時は無限)
' -------------------------------------------------------------------------------
Public Sub WaitForInputIdleProcess(ByVal lProcessId As Long, Optional ByVal lMilliseconds As Long = INFINITE)
Dim hProcessHandle As Long
lProcessId = CLng(Shell("C:\SCV8CL\ClientPack\ScMen.exe", vbNormalFocus))
' Process を開いてハンドルを取得する
hProcessHandle = OpenProcess(SYNCHRONIZE, 0&, lProcessId)
If hProcessHandle <> 0 Then
' アイドル状態になるまで、指定したミリ秒数待機する
Call WaitForInputIdle(hProcessHandle, lMilliseconds)
' アイドル状態
Sleep 250
SendKeys "LoginID", True
SendKeys "{Enter}"
SendKeys "LoginPass", True
SendKeys "{Enter}"
SendKeys "{Enter}"
' Process のハンドルを閉じる
Call CloseHandle(hProcessHandle)
End If
End Sub
Sub test()
Dim lProcessId As Long
Call WaitForInputIdleProcess(lProcessId)
End Sub
-
気になる質問をクリップする
クリップした質問は、後からいつでもマイページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
クリップを取り消します
-
良い質問の評価を上げる
以下のような質問は評価を上げましょう
- 質問内容が明確
- 自分も答えを知りたい
- 質問者以外のユーザにも役立つ
評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。
質問の評価を上げたことを取り消します
-
評価を下げられる数の上限に達しました
評価を下げることができません
- 1日5回まで評価を下げられます
- 1日に1ユーザに対して2回まで評価を下げられます
質問の評価を下げる
teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。
- プログラミングに関係のない質問
- やってほしいことだけを記載した丸投げの質問
- 問題・課題が含まれていない質問
- 意図的に内容が抹消された質問
- 広告と受け取られるような投稿
評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。
質問の評価を下げたことを取り消します
この機能は開放されていません
評価を下げる条件を満たしてません
質問の評価を下げる機能の利用条件
この機能を利用するためには、以下の事項を行う必要があります。
- 質問回答など一定の行動
-
メールアドレスの認証
メールアドレスの認証
-
質問評価に関するヘルプページの閲覧
質問評価に関するヘルプページの閲覧
checkベストアンサー
+3
アプリケーション自体の起動待ちは問題ないけど、そこから入力可能になるまでのラグがあるから待ちたいということですよね。
おそらく前回の質問からの継続ですよね。
送り先のテキストボックスのハンドルは取得できているはずですから、IsWindowEnabledでテキストボックスの状態を判定してみてはいかがでしょうか。
https://msdn.microsoft.com/ja-jp/library/cc410870.aspx
- 親ウィンドウのハンドル取得
- 子ウィンドウ(テキストボックス)のハンドル取得
- IsWindowEnabledで子ウィンドウの状態取得
- 戻り値が0の間3を繰り返す
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
+1
プロセスの一覧でターゲットのアプリケーションが起動しているかどうかを確認する方法があります。
以下参考。
実行中のタスク一覧(非API)
※上記はwordを利用しています。APIでプロセス一覧取得するほうが筋だとは思いますが。
外部アプリケーション(Oracle系データベースソフト)の起動は誰がおこなっているのでしょうか?
もし、そのアプリケーションがパラメータとしてログイン情報を受け付けるのなら、VBAで起動して完了したら次の処理というような事も可能かと思います。
追記
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
+1
プロセスの起動まで待機したとしても、そこからログインフォームの表示が完了するまでに
数秒のラグがあるため、どちらにしても早めにデータを送信してしまいます。
まず、事前に、ログインフォームのクラス名とウィンドウ名をツールを使って調べておきます。
APIのFindWindow関数でクラス名、ウィンドウ名を引数に、ウィンドウハンドルを取得する。開いてないと失敗するので、成功するまで、ループする。
ウィンドウハンドルが取得できたら、開いているということになります。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
15分調べてもわからないことは、teratailで質問しよう!
- ただいまの回答率 89.97%
- 質問をまとめることで、思考を整理して素早く解決
- テンプレート機能で、簡単に質問をまとめられる
2018/07/12 09:18
いつもお世話になっております。
前回に引き続き、ttyp03様にまたご教示いただけるとは、有難いことです。
しかも、まさにコレというような関数を示してくださり、大変感謝いたします。
早速コーディングしてみて、結果をこちらにご報告できればと思います。
2018/07/12 09:28
2018/07/12 17:52
IsWindowEnabled関数を使用して、
子ハンドルが入力可能かどうかの判断はできるようになったのですが、
どうにもループ処理がうまく行きません。
下記のように実行させているのですが、ループ中にフリーズし、強制終了しなければならなくなります。
データベースは立ち上がっていて、フォームも入力可能なのに
retに1が代入されていないのか、ずっと処理を続けて無限ループ気味になり、フリーズします。
データベースを事前に立ち上げた状態で実行すると、きちんとretに1が代入されています。
何か間違っている部分があれば、ご指摘の程、宜しくお願いいたします。
Call Shell("Oracleデータベース_フルパス", vbNormalFocus)
hwndParent = FindWindow("親ウインドウハンドル_クラス名", "親ウインドウハンドル_タイトル名")
Do
ノード展開_Click
For idx = 1 To TreeView1.Nodes.Count
If CLng(TreeView1.Nodes(idx)) = hwndParent Then
hWndChild = TreeView1.Nodes(idx + 9) ' 上から9番目の子ハンドルを取得
Exit For
End If
Next
ret = IsWindowEnabled(hWndChild)
If ret = 1 Then
MsgBox "ウインドウは有効です。"
Exit Do
End If
Loop While ret = 0
2018/07/12 18:14
またIsWindowEnabledの戻り値は0か0以外なので、1で見るのはよろしくありません。
フリーズの原因とは異なりますが…。
フリーズ対策としては、ループ内にDoEventsを置く、saziさんも書いてますがタイムアウトを設ける。
この2点が必要かと思います。
ただしこれで解決するかはわかりません。
こんな感じにしてみてはいかがでしょうか。
下記サンプルは60秒でタイムアウトします。
Do
~子ハンドル取得~
Next
st = Now
Do
DoEvents
ret = IsWindowEnabled(hWndChild)
If DateDiff("s", st, Now) >= 60 Then Exit Do
Loop While ret = 0
If ret = 0 Then
Debug.Print "time out"
Exit Sub
End If
テキスト送信
2018/07/13 09:10
sazi様
いつもお世話になります。
ttyp03様とsazi様のおかげで無事フリーズを回避することができました、本当にありがとうございます。
ttyp03様のコードに
少しだけ訂正を加えた私の下記コードが間違えていると思うのですが、
子ハンドルが取得できる状態になってもループから抜け出せず、
また、タイムアウトイベントも発生してくれません(変数stはDateで宣言しております)。
どこが間違っているのか、昨晩、色々と処理位置を変えて試したものの
どうにも結果は変わらず、途方に暮れております。
もしも助言していただけることがありましたら、何とぞご教示の程、宜しくお願いいたします。
Do
ノード展開_Click
For idx = 1 To TreeView2.Nodes.Count
If CLng(TreeView2.Nodes(idx)) = hwndParent Then
hWndChild = TreeView2.Nodes(idx + 9) ' nはテキストボックスが何番目にあるかを表す
Exit For
End If
Next
st = Now
DoEvents
ret = IsWindowEnabled(hWndChild)
If DateDiff("s", st, Now) >= 10 Then Exit Do
Loop While ret = 0
If ret = 0 Then
Debug.Print "time out"
Exit Sub
End If
2018/07/13 09:16 編集
なのでstの初期化のタイミングが間違っているためタイムアウトが発生しません。
構成を簡単に書くとこうなります。
Do
親ハンドル判定
子ハンドル取得→ループを抜ける
Loop
st = Now
Do
DoEvents
子ハンドル有効ならループを抜ける
タイムアウト発生ならループを抜ける
Loop
タイムアウト発生ならメッセージ出力して抜ける
テキスト送信
2018/07/13 09:21
IsWindowVisibleで表示状態かどうかを調べてみてはどうでしょうか。
2018/07/13 09:26
表示状態でも入力できなければテキスト送信しても意味ないのでIsWindowEnabledのほうがよいと思います。
まあどちらも実際に試していないので良いか悪いかは質問者さんに試してもらうしかないのですが。
2018/07/13 09:28
2018/07/13 09:30
さすがにそれは…。
相手アプリは理由があって入力を受け付けていないのでしょうから、外部から勝手に状態を変えるのはよろしくないかと。
2018/07/13 09:39
ttyp03さんが指摘される前のロジックはタイムアウトしないだけなので、なんで有効にならないかを色々試したほうが良いかとは思えたので、先に進むために試して状態を確認するのはアリかと思いました。
2018/07/13 09:58
2018/07/13 10:00
どちらが有効な手段なのかは質問者さんの検証結果を待ちましょう。
どちらもダメな可能性もありますが…。
2018/07/13 10:44
sazi様
御二方共、早急にご回答いただきまして、ありがとうございます。
せっかくのご提示に水を差すようで恐縮なのですが、
IsWindowVisibleはひとまず保留にしておきまして、
現状のまま続けたいと思っております。
ttyp03様からのご指摘にあった一度目のDo Loopですが、
IsWindowEnabled(hWndChild)をそのあとに判定しているため、
条件式をどう設定してよいやら悩んでおります。
Loop While hWndChild = 0 にしても早めにループを抜けてしまってデータが飛びません。
何か良い条件式は有りますでしょうか。
と、ここまで書いた後で、データ送信のコードに少し変更を加えましたことろ、なぜかデータが飛びました。
hWndChild = 0
Do
hwndParent = FindWindow("WindowsForms10.Window.8.app.0.141b42a_r12_ad1", "Login")
ノード展開_Click
For idx = 1 To TreeView2.Nodes.Count
If CLng(TreeView2.Nodes(idx)) = hwndParent Then
hWndChild = TreeView2.Nodes(idx + 9) ' nはテキストボックスが何番目にあるかを表す
Exit For
End If
Next
Loop While hWndChild = 0
st = Now
Do
DoEvents
ret = IsWindowEnabled(hWndChild)
If DateDiff("s", st, Now) >= 10 Then Exit Do
Loop While ret = 0
If ret = 0 Then
Debug.Print "time out"
Exit Sub
End If
hwndParent = FindWindow("WindowsForms10.Window.8.app.0.141b42a_r12_ad1", "Login")
For idx = 1 To TreeView2.Nodes.Count
If CLng(TreeView2.Nodes(idx)) = hwndParent Then
hWndChild = TreeView2.Nodes(idx + 9) ' nはテキストボックスが何番目にあるかを表す
Exit For
End If
Next
lnghWnd = FindWindowEx(0, 0, "WindowsForms10.Window.8.app.0.141b42a_r12_ad1", "Login") '親ウィンドウハンドル
strDt = "LoginID"
lngRc = SendMessageAny(hWndChild, WM_SETTEXT, 0, strDt)
hwndParent = FindWindow("WindowsForms10.Window.8.app.0.141b42a_r12_ad1", "Login")
For idx = 1 To TreeView2.Nodes.Count
If CLng(TreeView2.Nodes(idx)) = hwndParent Then
hWndChild = TreeView2.Nodes(idx + 10) ' nはテキストボックスが何番目にあるかを表す
Exit For
End If
Next
lnghWnd = FindWindowEx(0, 0, "WindowsForms10.Window.8.app.0.141b42a_r12_ad1", "Login") '親ウィンドウハンドル
strDt = "LoginPass"
lngRc = SendMessageAny(hWndChild, WM_SETTEXT, 0, strDt)
ループ内と、ループ外で同じ宣言をしている下記の部分ですが、
これをループ外にも置かないとデータがなぜか飛ばないのです。
hwndParent = FindWindow("WindowsForms10.Window.8.app.0.141b42a_r12_ad1", "Login")
For idx = 1 To TreeView2.Nodes.Count
If CLng(TreeView2.Nodes(idx)) = hwndParent Then
hWndChild = TreeView2.Nodes(idx + 9) ' nはテキストボックスが何番目にあるかを表す
Exit For
End If
Next
また、このデータは、飛ぶ時と飛ばないときがあり、
どうやら入力フォーム表示までの時間差があるときはスルーしているようで、
やはり、最初のDo Loopを早めに抜けてしまっている可能性が高いです。
またしてもフラフラした分かりにくい説明になってしまいましたが、
結果として上記のような判定になっております。
2018/07/13 10:58
これでどうなりますかね。
hWndChild1 = 0
hWndChild2 = 0
Do
hwndParent = FindWindow("WindowsForms10.Window.8.app.0.141b42a_r12_ad1", "Login")
If hwndParent <> vbNull Then
ノード展開_Click
For idx = 1 To TreeView2.Nodes.Count
If CLng(TreeView2.Nodes(idx)) = hwndParent Then
hWndChild1 = TreeView2.Nodes(idx + 9) ' nはテキストボックスが何番目にあるかを表す
hWndChild2 = TreeView2.Nodes(idx + 10) ' nはテキストボックスが何番目にあるかを表す
Exit For
End If
Next
End If
DoEvents
Loop
st = Now
Do
DoEvents
ret = IsWindowEnabled(hWndChild1)
If DateDiff("s", st, Now) >= 10 Then Exit Do
Loop While ret = 0
If ret = 0 Then
Debug.Print "time out"
Exit Sub
End If
strDt = "LoginID"
lngRc = SendMessageAny(hWndChild1, WM_SETTEXT, 0, strDt)
strDt = "LoginPass"
lngRc = SendMessageAny(hWndChild2, WM_SETTEXT, 0, strDt)
2018/07/13 11:00
ということはIsWindowEnabledで有効と判定されてループを抜けているということですね。
となるとIsWindowEnabledでは適していない可能性がありますね。
2018/07/13 11:16
0に置き換えてください。
2018/07/13 11:58
度々的を射たご回答をくださり、ありがとうございます。
また、わざわざリファクタリングまでしていただき、大変恐縮に思います。
いただいたコードを有難く頂戴して走らせたのですが、なぜかデータがうまく飛ばず、宣言をループ外に持って行っても、送信できていないようでした。
新たにログインボタンのクリックイベントを追加したコードを載せておきます。
やはり仰るように、IsWindowEnabled以外の判定方法、
たとえばsazi様のご提案してくださったIsWindowVisibleを用いるなど、
行なってみようと思います。
hWndChild1 = 0
hWndChild2 = 0
hWndChild3 = 0
Do
hwndParent = FindWindow("WindowsForms10.Window.8.app.0.141b42a_r12_ad1", "Login")
If hwndParent = 0 Then
ノード展開_Click
For idx = 1 To TreeView2.Nodes.Count
If CLng(TreeView2.Nodes(idx)) = hwndParent Then
hWndChild1 = TreeView2.Nodes(idx + 9)
hWndChild2 = TreeView2.Nodes(idx + 10)
hWndChild3 = TreeView2.Nodes(idx + 11)
Exit For
End If
Next
End If
DoEvents
Loop
st = Now
Do
DoEvents
ret = IsWindowEnabled(hWndChild1)
If DateDiff("s", st, Now) >= 10 Then Exit Do
Loop While ret = 0
If ret = 0 Then
Debug.Print "time out"
Exit Sub
End If
strDt = "LoginID"
lngRc = SendMessageAny(hWndChild1, WM_SETTEXT, 0, strDt)
strDt = "LoginPass"
lngRc = SendMessageAny(hWndChild2, WM_SETTEXT, 0, strDt)
lngRc = SendMessage(hWndChild3, BM_CLICK, 0, 0)
2018/07/13 12:01
hwndParent <> 0 ですね。
2018/07/13 13:05
お手数をおかけしてしまい申し訳ございません。
=だと逆の処理になってしまいますね。
しかし訂正してみても、なぜか飛んでくれないようです。
困りました。
2018/07/13 13:12
SendMessageのタイミングでよいので、hWndChild1~3の値を確認してみてください。
想定した値が取れていますか?
2018/07/13 13:27
hWndChild1~3のハンドルは取れております。
行ごとに処理を止めてしてみましたところ、
st = Now より下の処理まで行っていないことが分かりました。
(タイムアウトになっているわけではありません)
タイムアウト以外のところでExit Subは見受けられず、
一体どこでEnd処理がかかっているのか特定できませんでした。
2018/07/13 13:31
念のために補足いたします。
st = Now行より下とは、st = Now行も含みます。
st = Nowの処理も走っていません。
2018/07/13 13:33
End 処理がかかっている訳ではなく
Loopを抜け出せていないような気がします。
2018/07/13 13:35
2018/07/13 13:40
st = Now の上にある Do Loopに、抜け出す処理が無いような気がします。
このままだと無限ループになってしまうので、
Loop While hWndChild3 = 0 と付け加えましたところ、
入力フォームを立ち上げた状態だとデータが飛ぶようになりました。
やはりloopを早く抜け出してしまうようです。
2018/07/13 13:45
Exit For で抜けてるので、Do ループまで抜けないんですね。
Exit Do に変えてください。すみません。
あと、お気づきのようにExit Doに入らない限り無限ループしますので、こちらもタイムアウト対策は必要ですね。
敢えて書きませんでした気づかれたのでよかったです。
2018/07/13 13:51
なるほど、よく見たら確かにExit Doが適切ですね。
原因を示してくださり、ありがとうございます。
ただ、やはり2回目(入力フォーム起動後)の実行でなければ、
うまい具合に動作しません。
このLoopを早めに抜ける問題を、何とか解消したいです。
2018/07/13 13:54
ハンドルが取得できているのでしょうから。
問題は2つめのループですね。
IsWindowEnabledで0以外が返却されてしまっていますか?
2018/07/13 14:03
面白いことが分かりました。
一度目(データベース起動時)の場合、処理は最後まできっちり走っている(すべての値を取得できている)ようですが、データがデータベース側ではなく、なぜかExcelフォームに飛んでいました。
といいますのも、ユーザーフォームのウィンドウ名が、UserForm1となっていたところに、LoginIDと表示されていることに気付いたためです。
しかし、目当てのデータベースが起動していないからと言って、全然違うクラス名・タイトル名のハンドルにデータを飛ばしてしまうとは、一体どういうことなのでしょうか。
謎で仕方ありません。
2018/07/13 14:12
これが正しくデータベース側アプリを指しているなら、取得されるハンドルがExcel側ということはないと思うのですが。
SendMessageが変な動きをしているのでしょうかね。
こちらには同じ環境があるわけではないので確認できませんが、何か気づいたことがあればコメントします。
2018/07/13 14:17
こんな見ず知らずの私にここまでお付き合いくださって、
それだけでも大変有難いことなのに、
まだご協力くださるとは、まったく頭が下がります。
もちろんこちらでも懸命に原因を探しますので
何か分かった時点でまたご報告させていただきたいと思います。
2018/07/13 15:52
SendMessageAnyって調べてもマイクロソフトのページにすら載っていないので、相当古いAPIな気がします。
Any無しのSendMessageでもテキストボックスへは送信できるようなのでそちらを使ってみてはいかがでしょうか。
2018/07/13 16:42
現在ではttyp03様からいただいたコードを使わせていただいておりますので
SendMessageAnyではなく、SendMessageにしております。
それと、SendMessageが妙な挙動をしているのでしたら
PostMessageを使ってみようと考えておりますが、あまり意味がないでしょうか。
また、IsWindowVisibleを試しましたところ、結果は変わらずというところでした。
2018/07/13 16:56
2018/07/13 17:00
外部から何かしようとしたら、あとはそれくらいしか手がないような気がします。
2018/07/13 17:18
Sleep APIを使用してウエイトをかけてみましたところ、
なぜかExcelフォームに飛びました。
(入力フォームは既に立ち上がっていたにもかかわらず、Excelフォーム名がLoginIDに切り替わった)
Sleepは2000でようやくタイミングが合いました。
結構早くループを抜けています。
このことから分かるのは、Loopのタイミング問題を解消しても
一度目の実行でデータベースにデータを送信することは難しい、ということです。
それから、はじめにsazi様が仰っていたプロセス起動の判定が必要かもしれませんので、そちらも考慮し、コーディングしてみようと思います。
2018/07/13 17:25
あとプロセス起動の判定はFindWindowを繰り返し行っていることでできているのではと思います。
2018/07/17 09:28
いつもお世話になります。
一度目の起動時のハンドルを調べましたところ、やはりExcelフォームのハンドルを取得してループを抜けているようでした。
(クラス名もタイトル名も全く異なるのに)
Sleepをハンドル取得処理の前に配置すると、当然ですがきちんとデータベースのフォームを参照します。
なんとかExcelフォームのハンドルに逃げないよう食い止めることができれば、この問題は解決すると思うのですが、
なぜそうなるのか理屈が分かりません。
2018/07/17 09:58
2018/07/17 09:58
ちなみにそのExcelフォームのタイトルは"Login"なんでしょうか?
2018/07/17 10:54
ttyp03様
早速のご回答、ありがとうございます。
状況はttyp03様がまとめてくださった内容で間違いありません。
Excelフォームのタイトルは"UserForm1"で、クラス名も全く異なる"ThunderDFrame"となっています。
データベース親ウィンドウのクラス名とタイトルは分かっておりますので、
sazi様がご提案されたGetWindowTextを使用し、
クラス名とタイトルを条件式にしてループをかければ
うまくいくような気がします。
2018/07/17 11:06
しかしFindWindowで正しく取得できないのが腑に落ちません。
GetWindowTextを使うのはうわべの対応に過ぎず、根本的な解決になっていないですよね。
なんとなくですが、APIの定義が間違っているとかないでしょうか。
引数の値が正しくAPIに伝わっていなくて(NULL扱いになって)、全情報を返しているような気がします。
2018/07/17 11:07
GetWindowTextで意図したものを識別して、そのクラス名とウィンドウ名を確認されてみたほうが良いと思います。
2018/07/17 11:14 編集
2018/07/17 11:37
sazi様
いつも迅速なご回答ありがとうございます。非常に助かります。
たしかにGetWindowTextでは抜本的な解決にはなりませんね。
考え得る手がなかったため、逃げていたところはあります。
APIの宣言は下記のように行っています。
Public Declare Function FindWindowEx Lib "USER32.DLL" Alias "FindWindowExA" _
(ByVal hwndParent As Long, ByVal hwndChildAfter As Long, _
ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Public Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As Long
API以外にどこか提示してほしい箇所がありましたら、
お申し出いただけると幸いです。
2018/07/17 11:48
あとはShell~最初のループ終わりあたりまでのコードを見せてもらうとかですかね。
2018/07/17 12:57
考えたら、Bookごとお渡しした方が早そうですね。
https://www.dropbox.com/s/2bc8cwuklyoeymj/%E3%83%8F%E3%83%B3%E3%83%89%E3%83%AB%E5%8F%96%E5%BE%97%E3%83%86%E3%82%B9%E3%83%88.xlsm?dl=0
ダウンロードが完了しましたら削除いたします。
2018/07/17 13:15
2018/07/17 13:20
そうでしたか。
それでは、ギガファイル便はいかがでしょうか。
https://19.gigafile.nu/0724-b4e2cdd065943bfc3266722388592d521
これも無理でしたら、何か送信できるサービスをご紹介いただけると幸いです。
2018/07/17 13:22
基本的にクラウドサービス的なものは全てアウトです。
短いコードだと思うので、コピペでお願いします。
2018/07/17 13:26 編集
LongPtr には変更しておいたほうが良さそうですけど。
2018/07/17 13:26
承知しました。
足りるかどうか分かりませんが、下記のコードを載せておきます。
Private Sub CommandButton3_Click()
Call Shell("C:\SCV8CL\ClientPack\ScMen.exe", vbNormalFocus)
Dim hwndParent As Long
Dim hwnddc As Long
Dim hWndChild1 As Long
Dim hWndChild2 As Long
Dim hWndChild3 As Long
Dim idx As Long
Dim hwnd As Variant
Dim lnghWnd As Long
Dim lnghWndTarget As Long
Dim lngRc As Long
Dim strDt As String
Dim ret As Long
Dim st As Date
hWndChild1 = 0
hWndChild2 = 0
hWndChild3 = 0
Do
hwndParent = FindWindow("WindowsForms10.Window.8.app.0.141b42a_r12_ad1", "Login")
If hwndParent <> 0 Then
Call ノード展開_Click
For idx = 1 To TreeView2.Nodes.Count
If CLng(TreeView2.Nodes(idx)) = hwndParent Then
hWndChild1 = TreeView2.Nodes(idx + 9)
hWndChild2 = TreeView2.Nodes(idx + 10)
hWndChild3 = TreeView2.Nodes(idx + 11)
Exit Do
End If
Next
End If
DoEvents
Loop
st = Now
Do
DoEvents
ret = IsWindowEnabled(hWndChild1)
If DateDiff("s", st, Now) >= 10 Then Exit Do
Loop While ret = 0
If ret = 0 Then
Debug.Print "time out"
Exit Sub
End If
strDt = "LoginID"
lngRc = SendMessage(hWndChild1, WM_SETTEXT, 0, strDt)
strDt = "LoginPass"
lngRc = SendMessage(hWndChild2, WM_SETTEXT, 0, strDt)
lngRc = SendMessage(hWndChild3, BM_CLICK, 0, 0)
End Sub
2018/07/17 13:30
Officeは32bitです。
今回の処理がうまく通りましたら、APIにPtrSafeのif文をつけようと思っています。
2018/07/17 13:32
問題はなさそうですね。
やはりAPI呼び出しが上手くいってないのではと思えるのと、saziさん情報の64bitの影響があるような気がします。
OSが64bitであるなら、64bit用の宣言が必要なようです。
こちらのページを参考に宣言してみては。
https://qiita.com/RelaxTools/items/346d0d3b6e8c982015ab
2018/07/17 15:16
開発環境はWindows7 32bit Office2010 32bitですので
64bitのAPI宣言PtrSafeやLongPtr LongLongなどの記述は不要かと思います。
2018/07/17 15:19
とすると益々わからなくなりました・・・。
2018/07/17 15:40 編集
もしそうなら識別のためにGetWindowTextで識別を追加するのもありだと思うのですが、
ツールとエクセルのクラス名とウィンドウ名が同じものが存在しているかどうかは確認してもらうしかありません。
2018/07/17 15:42
いや、それが取得されるのはクラスもタイトルも異なるExcelのフォームらしいのですよ。
2018/07/17 15:54
ttyp03様がご指摘してくださっているように
OracleとExcelの親・子ウィンドウともに一致しているものはありません。
そもそも、クラス名がすべて異なりますので、
どう転がっても、本来であればExcelフォームを参照するはずがないのですが、まったくもって不思議なことです。
2018/07/17 16:08
エクセルのみを起動した状態で、指定したクラス名とウィンドウ名で返却されないかどうかは試されていますか?
有り得ないと思っているところが盲点だったりしますし。
2018/07/17 16:47
一部を載せておきます。
http://takeg.hatenadiary.jp/entry/2018/04/13/225630
FindWindowは、そもそも起動していないアプリの処理を待ってくれないようで、Sleepと組み合わせて使用することが定石など、非常に不安定なものだという記事がいくつかありました。
そのせいで、アクティブになっているExcelフォームにデータが逃げてしまっているのではないかと考えております。
2018/07/17 17:01
やってることは一緒ですね。
FindWindowで0以外が返るまで繰り返し見るということですね。
ただ記事では取れない(=0が返る)という話ですが、今回起きているのは異なるウィンドウの情報が返されるという点で違った現象だと思います。
そのような事例がないか調べてみたのですが、そういうのは今のところ見つかっていません。
2018/07/17 17:10
ttyp03様
様々な方向から考えてくださって本当にありがとうございます。
sazi様が仰られたように、一度、コード冒頭にあるデータベースの起動処理を抜いて、Excelのみ実行させてみましたが、処理が固まっているような様子でしたので、
データベースの代わりにメモ帳を起動させてみることにしました。
Call Shell("C:\SCV8CL\ClientPack\ScMen.exe", vbNormalFocus)
↓ ↓ ↓ ↓ ↓ ↓
Call Shell("notepad.exe", vbNormalFocus)
すると、データがどこにも飛ばず、ハンドルが0のままずっとループを続けていたため
その処理中に、データベースを通常のやり方で起動した途端、
今度はLoginIDとLoginPssがメモ帳の見出し(タイトル名)と本文に送信されました。
これは一体、どういうことなのでしょうか。
2018/07/17 17:18
・
・
・
ふと思ったのですが、FindWindowは正しく値を返していないでしょうか?
そのあとのツリービューを構築する処理へ移行するのが速くて、子ウィンドウが生成される前の情報を見てしまっているとか。
2018/07/17 17:44
sazi様
たぶんですが、処理が通りましたので解決?したのではと思います。
PC立ち上げ時の遅延も考慮して、再起動直後に走らせるなど、わざとラグが出る状況で試しましたが、正常に走ることを確認しました。
Do
hwndParent = FindWindow("WindowsForms10.Window.8.app.0.141b42a_r12_ad1", "Login")
If hwndParent <> 0 Then
Call ノード展開_Click
上記の部分にある Call ノード展開_Click を
下記のように If hwndParent <> 0 Then の外に出してみたところ、
正常に動作しました。
Do
hwndParent = FindWindow("WindowsForms10.Window.8.app.0.141b42a_r12_ad1", "Login")
Call ノード展開_Click
If hwndParent <> 0 Then
これで本当に解決したのか不安です。
2018/07/17 17:47
Do
hwndParent = FindWindow("WindowsForms10.Window.8.app.0.141b42a_r12_ad1", "Login")
Call ノード展開_Click
If hwndParent <> 0 Then
Call ノード展開_Click
2018/07/17 17:51
そもそもノード展開_Click処理を正しく把握していないのですが、この処理を呼び出すと現在の全ウィンドウを列挙したツリービューが構築されるということでよろしいでしょうか。
だとするとやはりFindWindowは正しく値を返していると思われますね。
逆に呼び出すたびに差分を補完していくような作りなら何回も呼び出さないといけなくなり理屈としては正しいような気もします。
とりあえずはやはりツリービューの構築に問題があるような気がします。
ノード展開_Clickを呼び出す前に数秒ウェイトするなどしてみてはどうでしょうか。
2018/07/17 18:07
やはり本解決ではないのですね。
ノード展開は仰るようにTreeViewのリストを更新するプロシージャです。
この更新をループ時に回さないと、データベースのウインドウハンドルが起動したことを判断できないためでしょうけど、
以前もそうしていたような気がしないでもないです。
とにかく現状はうまく走っておりますので、
ここまで無知極まりない私にお付き合いくださったttyp03様と
sazi様には、心より厚くお礼申し上げます。
今回はこれにて閉じさせていただきますが、
また何か進展がありましたら、新たにご質問させていただくかもしれません。
その際も何とぞご教示の程、宜しくお願いいたします。
追伸:
sazi様には大変申し訳ないのですが、
今回はttyp03様をベストアンサーに選ばせていただこうと思います。
御二方ともお選びしたい気持ちでいっぱいですが、どうかご容赦ください。
2018/07/17 18:13
2018/07/17 18:16
久々の難問でなかなか楽しかったです。
saziさんも合いの手ありがとうございました。
2018/08/02 16:52
いただいたコードのタイムアウトイベントが作動しておりませんでした。
Do
hwndParent = FindWindow("WindowsForms10.Window.8.app.0.141b42a_r12_ad1", "Login")
If hwndParent <> 0 Then
Call ノード展開_Click
For idx = 1 To TreeView2.Nodes.Count
If CLng(TreeView2.Nodes(idx)) = hwndParent Then
hWndChild1 = TreeView2.Nodes(idx + 9)
hWndChild2 = TreeView2.Nodes(idx + 10)
hWndChild3 = TreeView2.Nodes(idx + 11)
Exit Do
End If
Next
End If
DoEvents
Loop
st = Now
Do
DoEvents
ret = IsWindowEnabled(hWndChild1)
If DateDiff("s", st, Now) >= 10 Then Exit Do
Loop While ret = 0
If ret = 0 Then
Debug.Print "time out"
Exit Sub
End If
どうもDo Loop処理の配置が間違っているような気がするのですが、
お分かりになりますでしょうか。
後追いで申し訳ございません。
2018/08/02 17:00
問題はなさそうですが、もしかしたら最初のループの方で無限ループしているとかいう話でしょうか。
だとすればタイムアウトの処理は入っていないので当然そうなります。
こちらにも必要ですね、みたいな話をしたようなしていないような・・・。
2018/08/02 17:06
お久しぶりです、ttyp03様のおかげで
日々、プログラムの質が向上しております。
>こちらにも必要ですね、みたいな話をしたようなしていないような
あ”っ! そうでした;;
すみません! 以前にご指摘していただいたことを
すっかり忘れておりました。大変失礼いたしました。
早速、処理を追加してみたいと思います。
ありがとうございました。