Implement Explorer ContextMenu and pass multiple files to one program instance
Asked Answered
S

4

27

Situation

I have a 3rd party GUI application that accepts multiple files via CLI, for example:

MyProgram.exe "file1" "file2"

Then all the files are loaded at once into the same instance of the application.

To optimize my time I would like to load multiple files by doing right-mouse-click on some files from Windows Explorer (eg: Select 5 files > do right-click > select "Open in MyProgram" command)

I know how to create the needed registry keys to add that command in the context menu for specific file types, that is not a problem.

Problem

This 3rd party program does not comes with any driver, shell extension, or methodology that can catch multiple files from contextmenu, so instead of that If I select 2 files from explorer, each file is open in a separated instance of the program, and I don't have idea of developing drivers, so a driver is not what I'm looking for.

Focus

I'm open to suggestions, maybe this is not the efficient way but seems the easiest way:

My idea is to develop a mini CLI application to catch those multiple files (maybe based in windows messages or in SO inactivity, I don't know that's why I'm asking), write those files/arguments in a text file then join all the arguments in a single line to call my 3rd party program with those arguments to load all the files at once in a single instance of this program.

In other words, just a simple loader to use it from the contextmenu when selecting multiple files to open all the files at once in this 3rd party application.

Question

First of all I would like to know if exists a known term to name this thing of an application that is capable to load multiple files in the same instance selecting the files from explorer then contextmenu. I would like to research for that term.

Which could be the most efficient way to accomplish this task under a VB.NET/C# console application? (not a driver)

How to start developing this?

Any existent source-code example from known pages like codeproject...?

Sculpture answered 23/11, 2014 at 11:38 Comment(11)
The search term you're looking for is ".net 4 explorer context menu".Sterculiaceous
@Andrew Morton well, that is not a term, but thankyou for your help, anyways searching by that search pattern I only find info about how to add items/commands into the contextmenu... nothing interesting, I've already learned those things.Sculpture
I must have my google well-trained, as the first item found is .NET Shell Extensions - Shell Context Menus.Sterculiaceous
Yes, as I said is nothing interesting, or at least I can't percibe if that is what I really need to accomplish this (sorry for my ignorance if that is the case), because in that example only seems to talk about how to add/register items into contextmenu. thanksSculpture
I don't understand the problem: that article gives a complete example of creating a program which you can invoke by right-clicking in Explorer. All you need to do is write a program which does what you want instead of the example's "CountLines()".Sterculiaceous
@Andrew Morton I've deeped more on that article and on the library, yes you are right it does what I need (open multiple files at once in the same instance) i've tested and it works perfect, but the lib is focused to create the ENTIRE contextmenu with submenus and its items and also messing with COM install/registering and really hard things that I don't understand at all. Of course that seems the most efficient way, and thanks for that, but for me is enough to perform something more simple like the answer that I've marked as accepted. thanks again.Sculpture
it might help to know what app/utility the solution is supposed to work with.Picrotoxin
If you want an Explorer menu you need a ShellExtension. This will also associate the "Open" verb with the file type and your Shell. Then rather than doing anything, you create a command line and activate The Other Thing with it. The MS-PL one in VB is fairly simple the only trick may be in deployment because your ShellExt will need to know where the real applet is.Picrotoxin
@Plutonix Is not 100% necessary to register a ShellExtension, I can add few registry keys to create a contextmenu with its submenus. Anyways creating the menu is not a problem, the problem is select multiple files and open them in the same application instance from that contextmenu.Sculpture
I've tested SharpShell project and it does what I need (it can catch multiple files) but it does not support transparency images, and the author said its a pain to add images or to positionate the registered menu, I think a ShellExtension is a very "huge" and S.O. intrusive solution for my "tiny" issue.Sculpture
@Plutonix I didn't noted your other comment, sorry. I think it does not matter and can't help to know the application utility, I just would like to catch all the selected files to pass it a a single-line argument of a 3rd party app, just what the disadvantaged code of my answer below in this question does.Sculpture
P
47

You Do want a ShellExtension

What you want is not quite as simple as you think. The normal behavior for multiple file selections is to open each in a new Window/App instance. Actually, it just sends the selected files to the registered app and leaves it up to the app to decide how to work with them.

There is at least 1 quick and easy alternative though:

Method 1: Use Send-To

Open the Send To folder ("C:\Users\YOURNAME\AppData\Roaming\Microsoft\Windows\SendTo") and add an entry for the app. The target would be the app you wish to feed/send file selection to:

"C:\Program Files\That Other App\OtherApp.exe "

You don't need "%1" placeholders or anything else. You don't have to write an intermediary to do anything, just send the files directly to the actual app. It will work fine, as long as the app will accept more than one file on the command line.

The only minor thing is that it resides on a "shared" or general sub menu rather than a top level context menu. It is also not "smart" in so far as it is available for any file extension unlike a proper ContextMenu handler, but it is a quick and easy, no-code solution which has been around for a long time.


Method 2: Change the Verb Qualifier

You can also change the verb qualifier/mode, which sounds like the easiest way. Take for instance, VideoLan's VLC player:

If you click on multiple .MP4 files rather than open multiple instances, it opens with one of them and the rest are queued for play. This is done by modifying the verb in the registry:

+ VLC.MP4
   + shell    
       + Open   
           -  MultiSelectModel = Player
           + Command    
             - (Default) "C:\Program Files.... %1"
             

MultiSelectModel is a modifier for the Open verb:

  • Single for verbs that support only a single item
  • Player for verbs that support any number of items
  • Document for verbs which create a top level window for each item

For my MediaProps applet, since it is concerned with the same file types, I piggybacked my verb onto the file types of VLC by adding a ViewProps verb which was set as MultiSelectModel.Player and generally worked in so far as my verbs did not confuse VLC.

Unfortunately, there is still something amiss that I have not yet identified. Windows seems like it still is not gluing all the files together as expected - even if I make my own verbs. There is a step missing either in the registry config or with the app -- but with two other ways to do the same thing, I have never investigated further.


Method 3: Create ShellExtension / ContextMenu Handler

Many proposed solutions end up being a game of Whack-a-Mole where you have to fix the same 1 file-1 instance problem in an intervening app so it can feed concatenated arguments to the final actor. Since the end result is to have an Explorer ContextMenu to do something useful, lets just build a ShellExtension for this other application.

This is easy because a framework is already done and available on CodeProject: How to Write Windows Shell Extension with .NET Languages. This is an MS-PL article complete with a finished ShellExtension project.

With a few modifications, this will work perfectly to:

  • setup associations for multiple file types
  • collect multiple files clicked
  • format them into a command line arg set
  • pass the commandline to the actual worker app
  • provide a custom ContentMenu
  • display a snazzy menu icon

The test bed for this is an applet to display the MediaInfo properties of media files (things like Duration, Frame Size, Codec, format etc). In addition to accepting Dropped files, it uses a ContextMenu DLL helper to accept multiple files selected in Explorer and feed them to the Single Instance display app.


Very Important Note

Since this was first posted, I have revised and updated the original MS-PL article making it much easier to use. The revision is also at CodeProject Explorer Shell Extensions in .NET (Revised) and still contains a VB and C# version.

In the revised version, rather than having to make changes here and there, they are consolidated to a single block of variables. The article also explains why you might want to use the C# version, and provides links to articles explaining why it is not a good idea to use managed code for Shell Extensions.

The 'model' remains that of a Shell Extension to simply launch a related app.

The balance of this answer is still worth reading for the general concepts and background. It doesn't seem right to change it well after the fact even though much of the Code Changes section doesn't apply to the revision.


1. Update the Assembly/Project Values

For instance, I changed the assembly name to "MediaPropsShell". I also removed the root namespace but that is optional.

Add a PNG icon of your choosing.

Pick the appropriate platform. Since the original has 2 installers, you may have to specifically build an x86 version for a 32bit OS. AnyCPU works fine for 64bit OS, I'm not sure about x86. Most systems which use this model supply a 32 and 64 bit DLL for the shell extension helper, but most in the past could not be NET based either where AnyCPU is an option.

Keep the target platform as NET 4. If you did not read the CodeProject article or have not researched this previously, this is important.

2. Code changes

As published on CodeProject, the handler also only passes one file and associates itself with only one file type. The code below implements the handler for multiple file types. You will also want to fix the menu names and so forth. All the changes are noted in the code below prefaces with {PL}:

' {PL} - change the GUID to one you create!
<ClassInterface(ClassInterfaceType.None),
Guid("1E25BCD5-F299-496A-911D-51FB901F7F40"), ComVisible(True)>

Public Class MediaPropsContextMenuExt    ' {PL} - change the name
    Implements IShellExtInit, IContextMenu

    ' {PL} The nameS of the selected file
    Private selectedFiles As List(Of String)

    ' {PL} The names and text used in the menu
    Private menuText As String = "&View MediaProps"
    Private menuBmp As IntPtr = IntPtr.Zero
    Private verb As String = "viewprops"
    Private verbCanonicalName As String = "ViewMediaProps"
    Private verbHelpText As String = "View Media Properties"

    Private IDM_DISPLAY As UInteger = 0
    
    Public Sub New()
        ' {PL} - no NREs, please
        selectedFiles = New List(Of String)

        ' Load the bitmap for the menu item.
        Dim bmp As Bitmap = My.Resources.View         ' {PL} update menu image

        ' {PL} - not needed if you use a PNG with transparency (recommended):
        'bmp.MakeTransparent(bmp.GetPixel(0, 0))
        Me.menuBmp = bmp.GetHbitmap()
    End Sub

    Protected Overrides Sub Finalize()
        If (menuBmp <> IntPtr.Zero) Then
            NativeMethods.DeleteObject(menuBmp)
            menuBmp = IntPtr.Zero
        End If
    End Sub

    ' {PL} dont change the name (see note)
    Private Sub OnVerbDisplayFileName(ByVal hWnd As IntPtr)

        '' {PL} the command line, args and a literal for formatting
        'Dim cmd As String = "C:\Projects .NET\Media Props\MediaProps.exe"
        'Dim args As String = ""
        'Dim quote As String = """"

        '' {PL} concat args
        For n As Integer = 0 To selectedFiles.Count - 1
            args &= String.Format(" {0}{1}{0} ", quote, selectedFiles(n))
        Next

        ' Debug command line visualizer
        MessageBox.Show("Cmd to execute: " & Environment.NewLine & "[" & cmd & "]", "ShellExtContextMenuHandler")

        '' {PL} start the app with the cmd line we made
        'If selectedFiles.Count > 0 Then
        '    Process.Start(cmd, args)
        'End If

    End Sub
    
#Region "Shell Extension Registration"

    ' {PL} list of media files to show this menu on (short version)
    Private Shared exts As String() = {".avi", ".wmv", ".mp4", ".mpg", ".mp3"}

    <ComRegisterFunction()> 
    Public Shared Sub Register(ByVal t As Type)
        ' {PL}  use a loop to create the associations
        For Each s As String In exts
            Try
                ShellExtReg.RegisterShellExtContextMenuHandler(t.GUID, s,
                    "MediaPropsShell.MediaPropsContextMenuExt Class")
            Catch ex As Exception
                Console.WriteLine(ex.Message) 
                Throw ' Re-throw the exception
            End Try
        Next

    End Sub

    <ComUnregisterFunction()> 
    Public Shared Sub Unregister(ByVal t As Type)
        ' {PL}  use a loop to UNassociate
        For Each s As String In exts
            Try
                ShellExtReg.UnregisterShellExtContextMenuHandler(t.GUID, s)
            Catch ex As Exception
                Console.WriteLine(ex.Message) ' Log the error
                Throw ' Re-throw the exception
            End Try
        Next
    End Sub

#End Region

Just below a bit needs to be changed in the IShellExtInit Members REGION as well:

Public Sub Initialize(pidlFolder As IntPtr, pDataObj As IntPtr,
      hKeyProgID As IntPtr) Implements IShellExtInit.Initialize

    If (pDataObj = IntPtr.Zero) Then
        Throw New ArgumentException
    End If

    Dim fe As New FORMATETC
    With fe
        .cfFormat = CLIPFORMAT.CF_HDROP
        .ptd = IntPtr.Zero
        .dwAspect = DVASPECT.DVASPECT_CONTENT
        .lindex = -1
        .tymed = TYMED.TYMED_HGLOBAL
    End With

    Dim stm As New STGMEDIUM

    ' The pDataObj pointer contains the objects being acted upon. In this 
    ' example, we get an HDROP handle for enumerating the selected files 
    ' and folders.
    Dim dataObject As System.Runtime.InteropServices.ComTypes.IDataObject = Marshal.GetObjectForIUnknown(pDataObj)
    dataObject.GetData(fe, stm)

    Try
        ' Get an HDROP handle.
        Dim hDrop As IntPtr = stm.unionmember
        If (hDrop = IntPtr.Zero) Then
            Throw New ArgumentException
        End If

        ' Determine how many files are involved in this operation.
        Dim nFiles As UInteger = NativeMethods.DragQueryFile(hDrop,
                         UInt32.MaxValue, Nothing, 0)

        ' ********************
        ' {PL} - change how files are collected
        Dim fileName As New StringBuilder(260)
        If (nFiles > 0) Then
            For n As Long = 0 To nFiles - 1
                If (0 = NativeMethods.DragQueryFile(hDrop, CUInt(n), fileName,
                         fileName.Capacity)) Then
                    Marshal.ThrowExceptionForHR(WinError.E_FAIL)
                End If
                selectedFiles.Add(fileName.ToString)
            Next
        Else
            Marshal.ThrowExceptionForHR(WinError.E_FAIL)
        End If

        ' {/PL} 
        ' *** no more changes beyond this point ***

        ' [-or-]
        ' Enumerates the selected files and folders.
        '...
       
    Finally
        NativeMethods.ReleaseStgMedium((stm))
    End Try
End Sub

The original code actually does have code for a multi file method which is commented out. I actually did not see it before adding one. The changed part is between the star strings.

Also, it is sad to say, but with Option Strict, you will have to make 10 or so small changes to Microsoft's code. Just accept the changes IntelliSense suggests.


Important Notes

The model of a separate DLL to provide ContextMenu services on behalf of an EXE "engine" is very common. This is what all the xxxShell.DLL files are which you often see in folders along with program executables. The difference here is that you are building the DLL rather than the author of the app in question.

  1. All the changes except one are in the FileContextMenuExt class
  2. Be sure to change the GUID otherwise your handler could collide with others based on the same MS Template! There is a handy utility for this on your Tools menu.
  3. The BMP/PNG is optional
  4. The original MS version simply displayed the name of the file selected. So the relevant procedure is named OnVerbDisplayFileName. As you see, I did not change that. If you change it to match your actual operation, you will also need to change some references to it in the PInvoke heavy code for IContextMenu. Nobody but you will ever see that name though.
  5. A debug MessageBox is all that is there for the invoke action. You can see the actual code mine uses.

The ReadMe in the original MS project describes this, but after you compile, copy the file to where it will reside and register it:

regasm <asmfilename>.DLL /codebase

To unregister:

regasm <asmfilename>.DLL /unregister

Use the RegAsm found in your Microsoft.NET\Framework64\v4.0.xxxx folder. This will have to be done from a Command Window with Administrator privileges (or the equivalent script). Alternatively for a deployed app, you can have the target app register/unregister the helper DLL using the Public Regster/UnRegister methods.


Warning: make your code changes carefully and test things like loops and string formats before you compile; you want as few compile-test iterations as possible. The reason is that once you activate your new context menu, the DLL is in use by Explorer and cannot be replaced by a new build. You have to terminate the explorer.exe process (not just File Explorer!) to register and try a new build.

There may be a another way, but I just close any Explorer Windows, then log off and right back on.


Testing

If I right click on one of the registered file types, I get the menu as expected with the correct menu text and bitmap image:

enter image description here

click for larger image

If I click, the applet comes up as expected with multiple files in one instance:

enter image description hereenter image description here

click for larger image

Note how the Prev/Next buttons at the bottom are enabled to move from file to file which is not the case when 1 file only is loaded.

Works on My MachineTM


Resources

How to Write Windows Shell Extension with .NET Languages. This is an MS-PL article complete with a finished ShellExtension project. The above is a set of mods to make it work with multiple extensions and multiple files, so the original project is required as a starting point.

Best Practices for Shortcut Menu Handlers and Multiple Verbs

Choosing a Static or Dynamic Shortcut Menu Method

Verbs and File Associations

Perorate answered 1/12, 2014 at 14:13 Comment(8)
Wow! What an answer, thankyou!, the "sendto" approach gived me an idea, "sendto" feature could be called internally from an application maybe P/Invoking or something else? what do you think about this approach? it could work exactlly as the sendto feature?: · Create a contextmenu to call a CLI app that will catch all the selected files one by one to use the "sendto" feature for each file or for all files at once, to send it to my program. Do you think it could be done and it could work as expected?Sculpture
Only to know: did you tested a transparent PNG image with the "B" approach?, by the moment I didn't finished reading your answer, too much things to test! :)Sculpture
Send-To via PInvoke sounds like a kludge which might end up in more Whack-a-Mole, especially when a propert ContextMenu helper is so easy. See the larger image - it is using a transparent PNG though the transparent area is pretty small.Picrotoxin
I implemented the SendTo option for a project I've been working on, but after a release started getting a flood of complaints about users getting a "The filename or extension is too long." exception reported to them in a pop-up window. Sure enough the SendTo method is restricted by the shell's 32k environment limit (which includes the command to be executed by a link). Any thoughts or ideas to workaround that without implementing a full ShellExtension solution?Sonority
Interesting. I'd try a Explorer-Context-Menu You could also configure the app so that when they use the menu when the app is already open, the already running app respondsPicrotoxin
@Plutonix Hi, I tried method 2 and still just 1 file path sent to my program! what's wrong? what should i do? hopefully method 1 worked fine and all files paths sent to program, but you know send to is not a nice option for a music player! do the other methods pass multiple path to program by select&opening multiple files?Gca
@Gca Piggybacking as described in that part of the answer turns out to be a bad idea. Your file ext needs to be associated with your app and you have the context menu entries entered correctly, nut you also have to have your app setup as a SingleInstance app and 'forward' the args to the first instance. Explorer/Windows doesnt really bundle multiple selections together - it just sends the file selected to the registered app and it is up to the app to do what needs to be done. If the answer helped, please upvote it.Picrotoxin
@Plutonix sorry but i didn't understand what you said (a little), btw can i reach my goal (add selected fileS to my music player)? please take a look at this.Gca
T
3

Why not write a .exe with Make single instance application checked.

Then in that new application catch the MyApplication_StartupNextInstance as provided in the MyApplication class to catch all files being pushed by explorer, maybe let the application wait a second or 2 to make sure no following files are being sent by explorer and then combine those all into 1 string and parse them to your 3rd party app.

If interested i can place some code getting you started

Tensor answered 23/11, 2014 at 12:39 Comment(12)
Thanks for your help, no problem about the example, I've seen all what I need on the event-args of that event :) thanks again.Sculpture
Sorry I need to discard this solution or at least discard the focus that I given oin my answer because even as a single instance if I select/open 100 files from explorer it opens 100 different instances that tries to comunicate to the first instance to pass new commandline arguments, then in easy words those 100 processes collapses the system for seconds, and also some instances sometimes throws an CantStartSingleInstanceException exception doing that. its just horrible :(Sculpture
This shouldn't happen. If you make your app single instance just 1 appplication should open, if your application is already open that currently opened application triggers the MyApplication_StartupNextInstanceTensor
Nope, you are not right with what you think, I'm not any expert but the next instances should open/run to comunicate to the first instance... you can do a simple and easy test by yourself to proof this: 1) Compile an Vb.Net marked as single-instance app. 2) Open Windows TaskManager. 3) Select the compiled assembly in Explorer and hold the "ENTER" key, at this point you can note that a lot of different instances of the same assembly will be open and be shown in the taskmanager, and in a determinated moment a 'CantStartSingleInstanceException' exception will be thrown by some of those instances.Sculpture
I did what you described and my application only opens ONCE. no matter how many times i press enter, it always gets back to the original exeTensor
I'm not any expert but the next instances should open/run to comunicate to the first instance. not true, the .NET framework checks if your app is running and communicates directly with a running one instead of opening a new one first!Tensor
I'm open to corrections but that is not what I've experienced, my app runs a lot of instances, could you please upload the source that you are using? I would like to test it, thanks in advanceSculpture
it sounds to me youre not setting the make single instance correctly. if set correctly no other instances should execute if the app is already running in the background!Tensor
There is only one (automated) way to set the single-instance in an VB.Net Winforms project, I checked the option properly, and yes if I select between 10-16 files it works ok (but it opens those 10-16 instances during the selection), but when more files I select more chance to see that exception, and as I said anyways it collapses the system for seconds with all those instances opened.Sculpture
Let us continue this discussion in chat.Tensor
For /L %a in (0, 1, 100) do (start /B "" "Single Instance EXE.exe"), and see what happens in taskmanager... the 100 instances are open, and maybe some exception will be thrown, then the same that is what happens when I select/open multiple files from contextmenu using this approach of a single-instance app. :(Sculpture
@RoyvanderVelde Could you post code example? This idea sounds interesting, but in order to do that I had to use Inter Process Communication (IPC) methods, at least with Pythoh. Code/example would be interesting for your answer.Hilversum
S
2

EDIT: I discarded this solution because I've discovered that this approach has very bad disadvantages.


So, this is how it looks in VB.Net this easy approach (thanks for @Roy van der Velde)

It stores the filepaths in a string builder in this format:

"File1" "File2 "File3"

After an inactivity time (using a Timer), the filepath arguments are passed to the specified application, and that's all.

The code is rehusable and customizable :)

It should be marked as single-instance if VB.Net, if C# then use a Mutex or... I don't know how to.

Main Form Class:

Public Class Main

    Public Sub New()

        ' This call is required by the designer.
        InitializeComponent()

        ' Add any initialization after the InitializeComponent() call.
        Me.Size = New Size(0, 0)
        Me.Hide()
        Me.SuspendLayout()

    End Sub

End Class

Application Events class:

#Region " Option Statements "

Option Strict On
Option Explicit On
Option Infer Off

#End Region

#Region " Imports "

Imports Microsoft.VisualBasic.ApplicationServices
Imports System.IO
Imports System.Text

#End Region

Namespace My

    ''' <summary>
    ''' Class MyApplication.
    ''' </summary>
    Partial Friend Class MyApplication

#Region " Properties "

        ''' <summary>
        ''' Gets the application path to pass the filepaths as a single-line argument.
        ''' </summary>
        ''' <value>The application path.</value>
        Private ReadOnly Property AppPath As String
            Get
                Return Path.Combine(My.Application.Info.DirectoryPath, "MP3GainGUI.exe")
            End Get
        End Property

        ''' <summary>
        ''' Gets the inactivity timeout, in milliseconds.
        ''' </summary>
        ''' <value>The inactivity timeout, in milliseconds.</value>
        Private ReadOnly Property TimeOut As Integer
            Get
                Return 750
            End Get
        End Property

        ''' <summary>
        ''' Gets the catched filepaths.
        ''' </summary>
        ''' <value>The catched filepaths.</value>
        Private ReadOnly Property FilePaths As String
            Get
                Return Me.filePathsSB.ToString
            End Get
        End Property

#End Region

#Region " Misc. Objects "

        ''' <summary>
        ''' Stores the catched filepaths.
        ''' </summary>
        Private filePathsSB As StringBuilder

        ''' <summary>
        ''' Keeps track of the current filepath count.
        ''' </summary>
        Private filePathCount As Integer

        ''' <summary>
        ''' Timer that determines whether the app is inactive.
        ''' </summary>
        Private WithEvents inactivityTimer As New Timer With
            {
                .Enabled = False,
                .Interval = Me.TimeOut
            }

#End Region

#Region " Event Handlers "

        ''' <summary>
        ''' Handles the Startup event of the application.
        ''' </summary>
        ''' <param name="sender">The source of the event.</param>
        ''' <param name="e">The <see cref="ApplicationServices.StartupEventArgs"/> instance containing the event data.</param>
        Private Sub Me_Startup(ByVal sender As Object, ByVal e As StartupEventArgs) _
        Handles Me.Startup

            Select Case e.CommandLine.Count

                Case 0 ' Terminate the application.
                    e.Cancel = True

                Case Else ' Add the filepath argument and keep listen to next possible arguments.
                    Me.filePathsSB = New StringBuilder
                    Me.filePathsSB.AppendFormat("""{0}"" ", e.CommandLine.Item(0))
                    Me.filePathCount += 1

                    With Me.inactivityTimer
                        .Tag = Me.filePathCount
                        .Enabled = True
                        .Start()
                    End With

            End Select

        End Sub

        ''' <summary>
        ''' Handles the StartupNextInstance event of the application.
        ''' </summary>
        ''' <param name="sender">The source of the event.</param>
        ''' <param name="e">The <see cref="ApplicationServices.StartupNextInstanceEventArgs"/> instance containing the event data.</param>
        Private Sub Me_StartupNextInstance(ByVal sender As Object, ByVal e As StartupNextInstanceEventArgs) _
        Handles Me.StartupNextInstance

            Select Case e.CommandLine.Count

                Case 0 ' Terminate the timer and run the application.
                    Me.TerminateTimer()

                Case Else ' Add the filepath argument and keep listen to next possible arguments.
                    Me.filePathsSB.AppendFormat("""{0}"" ", e.CommandLine.Item(0))
                    Me.filePathCount += 1

            End Select

        End Sub

        ''' <summary>
        ''' Handles the Tick event of the InactivityTimer control.
        ''' </summary>
        ''' <param name="sender">The source of the event.</param>
        ''' <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
        Private Sub InactivityTimer_Tick(ByVal sender As Object, ByVal e As EventArgs) _
        Handles inactivityTimer.Tick

            Dim tmr As Timer = DirectCast(sender, Timer)

            If DirectCast(tmr.Tag, Integer) = Me.filePathCount Then
                Me.TerminateTimer()

            Else
                tmr.Tag = Me.filePathCount

            End If

        End Sub

#End Region

#Region " Methods "

        ''' <summary>
        ''' Terminates the inactivity timer and runs the application.
        ''' </summary>
        Private Sub TerminateTimer()

            Me.inactivityTimer.Enabled = False
            Me.inactivityTimer.Stop()
            Me.RunApplication()

        End Sub

        ''' <summary>
        ''' Runs the default application passing all the filepaths as a single-line argument.
        ''' </summary>
        Private Sub RunApplication()

#If DEBUG Then
            Debug.WriteLine(Me.FilePaths)
#End If
            Try
                Process.Start(Me.AppPath, Me.FilePaths)

            Catch ex As FileNotFoundException
                ' Do Something?
            End Try

            ' Terminate the application.
            MyBase.MainForm.Close()

        End Sub

#End Region

    End Class

End Namespace
Sculpture answered 23/11, 2014 at 16:18 Comment(1)
How is this not how i suggested??Tensor
S
0

SUMMARY

  1. In Registry put the target program as Context Menu Create File.exe which writes reg.txt file.

  2. In your main program loop every 1 second to check if reg.txt exists. If it exists, kill the Context Menu Create File.exe and delete the reg.txt file. Then copy the selected file paths and manipulate with them.

  3. If your program loops to check for reg.txt, then you need to start the program before executing context menu either on startup or manually.

I did this with AutoHotkey.

These 2 AutoHotkey scripts below allow you to add a Open With Chrome context menu item in Windows Explorer to open multiple selected files.

You can leave all variable values as they are, but if you want to change the contextMenu value and program name then see 3.1.

INSTRUCTIONS:

Create 2 files in the same directory:

  1. Create 1st program Add To Context Menu And Create Startup Shortcut.ahk

    1. RunAsAdmin Label ensures that the script runs as admin (fixes adding Registry values).

      1. The If (!A_IsAdmin) checks if current user is NOT admin, A_IsAdmin is a built in AutoHotkey variable that returns 1 if user is admin, 0 otherwise.
      2. Run, \*RunAs "%A_ScriptFullPath%" *RunAs parameter runs the script as admin, "%A_ScriptFullPath%" gets the full path of the current executing script.
      3. ExitApp exits the current script instance running without admin privileges.
      4. Because the Run command runs the script again with admin privileges it will skip the IF condition and continue executing code below.
    2. ContextMenuCreateFile: Label creates a Context Menu Create File.exe which creates a file reg.txt and exits Context Menu Create File.exe after it has written the file reg.txt. Make sure you specify where your Ahk2Exe.exe path is in the RunWait command.

    3. Add To Context Menu: Label adds the Registry entry which runs Context Menu Create File.exe.

      1. Set the contextMenu variable to what needs to be displayed in the Context Menu. (The program name is set to contextMenu)
      2. Set the regPath to your desired Registry path.
      3. When it executes the MsgBox, check if the command is added to the Registry in the address bar.
    4. CreateStartupShortcut: Label creates the shortcut of the main program Open With Chrome.exe in Startup folder.

Add To Context Menu And Create Startup Shortcut.ahk

; =============Recommended Settings=============
#NoEnv
SetWorkingDir %A_ScriptDir%
#Warn
CoordMode, Mouse, Window
SendMode Input
#SingleInstance Force
SetTitleMatchMode 2
SetTitleMatchMode Fast
DetectHiddenWindows Off
DetectHiddenText On
#WinActivateForce
#NoTrayIcon
SetControlDelay 1
SetWinDelay 0
SetKeyDelay -1
SetMouseDelay -1
SetBatchLines -1
#Persistent
#MaxThreadsPerHotkey 2
; =============Recommended Settings=============

AddToContextMenuAndCreateStartupShortcut:
RunAsAdmin:  ; =============RunAsAdmin=============
If (!A_IsAdmin)  ; IF NOT Admin
{
    Run, *RunAs "%A_ScriptFullPath%"  ; Run script as admin
    ExitApp  ; Exit the current instance running without admin privileges
}
ContextMenuCreateFile:  ; =============ContextMenuCreateFile=============
contextMenuCreateFileAhk := 
(LTrim
"#NoEnv
#NoTrayIcon
#SingleInstance Force
SetWorkingDir %A_ScriptDir%
SetBatchLines -1

ContextMenuCreateFile:
FileDelete, reg.txt
FileAppend, , reg.txt
ExitApp
Return"
)  ; contextMenuCreateFileAhk
FileDelete, Context Menu Create File.exe  ; DEL Context Menu Create File.exe
FileDelete, Context Menu Create File.ahk  ; DEL Context Menu Create File.ahk
FileAppend, %contextMenuCreateFileAhk%, Context Menu Create File.ahk  ; MAKE Context Menu Create File.ahk
RunWait, C:\Program Files\AutoHotkey\Compiler\Ahk2Exe.exe /in "Context Menu Create File.ahk" /out "Context Menu Create File.exe"  ; Convert AHK to EXE
FileDelete, Context Menu Create File.ahk  ; DEL Context Menu Create File.ahk
AddToContextMenu:  ; =============AddToContextMenu=============
path := ""  ; path
program := "Context Menu Create File"  ; program
contextMenu := "Open With Chrome"  ; contextMenu
regPath := "HKCR\*\shell"  ; regPath
StringReplace, regKey, contextMenu, %A_Space%, , A  ; regKey
regKey := 0 regKey  ; regKey
Loop, Files, %program%.exe, F  ; Find Program.exe In Current Dir
{
    path := A_LoopFileLongPath  ; Set Program Path
}
cmd := 
(LTrim
"reg add """ regPath "\" regKey """ /ve /t REG_SZ /d """ contextMenu """ /f
reg add """ regPath "\" regKey "\command"" /ve /t REG_SZ /d ""\""" path "\""`"" /f"
)  ; Registry
FileDelete, Add To Context Menu.bat  ; CREATE Add To Context Menu.bat
FileAppend, %cmd%, Add To Context Menu.bat  ; CREATE Add To Context Menu.bat
RunWait, Add To Context Menu.bat, , Hide  ; RUN Add To Context Menu.bat (*RunAs ADMIN)
FileDelete, Add To Context Menu.bat  ; DEL Add To Context Menu.bat
Run, regedit  ; regedit
WinWait, Registry Editor ahk_class RegEdit_RegEdit ahk_exe regedit.exe  ; Registry
Sleep, 333
ControlSetText, Edit1, %regPath%\%regKey%\command, Registry Editor ahk_class RegEdit_RegEdit ahk_exe regedit.exe  ; regPath
ControlFocus, Edit1, Registry Editor ahk_class RegEdit_RegEdit ahk_exe regedit.exe  ; regPath
ControlSend, Edit1, {Enter}, Registry Editor ahk_class RegEdit_RegEdit ahk_exe regedit.exe  ; regPath
ControlSend, SysListView321, {Control Down}{NumpadAdd}{Control Up}, Registry Editor ahk_class RegEdit_RegEdit ahk_exe regedit.exe  ; regPath
ControlSend, SysListView321, {F5}, Registry Editor ahk_class RegEdit_RegEdit ahk_exe regedit.exe  ; regPath
MsgBox, 262192, CHECK, Check If Added %contextMenu% To Registry  ; CHECK
CreateStartupShortcut:  ; =============CreateStartupShortcut=============
path := ""  ; path
program := contextMenu  ; program
Loop, Files, %program%.exe, F  ; Find Program.exe In Current Dir
{
    path := A_LoopFileLongPath  ; Set Program Path
}
FileCreateShortcut, %path%, %A_Startup%\%program%.lnk  ; Create Startup Shortcut
Run, %A_Startup%, , Max  ; Check If Shortcut Created
Run, "%program%.exe"  ; Run Program
MsgBox, 262144, CHECK, Check If Shortcut Created  ; CHECK
ExitApp  ; ExitApp
Return
  1. Create 2nd program Open With Chrome.ahk which is the main program.
    1. Here a Loop is created and checks every 1 second if reg.txt exists.
    2. IfExist, reg.txt it kills the Context Menu Create File.exe and deletes the reg.txt.
    3. Then it activates explorer.exe window and copies all selected file paths to CLIPBOARD.
    4. If CLIPBOARD contains .,\ to make sure CLIPBOARD contains path "\" with extension ".".
    5. The list of selected files is saved in selectedFiles variable.
    6. The Loop below chromeParams := "" loops through selected files, gets the filePaths and surrounds them with double quotes, and StringReplace replaces the Windows path as url file path ex: C:\path\file.jpg to file:///path/file.jpg.
    7. Then the filePath is concatenated to chromeParams.
    8. StringTrimRight removes the last space from chromeParams string.
    9. Then Run, chrome.exe %chromeParams% is executed with %chromeParams% (list of selected files). (If the command doesn't open Chrome then put full path to Chrome, ex: Run, C:\Program Files (x86)\Google\Chrome\Application\chrome.exe with the same parameters)

Open With Chrome.ahk

; =============Recommended Settings=============
#NoEnv
SetWorkingDir %A_ScriptDir%
#Warn
CoordMode, Mouse, Window
SendMode Input
#SingleInstance Force
SetTitleMatchMode 2
SetTitleMatchMode Fast
DetectHiddenWindows Off
DetectHiddenText On
#WinActivateForce
#NoTrayIcon
SetControlDelay 1
SetWinDelay 0
SetKeyDelay -1
SetMouseDelay -1
SetBatchLines -1
#Persistent
#MaxThreadsPerHotkey 2
; =============Recommended Settings=============

OpenWithChrome:
Loop  ; Loop Start
{
    Sleep, 1000  ; Fix High CPU
    IfExist, reg.txt  ; IF reg.txt EXIST
    {
        RunWait, cmd /c taskkill /im "Context Menu Create File.exe" /f, , Hide  ; Fix Opening 2 Compose Windows
        FileDelete, reg.txt  ; DEL reg.txt
        WinActivate, ahk_class CabinetWClass ahk_exe explorer.exe  ; Explorer
        CLIPBOARD := ""  ; Clear Clipboard
        Send, {Control Down}{c}{Control Up}  ; Copy File Paths
        ClipWait, 0  ; Clip Wait
        If CLIPBOARD contains .,\  ; IF CLIPBOARD contains .,\
        {
            selectedFiles := CLIPBOARD  ; selectedFiles
            chromeParams := ""  ; chromeParams
            Loop, Parse, selectedFiles, `n, `r  ; Loop Start selectedFiles
            {
                filePath := """file:///" A_LoopField """"  ; filePath
                StringReplace, filePath, filePath, \, /, A  ; Replace \ with /
                chromeParams .= filePath . " "  ; chromeParams .= %filePath%,
            }
            StringTrimRight, chromeParams, chromeParams, 1  ; Remove Last Space
            Run, chrome.exe %chromeParams%  ; Open Files In Chrome
        }
    }
}
Return
  1. Convert both Add To Context Menu And Create Startup Shortcut.ahk and Open With Chrome.ahk to EXE files in the same directory using Ahk2Exe.exe --> (find in Start Menu, just browse file and hit convert)

  2. Execute the Add To Context Menu And Create Startup Shortcut.exe

  3. Select files, right click and the Open With Chrome context menu item should appear.

Samsun answered 5/12, 2023 at 11:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.