I need to display the Windows native context menu of Open with
in my application and I already can show it. However, I encountered a problem is I can't execute any Apps (Photos/Paint/...) in Open with
submenu properly.
For example, I press right click on a jpg image and hover cursor to open with, then choose Paint to open it, but nothing happen (no exeception, error) after clicking Paint (There is no Paint process in Task Manager).
The screenshot below can reveals my problem precisely, Apps in red block can't be execute properly (Neither native nor third-party applications can be executed). But Search the Microsoft Store
and Choose another app
can work well.
I found that @yberk 's post also mentioned this problem, but he didn't find any solution
I have read lots of documents and examples, but still can't figure out the problem.
By the way, my development environment is .NET Framework 4.7.2 on Windows10 2004 version.
The following is my code snippet
// My entry point. Right click on the tofu.png
static void Main(string[] args)
{
FileInfo[] files = new FileInfo[1];
files[0] = new FileInfo(@"k:\qqq\tofu.png");
ShellContextMenu scm = new ShellContextMenu();
scm.ShowContextMenu(files, Cursor.Position);
}
Overwrite WindowMessages - Handle user's behavior on the context menu
protected override void WndProc(ref Message m)
{
#region IContextMenu
if (_oContextMenu != null &&
m.Msg == (int)WM.MENUSELECT &&
((int)ShellHelper.HiWord(m.WParam) & (int)MFT.SEPARATOR) == 0 &&
((int)ShellHelper.HiWord(m.WParam) & (int)MFT.POPUP) == 0)
{
string info = string.Empty;
if (ShellHelper.LoWord(m.WParam) == (int)CMD_CUSTOM.ExpandCollapse)
info = "Expands or collapses the current selected item";
else
{
info = ""
}
}
#endregion
#region IContextMenu2
if (_oContextMenu2 != null &&
(m.Msg == (int)WM.INITMENUPOPUP ||
m.Msg == (int)WM.MEASUREITEM ||
m.Msg == (int)WM.DRAWITEM))
{
if (_oContextMenu2.HandleMenuMsg((uint)m.Msg, m.WParam, m.LParam) == S_OK)
return;
}
#endregion
#region IContextMenu3
if (_oContextMenu3 != null &&
m.Msg == (int)WM.MENUCHAR)
{
if (_oContextMenu3.HandleMenuMsg2((uint)m.Msg, m.WParam, m.LParam, IntPtr.Zero) == S_OK)
return;
}
#endregion
base.WndProc(ref m);
}
Show the context menu while right clicking on a file
private void ShowContextMenu(Point pointScreen)
{
IntPtr pMenu = IntPtr.Zero,
iContextMenuPtr = IntPtr.Zero,
iContextMenuPtr2 = IntPtr.Zero,
iContextMenuPtr3 = IntPtr.Zero;
try
{
// Gets the interfaces to the context menu (IContextMenu)
if (false == GetContextMenuInterfaces(_oParentFolder, _arrPIDLs, out iContextMenuPtr))
{
ReleaseAll();
return;
}
// Create main context menu instance
pMenu = CreatePopupMenu();
// Get all items of context menu
int nResult = _oContextMenu.QueryContextMenu(
pMenu,
0,
CMD_FIRST,
CMD_LAST,
CMF.EXPLORE |
CMF.NORMAL |
((Control.ModifierKeys & Keys.Shift) != 0 ? CMF.EXTENDEDVERBS : 0));
Marshal.QueryInterface(iContextMenuPtr, ref IID_IContextMenu2, out iContextMenuPtr2);
Marshal.QueryInterface(iContextMenuPtr, ref IID_IContextMenu3, out iContextMenuPtr3);
_oContextMenu2 = (IContextMenu2)Marshal.GetTypedObjectForIUnknown(iContextMenuPtr2, typeof(IContextMenu2));
_oContextMenu3 = (IContextMenu3)Marshal.GetTypedObjectForIUnknown(iContextMenuPtr3, typeof(IContextMenu3));
// wait for the user to select an item and will return the id of the selected item.
uint nSelected = TrackPopupMenuEx(
pMenu,
TPM.RETURNCMD,
pointScreen.X,
pointScreen.Y,
this.Handle,
IntPtr.Zero);
if (nSelected != 0)
{
InvokeCommand(_oContextMenu, nSelected, _strParentFolder, pointScreen);
}
}
catch
{
throw;
}
finally
{
ReleaseAll();
}
}
InvokeCommand - trigger specific command based on lpverb (get that by id position)
private void InvokeCommand(IContextMenu oContextMenu, uint nCmd, string strFolder, Point pointInvoke)
{
CMINVOKECOMMANDINFOEX invoke = new CMINVOKECOMMANDINFOEX();
invoke.cbSize = cbInvokeCommand;
invoke.lpVerb = (IntPtr)(nCmd - CMD_FIRST);
invoke.lpDirectory = strFolder;
invoke.lpVerbW = (IntPtr)(nCmd - CMD_FIRST);
invoke.lpDirectoryW = strFolder;
invoke.fMask = CMIC.UNICODE | CMIC.PTINVOKE |
((Control.ModifierKeys & Keys.Control) != 0 ? CMIC.CONTROL_DOWN : 0) |
((Control.ModifierKeys & Keys.Shift) != 0 ? CMIC.SHIFT_DOWN : 0);
invoke.ptInvoke = new POINT(pointInvoke.X, pointInvoke.Y);
invoke.nShow = SW.SHOWNORMAL;
oContextMenu.InvokeCommand(ref invoke);
}
Finally, I found the reason. We need put the [STAThread]
on the entry point.
See windows document STAThread
This attribute must be present on the entry point of any application that uses Windows Forms; if it is omitted, the Windows components might not work correctly. If the attribute is not present, the application uses the multithreaded apartment model, which is not supported for Windows Forms.
namespace test
{
class Program
{
[STAThread]
static void Main(string[] args)
{
FileInfo[] files = new FileInfo[1];
files[0] = new FileInfo(@"K:\qqq\tofu.png");
ShellContextMenu scm = new ShellContextMenu();
scm.ShowContextMenu(files, Cursor.Position);
}
}
.
.
.
}