The Folder Customization Problem (and my take on it)

Post your working scripts, libraries and tools for AHK v1.1 and older
User avatar
Lezard
Posts: 5
Joined: 17 May 2020, 12:57

The Folder Customization Problem (and my take on it)

Post by Lezard » 19 Jun 2022, 02:18

Hi there I'm Lezard and usually I keep my stuff to myself, but a while back I stumbled on this problem and as I dug I came to realize it was more complicated and serious than one would expect. This problem really bothers me; so, first things first, let's talk about the problem. Here you can see my original post:
viewtopic.php?f=76&t=76056

On modern Windows if you wish to have a folder open in a certain format (explorer view, column sizes, etc) by default you must set it to use a certain "Template" (in Windows 10 you "Customize" your folder). You can config how your templates look, but you can't create new ones, so you're limited to 5 templates (but that's the least of the problems as we shall see). Here are some good references on how to make use of templates:
https://www.tenforums.com/tutorials/7923-change-folder-template-windows-10-a.html
https://www.tenforums.com/tutorials/35093-apply-folder-view-all-folders-same-type-windows-10-a.html

So far so good, but if you actually give it a try and a closer look you'll run into some concerning problems. First let's look deeper on how this template thing work, MS Windows stores the information about the folder on registry, this information is referred as "ShellBag", which is troublesome (1) from the usability aspect as it slowdown explorer, bloat the registry and it limits the amount of custom folders (see BagMRU size); (2) from the privacy aspect you're producing a lot of unnecessary info which is against your interest (and here I believe lies the reason why MS took such stupid rout to folder customization - they want that info registered). More sources to better understand the matter here:

BagMRU size (vs speed ...)
https://techjourney.net/fix-windows-not-remember-save-folder-types-or-folder-views-setting-increase-bagmru-size-cache-memory-size/
https://www.elevenforum.com/t/increase-folder-view-settings-cache-memory-size-in-windows-11.1277/

Forensics
https://ericmathison.com/blog/remove-shellbags-in-windows-for-privacy
https://www.sciencedirect.com/science/article/pii/S1742287609000413

Okay, I think you got the picture about the issue, now let's talk about the solution and here's where AHK comes into play. If using templates isn't an option for a reason (because you keep reaching the max BagMRU limit, or is a privacy minded individual, or simply find it painfully stupid), then I'd advise you to:
- configure the 5 templates (columns and all the stuff)
- clean Shellbags (ShellbagAnalyzerCleaner)
- set BagMRU max size to 1 ("BagMRU Size"=dword:00000001)
- set all folder to default to Documents template or any template that just lists files (I like details view) instead of showing thumbs, that's important as thumbs take ages to load on crowded folders (All_Folders_Use_Documents_Folder_Template.bat)
- have AHK script run on startup with code below (will read a file within the folder to determine the view)

Now I'm gonna share some code to make that work. That's my Frankenstein take on it, but I'm all ears for better solutions/improvements.

First we need a shell hook to trigger the view change, something like this:

Code: Select all

Gui +LastFound
hWnd := WinExist()
DllCall( "RegisterShellHookWindow", UInt,hWnd )
MsgNum := DllCall( "RegisterWindowMessage", Str,"SHELLHOOK" )
OnMessage( MsgNum, "ShellMessage" )
Return ; // End of Auto-Execute Section //

ShellMessage( wParam,lParam )
{
	If ( wParam = 6 || wParam = 1 ) ;  HSHELL_REDRAW or  HSHELL_WINDOWCREATED
	{
		WinGetClass, this_class, ahk_id %lParam%
		if this_class = CabinetWClass
		{
			exphWnd := WinActive("ahk_class CabinetWClass")
			ExplorerViewChange_Window(exphWnd)
		}
		else if this_class = ExploreWClass
		{
			exphWnd := WinActive("ahk_class ExploreWClass")
			ExplorerViewChange_Window(exphWnd)
		}
	}
}
Then let's define which file to read (I advise its name to start with !, the extention probably you better off inventing one instead of .txt - in this example !cf.txt will be present in all custom folders) and what happens on trigger:

Code: Select all

ExplorerViewChange_Window(explorerHwnd)
{
	if (!explorerHwnd)
		return
	Windows := ComObjCreate("Shell.Application").Windows
	for window in Windows
	{
		if (window.hWnd == explorerHwnd)
		{
			sFolder := window.Document
			Folder_Path := window.Document.Folder.Self.Path
			
			IfExist, %Folder_Path%\!cf.txt
			{
				CF_File = %Folder_Path%\!cf.txt
				New_View := ""
				FileReadLine, New_View, %CF_File%, 1
				
				if (New_View == "Tiles")
					sFolder.CurrentViewMode := 6 ; Tiles
				else if (New_View == "Details")
					sFolder.CurrentViewMode := 4 ; Details
				else if (New_View == "List")
					sFolder.CurrentViewMode := 3 ; List
				else if (New_View == "Icons_16") {
					sFolder.CurrentViewMode := 2 ; Small icons
					sFolder.IconSize := 16 ; Actually make the icons small...
				} else if (New_View == "Icons_48") {
					sFolder.CurrentViewMode := 1 ; Icons
					sFolder.IconSize := 48 ; Medium icon size
				} else if (New_View == "Icons_96") {
					sFolder.CurrentViewMode := 1 ; Icons
					sFolder.IconSize := 96 ; Large icon size
				} else if (New_View == "Icons_256") {
					sFolder.CurrentViewMode := 1 ; Icons
					sFolder.IconSize := 256 ; Extra large icons
				}
			}
		}
	}
}
Now here is a sample code to bind * as a way to create !cf files with easy

Code: Select all

#If (cf_hWnd := WinActive("ahk_class CabinetWClass"))
NumpadMult::
	ControlGetFocus, current_control, A
	if(current_control = "DirectUIHWND3")
	{
		cf_path := Explorer_GetPath(cf_hWnd) . "\!cf.txt"
		
		IfExist, %cf_path%
		{
			CF_View := ""
			FileReadLine, CF_View, %cf_path%, 1
			if (CF_View == "Tiles")
				Default_View := 1
			else if (CF_View == "Details")
				Default_View := 2
			else if (CF_View == "Icons_96")
				Default_View := 3
			else if (CF_View == "Icons_256")
				Default_View := 4
			else
				Default_View := 2
		}
		Else
		{
			Default_View := 2
		}
		
		Gui, New,, Customize Folder
		Gui, Add, Text,, Choose View:
		Gui, Add, Checkbox, X+71 vRecurse, Recurse?
		; Gui, Add, DropDownList, w200 Xs0 vChoosen_View Choose%Default_View%, Tiles|Details|List|Icons_16|Icons_48|Icons_96|Icons_256
		Gui, Add, DropDownList, w200 Xs0 vChoosen_View Choose%Default_View%, Tiles|Details|Icons_96|Icons_256
		Gui, Add, Button, Default Xs75 Y+10 w55, Criar_CF
		Gui, Show
		ControlFocus, ComboBox1, Customize Folder
		return
		
		ButtonCriar_CF:
			Gui, Submit
			WinActivateBottom, ahk_id %cf_hWnd%
			WinWaitActive, ahk_id %cf_hWnd%
			
			mod_date_new = 0
			FileGetTime, mod_date_old, %cf_path%
			
			FileSetAttrib, -H, %cf_path%, 0, 0
			file := FileOpen(cf_path, "w")
			file.Write(Choosen_View)
			file.Close()
			FileSetAttrib, +H, %cf_path%, 0, 0
			
			if(Recurse == True)
			{
				cf_upper_path := ParentFolder(cf_path,1)
				Loop, Files, % cf_upper_path "\*", DR
				{
					cf_subs_path := A_LoopFileFullPath . "\!cf.txt"
					FileSetAttrib, -H, %cf_subs_path%, 0, 0
					file := FileOpen(cf_subs_path, "w")
					file.Write(Choosen_View)
					file.Close()
					FileSetAttrib, +H, %cf_subs_path%, 0, 0
				}
			}
			
			FileGetTime, mod_date_new, %cf_path%
			stop_while := 0
			while (mod_date_new <= mod_date_old && stop_while <= 32)
			{
				Sleep, 62.5
				stop_while := stop_while+1
			}
			
			Send {F5}
		return
	}
	else
		Send *
#If
return
Sample folder creation

Code: Select all

#If (cf_hWnd := WinActive("ahk_class CabinetWClass"))
Capslock & Space::
	Gui, New,, Folder Creator
	Gui, Add, Text,, Num Folders:
	Gui, Add, Edit, w200 Xs0 vnum_folders gNum_Folders_Check_Length ; Gui, Add, Edit, Number w200 Xs0 vnum_folders gNum_Folders_Check_Length
	Gui, Add, Text,, Choose View:
	; Gui, Add, DropDownList, w200 Xs0 vChoosen_View Choose2, Tiles|Details|List|Icons_16|Icons_48|Icons_96|Icons_256
	Gui, Add, DropDownList, w200 Xs0 vChoosen_View Choose2, Tiles|Details|Icons_96|Icons_256
	Gui, Add, Button, Default Xs75 Y+10 w55, Criar_Dirs
	Gui, Show
	return
	
	Num_Folders_Check_Length:
	Gui, Submit, NoHide
	if (RegExMatch(%A_GuiControl%, "[0-9]+") && StrLen(%A_GuiControl%) = 3)
		Tab_Macro(1)
	return
	
	ButtonCriar_Dirs:
		Gui, Submit
		WinActivateBottom, ahk_id %cf_hWnd%
		WinWaitActive, ahk_id %cf_hWnd%
		
		; Template_Pictures := A_ScriptDir . "\Templates\Pictures"
		Upper_Dir := Explorer_GetPath()
		if(num_folders = "")
			num_folders := 1
		if(!RegExMatch(num_folders, "[0-9]+"))
		{
			num_folders := RegExReplace(num_folders, "[\\/:*?""<>|]", "_")
			FileCreateDir, %Upper_Dir%\%num_folders%
			
			cf_path := Upper_Dir . "\" . num_folders . "\!cf.txt"
			FileSetAttrib, -H, %cf_path%, 0, 0
			file := FileOpen(cf_path, "w")
			file.Write(Choosen_View)
			file.Close()
			FileSetAttrib, +H, %cf_path%, 0, 0
		}
		else if(num_folders > 0 && num_folders <= 1000) ; 1 to 1000 so folders 000 to 999
		{
			Loop, %num_folders%
			{
				formated_num_folders := Format("{1:03}", A_Index - 1 )
				if !FileExist("%Upper_Dir%\%formated_num_folders%")
				{
					FileCreateDir, %Upper_Dir%\%formated_num_folders%
					; FileCopyDir, %Template_Pictures%, %Upper_Dir%\%formated_num_folders%
					; FileMoveDir, %Template_Pictures%, %Upper_Dir%\%formated_num_folders%, 0
					
					cf_path := Upper_Dir . "\" . formated_num_folders . "\!cf.txt"
					FileSetAttrib, -H, %cf_path%, 0, 0
					file := FileOpen(cf_path, "w")
					file.Write(Choosen_View)
					file.Close()
					FileSetAttrib, +H, %cf_path%, 0, 0
				}
			}
		}
	return
; Return
#If
I almost forgot, here is used function ParentFolder

Code: Select all

ParentFolder(filepath,n)
{
	FoundPos := InStr(filepath, "\", 0, 0, n) ; Reverse search \ position of n ocurrence
	parentpath := SubStr(filepath, 1 , FoundPos-1)
	Return parentpath
}
Good reads:
https://www.autohotkey.com/board/topic/80644-how-to-hook-on-to-shell-to-receive-its-messages/
viewtopic.php?f=6&t=35041

User avatar
Lezard
Posts: 5
Joined: 17 May 2020, 12:57

Re: The Folder Customization Problem (and my take on it)

Post by Lezard » 26 Jun 2022, 08:40

So ... it seems I can't edit my post, is that normal or am I missing something? It's a work in progress and that post was supposed to be a placeholder, it needs some clearing/cleaning (take "Sample folder creation" for example: shouldn't use Capslock, "Template_Pictures " commentary that shouldn't be there and there's even a regex that doesn't work as expected, "[0-9]+" should be "^[0-9]+$"). On the other hand nobody seems interested anyway so maybe I should leave it be.

User avatar
boiler
Posts: 16767
Joined: 21 Dec 2014, 02:44

Re: The Folder Customization Problem (and my take on it)

Post by boiler » 26 Jun 2022, 09:03

You should be able to edit your post. It would require moderator approval before it appears just as for a new post by a new forum member.

User avatar
Lezard
Posts: 5
Joined: 17 May 2020, 12:57

Re: The Folder Customization Problem (and my take on it)

Post by Lezard » 04 Jul 2022, 10:15

I've improved navigation, it now auto focuses selected file when going back 'n forward. I had to delay and condition the focusing of the file so it's still possible to create new folders 'n files as usual (auto renaming), but I wonder if using creation date was the way to go and using sleep is never reliable (if computer is on heavy loads the time given may not suffice). I'm all ears if there's a better way to solve the issue, but for now it'll do.

New version of ExplorerViewChange_Window:

Code: Select all

ExplorerViewChange_Window(explorerHwnd)
{
	if (!explorerHwnd)
		return
	Windows := ComObjCreate("Shell.Application").Windows
	for window in Windows
	{
		if (window.hWnd == explorerHwnd)
		{
			sFolder := window.Document
			Folder_Path := window.Document.Folder.Self.Path
			
			IfExist, %Folder_Path%\!cf.txt
			{
				CF_File = %Folder_Path%\!cf.txt
				New_View := ""
				FileReadLine, New_View, %CF_File%, 1
				
				if (New_View == "Tiles")
					sFolder.CurrentViewMode := 6 ; Tiles
				else if (New_View == "Details")
					sFolder.CurrentViewMode := 4 ; Details
				else if (New_View == "List")
					sFolder.CurrentViewMode := 3 ; List
				else if (New_View == "Icons_16") {
					sFolder.CurrentViewMode := 2 ; Small icons
					sFolder.IconSize := 16 ; Actually make the icons small...
				} else if (New_View == "Icons_48") {
					sFolder.CurrentViewMode := 1 ; Icons
					sFolder.IconSize := 48 ; Medium icon size
				} else if (New_View == "Icons_96") {
					sFolder.CurrentViewMode := 1 ; Icons
					sFolder.IconSize := 96 ; Large icon size
				} else if (New_View == "Icons_256") {
					sFolder.CurrentViewMode := 1 ; Icons
					sFolder.IconSize := 256 ; Extra large icons
				}
			}
			
			Sleep, 200  ; Important when New Folder/File created under Details view
			sFile := JEE_ExpWinGetFoc(explorerHwnd)
			FileGetTime, sFile_mod_date, %sFile%, C
			sFile_Timing := A_Now
			EnvSub, sFile_Timing, %sFile_mod_date%, Seconds
			If ( sFile_Timing > 10 )
			{
				try window.Document.SelectItem(sFile, 0x1D)
				break
			}
		}
	}
}
I'm using this func from user jeeswg found somewhere:

Code: Select all

JEE_ExpWinGetFoc(hWnd:=0)
{
	local oItem, oWin, oWindows, vPath, vWinClass
	DetectHiddenWindows, On
	(!hWnd) && hWnd := WinExist("A")
	WinGetClass, vWinClass, % "ahk_id " hWnd
	if (vWinClass = "CabinetWClass") || (vWinClass = "ExploreWClass")
	{
		for oWin in ComObjCreate("Shell.Application").Windows
			if (oWin.HWND = hWnd)
				break
	}
	else if (vWinClass = "Progman") || (vWinClass = "WorkerW")
	{
		oWindows := ComObjCreate("Shell.Application").Windows
		VarSetCapacity(hWnd, 4, 0)
		;SWC_DESKTOP := 0x8 ;VT_BYREF := 0x4000 ;VT_I4 := 0x3 ;SWFO_NEEDDISPATCH := 0x1
		oWin := oWindows.FindWindowSW(0, "", 8, ComObject(0x4003, &hWnd), 1)
	}
	else
		return

	vPath := oWin.Document.FocusedItem.path
	oWindows := oWin := oItem := ""
	return vPath
}
Here's Tab_Macro that I also forgot to share:

Code: Select all

; Tab_Macro(3) will tab 3 times
; Tab_Macro(2,, 1) will shift tab 2 times
Tab_Macro(n, tab_time = 62.5, shift_down = 0)
{
	if (shift_down = 0)
	{
		Loop %n%
		{
			Send, {Tab}
			Sleep, %tab_time%
		}
	}
	else
	{
		Loop %n%
		{
			Send, {Shift Down}{Tab}{Shift Up}
			Sleep, %tab_time%
		}
	}
	Return
}

Post Reply

Return to “Scripts and Functions (v1)”