Explorer: jump to first file/folder

Get help with using AutoHotkey and its commands and hotkeys
User avatar
jeeswg
Posts: 5738
Joined: 19 Dec 2016, 01:58
Location: UK

Explorer: jump to first file/folder

12 Jun 2017, 12:45

I've written a script to toggle jumping to the first file/folder in a folder. It works, but it's slow on folders with lots of files/folders, in case anyone has any ideas. Cheers.

Code: Select all

^#j:: ;explorer - toggle jump to first file/folder (tested on Windows 7)
WinGet, hWnd, ID, A
WinGetClass, vWinClass, % "ahk_id " hWnd
if vWinClass not in CabinetWClass,ExploreWClass
	return

for oWin in ComObjCreate("Shell.Application").Windows
	if (oWin.HWND = hWnd)
	{
		vIsDir := (oWin.Document.FocusedItem.type = "File folder")
		for oItem in oWin.Document.Folder.Items
		{
			;only check every 10th item
			;if Mod(A_Index,10)
			;	continue
			vIsDir2 := (oItem.type = "File folder")
			if !(vIsDir = vIsDir2)
			{
				oWin.Document.SelectItem(oItem, 0x1D)
				break
			}
		}
		oItem := ""
		break
	}
oWin := ""
return
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
BoBo
Posts: 2517
Joined: 13 May 2014, 17:15

Re: Explorer: jump to first file/folder

12 Jun 2017, 13:49

[Pos1]<>[End] :?: and beside that alphabetically, right?!
User avatar
jeeswg
Posts: 5738
Joined: 19 Dec 2016, 01:58
Location: UK

Re: Explorer: jump to first file/folder

12 Jun 2017, 21:54

E.g. the My Pictures folder, around 6000 files/folders, folders first (not alphabetical), then files (not alphabetical).

This worked, but was actually slower, I thought it might be quicker, but the object seems to be slow when you request it to get the nth item.

I did discover some useful object properties for folder windows however.

Although the code worked, it is quite fiddly, so potentially a mistake could have slipped in.

Code: Select all

^#j:: ;explorer - toggle jump to first file/folder (tested on Windows 7)
WinGet, hWnd, ID, A
WinGetClass, vWinClass, % "ahk_id " hWnd
if vWinClass not in CabinetWClass,ExploreWClass
	return

for oWin in ComObjCreate("Shell.Application").Windows
	if (oWin.HWND = hWnd)
	{
		vIsDir := oWin.Document.FocusedItem.IsFolder ;-1=true, 0=false
		vCount := oWin.Document.Folder.Items.Count
		vIndex := 1
		vList := "1000,100,10,1"
		Loop, Parse, vList, % ","
		{
			vJump := A_LoopField
			if (vJump > vCount)
				continue
			Loop
			{
				ToolTip, % vIndex
				oItem := oWin.Document.Folder.Items.Item(vIndex-1) ;0-based
				if !(oItem.IsFolder = vIsDir)
					if (vJump = 1)
					{
						oWin.Document.SelectItem(oItem, 0x1D)
						oWin := oItem := ""
						MsgBox, % "done: " (vIndex-1)
						return
					}
					else if !(A_Index = 1)
					{
						vIndex -= vJump
						break
					}
					else
						break
				if (vIndex + vJump > vCount)
					break
				vIndex += vJump
			}
		}
		oItem := ""
		break
	}
oWin := ""
MsgBox, % "done"
return
The difficulty is that unlike a listview, you can't grab all the text in one go. If you use AccViewer, you can only retrieve information for the visible items.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
qwerty12
Posts: 468
Joined: 04 Mar 2016, 04:33
GitHub: qwerty12

Re: Explorer: jump to first file/folder

13 Jun 2017, 00:53

EDIT: There's a bug in the code I posted: the finally block will look at variables set from previous presses of the hotkey. Zeroing the variables once released, or putting the code back into a function, would probably sort it.

This might be faster (tested in a folder containing 5000 folders and ~4200 files; all sequentially named, however), though I found the time of your script acceptable IMO:

Code: Select all

#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
ListLines, Off
SetBatchLines, -1
#KeyHistory 0

VarSetCapacity(IID_IShellFolder, 16), DllCall("ole32\CLSIDFromString", "WStr", "{000214E6-0000-0000-C000-000000000046}", "Ptr", &IID_IShellFolder)
GroupAdd, Explorer, ahk_class ExploreWClass ; Unused on Vista and later
GroupAdd, Explorer, ahk_class CabinetWClass

#IfWinActive ahk_group Explorer
^#j:: ;explorer - toggle jump to first file/folder (tested on Windows 7)
hWnd := WinExist("A")
for oWin in ComObjCreate("Shell.Application").Windows
	if (oWin.HWND == hWnd)
	{
		vIsDir := (oWin.Document.FocusedItem.type == "File folder")
		try {
			 isp := ComObjQuery(oWin, "{6d5140c1-7436-11ce-8034-00aa006009fa}")
			,isb := ComObjQuery(isp, "{4C96BE40-915C-11CF-99D3-00AA004AE837}", "{000214E2-0000-0000-C000-000000000046}")
			if (DllCall(NumGet(NumGet(isb+0)+15*A_PtrSize), "Ptr", isb, "Ptr*", isv) < 0) ; QueryActiveShellView
				throw
			ifv := ComObjQuery(isv, "{cde725b0-ccc9-4519-917e-325d72fab4ce}")
			; GetFolder, EnumObjects, Next
			if (DllCall(NumGet(NumGet(ifv+0)+5*A_PtrSize), "Ptr", ifv, "Ptr", &IID_IShellFolder, "Ptr*", isf) < 0
			 || DllCall(NumGet(NumGet(isf+0)+4*A_PtrSize), "Ptr", isf, "Ptr", 0, "UInt", vIsDir ? 0x00040 : 0x00020, "Ptr*", eil) < 0 
			 ||	DllCall(NumGet(NumGet(eil+0)+3*A_PtrSize), "ptr", eil, "uint", 1, "Ptr*", pidlItem, "UInt*", celtFetched) < 0 || celtFetched != 1)
				throw
			DllCall(NumGet(NumGet(isv+0)+14*A_PtrSize), "Ptr", isv, "Ptr", pidlItem, "UInt", 0x1d) ; SelectItem - SVSI_SELECT | SVSI_ENSUREVISIBLE | SVSI_FOCUSED | SVSI_DESELECTOTHERS
		} catch {
			return
		} finally {
			if (pidlItem)
				DllCall("ole32\CoTaskMemFree", "Ptr", pidlItem)
			for _, obj in [eil, isf, ifv, isv, isb, isp]
				if (obj)
					ObjRelease(obj)
		}
		break
	}
#IfWinActive
return
Last edited by qwerty12 on 14 Jun 2017, 07:06, edited 1 time in total.
User avatar
jeeswg
Posts: 5738
Joined: 19 Dec 2016, 01:58
Location: UK

Re: Explorer: jump to first file/folder

13 Jun 2017, 01:55

Cheers qwerty12, that was lightning fast, although as you said, it assumes alphabetical order.

Just doing an object loop by itself takes a long time:

Code: Select all

;q:: ;explorer - time taken to loop through files (tested on Windows 7)
WinGet, hWnd, ID, A
WinGetClass, vWinClass, % "ahk_id " hWnd
if vWinClass not in CabinetWClass,ExploreWClass
	return
for oWin in ComObjCreate("Shell.Application").Windows
	if (oWin.HWND = hWnd)
	{
		vCount := oWin.Document.Folder.Items.Count
		vTickCount1 := A_TickCount
		for oItem in oWin.Document.Folder.Items
		{
		}
		vTickCount2 := A_TickCount
		MsgBox, % Clipboard := vCount " files`r`n" vTickCount2-vTickCount1 " msec"
	}
oWin := ""
return

;5980 files
;6724 msec
;6832 msec
;6770 msec
This slowness affects *anything* you try to do with files in a folder, e.g. selecting all files with a certain extension, or listing the selected files.

Btw is there a way to get a CLSID from an existing object? (I did do some searching.)

I take it that oWin, had Prog ID 'Shell.Application' and so had CLSID '{13709620-C279-11CE-A49E-444553540000}':
[EDIT: That would be the CLSID for the parent object of oWin.]

Code: Select all

w:: ;Prog ID to CLSID
RegRead, vCLSID, HKCR, Shell.Application\CLSID
MsgBox, % vCLSID ;{13709620-C279-11CE-A49E-444553540000}
return
It's interesting how you use ComObjQuery to get access to further Explorer interfaces. I had been wondering how to access them. (You can look up a CLSID on the Internet, to find out more about the interface.)
Last edited by jeeswg on 13 Jun 2017, 02:45, edited 1 time in total.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
qwerty12
Posts: 468
Joined: 04 Mar 2016, 04:33
GitHub: qwerty12

Re: Explorer: jump to first file/folder

13 Jun 2017, 02:20

jeeswg wrote:Cheers qwerty12, that was lightning fast, although as you said, it assumes alphabetical order.
Yeah, I don't know the magical shell incantations to get the list in sort order, sorry :\
Btw is there a way to get a CLSID from an existing object? (I did do some searching.)
ComObjType, I think, if the object interface allows for that to happen (see the remarks on that page). Though if not,
I take it that oWin, had Prog ID 'Shell.Application' and so had CLSID '{13709620-C279-11CE-A49E-444553540000}':
It appears to be a straight up IWebBrowser2 object...
It's interesting how you use ComObjQuery to get access to further Explorer interfaces. I had been wondering how to access them. (You can look up a CLSID on the Internet, to find out more about the interface.)
This shell stuff confuses me heavily... After reading somewhere on the Internet how to get to the IShellBrowser from the IWebBrowser2 object, the rest is just copy-and-paste code from this (it itself a mishmash of code from Lexikos' posts and The Old New Thing) and the desktop selected item scripts...
User avatar
jeeswg
Posts: 5738
Joined: 19 Dec 2016, 01:58
Location: UK

Re: Explorer: jump to first file/folder

13 Jun 2017, 02:59

Interesting comments re. interfaces.

Well ComObjType has been updated to support retrieving CLSIDs just now as luck would have it (the current version *is* v1.1.26).
ComObjType()
https://autohotkey.com/docs/commands/ComObjType.htm

[look up Shell.Application in the registry to get the CLSID]
[look up the CLSID, 13709620-C279-11CE-A49E-444553540000, which takes you to:]
Scriptable Shell Objects (Windows)
https://msdn.microsoft.com/en-us/librar ... s.85).aspx
Shell object (Windows)
https://msdn.microsoft.com/en-us/librar ... s.85).aspx

[which has a 'Windows' method, you get a ShellWindows object, which is a collection of items, each an 'InternetExplorer object that represents the Shell window', each with a 'Document' property]
Shell.Windows method (Windows)
https://msdn.microsoft.com/en-us/librar ... s.85).aspx
ShellWindows object (Windows)
https://msdn.microsoft.com/en-us/librar ... s.85).aspx
InternetExplorer object (Windows)
https://msdn.microsoft.com/en-us/librar ... s.85).aspx
Document property (Windows)
https://msdn.microsoft.com/en-us/librar ... s.85).aspx

==================================================

Object: VarType IName IID CName CLSID
- Shell: 9 IShellDispatch5 {866738B9-6CF2-4DE8-8767-F794EBE74F4E} Shell {13709620-C279-11CE-A49E-444553540000}
- Win: 9 IWebBrowser2 {D30C1661-CDAF-11D0-8A3E-00C04FC9E26E} WebBrowser {8856F961-340A-11D0-A96B-00C04FD705A2}
- Doc: 9 IShellFolderViewDual3 {29EC8E6C-46D3-411F-BAAA-611A6C9CAC66} ShellFolderView {62112AA1-EBE4-11CF-A5FB-0020AFE7292D}
- Folder: 9 Folder3 {A7AE5F64-C4D7-4D7F-9307-4D24EE54B841}
- Items: 9 FolderItems3 {EAA7C309-BBEC-49D5-821D-64D966CB667F}

Code: Select all

q:: ;get Explorer window object info
WinGet, hWnd, ID, A
WinGetClass, vWinClass, % "ahk_id " hWnd
if vWinClass not in CabinetWClass,ExploreWClass
	return
oShell := ComObjCreate("Shell.Application")
for oWin in oShell.Windows
	if (oWin.HWND = hWnd)
	{
		oDoc := oWin.Document
		oFolder := oDoc.Folder
		oItems := oFolder.Items

		vList := "Shell,Win,Doc,Folder,Items"
		vOutput := ""
		Loop, Parse, vList, % ","
		{
			vObj := A_LoopField

			vVarType := ComObjType(o%vObj%)
			vIName   := ComObjType(o%vObj%, "Name")
			vIID     := ComObjType(o%vObj%, "IID")
			vCName   := ComObjType(o%vObj%, "Class")
			vCLSID   := ComObjType(o%vObj%, "CLSID")
			vOutput .= RTrim(vObj ": " vVarType " " vIName " " vIID " " vCName " " vCLSID) "`r`n"
		}
	}
MsgBox, % Clipboard := vOutput
return
==================================================

[EDIT:] Are these the interfaces you made reference to:

Code: Select all

eil	IEnumIDList
isf	IShellFolder
ifv	IFolderView
isv	IShellView
isb	IShellBrowser
isp	IServiceProvider
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
User avatar
jeeswg
Posts: 5738
Joined: 19 Dec 2016, 01:58
Location: UK

Re: Explorer: jump to first file/folder

14 Jun 2017, 13:51

On my big folder this is still slightly slow, but I'd say it's just about acceptable:

I don't know if there's a way to select a file directly by its path, without the object looping through items checking the Path property until you find it. However you can select a file directly by its index. If I know that the folder window will list folders at the top and that is has n folders, then to select the 1st file, I select the (n+1)th item.

Code: Select all

q:: ;explorer - toggle jump to first file/folder (tested on Windows 7)
;assumes that the Explorer window refers to a standard folder (i.e. not 'Desktop')
WinGet, hWnd, ID, A
WinGetClass, vWinClass, % "ahk_id " hWnd
if vWinClass not in CabinetWClass,ExploreWClass
	return

for oWin in ComObjCreate("Shell.Application").Windows
	if (oWin.HWND = hWnd)
	{
		vIsDir := oWin.Document.FocusedItem.IsFolder ;-1=true, 0=false
		oItem := oWin.Document.Folder.Items.Item(0)
		vIsDir2 := oItem.IsFolder ;0-based index
		if !(vIsDir = vIsDir2)
		{
			oWin.Document.SelectItem(oItem, 0x1D)
			break
		}
		vDir := oWin.Document.Folder.Self.Path
		Loop, Files, % vDir "\*", % vIsDir2?"D":"F"
			vCount := A_Index
		oItem := oWin.Document.Folder.Items.Item(vCount+0) ;0-based index, needs '+0' to be seen as numeric
		oWin.Document.SelectItem(oItem, 0x1D)
		break
	}
oWin := oItem := ""
;MsgBox, % "done"
return
==================================================

This works, but unfortunately the folder displays items in alphabetical order, not in the last sort order I used:

Code: Select all

q:: ;open containing folder and select newest file
vDir1 = C:\Users\%A_UserName%\Pictures
vDateMax := 0
Loop, Files, % vDir1 "\*", F
{
	if (A_LoopFileTimeModified < vDateMax)
		continue
	vDateMax := A_LoopFileTimeModified
	vPath := A_LoopFileFullPath
}

;this opens the folder with folders alphabetical, then files alphabetical
Run, %ComSpec% /c explorer.exe /select`, "%vPath%",, Hide

;this opens the folder with folders oldest first, then files oldest first (my last sort order)
;Run, %vDir1%
return
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA

Return to “Ask For Help”

Who is online

Users browsing this forum: Bing [Bot] and 58 guests