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

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

ただいまの
回答率

90.32%

  • VBA

    1906questions

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

  • Excel

    1637questions

    Excelは、マイクロソフト社が開発しているデータ集計や分析を行う表計算ソフトの一つです。文書作成や表計算、資料作成などの多彩な機能を備えており、統合パッケージであるMicrosoft Officeに含まれています。

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

解決済

回答 3

投稿

  • 評価
  • クリップ 0
  • VIEW 1,230

dewdtm

score 2

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

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

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

checkベストアンサー

+3

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

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

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

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

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/07/12 09:18

    ttyp03様

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

    キャンセル

  • 2018/07/12 09:28

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

    キャンセル

  • 2018/07/12 17: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

    キャンセル

  • 2018/07/12 18: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
    テキスト送信

    キャンセル

  • 2018/07/13 09: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

    キャンセル

  • 2018/07/13 09:16 編集

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

    キャンセル

  • 2018/07/13 09:21

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

    キャンセル

  • 2018/07/13 09:26

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

    キャンセル

  • 2018/07/13 09:28

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

    キャンセル

  • 2018/07/13 09:30

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

    キャンセル

  • 2018/07/13 09:39

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

    キャンセル

  • 2018/07/13 09:58

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

    キャンセル

  • 2018/07/13 10:00

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

    キャンセル

  • 2018/07/13 10: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を早めに抜けてしまっている可能性が高いです。

    またしてもフラフラした分かりにくい説明になってしまいましたが、
    結果として上記のような判定になっております。

    キャンセル

  • 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

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

    キャンセル

  • 2018/07/13 11: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)

    キャンセル

  • 2018/07/13 12:01

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

    キャンセル

  • 2018/07/13 13:05

    ttyp03様

    お手数をおかけしてしまい申し訳ございません。
    =だと逆の処理になってしまいますね。

    しかし訂正してみても、なぜか飛んでくれないようです。
    困りました。

    キャンセル

  • 2018/07/13 13:12

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

    キャンセル

  • 2018/07/13 13:27

    ttyp03様

    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

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

    キャンセル

  • 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

    ttyp03様

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

    キャンセル

  • 2018/07/13 13:54

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

    キャンセル

  • 2018/07/13 14:03

    ttyp03様

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

    キャンセル

  • 2018/07/13 14:12

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

    キャンセル

  • 2018/07/13 14:17

    ttyp03様

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

    キャンセル

  • 2018/07/13 15:52

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

    キャンセル

  • 2018/07/13 16:42

    ttyp03様

    現在ではttyp03様からいただいたコードを使わせていただいておりますので
    SendMessageAnyではなく、SendMessageにしております。

    それと、SendMessageが妙な挙動をしているのでしたら
    PostMessageを使ってみようと考えておりますが、あまり意味がないでしょうか。
    また、IsWindowVisibleを試しましたところ、結果は変わらずというところでした。

    キャンセル

  • 2018/07/13 16:56

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

    キャンセル

  • 2018/07/13 17:00

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

    キャンセル

  • 2018/07/13 17:18

    ttyp03様

    Sleep APIを使用してウエイトをかけてみましたところ、
    なぜかExcelフォームに飛びました。
    (入力フォームは既に立ち上がっていたにもかかわらず、Excelフォーム名がLoginIDに切り替わった)
    Sleepは2000でようやくタイミングが合いました。
    結構早くループを抜けています。

    このことから分かるのは、Loopのタイミング問題を解消しても
    一度目の実行でデータベースにデータを送信することは難しい、ということです。
    それから、はじめにsazi様が仰っていたプロセス起動の判定が必要かもしれませんので、そちらも考慮し、コーディングしてみようと思います。

    キャンセル

  • 2018/07/13 17:25

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

    キャンセル

  • 2018/07/17 09:28

    ttyp03様

    いつもお世話になります。
    一度目の起動時のハンドルを調べましたところ、やはりExcelフォームのハンドルを取得してループを抜けているようでした。
    (クラス名もタイトル名も全く異なるのに)
    Sleepをハンドル取得処理の前に配置すると、当然ですがきちんとデータベースのフォームを参照します。

    なんとかExcelフォームのハンドルに逃げないよう食い止めることができれば、この問題は解決すると思うのですが、
    なぜそうなるのか理屈が分かりません。

    キャンセル

  • 2018/07/17 09:58

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

    キャンセル

  • 2018/07/17 09:58

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

    キャンセル

  • 2018/07/17 10:54

    sazi様
    ttyp03様

    早速のご回答、ありがとうございます。
    状況はttyp03様がまとめてくださった内容で間違いありません。
    Excelフォームのタイトルは"UserForm1"で、クラス名も全く異なる"ThunderDFrame"となっています。

    データベース親ウィンドウのクラス名とタイトルは分かっておりますので、
    sazi様がご提案されたGetWindowTextを使用し、
    クラス名とタイトルを条件式にしてループをかければ
    うまくいくような気がします。

    キャンセル

  • 2018/07/17 11:06

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

    キャンセル

  • 2018/07/17 11:07

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

    キャンセル

  • 2018/07/17 11:14 編集

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

    キャンセル

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

    キャンセル

  • 2018/07/17 11:48

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

    キャンセル

  • 2018/07/17 12:57

    ttyp03様

    考えたら、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

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

    キャンセル

  • 2018/07/17 13:20

    ttyp03様

    そうでしたか。
    それでは、ギガファイル便はいかがでしょうか。
    https://19.gigafile.nu/0724-b4e2cdd065943bfc3266722388592d521
    これも無理でしたら、何か送信できるサービスをご紹介いただけると幸いです。

    キャンセル

  • 2018/07/17 13:22

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

    キャンセル

  • 2018/07/17 13:26 編集

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

    キャンセル

  • 2018/07/17 13: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

    キャンセル

  • 2018/07/17 13:30

    sazi様

    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

    ttyp03様

    開発環境は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

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

    キャンセル

  • 2018/07/17 15:54

    sazi様

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

    キャンセル

  • 2018/07/17 16:08

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

    キャンセル

  • 2018/07/17 16:47

    FindWindowについて、興味深い情報がいくつか出てきましたので
    一部を載せておきます。
    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

    sazi様
    ttyp03様

    様々な方向から考えてくださって本当にありがとうございます。
    sazi様が仰られたように、一度、コード冒頭にあるデータベースの起動処理を抜いて、Excelのみ実行させてみましたが、処理が固まっているような様子でしたので、
    データベースの代わりにメモ帳を起動させてみることにしました。

    Call Shell("C:\SCV8CL\ClientPack\ScMen.exe", vbNormalFocus)
    ↓   ↓   ↓   ↓   ↓   ↓
    Call Shell("notepad.exe", vbNormalFocus)

    すると、データがどこにも飛ばず、ハンドルが0のままずっとループを続けていたため
    その処理中に、データベースを通常のやり方で起動した途端、
    今度はLoginIDとLoginPssがメモ帳の見出し(タイトル名)と本文に送信されました。

    これは一体、どういうことなのでしょうか。

    キャンセル

  • 2018/07/17 17:18

    外部アプリの起動待ちは問題なくできている、しかしShellで起動直後にはFindWindowがあらぬ値を返してくる、ということですかね。



    ふと思ったのですが、FindWindowは正しく値を返していないでしょうか?
    そのあとのツリービューを構築する処理へ移行するのが速くて、子ウィンドウが生成される前の情報を見てしまっているとか。

    キャンセル

  • 2018/07/17 17: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

    これで本当に解決したのか不安です。

    キャンセル

  • 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

    ttyp03様

    やはり本解決ではないのですね。
    ノード展開は仰るように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様

    お久しぶりです、ttyp03様のおかげで
    日々、プログラムの質が向上しております。

    >こちらにも必要ですね、みたいな話をしたようなしていないような
    あ”っ! そうでした;;
    すみません! 以前にご指摘していただいたことを
    すっかり忘れておりました。大変失礼いたしました。
    早速、処理を追加してみたいと思います。
    ありがとうございました。

    キャンセル

+1

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

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

追記

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

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

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

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

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/07/11 16:33

    sazi様

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

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

    せっかくご回答いただいたのに私の力不足で内容の趣旨が理解できていないのですが、
    プロセスの起動まで待機したとしても、そこからログインフォームの表示が完了するまでに
    数秒のラグがあるため、どちらにしても早めにデータを送信してしまいます。

    的外れの回答でしたら、申し訳ございません。

    キャンセル

  • 2018/07/11 17:36

    追記しました。

    キャンセル

  • 2018/07/12 09:07

    sazi様

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

    キャンセル

+1

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

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

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

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

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

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/07/12 09:11

    hatena19様

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

    キャンセル

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

  • VBA

    1906questions

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

  • Excel

    1637questions

    Excelは、マイクロソフト社が開発しているデータ集計や分析を行う表計算ソフトの一つです。文書作成や表計算、資料作成などの多彩な機能を備えており、統合パッケージであるMicrosoft Officeに含まれています。