CreateOpenWithMenu()

Post your working scripts, libraries and tools for AHK v1.1 and older
just me
Posts: 9559
Joined: 02 Oct 2013, 08:51
Location: Germany

CreateOpenWithMenu()

20 May 2016, 09:52

Credits: The whole function is based on this code by querty12. Before I saw this code, I hadn't even heard anything about the related API functions. Thanks for sharing!


Function CreateOpenWithMenu():

Code: Select all

; ==================================================================================================================================
; Creates an 'open with' menu for the passed file.
; Parameters:
;     FilePath    -  Fully qualified path of a single file.
;     Recommended -  Show only recommended apps (True/False).
;                    Default: True
;     ShowMenu    -  Immediately show the menu (True/False).
;                    Default: False
;     MenuName    -  The name of the menu.
;                    Default: OpenWithMenu
;     Others      -  Name of the submenu holding not recommended apps (if Recommended has been set to False).
;                    Default: Others
; Return values:
;     On success the function returns the menu's name unless ShowMenu has been set to True.
;     If the menu couldn't be created, the function returns False.
; Remarks:
;     Requires AHK 1.1.23.07+ and Win Vista+!!!
;     The function registers itself as the menu handler.
; Credits:
;     Based on code by querty12 -> autohotkey.com/boards/viewtopic.php?p=86709#p86709.
;     I hadn't even heard anything about the related API functions before.
; MSDN:
;     SHAssocEnumHandlers -> msdn.microsoft.com/en-us/library/bb762109%28v=vs.85%29.aspx
;     SHCreateItemFromParsingName -> msdn.microsoft.com/en-us/library/bb762134%28v=vs.85%29.aspx
; ==================================================================================================================================
CreateOpenWithMenu(FilePath, Recommended := True, ShowMenu := False, MenuName := "OpenWithMenu", Others := "Others") {
   Static RecommendedHandlers := []
        , OtherHandlers := []
        , HandlerID := A_TickCount
        , HandlerFunc := 0
        , ThisMenuName := ""
        , ThisOthers := ""
   ; -------------------------------------------------------------------------------------------------------------------------------
   Static IID_IShellItem := 0, BHID_DataObject := 0, IID_IDataObject := 0
        , Init := VarSetCapacity(IID_IShellItem, 16, 0) . VarSetCapacity(BHID_DataObject, 16, 0)
          . VarSetCapacity(IID_IDataObject, 16, 0)
          . DllCall("Ole32.dll\IIDFromString", "WStr", "{43826d1e-e718-42ee-bc55-a1e261c37bfe}", "Ptr", &IID_IShellItem)
          . DllCall("Ole32.dll\IIDFromString", "WStr", "{B8C0BD9F-ED24-455c-83E6-D5390C4FE8C4}", "Ptr", &BHID_DataObject)
          . DllCall("Ole32.dll\IIDFromString", "WStr", "{0000010e-0000-0000-C000-000000000046}", "Ptr", &IID_IDataObject)
   ; -------------------------------------------------------------------------------------------------------------------------------
   ; Handler call
   If (Recommended = HandlerID) {
      AssocHandlers := A_ThisMenu = ThisMenuName ? RecommendedHandlers : OtherHandlers
      If (AssocHandler := AssocHandlers[A_ThisMenuItemPos]) && FileExist(FilePath) {
         AssocHandlerInvoke := NumGet(NumGet(AssocHandler + 0, "UPtr"), A_PtrSize * 8, "UPtr")
         If !DllCall("Shell32.dll\SHCreateItemFromParsingName", "WStr", FilePath, "Ptr", 0, "Ptr", &IID_IShellItem, "PtrP", Item) {
            BindToHandler := NumGet(NumGet(Item + 0, "UPtr"), A_PtrSize * 3, "UPtr")
            If !DllCall(BindToHandler, "Ptr", Item, "Ptr", 0, "Ptr", &BHID_DataObject, "Ptr", &IID_IDataObject, "PtrP", DataObj) {
               DllCall(AssocHandlerInvoke, "Ptr", AssocHandler, "Ptr", DataObj)
               ObjRelease(DataObj)
            }
            ObjRelease(Item)
         }
      }
      Try Menu, %ThisMenuName%, DeleteAll
      For Each, AssocHandler In RecommendedHandlers
         ObjRelease(AssocHandler)
      For Each, AssocHandler In OtherHandlers
         ObjRelease(AssocHandler)
      RecommendedHandlers:= []
      OtherHandlers:= []
      Return
   }
   ; -------------------------------------------------------------------------------------------------------------------------------
   ; User call
   If !FileExist(FilePath)
      Return False
   ThisMenuName := MenuName
   ThisOthers := Others
   SplitPath, FilePath, , , Ext
   For Each, AssocHandler In RecommendedHandlers
      ObjRelease(AssocHandler)
   For Each, AssocHandler In OtherHandlers
      ObjRelease(AssocHandler)
   RecommendedHandlers:= []
   OtherHandlers:= []
   Try Menu, %ThisMenuName%, DeleteAll
   Try Menu, %ThisOthers%, DeleteAll
   ; Try to get the default association
   Size := VarSetCapacity(FriendlyName, 520, 0) // 2
   DllCall("Shlwapi.dll\AssocQueryString", "UInt", 0, "UInt", 4, "Str", "." . Ext, "Ptr", 0, "Str", FriendlyName, "UIntP", Size)
   HandlerID := A_TickCount
   HandlerFunc := Func(A_ThisFunc).Bind(FilePath, HandlerID)
   Filter := !!Recommended ; ASSOC_FILTER_NONE = 0, ASSOC_FILTER_RECOMMENDED = 1
   ; Enumerate the apps and build the menu
   If DllCall("Shell32.dll\SHAssocEnumHandlers", "WStr", "." . Ext, "UInt", Filter, "PtrP", EnumHandler)
      Return False
   EnumHandlerNext := NumGet(NumGet(EnumHandler + 0, "UPtr"), A_PtrSize * 3, "UPtr")
   While (!DllCall(EnumHandlerNext, "Ptr", EnumHandler, "UInt", 1, "PtrP", AssocHandler, "UIntP", Fetched) && Fetched) {
      VTBL := NumGet(AssocHandler + 0, "UPtr")
      AssocHandlerGetUIName := NumGet(VTBL + 0, A_PtrSize * 4, "UPtr")
      AssocHandlerGetIconLocation := NumGet(VTBL + 0, A_PtrSize * 5, "UPtr")
      AssocHandlerIsRecommended := NumGet(VTBL + 0, A_PtrSize * 6, "UPtr")
      UIName := ""
      If !DllCall(AssocHandlerGetUIName, "Ptr", AssocHandler, "PtrP", StrPtr, "UInt") {
         UIName := StrGet(StrPtr, "UTF-16")
         DllCall("Ole32.dll\CoTaskMemFree", "Ptr", StrPtr)
      }
      If (UIName <> "") {
         If !DllCall(AssocHandlerGetIconLocation, "Ptr", AssocHandler, "PtrP", StrPtr, "IntP", IconIndex := 0, "UInt") {
            IconPath := StrGet(StrPtr, "UTF-16")
            DllCall("Ole32.dll\CoTaskMemFree", "Ptr", StrPtr)
         }
         If (SubStr(IconPath, 1, 1) = "@") {
            VarSetCapacity(Resource, 4096, 0)
            If !DllCall("Shlwapi.dll\SHLoadIndirectString", "WStr", IconPath, "Ptr", &Resource, "UInt", 2048, "PtrP", 0)
               IconPath := StrGet(&Resource, "UTF-16")
         }
         ItemName := StrReplace(UIName, "&", "&&")
         If (Recommended || !DllCall(AssocHandlerIsRecommended, "Ptr", AssocHandler, "UInt")) {
            If (UIName = FriendlyName) {
               If RecommendedHandlers.Length() {
                  Menu, %ThisMenuName%, Insert, 1&, %ItemName%, % HandlerFunc
                  RecommendedHandlers.InsertAt(1, AssocHandler)
               }
               Else {
                  Menu, %ThisMenuName%, Add, %ItemName%, % HandlerFunc
                  RecommendedHandlers.Push(AssocHandler)
               }
               Menu, %ThisMenuName%, Default, %ItemName%
            }
            Else {
               Menu, %ThisMenuName%, Add, %ItemName%, % HandlerFunc
               RecommendedHandlers.Push(AssocHandler)
            }
            Try Menu, %ThisMenuName%, Icon, %ItemName%, %IconPath%, %IconIndex%
         }
         Else {
            Menu, %ThisOthers%, Add, %ItemName%, % HandlerFunc
            OtherHandlers.Push(AssocHandler)
            Try Menu, %ThisOthers%, Icon, %ItemName%, %IconPath%, %IconIndex%
         }
      }
      Else
         ObjRelease(AssocHandler)
   }
   ObjRelease(EnumHandler)
   ; All done
   If !RecommendedHandlers.Length() && !OtherHandlers.Length()
      Return False
   If OtherHandlers.Length()
      Menu, %ThisMenuName%, Add, %ThisOthers%, :%ThisOthers%
   If (ShowMenu)
      Menu, %ThisMenuName%, Show
   Else
      Return ThisMenuName
}
Sample script:

Code: Select all

#NoEnv
SetBatchLines, -1
FilePath := ""
Loop, Files, %A_WinDir%\Web\Wallpaper\*.jpg, R
   FilePath := A_LoopFileLongPath
Until FilePath
Menu, MyMenu, Add, Open with, % ":" . CreateOpenWithMenu(FilePath)
Menu, MyMenu, Show
ExitApp
Last edited by just me on 04 Jun 2016, 23:53, edited 1 time in total.
zcooler
Posts: 455
Joined: 11 Jan 2014, 04:59

Re: CreateOpenWithMenu()

28 May 2016, 13:03

Great stuff, Just Me :D
The thing I was struggeling hardest with was how to add a menu separator line between associated apps and Others := CM_ChooseOtherApp submenu by calling the function. Had to add the separator line inside the function in the end. IMHO I think that looks nicer :)

Also, the ItemName := StrReplace(UIName, "@", "@@") should be ampersands. I have quite many Apps with em inside the App name and the ampersands are not showing up on the menu. While it does if changing the @.

No biggie, but I'll also ask the same question to you as I did to the inspirator qwerty12. Icons for Metro Apps looks rather ragged around the edges and with wrong background color, well when study them closely the shell seem to use a completely different iconset. Do you know if it might be achievable to fetch those icons instead?

Thanks :wave:
IMEime
Posts: 750
Joined: 20 Sep 2014, 06:15

Re: CreateOpenWithMenu()

28 May 2016, 20:08

Nice.. Thanks guys..
zcooler
Posts: 455
Joined: 11 Jan 2014, 04:59

Re: CreateOpenWithMenu()

31 May 2016, 08:40

I did encounter a bug. Most of the times the OpenWithMenu is loaded fine. Sometimes I get this errormessage right before Menu should be loaded:
---------------------------
Step.ahk
---------------------------
Error: Submenu does not exist.

Specifically: 0

Line#
919: Menu,TreeviewContextMenu,Add,%CM_Play% %KS_Play%,PlayFile
920: if (NotepadPlusPlus)
920: {
921: Menu,TreeviewContextMenu,Add,%CM_NotePadPlusPlus%,Notepad
922: Menu,TreeviewContextMenu,Icon,%CM_NotePadPlusPlus%,%NotepadPlusPlus%
923: }
924: Menu,TreeviewContextMenu,Add
---> 925: Menu,TreeviewContextMenu,Add,%CM_OpenWith%,":" . CreateOpenWithMenu(FilePath, Recommended := False,,, Others := CM_ChooseOtherApp)
926: Menu,TreeviewContextMenu,Add
927: Menu,TreeviewContextMenu,Add,%CM_Cut% Ctrl+X,CutFile
928: Menu,TreeviewContextMenu,Add,%CM_Copy% Ctrl+C,CopyFile
929: Menu,TreeviewContextMenu,Add,%CM_Paste% Ctrl+V,PasteFile
930: Menu,TreeviewContextMenu,Add
931: Menu,TreeviewContextMenu,Add,%CM_Delete% DEL,DeleteFile
932: Menu,TreeviewContextMenu,Add,%CM_SelectAll% Ctrl+A,SelectAll

The current thread will exit.
---------------------------
OK
---------------------------
Call line is: Menu, TreeviewContextMenu, Add, %CM_OpenWith%, % ":" . CreateOpenWithMenu(FilePath, Recommended := False,,, Others := CM_ChooseOtherApp)
just me
Posts: 9559
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: CreateOpenWithMenu()

05 Jun 2016, 00:17

zcooler wrote:Also, the ItemName := StrReplace(UIName, "@", "@@") should be ampersands.
Thanks for reporting. It's a typo, I changed the script in the OP.
zcooler wrote:Icons for Metro Apps looks rather ragged around the edges and with wrong background color, well when study them closely the shell seem to use a completely different iconset.
I have no idea what could be changed. The script is using the icon path provided by the assoc handler. The shell might use other options depending on the kind of application.
zcooler wrote:I did encounter a bug. ...
That's no bug. If the function fails to create a menu it returns 0 (-> "Specifically: 0"). Some of the 'associated apps' don't provide a 'UIName'. I wasn't sure what to do in this case, so I did the same as querty12 and skipped them. If the apps are flagged as 'recommended', it might be safe to use the file name part of the path returned by the GetName method of the IAssocHandler interface instead.
zcooler
Posts: 455
Joined: 11 Jan 2014, 04:59

Re: CreateOpenWithMenu()

05 Jun 2016, 02:45

just me wrote:That's no bug. If the function fails to create a menu it returns 0 (-> "Specifically: 0"). Some of the 'associated apps' don't provide a 'UIName'. I wasn't sure what to do in this case, so I did the same as querty12 and skipped them. If the apps are flagged as 'recommended', it might be safe to use the file name part of the path returned by the GetName method of the IAssocHandler interface instead.
Hello Just me! I got a chance to do some comparative testing between the qwerty12 version and yours. This error never shows up in the qwerty12 version. The error triggers when rightclicking an empty area of the TreeView. In those cases the OpenWithMenu in the qwerty12 version, just contained the Choose Other App submenu item (which if pressed opens up the SHOpenWithDialog) since there are no associated apps to find for empty ItemIDs/FilePaths. In your version I had to remove Choose Other App submenu and label and use the Others param instead to create the corresponding submenu (which displays other apps) with your function, which was problematic cuz the FilePath can be empty clicking blank areas.

Regards
zcooler :wave:
Johnny R
Posts: 348
Joined: 03 Oct 2013, 02:07

Re: CreateOpenWithMenu()

05 Jun 2016, 10:17

[Solved]
Opening a mp4-file, I have a problem in the row 113 of the script above:

Code: Select all

Error in #include file "A:\System\AHK\Library.ahk":
     Target label does not exist.
Specifically: VLC Media Player Portable
--->	xxx: Menu,%ThisMenuName%,Insert,1&,%ItemName%,HandlerFunc
VLC Media Player Portable v2.2.1 is my standard-program for mp4-files.

EDIT:
The new version 2.2.3 solved the problem.
raylan
Posts: 12
Joined: 29 Dec 2015, 06:30

Re: CreateOpenWithMenu()

06 Nov 2016, 04:50

nice Function
zcooler
Posts: 455
Joined: 11 Jan 2014, 04:59

Re: CreateOpenWithMenu()

24 Mar 2017, 09:26

Hello Just me :wave:

Im having some issues with the CreateOpenWithMenu() giving me peculiar Context Menu quirks. Not sure if its due to I messed up the implementation using it or if it has a bug. A little description of the issue:

If open the context menu and choose Edit. When editing the filename and rightclick some other TVitem, while
still in EdIT mode. When the menu shows up, I click Edit again, then quite often two things happens when it goes wrong.
1. TV goes into edit mode, but surprisingly the context menu can pop up again a thenth of a second later for unknown reasons, TV is still in edit mode which is slightly visible behind the context menu.
2. When the menu wrongly pops up like that it either stays in its correct %A_GuiX% %A_GuiY% position, but with four empty lines in the bottom of the menu.
3. In the other case the whole menu leaves the %A_GuiX% %A_GuiY% positions entirely and pops up way outside the TV (the four empty lines here too).

Trying to fix it I have moved around the "ObjRelease - Free the OpenWith handlers we kept after the menu is displayed" code between my Menu labels, but to no avail. EDIT: This code is from the qwerty12 version and doesnt do anything since Im no longer are using the handlers object :oops: So it does not have any effect on the present issue.

When disable the CreateOpenWithMenu function call within the ContextMenu: label completely these quirks can no longer be observed.

If you wanna look into it, here is the context menu labels im using:

Code: Select all

Gui2GuiContextMenu: ; <<<<<Step3.8 - changed
InEditMode := False
GoSub, TV_ToolTip_Off
If (A_GuiEvent = "RightClick") && (A_GuiControl = "TVVar") ; <<<<<20151203
{
   ItemID := A_EventInfo ; <<<<<Step3.9.2 - re-added for Paste
   FilePath := TVCache[ItemID, "Path"]
   SplitPath, FilePath,,, Extension
   Menu, TreeviewContextMenu, Add
   Menu, TreeviewContextMenu, DeleteAll
   Menu, TreeviewContextSubMenu1, Add
   Menu, TreeviewContextSubMenu1, DeleteAll
   Menu, TreeviewContextSubMenu2, Add
   Menu, TreeviewContextSubMenu2, DeleteAll
   GoSub, ContextMenu
   If (Extension = "ahk") {
	  Menu, TreeviewContextMenu, Insert, %CM_Play%`t%KS_Play%, %CM_EditScript%, EditScript
	  Menu, TreeviewContextMenu, Insert, %CM_EditScript%, %CM_CompileScript%, CompileScript
	  Menu, TreeviewContextMenu, Insert, %CM_CompileScript%, %CM_RunScript%, RunScript
	  Menu, TreeviewContextMenu, Delete, %CM_Play%`t%KS_Play%
   }
   If (Clipboard = "")
      Menu, TreeviewContextMenu, Disable, %CM_Paste%`tCtrl+V
   Else
      Menu, TreeviewContextMenu, Enable, %CM_Paste%`tCtrl+V
   If (TVSel.Count > 1) {
	  Menu, TreeviewContextMenu, Disable, %CM_Edit%`tF2
      Menu, TreeviewContextMenu, Disable, %CM_New%
   }
   Else {
      Menu, TreeviewContextMenu, Enable, %CM_Edit%`tF2
      Menu, TreeviewContextMenu, Enable, %CM_New%
   }
   If (IsUndoHistory)  ; UndoHistory still contains Undo operations 
     Menu, TreeviewContextMenu, Enable, %CM_Undo%`tCtrl+Z
   Else                     ; UndoHistory is empty
     Menu, TreeviewContextMenu, Disable, %CM_Undo%`tCtrl+Z
   Menu, TreeviewContextMenu, Show, %A_GuiX%, %A_GuiY%
   ; Free the OpenWith handlers we kept after the menu is displayed
   for index, element in handlers
	  ObjRelease(element), handlers[index] := 0
}
If !(InEditMode)
   ShowTooltip := Settings.ToolTip
Return
; ----------------------------------------------------------------------------------------------------------------------
ContextMenu:
Menu, TreeviewContextMenu, Add, %CM_Play%`t%KS_Play%, PlayFile
If (NotepadPlusPlus) {
  Menu, TreeviewContextMenu, Add, %CM_NotePadPlusPlus%, Notepad
  Menu, TreeviewContextMenu, Icon, %CM_NotePadPlusPlus%, %NotepadPlusPlus%
}
Menu, TreeviewContextMenu, Add
;If (FilePath) { ; Create the OpenWithMenu only if an item is clicked. Clicking empty areas no menu.
;  Menu, TreeviewContextMenu, Add, %CM_OpenWith%, % ":" . CreateOpenWithMenu(FilePath, Recommended := False,,, Others := CM_ChooseOtherApp)
;  Menu, TreeviewContextMenu, Add
;}
Menu, TreeviewContextMenu, Add, %CM_Cut%`tCtrl+X, CutFile
Menu, TreeviewContextMenu, Add, %CM_Copy%`tCtrl+C, CopyFile
Menu, TreeviewContextMenu, Add, %CM_Paste%`tCtrl+V, PasteFile
Menu, TreeviewContextMenu, Add
Menu, TreeviewContextMenu, Add, %CM_Delete%`tDEL, DeleteFile
Menu, TreeviewContextMenu, Add, %CM_SelectAll%`tCtrl+A, SelectAll
Menu, TreeviewContextMenu, Add, %CM_Edit%`tF2, EditFile
Menu, TreeviewContextMenu, Add, %CM_Undo%`tCtrl+Z, Undo
Menu, TreeviewContextMenu, Add
Menu, TreeviewContextSubMenu2, Add, %CMS_Folder%`tCtrl+Shift+N, NewFolder
If FileExist(FolderImage)
  Menu, TreeviewContextSubMenu2, Icon, %CMS_Folder%`tCtrl+Shift+N, %FolderImage%
Menu, TreeviewContextSubMenu2, Add
Menu, TreeviewContextSubMenu2, Add, %CMS_Shortcut%, Shortcut
If FileExist(ShortcutImage)
  Menu, TreeviewContextSubMenu2, Icon, %CMS_Shortcut%, %ShortcutImage%
Menu, TreeviewContextMenu, Add, %CM_New%, :TreeviewContextSubMenu2
Menu, TreeviewContextMenu, Add
Menu, TreeviewContextMenu, Add, %CM_Properties%`tAlt+%KS_Play%, Properties
Return
Regards
zcooler
zcooler
Posts: 455
Joined: 11 Jan 2014, 04:59

Re: CreateOpenWithMenu()

25 Mar 2017, 03:47

When disable the CreateOpenWithMenu function call within the ContextMenu: label completely these quirks can no longer be observed.
No, sorry I was mistaken :oops: The menu pops up in the wrong sequence/place even if commenting out the CreateOpenWithMenu() call. It just took a lot of tries before it happened. This problem is due to something else in my script :(

Return to “Scripts and Functions (v1)”

Who is online

Users browsing this forum: No registered users and 75 guests