Click here to Skip to main content
16,016,736 members
Articles / Programming Languages / C#

Icon,Contextmenu,Column and Tooltip handler in c#

Rate me:
Please Sign up or sign in to vote.
2.74/5 (15 votes)
14 Dec 2016CPOL3 min read 59.3K   966   28   12
Extend windows shell in c#.

Introduction

...........................................................................................................................

Very Very Important.....it is risky to use dot Net 3.5 for this , it crashes other application when opening a file open dialog box ...try using unmanaged code like VC++...or older version of dot net like 1.1...not sure all about this why this happining, will do some R&D.

............................................................................................................................

This code can be used to do Icon, Column, Context menu and Tootip handling in windows Explorer. So first of all let me tell you what a shell extension is. Shell Extensions are programes which can add functionalty or can change the existing shell behaviour. Shell is the GUI which we see in windows explorer like progress bars, folders, start menu, desktop etc.

Background

Writing shell extensions for windows is not easy as there is not much code avalaible. I searched the net and found samples in VC++ and VB but there was no code in c#. So I collected informations from different places and converted VB and VC++ code to c#.

Using the Code

You can also take out only the functionalty you want,its easy to extract the single interface like:

        [ComImport(), InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
   GuidAttribute("0000010e-0000-0000-C000-000000000046")]
public interface IDataObject
{
    [PreserveSig()]
    int GetData(ref FORMATETC a, ref STGMEDIUM b);
    [PreserveSig()]
    void GetDataHere(int a, ref STGMEDIUM b);
    [PreserveSig()]
    int QueryGetData(int a);
    [PreserveSig()]
    int GetCanonicalFormatEtc(int a, ref int b);
    [PreserveSig()]
    int SetData(int a, int b, int c);
    [PreserveSig()]
    int EnumFormatEtc(uint a, ref Object b);
    [PreserveSig()]
    int DAdvise(int a, uint b, Object c, ref uint d);
    [PreserveSig()]
    int DUnadvise(uint a);
    [PreserveSig()]
    int EnumDAdvise(ref Object a);
}




[ComImport(), InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
    GuidAttribute("000214e8-0000-0000-c000-000000000046")]
public interface IShellExtInit
{
    [PreserveSig()]
    int Initialize(IntPtr pidlFolder, IntPtr lpdobj, uint /*HKEY*/ hKeyProgID);
}


[ComImport(), InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
   GuidAttribute("000214e4-0000-0000-c000-000000000046")]
public interface IContextMenu
{
    [PreserveSig()]
    int QueryContextMenu(uint hmenu, uint iMenu, int idCmdFirst, int idCmdLast,
        uint uFlags);
    [PreserveSig()]
    void InvokeCommand(IntPtr pici);
    [PreserveSig()]
    void GetCommandString(int idcmd, uint uflags, int reserved,
        StringBuilder commandstring, int cch);
}

 

This defines the Interface only. You can look for different structs types in the code because pasting all code here will make article unnessery lengthy. So now comes the methods implimentations. First of all we have to implement Initialize of IShellExtInit. The Initialize method is called when File Explorer is initializing a context menu extension, a property sheet extension, or a non-default drag-and-drop extension. Parameters: pidlFolder [in] In the case of context menu extension or property sheet extension, this specifies the parent folder. In the case of non-default drag-and-drop extension, this specifies the target folder. lpdobj [in] In the case of context menu extension or property sheet extension, this specifies the set of items selected in that folder. In the case of non-default drag-and-drop extension, this specifies the items that are dropped. hKeyProgID [in] In the case of context menu extension or property sheet extension, this specifies the type of the focused item in the selection. In the case of non-default drag-and-drop extension, this specifies the folder type. Return Values This method should return S_OK if it was successful or appropriate errors if not.

int IShellExtInit.Initialize(IntPtr pidlFolder, IntPtr lpdobj,
     uint hKeyProgID)///Uses IShellExtInit for IColumnProvider and IContextMenu
        {
            try
            {
                if (lpdobj != (IntPtr)0)
                {
                    // Get info about the directory
                    IDataObject dataObject = (
                        IDataObject)Marshal.GetObjectForIUnknown(lpdobj);
                    FORMATETC fmt = new FORMATETC();
                    fmt.cfFormat = CLIPFORMAT.CF_HDROP;
                    fmt.ptd = 0;
                    fmt.dwAspect = DVASPECT.DVASPECT_CONTENT;
                    fmt.lindex = -1;
                    fmt.tymed = TYMED.TYMED_HGLOBAL;
                    STGMEDIUM medium = new STGMEDIUM();
                    dataObject.GetData(ref fmt, ref medium);
                    m_hDrop = medium.hGlobal;
                }
            }
            catch (Exception e)
            {
                Logger.WriteLog("Initialize " + e.Message);
            }
            return 0;
        }

The QueryContextMenu method adds the new menu items to a context menu. Parameters : hmenu [in] The menu to which additional items are added. iMenu [in] Specifies the location on the menu (must never be –1). idCmdFirst [in] The beginning of the specified range. idCmdLast [in] The end of the specified range. uFlags [in] Specifies the context. Return Value : QueryContextMenu returns the number of items that were added to the context menu. For more information see http://msdn.microsoft.com/en-us/library/bb416552.aspx

int IContextMenu.QueryContextMenu(uint hMenu, uint iMenu, int idCmdFirst,
     int idCmdLast, uint uFlags)//Using IShellExtInit.Initialize and IContextMenu
        {
                        // Create the popup to insert
            uint handleMenuPopup = Win32Helpers.CreatePopupMenu();
            StringBuilder szFile = new StringBuilder(260);

            int id = 1;
            if ((uFlags & 0xf) == 0 || (uFlags & (uint)CMF.CMF_EXPLORE) != 0)
            {
//Take the number of selected files
                uint nselected = Win32Helpers.DragQueryFile(m_hDrop, 0xffffffff,
                null, 0);
//Get each file name
                for (uint uFile = 0; uFile < nselected; uFile++)
                {
                    // Get the next filename.
                    Win32Helpers.DragQueryFile(m_hDrop, uFile, szFile,
                        szFile.Capacity + 1);
                    fileNames += szFile.ToString() + "|";

                }
                id = PopulateMenu(handleMenuPopup, idCmdFirst + id);


                // Add the popup to the context menu
                MENUITEMINFO menuItemInfo = new MENUITEMINFO();
                menuItemInfo.cbSize = 48;
                menuItemInfo.fMask = (uint)MIIM.TYPE | (uint)MIIM.STATE | 
                    (uint)MIIM.SUBMENU;
                menuItemInfo.hSubMenu = (int)handleMenuPopup;
                menuItemInfo.fType = (uint)MF.STRING;
                menuItemInfo.dwTypeData = "&My Options"; // adding a new menu
                menuItemInfo.fState = (uint)MF.ENABLED;
                Win32Helpers.InsertMenuItem(hMenu, (uint)iMenu, 1, ref menuItemInfo);

                // Add a separator
                MENUITEMINFO seperator = new MENUITEMINFO();
                seperator.cbSize = 48;
                seperator.fMask = (uint)MIIM.TYPE;
                seperator.fType = (uint)MF.SEPARATOR;
                Win32Helpers.InsertMenuItem(hMenu, iMenu + 1, 1, ref seperator);

            }
            return id;
        }

This portion add the actual menu to Context menu

// Add the popup to the context menu
                MENUITEMINFO menuItemInfo = new MENUITEMINFO();
                menuItemInfo.cbSize = 48;
                menuItemInfo.fMask = (uint)MIIM.TYPE | (uint)MIIM.STATE |
                    (uint)MIIM.SUBMENU;
                menuItemInfo.hSubMenu = (int)handleMenuPopup;
                menuItemInfo.fType = (uint)MF.STRING;
                menuItemInfo.dwTypeData = "&My Options"; // adding a new menu
                menuItemInfo.fState = (uint)MF.ENABLED;
                Win32Helpers.InsertMenuItem(menuItemInfo);

Registration/Unregistration:

First of all we have to put the GUID of our class in approved extensions.

// For set as  approved shellex
                RegistryKey root;
                RegistryKey rk;
                root = Registry.LocalMachine;
                rk = root.OpenSubKey(
               "Software\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved",
                true);
                rk.SetValue(guid.ToString(), "My shell extension");
                rk.Close();
                root.Close();

Then create a .Myf entry in root and set its value to our file type.This name can be anything like “Myf” or “MyFile”.

root = Registry.ClassesRoot;
                rk = root.CreateSubKey(".Myf");
                rk.SetValue("", "Myf");
                rk.Close();

Then under “Myf\\shellx\\ContextMenuHandlers” create “Myf” subkey and set its value to GUID of our class.

rk = root.CreateSubKey("Myf\\shellex\\ContextMenuHandlers\\Myf");
                rk.SetValue("", guid.ToString());
                rk.Close();

Again create a subkey “\\shellex\\IconHandler” under created “Myf” subkey and set its value of our GUID class.

rk = root.CreateSubKey("Myf\\shellex\\IconHandler");
                rk.SetValue("", guid.ToString());
                rk.Close();

Now in root create a subkey of our class guid under “Folder\shellex\ColumnHandlers\" and set its value as “RuntimeType”(I don’t know if we can give some other name or anything)

rk = root.CreateSubKey(@"Folder\shellex\ColumnHandlers\" + guid.ToString());
                rk.SetValue(string.Empty, "RuntimeType");
                rk.Close();

Finally enter following under “.Myf” for tooltips.

rk = root.CreateSubKey(".Myf\\shellex\\{00021500-0000-0000-C000-000000000046}");
                rk.SetValue("", guid.ToString());
                rk.Close();

To refresh explorer automatically.

// Tell Explorer to refresh
                SHChangeNotify(SHCNE_ASSOCCHANGED, 0, IntPtr.Zero, IntPtr.Zero); 

For Unregistration just delete all keys we have created.

Full Code for Register/ Unregister:

[ComRegisterFunction]
    static void RegisterServer(Type t)
    {
        try
        {
            // For Winnt set me as an approved shellex
            RegistryKey root;
            RegistryKey rk;
            root = Registry.LocalMachine;
            rk = root.OpenSubKey(
           "Software\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved",
                true);
            rk.SetValue(guid.ToString(), "My shell extension");
            rk.Close();
            root.Close();

            root = Registry.ClassesRoot;
            rk = root.CreateSubKey(".Myf");
            rk.SetValue("", "Myf");
            rk.Close();

            rk = root.CreateSubKey("Myf\\shellex\\ContextMenuHandlers\\Myf");
            rk.SetValue("", guid.ToString());
            rk.Close();
            rk = root.CreateSubKey("Myf\\shellex\\IconHandler");
            rk.SetValue("", guid.ToString());
            rk.Close();

            rk = root.CreateSubKey(@"Folder\shellex\ColumnHandlers\" +
                guid.ToString());
            rk.SetValue(string.Empty, "RuntimeType");
            rk.Close();


            rk = root.CreateSubKey(
                ".Myf\\shellex\\{00021500-0000-0000-C000-000000000046}");
            rk.SetValue("", guid.ToString());
            rk.Close();

            // Tell Explorer to refresh
            SHChangeNotify(SHCNE_ASSOCCHANGED, 0, IntPtr.Zero, IntPtr.Zero);
            Logger.WriteLog("Registered at: " + System.DateTime.Now);
        }

        catch (Exception e)
        {
            Logger.WriteLog(e.Message);
        }
    }

    //[System.Runtime.InteropServices.ComUnregisterFunctionAttribute()]
    [ComUnregisterFunction]
    static void UnregisterServer(Type t)
    {
        try
        {
            RegistryKey root;
            RegistryKey rk;

            // Remove ShellExtenstions registration
            root = Registry.LocalMachine;
            rk = root.OpenSubKey(
           "Software\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved",
                true);
            rk.DeleteValue(guid);
            rk.Close();
            root.Close();

            // Delete  regkey
            root = Registry.ClassesRoot;
            root.DeleteSubKeyTree(".Myf");
            root.DeleteSubKeyTree("Myf");
            root.DeleteSubKey(@"Folder\shellex\ColumnHandlers\" + guid.ToString());
            root.Close();
            SHChangeNotify(SHCNE_ASSOCCHANGED, 0, IntPtr.Zero, IntPtr.Zero);
            Logger.WriteLog("Un-Registered at: " + System.DateTime.Now);
        }
        catch (Exception e)
        {
            Logger.WriteLog(e.Message);
        }
    }

Points to Remember

  1. You can register this dll by regasm
  2. By Checking Project->Properties->Buid->Register for COM Interop.

Compile and run to update registry and DLL

While compiling again you can ger copy error of dll so restart explorer from end task and new task.

Last words

You can send me mail at ashwanisihag@yahoo.com if needs any help.

This article is inspired from
http://www.kbcafe.com/juice/?guid=20041022155459
and
shellextguide8.aspx


Thanks to these people for their great articles.
Warm Regards
Ashwani Sihag
ION Solutions
Mohali

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Team Leader ION
India India
Worked at TCS,Keane,RMS and now working at ION as Team Lead.
Worked in VB.NET,C#,VC++,assembly,COM,DCOM,SQLserver,VBA,3DS MAX.

Likes Hill Climbing,Jogging,swimming,rope jumping,boxing,football,hockey,basketball,cricket,chess,golf.

Have been national athlete in 100 and 200 Meters(Timings 11.00,22.8)

Comments and Discussions

 
QuestionCan you show library in action? Pin
almend9-Mar-12 15:14
professionalalmend9-Mar-12 15:14 
GeneralSample in VB.NET Pin
almend1-Sep-09 9:58
professionalalmend1-Sep-09 9:58 
GeneralRe: Sample in VB.NET Pin
ashwanisihag10-May-11 6:04
ashwanisihag10-May-11 6:04 
GeneralRe: Sample in VB.NET Pin
almend10-May-11 6:41
professionalalmend10-May-11 6:41 
AnswerRe: Sample in VB.NET Pin
thatraja26-Jan-12 20:59
professionalthatraja26-Jan-12 20:59 
GeneralNooooo! Pin
SteveKing20-Jul-08 19:53
SteveKing20-Jul-08 19:53 
GeneralRe: Nooooo! Pin
ashwanisihag29-Jul-08 21:06
ashwanisihag29-Jul-08 21:06 
GeneralRe: Nooooo! [modified] Pin
ashwanisihag29-Sep-08 1:49
ashwanisihag29-Sep-08 1:49 
GeneralRe: Thank you Pin
miliu7-Jan-10 23:33
miliu7-Jan-10 23:33 
GeneralRe: Thank you Pin
ashwanisihag8-Jan-10 0:03
ashwanisihag8-Jan-10 0:03 
GeneralRe: Thank you Pin
ashwanisihag10-May-11 5:59
ashwanisihag10-May-11 5:59 
GeneralRe: Nooooo! Pin
ashwanisihag10-May-11 6:01
ashwanisihag10-May-11 6:01 
Right! Smile | :)

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.