質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
86.12%
VBA

VBAはオブジェクト指向プログラミング言語のひとつで、マクロを作成によりExcelなどのOffice業務を自動化することができます。

解決済

ExcelVBA 外部アプリ→起動から入力可能になるまで待機する処理

dewdtm
dewdtm

総合スコア8

VBA

VBAはオブジェクト指向プログラミング言語のひとつで、マクロを作成によりExcelなどのOffice業務を自動化することができます。

3回答

0グッド

0クリップ

16681閲覧

投稿2018/07/11 06:53

いつもお世話になっております。

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ページの「注目」タブのフィードに表示されやすくなります。

気になる質問をクリップする

クリップした質問は、後からいつでもマイページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

下記のような質問は推奨されていません。

  • 間違っている
  • 質問になっていない投稿
  • スパムや攻撃的な表現を用いた投稿

適切な質問に修正を依頼しましょう。

回答3

3

ベストアンサー

アプリケーション自体の起動待ちは問題ないけど、そこから入力可能になるまでのラグがあるから待ちたいということですよね。
おそらく前回の質問からの継続ですよね。
送り先のテキストボックスのハンドルは取得できているはずですから、IsWindowEnabledでテキストボックスの状態を判定してみてはいかがでしょうか。
https://msdn.microsoft.com/ja-jp/library/cc410870.aspx

  1. 親ウィンドウのハンドル取得
  2. 子ウィンドウ(テキストボックス)のハンドル取得
  3. IsWindowEnabledで子ウィンドウの状態取得
  4. 戻り値が0の間3を繰り返す

投稿2018/07/11 23:37

ttyp03

総合スコア16988

sazi, TanakaHiroaki, dewdtm👍を押しています

良いと思った回答にはグッドを送りましょう。
グッドが多くついた回答ほどページの上位に表示されるので、他の人が素晴らしい回答を見つけやすくなります。

下記のような回答は推奨されていません。

  • 間違っている回答
  • 質問の回答になっていない投稿
  • スパムや攻撃的な表現を用いた投稿

このような回答には修正を依頼しましょう。

回答へのコメント

dewdtm

2018/07/12 00:18

ttyp03様 いつもお世話になっております。 前回に引き続き、ttyp03様にまたご教示いただけるとは、有難いことです。 しかも、まさにコレというような関数を示してくださり、大変感謝いたします。 早速コーディングしてみて、結果をこちらにご報告できればと思います。
sazi

2018/07/12 00:28

4.についてはタイムアウトの考慮も。
dewdtm

2018/07/12 08:52

ttyp03様 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
ttyp03

2018/07/12 09:14

ハンドルを毎回取得する必要はないのでDoの位置はIsWindowEnabledの前がよいでしょう。 また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 テキスト送信
dewdtm

2018/07/13 00:10

ttyp03様 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
ttyp03

2018/07/13 00:17 編集

ループの構成が昨日私がコメントしたものと違ってますね。 なのでstの初期化のタイミングが間違っているためタイムアウトが発生しません。 構成を簡単に書くとこうなります。 Do  親ハンドル判定   子ハンドル取得→ループを抜ける Loop st = Now Do  DoEvents  子ハンドル有効ならループを抜ける  タイムアウト発生ならループを抜ける Loop タイムアウト発生ならメッセージ出力して抜ける テキスト送信
sazi

2018/07/13 00:21

IsWindowEnabledの有効という状態は入力が有効という状態です。 IsWindowVisibleで表示状態かどうかを調べてみてはどうでしょうか。
ttyp03

2018/07/13 00:26

saziさん 表示状態でも入力できなければテキスト送信しても意味ないのでIsWindowEnabledのほうがよいと思います。 まあどちらも実際に試していないので良いか悪いかは質問者さんに試してもらうしかないのですが。
sazi

2018/07/13 00:28

若しくはハンドルが見つかったら、EnableWindowで有効になるまで設定して値を送るとか。
ttyp03

2018/07/13 00:30

saziさん さすがにそれは…。 相手アプリは理由があって入力を受け付けていないのでしょうから、外部から勝手に状態を変えるのはよろしくないかと。
sazi

2018/07/13 00:39

「さすがにそれは・・・」は私も同意です。 ttyp03さんが指摘される前のロジックはタイムアウトしないだけなので、なんで有効にならないかを色々試したほうが良いかとは思えたので、先に進むために試して状態を確認するのはアリかと思いました。
sazi

2018/07/13 00:58

IsWindowEnabledは有効にはならないけど、IsWindowVisibleはOKでその状態で値を送って受け付けられる、ということならそれはそれでOKかなと思った次第。
ttyp03

2018/07/13 01:00

それはそれでOKですね。 どちらが有効な手段なのかは質問者さんの検証結果を待ちましょう。 どちらもダメな可能性もありますが…。
dewdtm

2018/07/13 01:44

ttyp03様 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を早めに抜けてしまっている可能性が高いです。 またしてもフラフラした分かりにくい説明になってしまいましたが、 結果として上記のような判定になっております。
ttyp03

2018/07/13 01: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)
ttyp03

2018/07/13 02:00

>また、このデータは、飛ぶ時と飛ばないときがあり、 ということはIsWindowEnabledで有効と判定されてループを抜けているということですね。 となるとIsWindowEnabledでは適していない可能性がありますね。
ttyp03

2018/07/13 02:16

すみません、先ほどのコード、vbNull だと判定できませんね。 0に置き換えてください。
dewdtm

2018/07/13 02:58

ttyp03様 度々的を射たご回答をくださり、ありがとうございます。 また、わざわざリファクタリングまでしていただき、大変恐縮に思います。 いただいたコードを有難く頂戴して走らせたのですが、なぜかデータがうまく飛ばず、宣言をループ外に持って行っても、送信できていないようでした。 新たにログインボタンのクリックイベントを追加したコードを載せておきます。 やはり仰るように、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)
ttyp03

2018/07/13 03:01

FindWindowの判定がhwndParent = 0になってます。 hwndParent <> 0 ですね。
dewdtm

2018/07/13 04:05

ttyp03様 お手数をおかけしてしまい申し訳ございません。 =だと逆の処理になってしまいますね。 しかし訂正してみても、なぜか飛んでくれないようです。 困りました。
ttyp03

2018/07/13 04:12

タイムアウトにはなっていなくて、SendMessageまでは動いているんですよね? SendMessageのタイミングでよいので、hWndChild1~3の値を確認してみてください。 想定した値が取れていますか?
dewdtm

2018/07/13 04:27

ttyp03様 hWndChild1~3のハンドルは取れております。 行ごとに処理を止めてしてみましたところ、 st = Now より下の処理まで行っていないことが分かりました。 (タイムアウトになっているわけではありません) タイムアウト以外のところでExit Subは見受けられず、 一体どこでEnd処理がかかっているのか特定できませんでした。
dewdtm

2018/07/13 04:31

すみません、語弊があるかもしれませんので 念のために補足いたします。 st = Now行より下とは、st = Now行も含みます。 st = Nowの処理も走っていません。
dewdtm

2018/07/13 04:33

何度も失礼します。 End 処理がかかっている訳ではなく Loopを抜け出せていないような気がします。
ttyp03

2018/07/13 04:35

では最初のループで親ウィンドウのハンドルが取得できていない(FindWindowの戻り値が0)もしくはTreeView2のノードに親ウィンドウのハンドルが登録されていないのどちらかになりますね。
dewdtm

2018/07/13 04:40

少し気になる点があるのですが、 st = Now の上にある Do Loopに、抜け出す処理が無いような気がします。 このままだと無限ループになってしまうので、 Loop While hWndChild3 = 0 と付け加えましたところ、 入力フォームを立ち上げた状態だとデータが飛ぶようになりました。 やはりloopを早く抜け出してしまうようです。
ttyp03

2018/07/13 04:45

あ、わかりました。 Exit For で抜けてるので、Do ループまで抜けないんですね。 Exit Do に変えてください。すみません。 あと、お気づきのようにExit Doに入らない限り無限ループしますので、こちらもタイムアウト対策は必要ですね。 敢えて書きませんでした気づかれたのでよかったです。
dewdtm

2018/07/13 04:51

ttyp03様 なるほど、よく見たら確かにExit Doが適切ですね。 原因を示してくださり、ありがとうございます。 ただ、やはり2回目(入力フォーム起動後)の実行でなければ、 うまい具合に動作しません。 このLoopを早めに抜ける問題を、何とか解消したいです。
ttyp03

2018/07/13 04:54

最初のループを早く抜けるのは仕方ないと思います。 ハンドルが取得できているのでしょうから。 問題は2つめのループですね。 IsWindowEnabledで0以外が返却されてしまっていますか?
dewdtm

2018/07/13 05:03

ttyp03様 面白いことが分かりました。 一度目(データベース起動時)の場合、処理は最後まできっちり走っている(すべての値を取得できている)ようですが、データがデータベース側ではなく、なぜかExcelフォームに飛んでいました。 といいますのも、ユーザーフォームのウィンドウ名が、UserForm1となっていたところに、LoginIDと表示されていることに気付いたためです。 しかし、目当てのデータベースが起動していないからと言って、全然違うクラス名・タイトル名のハンドルにデータを飛ばしてしまうとは、一体どういうことなのでしょうか。 謎で仕方ありません。
ttyp03

2018/07/13 05:12

FindWindow("WindowsForms10.Window.8.app.0.141b42a_r12_ad1", "Login") これが正しくデータベース側アプリを指しているなら、取得されるハンドルがExcel側ということはないと思うのですが。 SendMessageが変な動きをしているのでしょうかね。 こちらには同じ環境があるわけではないので確認できませんが、何か気づいたことがあればコメントします。
dewdtm

2018/07/13 05:17

ttyp03様 こんな見ず知らずの私にここまでお付き合いくださって、 それだけでも大変有難いことなのに、 まだご協力くださるとは、まったく頭が下がります。 もちろんこちらでも懸命に原因を探しますので 何か分かった時点でまたご報告させていただきたいと思います。
ttyp03

2018/07/13 06:52

少し気になったのですが、SendMessageAnyはSendMessageじゃダメなんでしょうか。 SendMessageAnyって調べてもマイクロソフトのページにすら載っていないので、相当古いAPIな気がします。 Any無しのSendMessageでもテキストボックスへは送信できるようなのでそちらを使ってみてはいかがでしょうか。
dewdtm

2018/07/13 07:42

ttyp03様 現在ではttyp03様からいただいたコードを使わせていただいておりますので SendMessageAnyではなく、SendMessageにしております。 それと、SendMessageが妙な挙動をしているのでしたら PostMessageを使ってみようと考えておりますが、あまり意味がないでしょうか。 また、IsWindowVisibleを試しましたところ、結果は変わらずというところでした。
ttyp03

2018/07/13 07:56

PostMessageが有効かどうかは判断つかないので、実際にやってみてどうかかなと思います。
ttyp03

2018/07/13 08:00

試しにSendMessageの前に数秒ウェイトを入れたらどうなるでしょうか。 外部から何かしようとしたら、あとはそれくらいしか手がないような気がします。
dewdtm

2018/07/13 08:18

ttyp03様 Sleep APIを使用してウエイトをかけてみましたところ、 なぜかExcelフォームに飛びました。 (入力フォームは既に立ち上がっていたにもかかわらず、Excelフォーム名がLoginIDに切り替わった) Sleepは2000でようやくタイミングが合いました。 結構早くループを抜けています。 このことから分かるのは、Loopのタイミング問題を解消しても 一度目の実行でデータベースにデータを送信することは難しい、ということです。 それから、はじめにsazi様が仰っていたプロセス起動の判定が必要かもしれませんので、そちらも考慮し、コーディングしてみようと思います。
ttyp03

2018/07/13 08:25

Excelに飛んだときのハンドルは、外部アプリのハンドルを指しているのでしょうか? あとプロセス起動の判定はFindWindowを繰り返し行っていることでできているのではと思います。
dewdtm

2018/07/17 00:28

ttyp03様 いつもお世話になります。 一度目の起動時のハンドルを調べましたところ、やはりExcelフォームのハンドルを取得してループを抜けているようでした。 (クラス名もタイトル名も全く異なるのに) Sleepをハンドル取得処理の前に配置すると、当然ですがきちんとデータベースのフォームを参照します。 なんとかExcelフォームのハンドルに逃げないよう食い止めることができれば、この問題は解決すると思うのですが、 なぜそうなるのか理屈が分かりません。
sazi

2018/07/17 00:58

GetWindowTextでアプリケーションタイトルを取得してskipするとかどうでしょう
ttyp03

2018/07/17 00:58

FindWindowには外部アプリのクラス名・タイトルを指定しているのに、0以外の値が返ってきて、しかもそれはExcelフォームのハンドルを指している、という状況で正しいでしょうか? ちなみにそのExcelフォームのタイトルは"Login"なんでしょうか?
dewdtm

2018/07/17 01:54

sazi様 ttyp03様 早速のご回答、ありがとうございます。 状況はttyp03様がまとめてくださった内容で間違いありません。 Excelフォームのタイトルは"UserForm1"で、クラス名も全く異なる"ThunderDFrame"となっています。 データベース親ウィンドウのクラス名とタイトルは分かっておりますので、 sazi様がご提案されたGetWindowTextを使用し、 クラス名とタイトルを条件式にしてループをかければ うまくいくような気がします。
ttyp03

2018/07/17 02:06

"Login"という同一ウィンドウが他になければGetWindowTextで対処できるかもしれません。 しかしFindWindowで正しく取得できないのが腑に落ちません。 GetWindowTextを使うのはうわべの対応に過ぎず、根本的な解決になっていないですよね。 なんとなくですが、APIの定義が間違っているとかないでしょうか。 引数の値が正しくAPIに伝わっていなくて(NULL扱いになって)、全情報を返しているような気がします。
sazi

2018/07/17 02:07

ttyp03さんの指摘にあるように、クラス名とウィンドウ名が意図したものを指定できていない可能性があります。 GetWindowTextで意図したものを識別して、そのクラス名とウィンドウ名を確認されてみたほうが良いと思います。
sazi

2018/07/17 03:03 編集

私の提案は、上手く行ってないなら取り敢えず、という場当たり的なもので、真っ当な処理で対応するのが筋ですので。
dewdtm

2018/07/17 02:37

ttyp03様 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以外にどこか提示してほしい箇所がありましたら、 お申し出いただけると幸いです。
ttyp03

2018/07/17 02:48

宣言自体には問題ないように思えます。 あとはShell~最初のループ終わりあたりまでのコードを見せてもらうとかですかね。
ttyp03

2018/07/17 04:15

ごめんなさい。うちの職場、Dropboxは規制されてて見れないのです。
ttyp03

2018/07/17 04:22

これもダメでした。 基本的にクラウドサービス的なものは全てアウトです。 短いコードだと思うので、コピペでお願いします。
sazi

2018/07/17 04:27 編集

API宣言が64bit互換対応(PtrSafe)してないですけど、officeは32bit版なんですか? LongPtr には変更しておいたほうが良さそうですけど。
dewdtm

2018/07/17 04:26

ttyo03様 承知しました。 足りるかどうか分かりませんが、下記のコードを載せておきます。 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
dewdtm

2018/07/17 04:30

sazi様 Officeは32bitです。 今回の処理がうまく通りましたら、APIにPtrSafeのif文をつけようと思っています。
ttyp03

2018/07/17 04:32

コードありがとうございます。 問題はなさそうですね。 やはりAPI呼び出しが上手くいってないのではと思えるのと、saziさん情報の64bitの影響があるような気がします。 OSが64bitであるなら、64bit用の宣言が必要なようです。 こちらのページを参考に宣言してみては。 https://qiita.com/RelaxTools/items/346d0d3b6e8c982015ab
dewdtm

2018/07/17 06:16

ttyp03様 開発環境はWindows7 32bit Office2010 32bitですので 64bitのAPI宣言PtrSafeやLongPtr LongLongなどの記述は不要かと思います。
ttyp03

2018/07/17 06:19

あ、そうなんですね。 とすると益々わからなくなりました・・・。
sazi

2018/07/17 06:52 編集

処理が正しいとするなら、クラス名とウィンドウ名が同じものが存在すると考えるのが妥当な気がします。 もしそうなら識別のためにGetWindowTextで識別を追加するのもありだと思うのですが、 ツールとエクセルのクラス名とウィンドウ名が同じものが存在しているかどうかは確認してもらうしかありません。
ttyp03

2018/07/17 06:42

saziさん いや、それが取得されるのはクラスもタイトルも異なるExcelのフォームらしいのですよ。
dewdtm

2018/07/17 06:54

sazi様 ttyp03様がご指摘してくださっているように OracleとExcelの親・子ウィンドウともに一致しているものはありません。 そもそも、クラス名がすべて異なりますので、 どう転がっても、本来であればExcelフォームを参照するはずがないのですが、まったくもって不思議なことです。
sazi

2018/07/17 07:08

でも、検索結果で返却されたのですよね。 エクセルのみを起動した状態で、指定したクラス名とウィンドウ名で返却されないかどうかは試されていますか? 有り得ないと思っているところが盲点だったりしますし。
dewdtm

2018/07/17 07:47

FindWindowについて、興味深い情報がいくつか出てきましたので 一部を載せておきます。 http://takeg.hatenadiary.jp/entry/2018/04/13/225630 FindWindowは、そもそも起動していないアプリの処理を待ってくれないようで、Sleepと組み合わせて使用することが定石など、非常に不安定なものだという記事がいくつかありました。 そのせいで、アクティブになっているExcelフォームにデータが逃げてしまっているのではないかと考えております。
ttyp03

2018/07/17 08:01

リンク先見ました。 やってることは一緒ですね。 FindWindowで0以外が返るまで繰り返し見るということですね。 ただ記事では取れない(=0が返る)という話ですが、今回起きているのは異なるウィンドウの情報が返されるという点で違った現象だと思います。 そのような事例がないか調べてみたのですが、そういうのは今のところ見つかっていません。
dewdtm

2018/07/17 08:10

sazi様 ttyp03様 様々な方向から考えてくださって本当にありがとうございます。 sazi様が仰られたように、一度、コード冒頭にあるデータベースの起動処理を抜いて、Excelのみ実行させてみましたが、処理が固まっているような様子でしたので、 データベースの代わりにメモ帳を起動させてみることにしました。 Call Shell("C:\SCV8CL\ClientPack\ScMen.exe", vbNormalFocus) ↓   ↓   ↓   ↓   ↓   ↓ Call Shell("notepad.exe", vbNormalFocus) すると、データがどこにも飛ばず、ハンドルが0のままずっとループを続けていたため その処理中に、データベースを通常のやり方で起動した途端、 今度はLoginIDとLoginPssがメモ帳の見出し(タイトル名)と本文に送信されました。 これは一体、どういうことなのでしょうか。
ttyp03

2018/07/17 08:18

外部アプリの起動待ちは問題なくできている、しかしShellで起動直後にはFindWindowがあらぬ値を返してくる、ということですかね。 ・ ・ ・ ふと思ったのですが、FindWindowは正しく値を返していないでしょうか? そのあとのツリービューを構築する処理へ移行するのが速くて、子ウィンドウが生成される前の情報を見てしまっているとか。
dewdtm

2018/07/17 08:44

ttyp03様 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 これで本当に解決したのか不安です。
dewdtm

2018/07/17 08:47

すみません、外にも内にも書いておかないと正常に走りませんでした。 Do hwndParent = FindWindow("WindowsForms10.Window.8.app.0.141b42a_r12_ad1", "Login") Call ノード展開_Click If hwndParent <> 0 Then Call ノード展開_Click
ttyp03

2018/07/17 08:51

えっとこれは本解決ではないですね。 そもそもノード展開_Click処理を正しく把握していないのですが、この処理を呼び出すと現在の全ウィンドウを列挙したツリービューが構築されるということでよろしいでしょうか。 だとするとやはりFindWindowは正しく値を返していると思われますね。 逆に呼び出すたびに差分を補完していくような作りなら何回も呼び出さないといけなくなり理屈としては正しいような気もします。 とりあえずはやはりツリービューの構築に問題があるような気がします。 ノード展開_Clickを呼び出す前に数秒ウェイトするなどしてみてはどうでしょうか。
dewdtm

2018/07/17 09:07

ttyp03様 やはり本解決ではないのですね。 ノード展開は仰るようにTreeViewのリストを更新するプロシージャです。 この更新をループ時に回さないと、データベースのウインドウハンドルが起動したことを判断できないためでしょうけど、 以前もそうしていたような気がしないでもないです。 とにかく現状はうまく走っておりますので、 ここまで無知極まりない私にお付き合いくださったttyp03様と sazi様には、心より厚くお礼申し上げます。 今回はこれにて閉じさせていただきますが、 また何か進展がありましたら、新たにご質問させていただくかもしれません。 その際も何とぞご教示の程、宜しくお願いいたします。 追伸: sazi様には大変申し訳ないのですが、 今回はttyp03様をベストアンサーに選ばせていただこうと思います。 御二方ともお選びしたい気持ちでいっぱいですが、どうかご容赦ください。
sazi

2018/07/17 09:13

いや、私は合いの手入れていただけなので大丈夫ですよ。
ttyp03

2018/07/17 09:16

本解決ではないと思っていますが、ひとまず動いているということで了解しました。 久々の難問でなかなか楽しかったです。 saziさんも合いの手ありがとうございました。
dewdtm

2018/08/02 07: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処理の配置が間違っているような気がするのですが、 お分かりになりますでしょうか。 後追いで申し訳ございません。
ttyp03

2018/08/02 08:00

どうも、お久しぶりです。 問題はなさそうですが、もしかしたら最初のループの方で無限ループしているとかいう話でしょうか。 だとすればタイムアウトの処理は入っていないので当然そうなります。 こちらにも必要ですね、みたいな話をしたようなしていないような・・・。
dewdtm

2018/08/02 08:06

ttyp03様 お久しぶりです、ttyp03様のおかげで 日々、プログラムの質が向上しております。 >こちらにも必要ですね、みたいな話をしたようなしていないような あ”っ! そうでした;; すみません! 以前にご指摘していただいたことを すっかり忘れておりました。大変失礼いたしました。 早速、処理を追加してみたいと思います。 ありがとうございました。

1

プロセスの起動まで待機したとしても、そこからログインフォームの表示が完了するまでに
数秒のラグがあるため、どちらにしても早めにデータを送信してしまいます。

まず、事前に、ログインフォームのクラス名とウィンドウ名をツールを使って調べておきます。
APIのFindWindow関数でクラス名、ウィンドウ名を引数に、ウィンドウハンドルを取得する。開いてないと失敗するので、成功するまで、ループする。
ウィンドウハンドルが取得できたら、開いているということになります。

自作あぷりからAPIで他のあぷりをいじるときのめも。(1/4) - ×××Diary

投稿2018/07/11 08:01

hatena19

総合スコア32001

dewdtm👍を押しています

良いと思った回答にはグッドを送りましょう。
グッドが多くついた回答ほどページの上位に表示されるので、他の人が素晴らしい回答を見つけやすくなります。

下記のような回答は推奨されていません。

  • 間違っている回答
  • 質問の回答になっていない投稿
  • スパムや攻撃的な表現を用いた投稿

このような回答には修正を依頼しましょう。

回答へのコメント

dewdtm

2018/07/12 00:11

hatena19様 お忙しい中ご回答いただきまして、感謝申し上げます。 ハンドルの状態で入力可否を判断する方法は頭に浮かびませんでした。 とても有用な情報をくださり、ありがとうございます。 早速、試してみまして、このページ内に結果をご報告できればと思います。

1

プロセスの一覧でターゲットのアプリケーションが起動しているかどうかを確認する方法があります。
以下参考。
実行中のタスク一覧(非API)
※上記はwordを利用しています。APIでプロセス一覧取得するほうが筋だとは思いますが。

外部アプリケーション(Oracle系データベースソフト)の起動は誰がおこなっているのでしょうか?
もし、そのアプリケーションがパラメータとしてログイン情報を受け付けるのなら、VBAで起動して完了したら次の処理というような事も可能かと思います。
追記

VBAで他のアプリケーションを同期起動する(WshShell)

投稿2018/07/11 07:09

編集2018/07/11 08:35
sazi

総合スコア24562

dewdtm👍を押しています

良いと思った回答にはグッドを送りましょう。
グッドが多くついた回答ほどページの上位に表示されるので、他の人が素晴らしい回答を見つけやすくなります。

下記のような回答は推奨されていません。

  • 間違っている回答
  • 質問の回答になっていない投稿
  • スパムや攻撃的な表現を用いた投稿

このような回答には修正を依頼しましょう。

回答へのコメント

dewdtm

2018/07/11 07:33

sazi様 ご回答ありがとうございます。 >外部アプリケーション(Oracle系データベースソフト)の起動は誰がおこなっているのでしょうか? >もし、そのアプリケーションがパラメータとしてログイン情報を受け付けるのなら、 >VBAで起動して完了したら次の処理というような事も可能かと思います。 せっかくご回答いただいたのに私の力不足で内容の趣旨が理解できていないのですが、 プロセスの起動まで待機したとしても、そこからログインフォームの表示が完了するまでに 数秒のラグがあるため、どちらにしても早めにデータを送信してしまいます。 的外れの回答でしたら、申し訳ございません。
sazi

2018/07/11 08:36

追記しました。
dewdtm

2018/07/12 00:07

sazi様 追記情報、ありがとうございました。 私もURL先に書かれているようなことは以前に試しましたが、 やはり起動する・しない以外の判定が必要でした。 他の方がお答えしていただいている子ハンドルの状態取得で対応したいと思います。 お忙しい中、ご回答いただきまして感謝申し上げます。

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
86.12%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問

同じタグがついた質問を見る

VBA

VBAはオブジェクト指向プログラミング言語のひとつで、マクロを作成によりExcelなどのOffice業務を自動化することができます。