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 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) {
try
{
if (lpdobj != (IntPtr)0)
{
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)
{
uint handleMenuPopup = Win32Helpers.CreatePopupMenu();
StringBuilder szFile = new StringBuilder(260);
int id = 1;
if ((uFlags & 0xf) == 0 || (uFlags & (uint)CMF.CMF_EXPLORE) != 0)
{
uint nselected = Win32Helpers.DragQueryFile(m_hDrop, 0xffffffff,
null, 0);
for (uint uFile = 0; uFile < nselected; uFile++)
{
Win32Helpers.DragQueryFile(m_hDrop, uFile, szFile,
szFile.Capacity + 1);
fileNames += szFile.ToString() + "|";
}
id = PopulateMenu(handleMenuPopup, idCmdFirst + id);
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";
menuItemInfo.fState = (uint)MF.ENABLED;
Win32Helpers.InsertMenuItem(hMenu, (uint)iMenu, 1, ref menuItemInfo);
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
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";
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.
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.
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
{
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();
SHChangeNotify(SHCNE_ASSOCCHANGED, 0, IntPtr.Zero, IntPtr.Zero);
Logger.WriteLog("Registered at: " + System.DateTime.Now);
}
catch (Exception e)
{
Logger.WriteLog(e.Message);
}
}
[ComUnregisterFunction]
static void UnregisterServer(Type t)
{
try
{
RegistryKey root;
RegistryKey rk;
root = Registry.LocalMachine;
rk = root.OpenSubKey(
"Software\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved",
true);
rk.DeleteValue(guid);
rk.Close();
root.Close();
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
- You can register this dll by regasm
- 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
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)