Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
161 views
in Technique[技术] by (71.8m points)

c# - Implement Explorer ContextMenu and pass multiple files to one program instance

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...?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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:UsersYOURNAMEAppDataRoamingMicrosoftWindowsSendTo") and add an entry for the app. The target would be the app you wish to feed/send file selection to:

"C:Program FilesThat Other AppOtherApp.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 .NETMedia PropsMediaProps.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 = Native

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...