Page 1 of 1

FileSelectFile, add controls to the Open/Save As dialog

Posted: 31 Mar 2017, 19:00
by jeeswg
If you do this:
FileSelectFile, vPath, % "", % A_Desktop, % "Open", % "Text Documents (*.txt)"
and compare it with Notepad's Open menu, Notepad's dialog has a Static control containing 'Encoding:' and a ComboBox next to it.

Is there a best way to do this in AHK?
Cheers.

Re: FileSelectFile, add controls to the Open/Save As dialog

Posted: 31 Mar 2017, 22:47
by qwerty12
On Vista and later, the Common File Dialog lets you add controls to it. Here's a small modification of just me's SelectFolder that adds a group, combo box and two items to the dialog box, and then returns whatever was selected in the combo box.

The dialog allows for far more customisation than what I've simply done, look at the following for ideas and C++ code:
https://github.com/pauldotknopf/Windows ... filedialog
https://www.codeproject.com/Articles/16 ... ile-Dialog

If you implement the IFileDialogControlEvents interface, It is also possible to get notifications then and there when a combo box etc. item has been selected so that you could, say, change parts of the file dialog on-the-fly, but even if it weren't 04:30 here, I'd still end up half-assing the implementation as implementing COM interfaces is not something I particularly enjoy doing and nor am I any good at it anyway :-)

Code: Select all

SelectFolder() {
   ; Common Item Dialog -> msdn.microsoft.com/en-us/library/bb776913%28v=vs.85%29.aspx
   ; IFileDialog        -> msdn.microsoft.com/en-us/library/bb775966%28v=vs.85%29.aspx
   ; IShellItem         -> msdn.microsoft.com/en-us/library/bb761140%28v=vs.85%29.aspx
   Static OsVersion := DllCall("GetVersion", "UChar")
   Static Show := A_PtrSize * 3
   Static SetOptions := A_PtrSize * 9
   Static GetResult := A_PtrSize * 20
   SelectedFolder := ""
   If (OsVersion < 6) { ; IFileDialog requires Win Vista+
      FileSelectFolder, SelectedFolder
      Return SelectedFolder
   }
   If !(FileDialog := ComObjCreate("{DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7}", "{42f85136-db7e-439c-85f1-e4075d135fc8}"))
      Return ""
   VTBL := NumGet(FileDialog + 0, "UPtr")
   DllCall(NumGet(VTBL + SetOptions, "UPtr"), "Ptr", FileDialog, "UInt", 0x00000028, "UInt") ; FOS_NOCHANGEDIR | FOS_PICKFOLDERS
   
   try if ((FileDialogCustomize := ComObjQuery(FileDialog, "{e6fdd21a-163f-4975-9c8c-a69f1ba37034}"))) {
		groupId := 616 ; arbitrarily chosen
		comboboxId := 93270
		itemOneId := 015
		itemTwoId := 666

		DllCall(NumGet(NumGet(FileDialogCustomize+0)+26*A_PtrSize), "Ptr", FileDialogCustomize, "UInt", groupId, "WStr", "Gruppe name") ; IFileDialogCustomize::StartVisualGroup		
		DllCall(NumGet(NumGet(FileDialogCustomize+0)+6*A_PtrSize), "Ptr", FileDialogCustomize, "UInt", comboboxId) ; IFileDialogCustomize::AddComboBox
		DllCall(NumGet(NumGet(FileDialogCustomize+0)+19*A_PtrSize), "Ptr", FileDialogCustomize, "UInt", comboboxId, "UInt", itemOneId, "WStr", "ZeroQuinze") ; IFileDialogCustomize::AddControlItem
		DllCall(NumGet(NumGet(FileDialogCustomize+0)+19*A_PtrSize), "Ptr", FileDialogCustomize, "UInt", comboboxId, "UInt", itemTwoId, "WStr", "Sosa") ; IFileDialogCustomize::AddControlItem
		DllCall(NumGet(NumGet(FileDialogCustomize+0)+25*A_PtrSize), "Ptr", FileDialogCustomize, "UInt", comboboxId, "UInt", itemOneId) ; IFileDialogCustomize::SetSelectedControlItem
		DllCall(NumGet(NumGet(FileDialogCustomize+0)+27*A_PtrSize), "Ptr", FileDialogCustomize) ; IFileDialogCustomize::EndVisualGroup
	}
	
   If !DllCall(NumGet(VTBL + Show, "UPtr"), "Ptr", FileDialog, "Ptr", 0, "UInt") {
      If !DllCall(NumGet(VTBL + GetResult, "UPtr"), "Ptr", FileDialog, "PtrP", ShellItem, "UInt") {
         GetDisplayName := NumGet(NumGet(ShellItem + 0, "UPtr"), A_PtrSize * 5, "UPtr")
         If !DllCall(GetDisplayName, "Ptr", ShellItem, "UInt", 0x80028000, "PtrP", StrPtr) ; SIGDN_DESKTOPABSOLUTEPARSING
            SelectedFolder := StrGet(StrPtr, "UTF-16"), DllCall("Ole32.dll\CoTaskMemFree", "Ptr", StrPtr)
         ObjRelease(ShellItem)
         
         if (FileDialogCustomize) {
			if (DllCall(NumGet(NumGet(FileDialogCustomize+0)+24*A_PtrSize), "Ptr", FileDialogCustomize, "UInt", comboboxId, "UInt*", selectedItemId) == 0) { ; IFileDialogCustomize::GetSelectedControlItem
				if (selectedItemId == itemOneId)
					MsgBox ,,ZeroQuinze, %SelectedFolder%
				else if (selectedItemId == itemTwoId)
					MsgBox ,,Sosa, %SelectedFolder%
			}	
         }
      }
   }
   if (FileDialogCustomize)
		ObjRelease(FileDialogCustomize)

   ObjRelease(FileDialog)
   Return SelectedFolder
}

Re: FileSelectFile, add controls to the Open/Save As dialog

Posted: 01 Apr 2017, 12:14
by jeeswg
This is very good, many thanks qwerty12. Great links, and thanks for the information regarding notifications, I don't personally need it for this project, although it's potentially useful for me in future.

Btw, I was hoping to make a script compatible with Windows XP, is there a way to do this with the old-style Common File Dialog.

Common Item Dialog (Windows)
https://msdn.microsoft.com/en-us/librar ... s.85).aspx

I know for example that Notepad's Open/Save As dialogs in Windows XP, also add the two controls, but use the old-style dialog.
Thanks.

Re: FileSelectFile, add controls to the Open/Save As dialog

Posted: 01 Apr 2017, 21:13
by qwerty12
jeeswg wrote: Btw, I was hoping to make a script compatible with Windows XP, is there a way to do this with the old-style Common File Dialog.
The OK news: kinda.

Code: Select all

#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.

VarSetCapacity(OPENFILENAMEW, (cbOFN := A_PtrSize == 8 ? 152 : 88), 0)
NumPut(cbOFN, OPENFILENAMEW,, "UInt") ; lStructSize
NumPut(A_ScriptHwnd, OPENFILENAMEW, A_PtrSize, "Ptr") ; hwndOwner

filters := [ "Text Documents", "*.txt"
			,"Test",           "*.nfo"
			,"All files",      "*.*"]

if (IsObject(filters) && Mod(filters.MaxIndex(), 2) == 0) {
	finalFilterString := ""
	for _, filter in filters
		finalFilterString .= Mod(A_Index, 2) ? filter . " (" : filter . ")|" . filter . "|"
	
	while ((char := DllCall("ntdll\wcsrchr", "Ptr", &finalFilterString, "UShort", Asc("|"), "CDecl Ptr")))
		NumPut(0, char+0,, "UShort")

	NumPut(&finalFilterString, OPENFILENAMEW, A_PtrSize*3, "Ptr") ; lpstrCustomFilter
	NumPut(1, OPENFILENAMEW, A_PtrSize*(5 + (A_PtrSize == 4)), "UInt") ; nFilterIndex
}

max_path := 260 ; if keeping the option to select multiple files, consider raising the size
VarSetCapacity(vPath, (max_path+2)*2, 0)
NumPut(&vPath, OPENFILENAMEW, A_PtrSize*(6 + (A_PtrSize == 4)), "Ptr") ; lpstrFile
NumPut(max_path, OPENFILENAMEW, A_PtrSize*(7 + (A_PtrSize == 4)), "UInt") ; nMaxFile

NumPut(&(initialDir := A_Desktop), OPENFILENAMEW, A_PtrSize*(10 + (A_PtrSize == 4)), "Ptr") ; lpstrInitialDir
NumPut(&(title := "Open"), OPENFILENAMEW, A_PtrSize*(11 + (A_PtrSize == 4)), "Ptr") ; lpstrTitle
NumPut((cb := RegisterCallback("OFNHookProc")), OPENFILENAMEW, A_PtrSize == 8 ? 120 : 68, "Ptr") ; lpfnHook
NumPut(OFN_EXPLORER := 0x00080000 | OFN_HIDEREADONLY := 0x00000004 | OFN_ALLOWMULTISELECT := 0x00000200 | OFN_ENABLEHOOK := 0x00000020, OPENFILENAMEW, A_PtrSize*(12 + (A_PtrSize == 4)), "UInt") ; Flags

if (DllCall("comdlg32\GetOpenFileNameW", "Ptr", &OPENFILENAMEW)) {
	dirOrFile := StrGet(&vPath,, "UTF-16")
	if (!NumGet(vPath, (StrLen(dirOrFile) + 1) * 2, "UShort")) {
		MsgBox %dirOrFile%
	} else {
		; Multiple files selected
		fileNames := &vPath + (NumGet(OPENFILENAMEW, A_PtrSize == 8 ? 100 : 56, "UShort") * 2)
		while (*fileNames) {
			MsgBox % dirOrFile . "\" . StrGet(fileNames,, "UTF-16")
			fileNames += (DllCall("ntdll\wcslen", "Ptr", fileNames, "CDecl Ptr") * 2) + 2
		}		
	}
	if (ColorSelection) {
		MsgBox % ColorSelection
		ColorSelection := ""
	}
}
DllCall("GlobalFree", "Ptr", cb, "Ptr")
ExitApp

OFNHookProc(hdlg, uiMsg, wParam, lParam)
{
	Critical
	global ColorSelection
	if (uiMsg == 0x0110) { ; WM_INITDIALOG
		dlgWindow := DllCall("GetParent", "Ptr", hdlg, "Ptr")
		if ((filterCb := DllCall("GetDlgItem", "Ptr", dlgWindow, "Int", cmb1 := 0x0470,"Ptr"))) {
			Gui, Add, Text, hwndhColourLabel, Colour:
			filterLabel := DllCall("GetDlgItem", "Ptr", dlgWindow, "Int", stc2 := 0x0441,"Ptr")
			DllCall("SetParent", "Ptr", hColourLabel, "Ptr", dlgWindow, "Ptr")
			ControlGetPos X, Y, Width, Height,, ahk_id %filterLabel%
			ControlMove ,, %X%, % Y + Height + 12, %Width%, %Height%, ahk_id %hColourLabel%

			Gui, Add, DropDownList, hwndhColourSelection vColorSelection, Red||Green|Blue|Black|White
			DllCall("SetParent", "Ptr", hColourSelection, "Ptr", dlgWindow, "Ptr")
			ControlGetPos X, Y, Width, Height,, ahk_id %filterCb%
			ControlMove ,, %X%, % Y + Height + 5, %Width%, %Height%, ahk_id %hColourSelection% ; TODO: determine spacing properly :-)

			DetectHiddenWindows On
			WinGetPos, X, Y, Width, Height2, ahk_id %dlgWindow%
			WinMove ahk_id %dlgWindow%,,,,, % Height2 + Height + 5
			DetectHiddenWindows Off
		}
	} else if (uiMsg == 0x004E) { ; WM_NOTIFY
		CDN := NumGet(lParam+0, A_PtrSize * 2, "UInt")
		if (CDN == 4294966690) { ; CDN_FILEOK
			Gui submit
		}
	} else if (uiMsg == 0x0002) { ; WM_DESTROY
		Gui Destroy
	}
	Critical Off
	return 0
}
The bad news: the typical (I think) way of adding controls to the dialog is to have them already defined in a resource and the dialog box itself will take care of the positioning and sizing for you, as seen with XP's notepad. I don't think I can easily do that from AutoHotkey, so I added the control myself; the code to do is pretty bad and makes a lot of assumptions. Perhaps enlist the help of a GUI expert to determine how to properly get the spacing etc. I don't know anything about GUIs.
I also assume a Unicode build of AutoHotkey is used, but this should "work" with 32-bit and 64-bit builds of such.

Re: FileSelectFile, add controls to the Open/Save As dialog

Posted: 02 Apr 2017, 09:50
by jeeswg
@qwerty12. Excellent work. I've basically got what I want now:

Code: Select all

#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
#SingleInstance force
DetectHiddenWindows, On

VarSetCapacity(OPENFILENAMEW, (cbOFN := A_PtrSize == 8 ? 152 : 88), 0)
NumPut(cbOFN, OPENFILENAMEW,, "UInt") ; lStructSize
NumPut(A_ScriptHwnd, OPENFILENAMEW, A_PtrSize, "Ptr") ; hwndOwner

filters := [ "Text Documents", "*.txt"
			,"Test",           "*.nfo"
			,"All files",      "*.*"]

if (IsObject(filters) && Mod(filters.MaxIndex(), 2) == 0) {
	finalFilterString := ""
	for _, filter in filters
		finalFilterString .= Mod(A_Index, 2) ? filter . " (" : filter . ")|" . filter . "|"

	while ((char := DllCall("ntdll\wcsrchr", "Ptr", &finalFilterString, "UShort", Asc("|"), "CDecl Ptr")))
		NumPut(0, char+0,, "UShort")

	NumPut(&finalFilterString, OPENFILENAMEW, A_PtrSize*3, "Ptr") ; lpstrCustomFilter
	NumPut(1, OPENFILENAMEW, A_PtrSize*(5 + (A_PtrSize == 4)), "UInt") ; nFilterIndex
}

VarSetCapacity(vPath, (260+2)*2, 0) ; if keeping the option to select multiple files, consider raising the size
vPath .= "*.txt"
NumPut(&vPath, OPENFILENAMEW, A_PtrSize*(6 + (A_PtrSize == 4)), "Ptr") ; lpstrFile
NumPut(260, OPENFILENAMEW, A_PtrSize*(7 + (A_PtrSize == 4)), "UInt") ; nMaxFile

initialDir := A_Desktop
title := "Open"
NumPut(&initialDir, OPENFILENAMEW, A_PtrSize*(10 + (A_PtrSize == 4)), "Ptr") ; lpstrInitialDir
NumPut(&title, OPENFILENAMEW, A_PtrSize*(11 + (A_PtrSize == 4)), "Ptr") ; lpstrTitle

NumPut((cb := RegisterCallback("OFNHookProc")), OPENFILENAMEW, A_PtrSize == 8 ? 120 : 68, "Ptr")
NumPut(OFN_EXPLORER := 0x00080000 | OFN_HIDEREADONLY := 0x00000004 | OFN_ALLOWMULTISELECT := 0x00000200 | OFN_ENABLEHOOK := 0x00000020, OPENFILENAMEW, A_PtrSize*(12 + (A_PtrSize == 4)), "UInt") ; Flags

if (DllCall("comdlg32\GetOpenFileNameW", "Ptr", &OPENFILENAMEW)) {
	dirOrFile := StrGet(&vPath,, "UTF-16")
	if (InStr(FileExist(dirOrFile), "D")) {
		; Multiple files selected
		fileNames := &vPath + (NumGet(OPENFILENAMEW, A_PtrSize == 8 ? 100 : 56, "UShort") * 2)
		while (*fileNames) {
			MsgBox % dirOrFile . "\" . (filename := StrGet(fileNames,, "UTF-16"))
			fileNames += (DllCall("ntdll\wcslen", "Ptr", fileNames, "CDecl Ptr") * 2) + 2
		}
	} else {
		MsgBox %dirOrFile%
	}
	if (ColorSelection)
		MsgBox % ColorSelection
}
DllCall("GlobalFree", "Ptr", cb, "Ptr")
return

OFNHookProc(hdlg, uiMsg, wParam, lParam)
{
	global ColorSelection
	if (uiMsg == 0x0110) { ; WM_INITDIALOG

;===============
;some experiments:
;WinGetPos returns width/height values of 0
if 0
{
	WinGetPos, vPosX, vPosY, vPosW, vPosH, % "ahk_id " hdlg
	MsgBox, % vPosX " " vPosY " " vPosW " " vPosH
}

;WinMove at this stage has surprising results
;the resulting dialog box will be than it would have been
;in pixels, by the height value specified
;(note: the dialog height is set in a WinMove line lower down, so that will have to
;be commented out in order to set the height here)
if 0
	WinMove, % "ahk_id " hdlg, , , , 400, 200 ;does not affect width

if 0
{
	vWinStyle := 0x96CC00C4, vWinExStyle := 0x00010101
	WinSet, Style, % vWinStyle, % "ahk_id " hdlg
	WinSet, ExStyle, % vWinExStyle, % "ahk_id " hdlg
}
;===============

WinMove, % "ahk_id " hdlg, , , , , 46 ;resulting dialog box will be 46 pixels taller than it would have been
vPosX1 := 102, vPosY1 := 387, vPosW1 := 60, vPosH1 := 65
vPosX2 := 195, vPosY2 := 385, vPosW2 := 246, vPosH2 := 21

		Gui, Add, Text, x%vPosX1% y%vPosY1% w%vPosW1% h%vPosH1% hWndhCtl1, &Encoding:
                ;h%vPosH2% was affecting the height of the drop-down list
		;note: for 'Gui, Add, DropDownList', h determines the height of the popup list not the ComboBox
		Gui, Add, DropDownList, x%vPosX2% y%vPosY2% w%vPosW2% hwndhCtl2 vColorSelection, ANSI||Unicode|Unicode big endian|UTF-8
		DllCall("SetParent", "Ptr", hCtl1, "Ptr", DllCall("GetParent", "Ptr", hdlg, "Ptr"), "Ptr")
		DllCall("SetParent", "Ptr", hCtl2, "Ptr", DllCall("GetParent", "Ptr", hdlg, "Ptr"), "Ptr")
	} else if (uiMsg == 0x004E) {
		if (NumGet(lParam+0, A_PtrSize * 2, "UInt") == 4294966690) { ; CDN_FILEOK
			Gui submit
		}
	} else if (uiMsg == 0x0002) { ; WM_DESTROY
		Gui Destroy
	}
	return 0
}
You could use WinMerge to see the changes I've made.

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

I used this function to help get the coordinates for controls. Although possibly even this may be off by a few pixels, and so vPosX and vPosY would need subsequent adjustment:

Code: Select all

;hWnd is the parent window hWnd, hCtl is the child control hWnd
JEE_ControlGetPosClient(hWnd, hCtl, ByRef vPosX, ByRef vPosY, ByRef vPosW, ByRef vPosH)
{
	VarSetCapacity(RECT, 16, 0)
	DllCall("GetWindowRect", Ptr,hCtl, Ptr,&RECT)
	DllCall("MapWindowPoints", Ptr,0, Ptr,hWnd, Ptr,&RECT, UInt,2)
	vPosX := NumGet(RECT, 0, "Int"), vPosY := NumGet(RECT, 4, "Int")
	vPosW := NumGet(RECT, 8, "Int")-vPosX, vPosH := NumGet(RECT, 12, "Int")-vPosY
	return
}
Some queries:
- get/set the window/control styles/positions/sizes (what is the earliest point at which this can be done?) [EDIT: you can do it just after if (uiMsg == 0x0110) { ; WM_INITDIALOG]
- where does the system keep information about the dialog's last position/size?
- handle different font sizes [EDIT: you can base the size/position of the custom controls that you add in (e.g. Static and ComboBox) on existing controls in the dialog]
- handle window resize [EDIT: OFN_ENABLESIZING flag (0x00800000)]
- some of the curious results I got in the 'experiments' that I mention in the code [EDIT: see below re. A_DetectHiddenWindows]

[EDIT: even though I'd set DetectHiddenWindows, On in the auto-execute section, it was still Off somehow in the hook procedure, causing confusion and leading to some of the queries]

Btw in Notepad (XP version), which I've copied onto Windows 7, the 'Encoding' control, does not resize, when you resize the window, although the 'File name' and 'Files of type' controls do ... 'pobody's nerfect'. It might be that Notepad adds in the controls like we would!

Re: FileSelectFile, add controls to the Open/Save As dialog

Posted: 02 Apr 2017, 12:35
by qwerty12
jeeswg wrote:I've basically got what I want now
Good to hear :-)
You could use WinMerge to see the changes I've made.
Will do. That GUI in WM_INITDIALOG I uploaded is a mess, always interested to see improvements...
I used this function to help get the coordinates for controls.
Thanks for that. I was going to look at using GetWindowRect (but didn't know of MapWindowPoints) until I looked at AutoHotkey's help.
- get/set the window/control styles/positions/sizes (what is the earliest point at which this can be done?)
Erm, you should be good to go doing it in WM_INITDIALOG, I think. I was able to get the window's size before it was displayed.
where does the system keep information about the dialog's last position/size?
Not sure, sorry (also, I suspect one of my shell enhancements is interfering with the position on my system). Though of course, you can set it yourself in WM_INITDIALOG.
- handle window resize
OR the OFN_ENABLESIZING flag (0x00800000) to enable the user to resize the dialog. The hook procedure mostly likely gets called with a message when resized that you can act upon to move your encoding combobox appropriately.
- some of the curious results I got in the 'experiments' that I mention in the code
;WinGetPos returns width/height values of 0
;WinMove at this stage has surprising results
Presumably, you want to work on the HWND returned by GetParent - hdlg is "A handle to the child dialog box of the Open or Save As dialog box." And as MSDN says "Use the GetParent function to get the handle to the Open or Save As dialog box."
Btw in Notepad (XP version), which I've copied onto Windows 7
Just curious, why? I use Notepad2-mod anyway, but IIRC the 7 and XP versions are identical on the outside.
the 'Encoding' control, does not resize, when you resize the window, although the 'File name' and 'Files of type' controls do ... 'pobody's nerfect'. It might be that Notepad adds in the controls like we would!
You're right. Though XP Notepad does add the control the proper way - if you look at Notepad.exe in a resource editor, you'll see there's a DIALOG resource containing the combobox that's added to the Save dialog.

Re: FileSelectFile, add controls to the Open/Save As dialog

Posted: 02 Apr 2017, 20:58
by jeeswg
Indeed the 2 additional controls are there inside notepad.exe (the XP version) as a resource. And similarly in C:\Windows\System32\en-US\notepad.exe.mui for Windows 7.

I've made my dialog able to handle window resize. I've also made the 2 additional control sizes/positions relative to controls already within the dialog (in case the user uses a big font).

In my txt file of AutoHotkey.chm ...
htm to txt, AutoHotkey Help (chm file) to txt - AutoHotkey Community
https://autohotkey.com/boards/viewtopic ... 95#p132695

... I only see GetWindowRect mentioned once, https://autohotkey.com/docs/commands/DllCall.htm, and MapWindowPoints not at all. I'm not quite sure I understood your sentence.

I now have the core functionality I want, my concerns now are:
- Setting the size of hDlg, not the main window, worked to increase the size of the window to that needed for the additional controls, I'm not sure if there is a better method for this.
- To set the overall window size before it's shown (I can change the position). It's the sort of the thing I'd want to have as a user option in software. There's a comment in the code regarding setting the size in the Init. It just created 'greyspace' ('grayspace') on the dialog's right and bottom.
- I only really wanted the 'Encoding' box for the Save As prompt, not the Open prompt. For the Open prompt, each time a different file is selected, the 'Encoding' ComboBox should be updated to reflect the file. I.e. the first 3 bytes are checked for the presence of a UTF-8/UTF-16 (LE/BE) BOM (byte order mark). This would need to be done on both the new/old dialogs.
- If it exists, to use a cleaner method for positioning/sizing the 2 additional controls (which may be affected by the user choosing a big font).

Code: Select all

#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
#SingleInstance force
DetectHiddenWindows, On

VarSetCapacity(OPENFILENAMEW, (cbOFN := A_PtrSize == 8 ? 152 : 88), 0)
NumPut(cbOFN, OPENFILENAMEW,, "UInt") ; lStructSize
NumPut(A_ScriptHwnd, OPENFILENAMEW, A_PtrSize, "Ptr") ; hwndOwner

filters := [ "Text Documents", "*.txt"
			,"Test",           "*.nfo"
			,"All files",      "*.*"]

if (IsObject(filters) && Mod(filters.MaxIndex(), 2) == 0) {
	finalFilterString := ""
	for _, filter in filters
		finalFilterString .= Mod(A_Index, 2) ? filter . " (" : filter . ")|" . filter . "|"

	while ((char := DllCall("ntdll\wcsrchr", "Ptr", &finalFilterString, "UShort", Asc("|"), "CDecl Ptr")))
		NumPut(0, char+0,, "UShort")

	NumPut(&finalFilterString, OPENFILENAMEW, A_PtrSize*3, "Ptr") ; lpstrCustomFilter
	NumPut(1, OPENFILENAMEW, A_PtrSize*(5 + (A_PtrSize == 4)), "UInt") ; nFilterIndex
}

VarSetCapacity(vPath, (260+2)*2, 0) ; if keeping the option to select multiple files, consider raising the size
vPath .= "*.txt"
NumPut(&vPath, OPENFILENAMEW, A_PtrSize*(6 + (A_PtrSize == 4)), "Ptr") ; lpstrFile
NumPut(260, OPENFILENAMEW, A_PtrSize*(7 + (A_PtrSize == 4)), "UInt") ; nMaxFile

initialDir := A_Desktop
title := "Open"
NumPut(&initialDir, OPENFILENAMEW, A_PtrSize*(10 + (A_PtrSize == 4)), "Ptr") ; lpstrInitialDir
NumPut(&title, OPENFILENAMEW, A_PtrSize*(11 + (A_PtrSize == 4)), "Ptr") ; lpstrTitle

NumPut((cb := RegisterCallback("OFNHookProc")), OPENFILENAMEW, A_PtrSize == 8 ? 120 : 68, "Ptr")
;OFN_ENABLESIZING := 0x00800000
;OFN_EXPLORER := 0x00080000
;OFN_ALLOWMULTISELECT := 0x00000200
;OFN_ENABLEHOOK := 0x00000020
;OFN_HIDEREADONLY := 0x00000004
vOFNFlags := 0x00880224
NumPut(vOFNFlags, OPENFILENAMEW, A_PtrSize*(12 + (A_PtrSize == 4)), "UInt") ; Flags

if (DllCall("comdlg32\GetOpenFileNameW", "Ptr", &OPENFILENAMEW)) {
	dirOrFile := StrGet(&vPath,, "UTF-16")
	if (InStr(FileExist(dirOrFile), "D")) {
		; Multiple files selected
		fileNames := &vPath + (NumGet(OPENFILENAMEW, A_PtrSize == 8 ? 100 : 56, "UShort") * 2)
		while (*fileNames) {
			MsgBox % dirOrFile . "\" . (filename := StrGet(fileNames,, "UTF-16"))
			fileNames += (DllCall("ntdll\wcslen", "Ptr", fileNames, "CDecl Ptr") * 2) + 2
		}
	} else {
		MsgBox %dirOrFile%
	}
	if (ColorSelection)
		MsgBox % ColorSelection
}
DllCall("GlobalFree", "Ptr", cb, "Ptr")
return

;==================================================

OFNHookProc(hdlg, uiMsg, wParam, lParam)
{
DetectHiddenWindows, On
	global ColorSelection
	if (uiMsg == 0x0110) { ; WM_INITDIALOG
		hWndParent := DllCall("GetParent", "Ptr", hdlg, "Ptr")
		ControlGet, hCtl1, Hwnd, , Static4, % "ahk_id " hWndParent
		ControlGet, hCtl2, Hwnd, , ComboBox3, % "ahk_id " hWndParent
		JEE_ControlGetPosClient(hWndParent, hCtl1, vPosX1, vPosY1, vPosW1, vPosH1)
		JEE_ControlGetPosClient(hWndParent, hCtl2, vPosX2, vPosY2, vPosW2, vPosH2)

		WinMove, % "ahk_id " hdlg, , , , , % vPosH2+15
		vPosX1 += 1, vPosY1 += vPosH1+15, vPosW1 *=0.69, vPosH1 *= 5
		vPosY2 += vPosH2+8

		Gui, Add, Text, x%vPosX1% y%vPosY1% w%vPosW1% h%vPosH1% hWndhCtl1, &Encoding:
		;note: for 'Gui, Add, DropDownList', h determines the height of the popup list not the ComboBox
		Gui, Add, DropDownList, x%vPosX2% y%vPosY2% w%vPosW2% hwndhCtl2 vColorSelection, ANSI||Unicode|Unicode big endian|UTF-8
		DllCall("SetParent", "Ptr", hCtl1, "Ptr", hWndParent, "Ptr")
		DllCall("SetParent", "Ptr", hCtl2, "Ptr", hWndParent, "Ptr")

		;resize window - including this line will not resize the window as desired
		;WinMove, % "ahk_id " hWndParent, , 0, 0, 800, 600

		;centre window
		;WinGetPos,,, Width, Height, % "ahk_id " hWndParent
		;WinMove, % "ahk_id " hWndParent,, (A_ScreenWidth/2)-(Width/2), (A_ScreenHeight/2)-(Height/2)
	} else if (uiMsg == 0x004E) {
		if (NumGet(lParam+0, A_PtrSize * 2, "UInt") == 4294966690) { ; CDN_FILEOK
			Gui submit
		}
	} else if (uiMsg == 0x0002) { ; WM_DESTROY
		Gui Destroy
	}
	return 0
}

;==================================================

JEE_ControlGetPosClient(hWnd, hCtl, ByRef vPosX, ByRef vPosY, ByRef vPosW, ByRef vPosH)
{
	VarSetCapacity(RECT, 16, 0)
	DllCall("GetWindowRect", Ptr,hCtl, Ptr,&RECT)
	DllCall("MapWindowPoints", Ptr,0, Ptr,hWnd, Ptr,&RECT, UInt,2)
	vPosX := NumGet(RECT, 0, "Int"), vPosY := NumGet(RECT, 4, "Int")
	vPosW := NumGet(RECT, 8, "Int")-vPosX, vPosH := NumGet(RECT, 12, "Int")-vPosY
	return
}
==================================================

Btw re. Notepad (XP version). I never use it, it's there in case my scripts that grab the current file path from Notepad's address space ever break, subsequent to a Windows Update. Windows 7 did actually update Notepad at least once, but my function still worked, fortunately.

Notepad (XP version) is good to have on a dongle, in case you use a non-English computer. Although perhaps the English version is actually tucked away on such systems. Notepad (Win 7 version) wouldn't copy to the dongle.

Sometimes when on other computers, I have copies of Notepad in folders with ahk files, for quick drag-and-drop for editing. I could use shortcuts instead, but somehow my instinct tells me 'go with the files'.

It's good to have at least one program, that I know uses the old-style dialog, for testing purposes. It's good for the 'Bush hid the facts' error as well.

I like Notepad2, it's nice to hear when people know about it. It uses a Scintilla control. I plan to write my own Notepad, which looks like Notepad, but uses a Scintilla control, and which adds in some of the features I've added to Notepad via AutoHotkey, like ctrl+d copy line above and whole word search.

The WordPad and Paint, XP versions, have advantages over the Windows 7 versions e.g. WordPad XP strips formatting, Paint XP has no Ribbon. Also it is handy to copy other things like the games, I think on Windows 7, some complicated install is needed which I still haven't done. What is Windows without Minesweeper? Also sometimes older software with standard controls is easier to improve / fix / customise / make functional with AutoHotkey.

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

[EDIT:]
Btw I like this notation for dealing with the redundant parameter in for loops, it's the best approach I've seen so far, I hadn't found a choice of name I was yet happy with:
for _, filter in filters

Regarding this, for debugging/checking purposes, I'm now moving to a different approach:
Before:
A_PtrSize*(11 + (A_PtrSize == 4))
After:
(A_PtrSize=8?88:48)
(A_PtrSize=8 ? 88:48)

Although I did discuss the earlier approach here:
key dll issues: calls and structs - Page 2 - AutoHotkey Community
https://autohotkey.com/boards/viewtopic ... 73#p126973

My script tells me the now startlingly obvious fact that:
vSize := 8+10*A_PtrSize

- ways of seeing -

Re: FileSelectFile, add controls to the Open/Save As dialog

Posted: 03 Apr 2017, 08:03
by qwerty12
jeeswg wrote:I've made my dialog able to handle window resize. I've also made the 2 additional control sizes/positions relative to controls already within the dialog (in case the user uses a big font).
Looks good, thanks for sharing :thumbup:
... I only see GetWindowRect mentioned once, https://autohotkey.com/docs/commands/DllCall.htm, and MapWindowPoints not at all. I'm not quite sure I understood your sentence.
Sorry, there was quite a disconnect between what my mind thought I had typed and, well, what I had actually produced. :oops: What I meant to say was that using GetWindowRect to get the controls' positions was what I was originally going to do, until I looked at AutoHotkey's help file and saw it already had built-in commands for retrieving the information I needed - not having to resort to NumGet is nice. Also, since I didn't know MapWindowPoints existed and didn't use it like you did with your function, my GetWindowRect code might not have worked anyway. Not sure.
- To set the overall window size before it's shown (I can change the position). It's the sort of the thing I'd want to have as a user option in software. There's a comment in the code regarding setting the size in the Init. It just created 'greyspace' ('grayspace') on the dialog's right and bottom.
I don't really like this idea, but once WM_SHOWWINDOW (0x18) is sent, you could set a timer to wait for the window and see if resizing it works there. I tried resizing after WM_INITDIALOG through waiting for CDN_INITDONE, but it seems when OFN_ALLOWMULTISELECT is set, my attempts at doing so get ignored, or produce, as you've already seen, a not-so-desired result.
- I only really wanted the 'Encoding' box for the Save As prompt, not the Open prompt.
s/GetOpenFileNameW/GetSaveFileNameW and instant Save As prompt. Though having OFN_ALLOWMULTISELECT set doesn't make sense (and, also, on that note, it seems I edited my post before you made your changes. While the GUI code is still bad in my edit, I did switch to the proper way of determining if multiple files are selected)...
Btw re. Notepad (XP version). I never use it, it's there in case my scripts that grab the current file path from Notepad's address space ever break, subsequent to a Windows Update. Windows 7 did actually update Notepad at least once, but my function still worked, fortunately.
Ah, makes sense. I have an AutoHotkey script on here that uses MinHook on the lstrcmp function to do the same, but it's really convoluted...
I like Notepad2, it's nice to hear when people know about it. It uses a Scintilla control. I plan to write my own Notepad, which looks like Notepad, but uses a Scintilla control, and which adds in some of the features I've added to Notepad via AutoHotkey, like ctrl+d copy line above and whole word search.
Sounds fun! Good luck.
The WordPad and Paint, XP versions, have advantages over the Windows 7 versions e.g. WordPad XP strips formatting, Paint XP has no Ribbon. Also it is handy to copy other things like the games, I think on Windows 7, some complicated install is needed which I still haven't done. What is Windows without Minesweeper? Also sometimes older software with standard controls is easier to improve / fix / customise / make functional with AutoHotkey.
I see. I don't mind Paint with the ribbon personally, but I know where you're coming from - I wrote an installer in AutoHotkey that restores the standard Win32 Sticky Notes program because I hate the UWP/"Modern" contraption MS replaced it with in newer builds of Windows 10. Seconded on the standard controls - I don't like having to use Acc to get text from DirectUI-utilising windows...
Btw I like this notation for dealing with the redundant parameter in for loops, it's the best approach I've seen so far, I hadn't found a choice of name I was yet happy with:
for _, filter in filters
A convention stolen from Python ^-^
Regarding this, for debugging/checking purposes, I'm now moving to a different approach:
Yeah, it was a weird way of doing it, but the OPENFILENAME struct is a big one and being able to duplicate the previous NumPut line and make small changes saved me time.

Re: FileSelectFile, add controls to the Open/Save As dialog

Posted: 03 Apr 2017, 16:29
by jeeswg
I should explain about the Encoding box (although you personally probably know this):
- In Notepad you open/save one file at a time.
- At the Open prompt, when you select a file, the Encoding box updates with the file's encoding. This doesn't happen on the Save As prompt.
- If you change the Encoding on the Open prompt, this allows you to force open the file with the 'wrong' encoding. E.g. open a UTF-8 as ANSI and see the BOM (byte order mark) at the start of the file.
- If you change the Encoding on the Save prompt, this will determine what encoding the file is saved as.
- To replicate the Open prompt behaviour, you would need to be notified every time the file selection is changed, and what the selected file is. So that you can retrieve it's first 3 bytes and set the text accordingly in the Encoding box.
- Initially I only wanted the Encoding box for the Save As prompt, but it might be nice to add it in for the Open prompt as well, if I can handle notifications in both the old-style/new-style dialogs.
s/GetOpenFileNameW/GetSaveFileNameW
Did something happen to your text here?

It would be interesting to know more about your Notepad get path script. Btw here is my JEE_NotepadGetPath function:
notepad get path (get text file path) - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=6&t=30050

Yes, you edited your code after I first copied it. I've incorporated your changes, and turned off OFN_ALLOWMULTISELECT (i.e. I changed one digit in vOFNFlags) because obviously it should be single-select only, but I'd enjoyed playing about with multi-select.

You were getting a bit clever with changing it from:
NumPut(&(initialDir := A_Desktop)
to:
initialDir := A_Desktop
NumPut(&initialDir,
I kept the simpler version, so it's easier to see what's going on and customise the script later.

Yeah, you said what I'd thought you said re. GetWindowRect!

Interesting your use of GetDlgItem, which I didn't know about.

Interesting re. Python and the 'for' loop. Nice.

Code: Select all

#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
#SingleInstance force
DetectHiddenWindows, On

VarSetCapacity(OPENFILENAMEW, (cbOFN := A_PtrSize == 8 ? 152 : 88), 0)
NumPut(cbOFN, OPENFILENAMEW,, "UInt") ; lStructSize
NumPut(A_ScriptHwnd, OPENFILENAMEW, A_PtrSize, "Ptr") ; hwndOwner

filters := [ "Text Documents", "*.txt"
			,"Test",           "*.nfo"
			,"All files",      "*.*"]

if (IsObject(filters) && Mod(filters.MaxIndex(), 2) == 0) {
	finalFilterString := ""
	for _, filter in filters
		finalFilterString .= Mod(A_Index, 2) ? filter . " (" : filter . ")|" . filter . "|"

	while ((char := DllCall("ntdll\wcsrchr", "Ptr", &finalFilterString, "UShort", Asc("|"), "CDecl Ptr")))
		NumPut(0, char+0,, "UShort")

	NumPut(&finalFilterString, OPENFILENAMEW, A_PtrSize*3, "Ptr") ; lpstrCustomFilter
	NumPut(1, OPENFILENAMEW, A_PtrSize*(5 + (A_PtrSize == 4)), "UInt") ; nFilterIndex
}

max_path := 260 ; if keeping the option to select multiple files, consider raising the size
VarSetCapacity(vPath, (max_path+2)*2, 0)
vPath .= "*.txt"
NumPut(&vPath, OPENFILENAMEW, A_PtrSize*(6 + (A_PtrSize == 4)), "Ptr") ; lpstrFile
NumPut(max_path, OPENFILENAMEW, A_PtrSize*(7 + (A_PtrSize == 4)), "UInt") ; nMaxFile

initialDir := A_Desktop
title := "Open"
NumPut(&initialDir, OPENFILENAMEW, A_PtrSize*(10 + (A_PtrSize == 4)), "Ptr") ; lpstrInitialDir
NumPut(&title, OPENFILENAMEW, A_PtrSize*(11 + (A_PtrSize == 4)), "Ptr") ; lpstrTitle
NumPut((cb := RegisterCallback("OFNHookProc")), OPENFILENAMEW, A_PtrSize == 8 ? 120 : 68, "Ptr") ; lpfnHook

;OFN_ENABLESIZING := 0x00800000
;OFN_EXPLORER := 0x00080000
;OFN_ALLOWMULTISELECT := 0x00000200
;OFN_ENABLEHOOK := 0x00000020
;OFN_HIDEREADONLY := 0x00000004
vOFNFlags := 0x00880024
NumPut(vOFNFlags, OPENFILENAMEW, A_PtrSize*(12 + (A_PtrSize == 4)), "UInt") ; Flags

if (DllCall("comdlg32\GetOpenFileNameW", "Ptr", &OPENFILENAMEW)) {
	dirOrFile := StrGet(&vPath,, "UTF-16")
	if (!NumGet(vPath, (StrLen(dirOrFile) + 1) * 2, "UShort")) {
		MsgBox %dirOrFile%
	} else {
		; Multiple files selected
		fileNames := &vPath + (NumGet(OPENFILENAMEW, A_PtrSize == 8 ? 100 : 56, "UShort") * 2)
		while (*fileNames) {
			MsgBox % dirOrFile . "\" . StrGet(fileNames,, "UTF-16")
			fileNames += (DllCall("ntdll\wcslen", "Ptr", fileNames, "CDecl Ptr") * 2) + 2
		}
	}
	if (ColorSelection) {
		MsgBox % ColorSelection
		ColorSelection := ""
	}
}
DllCall("GlobalFree", "Ptr", cb, "Ptr")
ExitApp

;==================================================

OFNHookProc(hdlg, uiMsg, wParam, lParam)
{
	Critical
	DetectHiddenWindows, On
	global ColorSelection
	if (uiMsg == 0x0110) { ; WM_INITDIALOG
		hWndParent := DllCall("GetParent", "Ptr", hdlg, "Ptr")
		ControlGet, hCtl1, Hwnd, , Static4, % "ahk_id " hWndParent
		ControlGet, hCtl2, Hwnd, , ComboBox3, % "ahk_id " hWndParent
		JEE_ControlGetPosClient(hWndParent, hCtl1, vPosX1, vPosY1, vPosW1, vPosH1)
		JEE_ControlGetPosClient(hWndParent, hCtl2, vPosX2, vPosY2, vPosW2, vPosH2)

		WinMove, % "ahk_id " hdlg, , , , , % vPosH2+15
		vPosX1 += 1, vPosY1 += vPosH1+15, vPosW1 *=0.69, vPosH1 *= 5
		vPosY2 += vPosH2+8

		Gui, Add, Text, x%vPosX1% y%vPosY1% w%vPosW1% h%vPosH1% hWndhCtl1, &Encoding:
		;note: for 'Gui, Add, DropDownList', h determines the height of the popup list not the ComboBox
		Gui, Add, DropDownList, x%vPosX2% y%vPosY2% w%vPosW2% hwndhCtl2 vColorSelection, ANSI||Unicode|Unicode big endian|UTF-8
		DllCall("SetParent", "Ptr", hCtl1, "Ptr", hWndParent, "Ptr")
		DllCall("SetParent", "Ptr", hCtl2, "Ptr", hWndParent, "Ptr")

		;resize window - including this line will not resize the window as desired
		;WinMove, % "ahk_id " hWndParent, , 0, 0, 800, 600

		;centre window
		;WinGetPos,,, Width, Height, % "ahk_id " hWndParent
		;WinMove, % "ahk_id " hWndParent,, (A_ScreenWidth/2)-(Width/2), (A_ScreenHeight/2)-(Height/2)
	} else if (uiMsg == 0x004E) { ; WM_NOTIFY
		CDN := NumGet(lParam+0, A_PtrSize * 2, "UInt")
		if (CDN == 4294966690) { ; CDN_FILEOK
			Gui submit
		}
	} else if (uiMsg == 0x0002) { ; WM_DESTROY
		Gui Destroy
	}
	Critical Off
	return 0
}

;==================================================

JEE_ControlGetPosClient(hWnd, hCtl, ByRef vPosX, ByRef vPosY, ByRef vPosW, ByRef vPosH)
{
	VarSetCapacity(RECT, 16, 0)
	DllCall("GetWindowRect", Ptr,hCtl, Ptr,&RECT)
	DllCall("MapWindowPoints", Ptr,0, Ptr,hWnd, Ptr,&RECT, UInt,2)
	vPosX := NumGet(RECT, 0, "Int"), vPosY := NumGet(RECT, 4, "Int")
	vPosW := NumGet(RECT, 8, "Int")-vPosX, vPosH := NumGet(RECT, 12, "Int")-vPosY
	return
}

Re: FileSelectFile, add controls to the Open/Save As dialog

Posted: 03 Apr 2017, 17:34
by qwerty12
jeeswg wrote:- If you change the Encoding on the Open prompt, this allows you to force open the file with the 'wrong' encoding. E.g. open a UTF-8 as ANSI and see the BOM (byte order mark) at the start of the file.
- If you change the Encoding on the Save prompt, this will determine what encoding the file is saved as.
Right, I get that.
- To replicate the Open prompt behaviour, you would need to be notified every time the file selection is changed, and what the selected file is. So that you can retrieve it's first 3 bytes and set the text accordingly in the Encoding box.
In all honesty, for the open file dialog, I'd just add an "Auto" option at the top of the list of encodings which simplifies things but still allows you to force another encoding. Perhaps I'm just lazier.
Initially I only wanted the Encoding box for the Save As prompt, but it might be nice to add it in for the Open prompt as well, if I can handle notifications in both the old-style/new-style dialogs.
In the old-style dialog, in the WM_NOTIFY handler, you can see if CDN == CDN_SELCHANGE. For the new-style one, god, I'm not sure... Presumably you're required to implement a COM interface and that's a pain to do. I'd need help on getting the refcounting part of it correct at the very least :/
Did something happen to your text here?
I had this on my mind, as I was typing that. I was just trying to demonstrate how easy it is to turn that into a save file dialog, instead.
It would be interesting to know more about your Notepad get path script.
It's pretty bad, but here: https://autohotkey.com/boards/viewtopic ... 47#p114047
As long as the behaviour of Notepad passing the full path to the lstrcmp function doesn't change, the script should still continue to work...
Btw here is my JEE_NotepadGetPath function:
notepad get path (get text file path) - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=6&t=30050
Useful script. I prefer your approach - there's a lot of things that can go wrong with what I've written and it's probably much slower.

Re: FileSelectFile, add controls to the Open/Save As dialog

Posted: 07 Dec 2017, 06:25
by jeeswg
- I've since found out that for the Common File Dialog (old-style dialog) code, if you change:
vOFNFlags := 0x880024
to:
vOFNFlags := 0x880004
i.e. remove OFN_ENABLEHOOK (0x20), then you get a Common Item Dialog (new-style dialog) instead, however, the function OFNHookProc will never be called, and the additional controls won't be added.
- If you don't need additional controls, this looks like a good solution to handle both old and new style dialogs. If, however, you do want additional controls, one wonders if you can adapt the code for the Common File Dialog to also work with the Common Item Dialog (or whether for additional controls in a Common Item Dialog, you must use the object approach).

- [EDIT:] @qwerty12:
If you implement the IFileDialogControlEvents interface, It is also possible to get notifications then and there when a combo box etc. item has been selected so that you could, say, change parts of the file dialog on-the-fly,
For the new-style one, god, I'm not sure... Presumably you're required to implement a COM interface and that's a pain to do. I'd need help on getting the refcounting part of it correct at the very least :/
How do these things appear to you now? Still somewhat difficult/challenging, a little easier? I'm thinking of coming back to my Notepad clone project, and so I'm interested in everything Common Item Dialog-related again.
- [EDIT:] Btw with the old-style Common File Dialog, I've been trying to get notified, via SoundBeep, of the WM_NOTIFY messages for the 'Encoding' ComboBox. I tried giving the ComboBox its own WndProc, I tried OnMessage, and I added SoundBeep to the OFNHookProc function (which made a sound whenever the standard ComboBox selection was changed).

Re: FileSelectFile, add controls to the Open/Save As dialog

Posted: 07 Dec 2017, 10:03
by Delta Pythagorean
@qwerty12 You my friend need to make the miniature fileselect dialog (Without the extra controls) into a function and send it to the Scripts and Functions page on these forums. i think a lot of people could use it for more than just using AHK's standard select dialog (With doesn't allow multiple items into the combobox)
:bravo:

Re: FileSelectFile, add controls to the Open/Save As dialog

Posted: 07 Dec 2017, 11:44
by qwerty12
Hi jeeswg & Delta Pythagorean,
jeeswg wrote:How do these things appear to you now? Still somewhat difficult/challenging, a little easier? I'm thinking of coming back to my Notepad clone project, and so I'm interested in everything Common Item Dialog-related again.
I might be able to do it, but do you actually have a need to be notified or is this to sate your curiosity? 'Cause I got to be honest, I'm not inclined to waste time dealing with COM for something that doesn't really have much wortht.
[EDIT:] Btw with the old-style Common File Dialog, I've been trying to get notified, via SoundBeep, of the WM_NOTIFY messages for the 'Encoding' ComboBox. I tried giving the ComboBox its own WndProc, I tried OnMessage, and I added SoundBeep to the OFNHookProc function (which made a sound whenever the standard ComboBox selection was changed).
Did you try attaching a g-label to the control, as per the docs?
Delta Pythagorean wrote:@qwerty12 You my friend need to make the miniature fileselect dialog (Without the extra controls) into a function and send it to the Scripts and Functions page on these forums. i think a lot of people could use it for more than just using AHK's standard select dialog (With doesn't allow multiple items into the combobox)
:bravo:
IMHO this doesn't really have much utility outside of this narrowly specific area - FileSelectFile has the ability to allow the selection of multiple files with its M option, and it uses a more modern dialog to boot. just me's code allows for the even newer Vista file dialog to be used, whereas this is all just ancient stuff...

Re: FileSelectFile, add controls to the Open/Save As dialog

Posted: 07 Dec 2017, 13:51
by jeeswg
- @qwerty12: Sweet. I added gMyFunc here:

Code: Select all

Gui, Add, DropDownList, x%vPosX2% y%vPosY2% w%vPosW2% hwndhCtl2 vColorSelection gMyFunc, ANSI||Unicode|Unicode big endian|UTF-8
And then this amazing function:

Code: Select all

MyFunc()
{
	SoundBeep
}
The proof of concept is complete. I.e. creating a Common File Dialog with extra controls and getting notifications. I'm actually less and less used to using AutoHotkey's default GUI commands since I've been working on my own functions. I had thought that maybe OnMessage would capture those messages, I'm not exactly sure why it didn't.
- Re. adding controls to the Common Item Dialog. I think handling notifications for the Common Item Dialog is important whether or not you use additional controls. Whereas sometimes it's worth investigating things for the sake of curiosity, this is absolutely useful to me, the Common Item Dialog is a modern and useful dialog to know about. I think in IT you always pay the price if you try to cut corners, the aim here is to be able to have versatile dialog boxes which can be used with all software written in AutoHotkey. Ultimately, for interactivity, you could fall back to the Common File Dialog, or at the extreme end, an InputBox that you paste a file path into. I'm trying to create software, including a text editor at least as good as Notepad, plus extra features, potentially moving on to Scintilla controls, after perfecting Edit controls.

Re: FileSelectFile, add controls to the Open/Save As dialog

Posted: 07 Dec 2017, 17:52
by qwerty12
jeeswg wrote:- Re. adding controls to the Common Item Dialog. I think handling notifications for the Common Item Dialog is important whether or not you use additional controls. Whereas sometimes it's worth investigating things for the sake of curiosity, this is absolutely useful to me, the Common Item Dialog is a modern and useful dialog to know about.
Well, right now, your main use for knowing if the encoding has been changed is to produce a beep sound. I'm not saying there aren't uses, but it just seems like in this case, it's more to see the effects. For a choice like the encoding, finding the result once the file has been selected would be OK to me. (Unless, say, you suddenly plan on changing the dialog a bit once the encoding has been selected - something I know Notepad doesn't do.)

The simplest example I have on badly implementing a COM interface is here. (To me, it's the simplest, but it's not necessarily correct. To nobody's surprise, Coco(Belgica), just me and many others are far far better at it.)

I don't really wish to try myself because a) it's a time sink, b) again, I'm not particularly good at it (I quickly put something together through necessity - I needed to do things when I plugged my headphones in) and c) going back to a, I know it's going to be useless to pretty much everybody (including me).

That said, if you need particular explanations about the code I linked you may PM me.

Re: FileSelectFile, add controls to the Open/Save As dialog

Posted: 07 Dec 2017, 21:31
by jeeswg
- This is an interesting point, thinking through, perhaps I don't need notifications from the 'Encoding' ComboBox. [EDIT:] Note: my concern was that I plan to replace the usage of the Gui command, with custom functions, so if I did this, perhaps I would need to receive notifications from the 'Encoding' ComboBox to know what had been selected.
For a choice like the encoding, finding the result once the file has been selected would be OK to me. (Unless, say, you suddenly plan on changing the dialog a bit once the encoding has been selected - something I know Notepad doesn't do.)
- However, I was interested in receiving notifications every time the selected file was changed in an Open prompt (in a Common Item Dialog), so that I could change the selection in the 'Encoding' ComboBox to reflect the file's encoding (like Notepad does).
- Generally it seems to be a useful thing to receive notifications from the Common Item Dialog. Looking for workarounds: can that be done via WndProcs perhaps, rather than objects? What is the earliest that the hDlg can be returned when using a Common Item Dialog? And what about my comment re. removing the 0x20, can the same code be used for both the Common File Dialog and the Common Item Dialog?
- Btw it doesn't matter if there are flaws in the object implementation. If you were willing to help, it could be helpful to give one or two paragraphs, a map of what would be necessary, an estimate of the number of methods/code lines, and one or two examples e.g. with one method as a template, and then I could try to fill in the rest, like we did with Explorer column interaction. Otherwise, I envisage asking tonnes of questions, and possibly struggling to get, or very poorly getting, a foothold onto the interface.

Explorer column interaction (get/set: which appear, width, ascending/descending order) - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=6&t=33129
- I'm trying to finish some of my final AutoHotkey projects, or rather, projects where I'm unfamiliar with aspects of the code.

Re: FileSelectFile, add controls to the Open/Save As dialog

Posted: 08 Dec 2017, 06:14
by qwerty12
jeeswg wrote:- However, I was interested in receiving notifications every time the selected file was changed in an Open prompt (in a Common Item Dialog), so that I could change the selection in the 'Encoding' ComboBox to reflect the file's encoding (like Notepad does).
That's a fair reason and I can justify to myself why I'm spending time on something I don't like or understand (COM) instead of rewatching S01 of Les Revenants ;)

This is a poor implementation of IFileDialogEvents, which can tell you about changes to the selected file(s), not a poor implementation of IFileDialogControlEvents, which can tell you about changes to the controls' values.

Code: Select all

#NoEnv

IFileDialogEvents_new()
{
	vtbl := IFileDialogEvents_Vtbl()
	; VarSetCapacity apparently tries to emulate the peculiarities of stack allocation so use GlobalAlloc here
	fde := DllCall("GlobalAlloc", "UInt", 0x0000, "Ptr", A_PtrSize + 4, "Ptr") ; A_PtrSize to store the pointer to the vtable struct + sizeof unsigned int to store this object's refcount
	if (!fde)
		return 0

	NumPut(vtbl, fde+0,, "Ptr") ; place pointer to vtable in beginning of the IFileDialogEvents structure (you know how all this works from the other side)
	NumPut(1, fde+0, A_PtrSize, "UInt") ; Start with a refcount of one (thanks, just me)

	return fde
}

IFileDialogEvents_Vtbl(ByRef vtblSize := 0)
{
	/* This vtable approach is quite rigid and unflexible in its approach. 
		I mean, ideally, you'd want each object to have its own set of methods that are called.
		With this, however, nope - same methods, just a different "this".
	
		I leave fixing that part up to you. I imagine it involves each object getting its own
		vtable (you could leave all the callback pointers inside the IFileDialogEvents struct after the vtable pointer)
		instead of sharing this one, with the functions to be called for each object determined at creation. Or something like that.
	*/
	static vtable ; This mustn't be freed automatically when it goes out of scope
	if (!VarSetCapacity(vtable)) {
		; Three IUnknown methods that must be implemented, along with the many methods IFileDialogEvents adds on top
		extfuncs := ["QueryInterface", "AddRef", "Release", "OnFileOk", "OnFolderChanging", "OnFolderChange", "OnSelectionChange", "OnShareViolation", "OnTypeChange", "OnOverwrite"]

		; Create IFileDialogEventsVtbl struct
		VarSetCapacity(vtable, extfuncs.Length() * A_PtrSize)

		for i, name in extfuncs
			NumPut(RegisterCallback("IFileDialogEvents_" . name), vtable, (i-1) * A_PtrSize)
	}
	if (IsByRef(vtblSize))
		vtblSize := VarSetCapacity(vtable)
	return &vtable
}

; Called on a "ComObjQuery"
IFileDialogEvents_QueryInterface(this_, riid, ppvObject)
{
	static IID_IUnknown, IID_IFileDialogEvents
	if (!VarSetCapacity(IID_IUnknown))
		VarSetCapacity(IID_IUnknown, 16), VarSetCapacity(IID_IFileDialogEvents, 16)
		,DllCall("ole32\CLSIDFromString", "WStr", "{00000000-0000-0000-C000-000000000046}", "Ptr", &IID_IUnknown)
		,DllCall("ole32\CLSIDFromString", "WStr", "{973510db-7d7f-452b-8975-74a85828d354}", "Ptr", &IID_IFileDialogEvents)

	; If someone calls our QI asking for IUnknown or IFileDialogEvents, then respond by:
	if (DllCall("ole32\IsEqualGUID", "Ptr", riid, "Ptr", &IID_IFileDialogEvents) || DllCall("ole32\IsEqualGUID", "Ptr", riid, "Ptr", &IID_IUnknown)) {
		NumPut(this_, ppvObject+0, "Ptr") ; filling in the pointer to a pointer with the address of this object
		IFileDialogEvents_AddRef(this_)
		return 0 ; S_OK
	}

	; Else
	NumPut(0, ppvObject+0, "Ptr") ; no object for the caller
	return 0x80004002 ; E_NOINTERFACE
}

; Called on an "ObjAddRef"
IFileDialogEvents_AddRef(this_)
{
	; get and increment our reference count member inside the IFileDialogEvents struct
	NumPut((_refCount := NumGet(this_+0, A_PtrSize, "UInt") + 1), this_+0, A_PtrSize, "UInt")
	return _refCount ; new refcount must be returned
}

; Called on an "ObjRelease"
IFileDialogEvents_Release(this_) {
	_refCount := NumGet(this_+0, A_PtrSize, "UInt") ; read current refcount from IFileDialogEvents struct
	if (_refCount > 0) {
		_refCount -= 1 ; decrease it
		NumPut(_refCount, this_+0, A_PtrSize, "UInt") ; store it
		if (_refCount == 0) ; if it's zero, then
			DllCall("GlobalFree", "Ptr", this_, "Ptr") ; it's time for this object to free itself
	}
	return _refCount ; new refcount must be returned
}

IFileDialogEvents_OnFileOk(this_, pfd)
{
	return 0x80004001 ; E_NOTIMPL ("[IFileDialogEvents] methods that are not implemented should return E_NOTIMPL.")
}

IFileDialogEvents_OnFolderChanging(this_, pfd, psiFolder)
{
	return 0x80004001 ; E_NOTIMPL
}

IFileDialogEvents_OnFolderChange(this_, pfd)
{
	return 0x80004001 ; E_NOTIMPL
}

IFileDialogEvents_OnSelectionChange(this_, pfd)
{
	if (DllCall(NumGet(NumGet(pfd+0)+14*A_PtrSize), "Ptr", pfd, "Ptr*", psi) >= 0) { ; IFileDialog::GetCurrentSelection
         GetDisplayName := NumGet(NumGet(psi + 0, "UPtr"), A_PtrSize * 5, "UPtr")
         If !DllCall(GetDisplayName, "Ptr", psi, "UInt", 0x80028000, "PtrP", StrPtr) { ; SIGDN_DESKTOPABSOLUTEPARSING
            SelectedFolder := StrGet(StrPtr, "UTF-16"), DllCall("Ole32.dll\CoTaskMemFree", "Ptr", StrPtr)
			ToolTip % SelectedFolder
		 }
		ObjRelease(psi)
	}
	return 0 ; S_OK
}

IFileDialogEvents_OnShareViolation(this_, pfd, psi, pResponse)
{
	return 0x80004001 ; E_NOTIMPL
}

IFileDialogEvents_OnTypeChange(this_, pfd)
{
	return 0x80004001 ; E_NOTIMPL
}

IFileDialogEvents_OnOverwrite(this_, pfd, psi, pResponse)
{
	return 0x80004001 ; E_NOTIMPL
}

; ---

SelectFolder(fde := 0) {
   ; Common Item Dialog -> msdn.microsoft.com/en-us/library/bb776913%28v=vs.85%29.aspx
   ; IFileDialog        -> msdn.microsoft.com/en-us/library/bb775966%28v=vs.85%29.aspx
   ; IShellItem         -> msdn.microsoft.com/en-us/library/bb761140%28v=vs.85%29.aspx
   Static OsVersion := DllCall("GetVersion", "UChar")
   Static Show := A_PtrSize * 3
   Static SetOptions := A_PtrSize * 9
   Static GetResult := A_PtrSize * 20
   SelectedFolder := ""
   If (OsVersion < 6) { ; IFileDialog requires Win Vista+
      FileSelectFolder, SelectedFolder
      Return SelectedFolder
   }
   If !(FileDialog := ComObjCreate("{DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7}", "{42f85136-db7e-439c-85f1-e4075d135fc8}"))
      Return ""
   VTBL := NumGet(FileDialog + 0, "UPtr")
   DllCall(NumGet(VTBL + SetOptions, "UPtr"), "Ptr", FileDialog, "UInt", 0x00000028, "UInt") ; FOS_NOCHANGEDIR | FOS_PICKFOLDERS
 
	if (fde) {
		DllCall(NumGet(NumGet(FileDialog+0)+7*A_PtrSize), "Ptr", FileDialog, "Ptr", fde, "UInt*", dwCookie := 0)
	}

	showSucceeded := DllCall(NumGet(VTBL + Show, "UPtr"), "Ptr", FileDialog, "Ptr", 0) >= 0

	if (dwCookie)
		DllCall(NumGet(NumGet(FileDialog+0)+8*A_PtrSize), "Ptr", FileDialog, "UInt", dwCookie)

   If (showSucceeded) {
	   If !DllCall(NumGet(VTBL + GetResult, "UPtr"), "Ptr", FileDialog, "PtrP", ShellItem, "UInt") {
         GetDisplayName := NumGet(NumGet(ShellItem + 0, "UPtr"), A_PtrSize * 5, "UPtr")
         If !DllCall(GetDisplayName, "Ptr", ShellItem, "UInt", 0x80028000, "PtrP", StrPtr) ; SIGDN_DESKTOPABSOLUTEPARSING
            SelectedFolder := StrGet(StrPtr, "UTF-16"), DllCall("Ole32.dll\CoTaskMemFree", "Ptr", StrPtr)
         ObjRelease(ShellItem)
      }
   }

   ObjRelease(FileDialog)
   Return SelectedFolder
}

if ((fde := IFileDialogEvents_new())) {
	MsgBox % SelectFolder(fde)
	ObjRelease(fde)
}
can that be done via WndProcs perhaps, rather than objects?
Presumably not without hacks that aren't ensured to work from one Windows version to the next. No point delving into that when documented interfaces exist to avoid just that.
And what about my comment re. removing the 0x20, can the same code be used for both the Common File Dialog and the Common Item Dialog?
I didn't comment on it because IMO if you want the CID then you should call upon the CID API yourself instead of relying on some CFD compatibility code. Checking for Vista and later yourself is easily done.
- Btw it doesn't matter if there are flaws in the object implementation. If you were willing to help, it could be helpful to give one or two paragraphs, a map of what would be necessary, an estimate of the number of methods/code lines, and one or two examples e.g. with one method as a template, and then I could try to fill in the rest, like we did with Explorer column interaction. Otherwise, I envisage asking tonnes of questions, and possibly struggling to get, or very poorly getting, a foothold onto the interface.
I've left some comments for once, but be wary and all that... :)