C# - 如何正确触发使用上下文菜单打开的应用程序
C# - How to trigger the App in open with context menu properly
我需要在我的应用程序中显示 Open with
的 Windows 本机上下文菜单,我已经可以显示它了。但是,我遇到了一个问题,我无法正确执行 Open with
子菜单中的任何应用程序 (Photos/Paint/...)。
比如我在一张jpg图片上右击鼠标悬停打开,然后选择Paint打开,但是点击Paint后没有任何反应(没有异常,报错)(Task中没有Paint进程经理)。
下面的截图可以准确地揭示我的问题,红色方框内的应用程序无法正常执行(Native native nor third-party applications can be executed)。但是 Search the Microsoft Store
和 Choose another app
可以很好地工作。
我发现@yberk的post也提到了这个问题,但是他没有找到任何解决方案
看了很多文档和例子,还是没搞清楚问题所在
- A Raymond Chen blog series "How to host an IContextMenu"
- Explorer Shell Context Menu - Code Project
- 我从这里得到了几乎所有的示例代码
- C# File Browser - Code Project
- dwmkerr/sharpshell - github
- Gong Solutions Shell Library
对了,我的开发环境是.NET Framework 4.7.2 on Windows10 2004 version.
以下是我的代码片段
// 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);
}
覆盖 WindowMessages - 处理用户在上下文菜单上的行为
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);
}
右键单击文件时显示上下文菜单
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 - 触发基于 lpverb 的特定命令(通过 id 位置获取)
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);
}
终于找到原因了。我们需要把 [STAThread]
放在入口点上。
参见 windows 文档 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);
}
}
.
.
.
}
我需要在我的应用程序中显示 Open with
的 Windows 本机上下文菜单,我已经可以显示它了。但是,我遇到了一个问题,我无法正确执行 Open with
子菜单中的任何应用程序 (Photos/Paint/...)。
比如我在一张jpg图片上右击鼠标悬停打开,然后选择Paint打开,但是点击Paint后没有任何反应(没有异常,报错)(Task中没有Paint进程经理)。
下面的截图可以准确地揭示我的问题,红色方框内的应用程序无法正常执行(Native native nor third-party applications can be executed)。但是 Search the Microsoft Store
和 Choose another app
可以很好地工作。
我发现@yberk的post也提到了这个问题,但是他没有找到任何解决方案
看了很多文档和例子,还是没搞清楚问题所在
- A Raymond Chen blog series "How to host an IContextMenu"
- Explorer Shell Context Menu - Code Project
- 我从这里得到了几乎所有的示例代码
- C# File Browser - Code Project
- dwmkerr/sharpshell - github
- Gong Solutions Shell Library
对了,我的开发环境是.NET Framework 4.7.2 on Windows10 2004 version.
以下是我的代码片段
// 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);
}
覆盖 WindowMessages - 处理用户在上下文菜单上的行为
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);
}
右键单击文件时显示上下文菜单
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 - 触发基于 lpverb 的特定命令(通过 id 位置获取)
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);
}
终于找到原因了。我们需要把 [STAThread]
放在入口点上。
参见 windows 文档 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);
}
}
.
.
.
}