「これはヤバかったな」という遅刻エピソード

アドバイスをお願いします。
Excel-VBAで起動しているエクスプローラに対してハンドルを取得してクリックしたり、テキストボックスに文字をセットするプログラムを作って動かしています。
WindowsXPのときはできていたのですが、Windows7になったらエクスプローラが変わったため正しく動作しなくなりました。

やりたいのはエクスプローラに検索文字列(添付ファイル参照)をセットして検索をさせたいのです。
「windows7のエクスプローラをVBAで操作-1」「windows7のエクスプローラをVBAで操作-2」のタイトルで質問させていただき、下のコードを考えましたが、★のところでハンドルが返らず0でした。InspectObjectsで見ると「読み込み専用」と。これが原因ですか?どうしたらいいのだろう・・・。
またこのハンドルを取得して、AccessibleObjectFromWindowでIAccessible を取り出して、accDoDefaultActionしたいのですが、UUIDの値が分かりません。OLEViewで見たのですがExplorer関連がいくつかあり(添付ファイル参照)どれを使っていいものやら。ボタンクリックの後に文字列セットがあるのですが、まだたどり着けず。
IAccessible初心者です。
質問ばかりで済みませんがコーディングのアドバイスお願いします。

Option Explicit
Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As Any, ByVal lpWindowName As Any) As Long
Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" ( _
ByVal hwndParent As Long, _
ByVal hwndChildAfter As Long, _
ByVal lpClassName As String, _
ByVal lpWindowName As String) As Long
Private Declare Function AccessibleObjectFromWindow Lib "oleacc" _
(ByVal hWnd As Long, _
ByVal dwObjectID As Long, _
ByRef riid As Any, _
ByRef ppvObject As Any) As Long

Const WM_SETTEXT = &HC '文字列送信
Const BM_CLICK = &HF5 'クリック
Const OBJID_CLIENT As Integer = &HFFFFFFFC
Private Type UUID
Data1 As Long
Data2 As Integer
Data3 As Integer
Data4(7) As Byte
End Type
Private IID_IAccessible As UUID
Private oShell
Private ie
Private hWnd As Long
Private SearchBoxハンドル As Long
Private Search_Folder As String
Private RC As Long
'Private objAcc As IAccessible
Private objAcc As UUID
Private varChild As Variant
Private cnt
Private fullnames As String

Sub 検索指定のインプット()

Search_Folder = "c:\xxx"
Set oShell = CreateObject("Shell.Application")
For cnt = oShell.Windows().Count - 1 To 0 Step -1
' On Error Resume Next
Set ie = oShell.Windows().Item(cnt)
fullnames = ie.FullName
' On Error GoTo 0
If fullnames = "C:\Windows\Explorer.EXE" Then
ie.Navigate (Search_Folder)
hWnd = FindWindow("CabinetWClass", vbNullString)
hWnd = FindWindowEx(hWnd, 0, "WorkerW", vbNullString)
hWnd = FindWindowEx(hWnd, 0, "ReBarWindow32", vbNullString)
hWnd = FindWindowEx(hWnd, 0, "UniversalSearchBand", vbNullString)
hWnd = FindWindowEx(hWnd, 0, "Search Box", vbNullString)
hWnd = FindWindowEx(hWnd, 0, "SearchEditBoxWrapperClass", vbNullString)
hWnd = FindWindowEx(hWnd, 0, "SearchBox", vbNullString) ' ★--> 0 return
SearchBoxハンドル = hWnd
hWnd = FindWindowEx(SearchBoxハンドル, 0, "Button", "検索")
hWnd = FindWindowEx(SearchBoxハンドル, 0, "SearchEditBox", vbNullString)
' "SearchEditBox" に検索文字列入力の予定だった

With IID_IAccessible
.Data1 = &H68284FAA
.Data2 = &H6A48
.Data3 = &H11D0
.Data4(0) = &H8C
.Data4(1) = &H78
.Data4(2) = &H0
.Data4(3) = &HC0
.Data4(4) = &H4F
.Data4(5) = &HD9
.Data4(6) = &H18
.Data4(7) = &HB4
End With

RC = AccessibleObjectFromWindow(hWnd, OBJID_CLIENT, IID_IAccessible, objAcc)
' 80004005が返る
varChild = 2 ' Class="Button", Name="検索"のコントロールをクリックしたいが・・・
' RC = objAcc.accDoDefaultAction(varChild) ' 書き方が違うみたい・・・
' Class="SearchEditBox" に検索する文字列を入れたいが・・・
Exit Sub
End If
Set ie = Nothing
Next
End Sub

「windows7のエクスプローラをVBA」の質問画像

A 回答 (11件中1~10件)

ご報告ありがとうございました。



>2時間かかる検索もあります。
MsgWaitForMultipleObjectsを出した意図はDoEventsだけのループだと負荷が高くなるからなのですが、
使われないなら、Sleep APIぐらいは挟んでおいた方がいいですよ。

>検索したい設計書にはExcel、Word、PDF等があり、
了解です。

>WindowObject
不必要にVariant型を使うと遅くなるので固有型の方がいいのでは。
    • good
    • 1
この回答へのお礼

kumatti1さん
コメントありがとうございます。

> 使われないなら、Sleep APIぐらいは挟んでおいた方がいいですよ。
アドバイスありがとうございます、入れときました。

>>WindowObject 不必要にVariant型を使うと遅くなるので固有型の方がいいのでは。
as Object で定義しました。As InternetExplorerは自分的になじみが少ないので。

これでクローズしたいと思います。
いろいろとありがとうございました。感謝!

お礼日時:2014/05/30 05:27

>ネットワーク越しの検索をやっていて、1つの検索はMax2時間くらいで、


>トータルで1昼夜かかっています。
いや、違くてトータルの時間でなくて、一回の検索に時間が掛かるならと言う意味で書きました。

(今更ですが)そもそも、エクスプローラで検索しなくてはいけない状況は不思議な気がします。
FileSystemObjectで遅ければ、Dir関数とかコマンドプロンプトのDirとかAPIのFindFirstFileとか使えばいいのではと。
    • good
    • 0
この回答へのお礼

kumatti1さん
いろいろとありがとうございました。
ご提供のコードにNavigate処理を入れて、標準モジュールからCallして一応完了する
ことができました。
最後にUserformモジュールに書いたコードを載せています。
一応と言うのは、Navigateしたときに★(2)のie_DocumentCompleteのイベントは2回
発生しますが、SUB検索処理が★(1)のステップまで到達しているときに2回目の
ie_DocumentCompleteのイベントを受けることがあり、誤動作してしまうことがあります。
この誤動作を検知したときはNavigateからリトライすることで回避しています。
とは言え、ここまで来れたのはkumatti1さんのおかげです。
ありがとうございました。

>いや、違くてトータルの時間でなくて、一回の検索に時間が掛かるならと言う意味
で書きました。
10秒で終わる検索もありますが、2時間かかる検索もあります。

>(今更ですが)そもそも、エクスプローラで検索しなくてはいけない状況は不思議な
気がします。
仕事で納品されたシステムの設計書から文字列を探したいときに使います。
検索したい設計書にはExcel、Word、PDF等があり、これらは検索できなければなりません。
Excelで言えば、セルの中の文字だけではなく、吹き出しの図形の中の文字列も検索
させたくあります。
エクスプローラの検索ではこれらができますので使用しています。(フリーソフトは使用禁止です)
あっ、もしかしてFileSystemObjectとかFindFirstFileでも同様のことができるのかな?

Option Explicit
Private WithEvents EventHandler As Shell32.ShellFolderViewOC
Private WithEvents ie As SHDocVw.InternetExplorer
Private Filter_Done_flg As Boolean
Private Navigate_Done_flg As Boolean
Private Sub EventHandler_EnumDone()
Filter_Done_flg = True
EventHandler.SetFolderView Nothing
End Sub
Private Sub ie_DocumentComplete(ByVal pDisp As Object, URL As Variant)  ★ (2)
If Not EventHandler Is Nothing Then
EventHandler.SetFolderView pDisp.Document  
End If
End Sub
Private Sub ie_NavigateComplete2(ByVal pDisp As Object, URL As Variant)
Navigate_Done_flg = True
End Sub
Public Sub 検索処理(WindowObject, 検索フォルダ As String, 検索文字列 As
String)
Dim doc As IShellFolderViewDual3
Set EventHandler = Nothing
Set ie = WindowObject
Navigate_Done_flg = False
ie.Navigate (検索フォルダ)
While ie.Busy Or ie.ReadyState <> 4
DoEvents
Wend
Do
DoEvents
Loop Until Navigate_Done_flg = True 
Filter_Done_flg = False
Set EventHandler = New Shell32.ShellFolderViewOC ★(1)
Set doc = ie.Document
doc.FilterView 検索文字列
While ie.Busy Or ie.ReadyState <> 4
DoEvents
Wend
Do
DoEvents
Loop Until Filter_Done_flg = True
Set EventHandler = Nothing
Set ie = Nothing
End Sub

お礼日時:2014/05/28 21:26
    • good
    • 0

Microsoft Internet Controlsにも参照設定を行って、UserFormモジュールに貼り付けて、


リンク先の回答通りに直せば期待した動作をします。
フィルタリングに時間が掛かる状況は想像出来ませんが、もしそうならMsgWaitForMultipleObjectsを使うのもありかもしれません。
(再利用性を考慮すると)クラスモジュールもありかもしれませんが、その辺は適宜直してください。
    • good
    • 0
この回答へのお礼

kumatti1さん
回答ありがとうございました。
UserFormモジュールに貼り付けでできました。感謝!
kumatti1さんには大変お世話になりました。

> フィルタリングに時間が掛かる状況は想像出来ませんが、

ネットワーク越しの検索をやっていて、1つの検索はMax2時間くらいで、
トータルで1昼夜かかっています。

殆どクローズできる状況ですが、あと1~2日だけ開けさせて置いてください。

お礼日時:2014/05/26 21:21
    • good
    • 0
この回答へのお礼

kumatti1 さん、コードの提供ありがとうございました。
余り確認する時間がなかったのですが、コードのすべてを標準モジュールに
貼り付けたところ、以下の2行が赤く反転しました。
こちらExcel2010です。

Private WithEvents d As Shell32.ShellFolderViewOC
Private WithEvents ie As SHDocVw.InternetExplorer

一部をクラスモジュール、一部を標準モジュールに置くのでしょうか。
以下の部分をクラスモジュールに置き、Private Sub UserForm_Initialize()以下を
標準モジュールに置くと、dとかieの変数の宣言が足りません。

Option Explicit
Public WithEvents d As Shell32.ShellFolderViewOC
Public WithEvents ie As SHDocVw.InternetExplorer
Private flg As Boolean
Private Sub d_EnumDone()
flg = True
Debug.Print "d_EnumDone"
d.SetFolderView Nothing
End Sub
Private Sub ie_DocumentComplete(ByVal pDisp As Object, URL As Variant)
d.SetFolderView pDisp.Document
End Sub

クラスモジュールを使ったことがなく、基本的なところの質問で申し訳ありませんが
よろしくお願いします。

お礼日時:2014/05/24 07:55

ShellFolderViewOC.EnumDoneイベントですが、タイミングがシビアみたいでこちらではまだ、上手く行ってません。


http://msdn.microsoft.com/en-us/library/windows/ …

# 参照設定は「Microsoft Shell Controls And Automation」
    • good
    • 0
この回答へのお礼

kumatti1 さん、アドバイスありがとうございます。

> ShellFolderViewOC.EnumDoneイベントですが、タイミングがシビアみたいでこち
らではまだ、上手く行ってません。

ExcelVBA側でエクスプローラのイベントをハンドルすること自体から難しそうです
ね。
ShellFolderViewOC.EnumDoneno のネット上の例題もあまりありませんでした。
クラスモジュールに書かないといけない・・・?
エクスプローラを起動しないといけない・・・?
Windows7でテストしたいと思いますので(もしかして問題なく動くかも)コードを
お教え戴けないでしょうか。
以上よろしくお願いします。

お礼日時:2014/05/22 21:16

Win.document.FilterView "hoge"


の一文で済む様です。

IShellFolderViewDual3::FilterView method
http://msdn.microsoft.com/en-us/library/windows/ …
    • good
    • 0
この回答へのお礼

kumatti1さん、驚きの回答ありがとうございます。

>Win.document.FilterView "hoge"
>の一文で済む様です。

と、あったので「何が一文で済むのか」分からないまま動かしてみると
検索文字列として hoge が入るじゃないですか。
びっくりしました。
ありがとうございました。
もう自分のスキルが届くところではないですね。

最後にもうひとつアドバイス戴けたらと思います。
検索させる部分と検索結果を取り出す部分の骨格は以下のようになりましたが、
検索が終わったことをどのようにして判断するかが分かっておりません。
  (Sleep 10000 の部分)
定期的に検索結果を取得するとしても、終了を表す文字列も無さそうですし・・・。
何かのプロパティがあれば嬉しいのですが。
長い間お付き合いして戴き申し訳ありませんがよろしくお願いします。

Option Explicit
Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long) Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As Any, ByVal lpWindowName As Any) As Long Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" ( _
ByVal hwndParent As Long, _
ByVal hwndChildAfter As Long, _
ByVal lpClassName As String, _
ByVal lpWindowName As String) As Long

Sub 検索()
Dim Win As Object
Dim Search_Folder As String
Dim Oshell As Object
Dim Gyo As Long
Dim Col As Long
Dim hWnd As Long
Dim Works As String
Dim Folder As Object
Dim Folderitem As Object
Dim Folder_title As String

Set Oshell = CreateObject("Shell.Application")
Set Win = Nothing
For Each Win In Oshell.Windows()
If Win.FullName = "C:\Windows\Explorer.EXE" Then
Exit For
End If
Set Win = Nothing
Next

If Win Is Nothing Then Exit Sub
ThisWorkbook.Sheets("sheet1").Cells.ClearContents
Win.Navigate ("c:\xxx")

Sleep 500
Win.document.FilterView "検索文字列"

Sleep 10000 ' ここで検索終了を待ちたい

ThisWorkbook.Sheets("sheet1").Cells(1, 1).Value = "結果なし"
Set Folder = Win.document.Folder
On Error Resume Next
Folder_title = Folder.Items().Item().Path
On Error GoTo 0
If Left(Folder_title, 4) = "検索場所" Then
If Folder.Items().Count <> 0 Then
Gyo = 1
For Each Folderitem In Folder.Items()
For Col = 0 To 7
ThisWorkbook.Sheets("sheet1").Cells(Gyo, Col + 1).Value = Folder.GetDetailsOf(Folderitem, Col)
Next
Gyo = Gyo + 1
Next
End If
End If
End Sub

お礼日時:2014/05/20 20:18

こちらはWin8.1ですが、ここは


>hWnd = FindWindowEx(hWnd, 0, "SearchBox", vbNullString) ' ★--> 0 return
DirectUIHWND
になってますね。
    • good
    • 0
この回答へのお礼

ありがとうございます。
Windows7ですが、hWnd = FindWindowEx(hWnd, 0, "DirectUIHWND", vbNullString)
でハンドルが確かに返ってきました。
InspectObjectsで見るとClass="SearchBox"なのですが、Classが"DirectUIHWND"みたいな文言もあります。
よく分かりません。

お礼日時:2014/05/20 20:23

こちらでも確認したところ、Application.SendKeysでなくて、SendKeysなら 漢字文字列 もセット出来ました。


https://gist.github.com/kumatti1/9b6eac43e294db9 …

>UUID(IAccessible?)の値
継承関係のあるインターフェースIDならOKです。
IAccessible以外には IDispatch とか IUnknown とか。

---
Visual Studio評価版は3ヶ月利用できるようなので、C++を入れればSpy++も入るのでそちらからも確認されるとか。
IAccessibleの他にUI Automationてのもありますよ。
    • good
    • 0

アドレスバーと同じ理屈でクリックさせてからでないと無理なのかも。


(調査用と言うわけでもないが)AccessibleObjectFromPointとかもありますよ。

hwndプロパティでハンドルが得られるので、「Ctrl + F」相当のキー送信をWM_KEYDOWN辺りで送れば、検索ボックスにフォーカスされるので、
(他プロセスであっても)AttachThreadInputでGetFocus(APIの方)でハンドルが得られるのでWM_SETTEXTやらWM_CHARを送信するとか。で確定はWM_KEYDOWNでVK_RETURNとか。

↓コメントアウトしてる方が正しいです。
'Private objAcc As IAccessible
Private objAcc As UUID

>Const OBJID_CLIENT As Integer = &HFFFFFFFC
Long型

---

ただ、この辺を見るとIUnknown_QueryService(提示のコードで言えば引数にieを指定)で
IFolderView2インターフェースを得て制御するのが本来の実装なんでしょうね。
まあでもVBA向けのタイプライブラリがありませんので、C++でDLLを作ってVBAから呼び出すとかになるのかなと。
http://detail.chiebukuro.yahoo.co.jp/qa/question …
http://www.activebasic.com/forum/viewtopic.php?t …

この回答への補足

kumatti1さん
引き続きアドバイスありがとうございます。

>アドレスバーと同じ理屈でクリックさせてからでないと無理なのかも。

上位の"Search Box"のハンドルと "SearchEditBoxWrapperClass"のハンドルに対して
SendMessage(hWnd, BM_CLICK, 0, 0)を実行してみましたが、やはり結果は同じで "SearchBox",のハンドルは取得できませんでした。

>(調査用と言うわけでもないが)AccessibleObjectFromPointとかもありますよ。

あるポイントのAccessibleObjectを取得できるみたいですね。まだ試しておりません。

>「Ctrl + F」相当のキー送信をWM_KEYDOWN辺りで送れば、検索ボックスにフォーカスされるので、

Navigateしたあとに、以下のコードを実行したら検索文字列"ABC"は入りました。

AppActivate ("xxx") ・・・検索フォルダの最下位のフォルダ名
Sleep 1000
Application.SendKeys "^f"
Sleep 1000
Application.SendKeys "abc"

しかし、漢字文字列を検索したいので、やはり"SearchEditBox"に対して文字を入れないとダメかなと思っています。またSendKeysは動作時点にフォーカスされているところにSendkeyされるので操作の制約ができてしまうかなと思っています。
と言うことで、 IAccessibleの攻略が必須のようです。
まだ分かっていないのですが、今回場合、UUID(IAccessible?)の値には何を与えるのでしょうか?
OLEViewで見たのですがExplorer関連がいくつかありました(添付ファイル参照)。
Explorer Browser、Explorer Browser Results Folder、Explorer Navigation Bar、
Explorer Search Band、Explorer Travel Band、ExplorerCommandEnumeratorのどれ
かとは思うのですが・・・。DLL名から推測するとか・・・?
試しにExplorerBrowserを展開するとIAccssibleObjectと言う如何にもそれっぽいものもあります。表示されるInterface=に続く数字がUUIDなのかな?どなたか教えて戴ければ有難いです。
また、よろしくお願いします。

補足日時:2014/05/17 17:02
    • good
    • 0

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!


おすすめ情報