C# - 如何使用上下文菜单正确触发应用程序打开
Posted
技术标签:
【中文标题】C# - 如何使用上下文菜单正确触发应用程序打开【英文标题】:C# - How to trigger the App in open with context menu properly 【发布时间】:2021-03-16 06:45:53 【问题描述】:我需要在我的应用程序中显示 Open with
的 Windows 本机上下文菜单,并且我已经可以显示它。但是,我遇到了一个问题是我无法正确执行Open with
子菜单中的任何应用程序(照片/绘画/...)。
例如,我在一张jpg图片上按右键,鼠标悬停打开,然后选择Paint打开,但是点击Paint后没有任何反应(没有异常,错误)(任务管理器中没有Paint进程) .
下面的截图可以准确地揭示我的问题,红色块中的应用程序无法正常执行(无论是原生应用程序还是第三方应用程序都无法执行)。但是Search the Microsoft Store
和Choose another app
可以很好地工作。
我发现@yberk 的帖子也提到了这个问题,但他没有找到任何解决方案
Wait for process started by IContextMenu.InvokeCommand我已经阅读了很多文档和示例,但仍然无法找出问题所在。
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版本。
以下是我的代码sn-p
// 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);
【问题讨论】:
你有一个小的可编译的复制项目吗? @SimonMourier 你可以从这个google drive link 获得我的压缩VS 项目。感谢您的帮助。 对于您的示例,“打开方式”子菜单项适用于我,使用现有文件,例如带有“打开方式和“绘制”的 .png。 @SimonMourier 您介意分享您的环境吗? Windows 版本、Visual Studio 版本以及您是如何编译代码的?由于我是 C# 的新手并且对 Visual Studio 不熟悉,也许我错过了一些重要的步骤...顺便说一下,当您在探索中右键单击 png 文件时,您的上下文菜单中的项目与上下文菜单相同?因为我只能拿到基本的物品,错过了很多第三方应用的物品。 刚刚拿到你的项目,设置文件路径并运行。 Windows 10 20H2 x64 Visual Studio 2019。我试过调试版本编译 x64 x86 没有改变任何东西(它不应该)。不,项目菜单不完全相同,但可以正常打开。另一种方法是使用 SHCreateDefaultContextMenu,通过绝对 pidls 传递一个 IShellFolder 自定义实现(使用 ICustomQueryInferface 来确定需要什么):***.com/questions/61613374/… 【参考方案1】:终于找到了原因。我们需要将[STAThread]
放在入口点上。
见windows文档STAThread
此属性必须存在于任何使用 Windows 窗体的应用程序的入口点上;如果省略,Windows 组件可能无法正常工作。如果该属性不存在,则应用程序使用多线程单元模型,Windows 窗体不支持该模型。
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);
.
.
.
【讨论】:
以上是关于C# - 如何使用上下文菜单正确触发应用程序打开的主要内容,如果未能解决你的问题,请参考以下文章
在 SharpShell 从上下文菜单启动的程序中继承正确的 app.config