我正在尝试在解决方案资源管理器的实验实例中删除“剪切”和“删除”的键盘快捷键。 下面的代码在全球范围内运行(对于整个视觉工作室来说是有限制的)。 我需要将其限制为解决方案资源管理器,当我们单击任何特定文件时,剪切和删除键盘快捷键应该不起作用。
这是我尝试过的代码片段:
DTE dte = (EnvDTE.DTE)Package.GetGlobalService(typeof(EnvDTE.DTE));
Command command = dte.Commands.Item("Edit.Cut", -1);
if (command.Bindings != null)
{
command.Bindings = new object[];
}
这非常棘手,所以请耐心等待。
引自将 Visual Studio 编辑器与投影缓冲区分开:
Visual Studio 使用“命令链设计模式”来路由命令。本质上,链表是由对监听命令感兴趣的不同组件(全部继承自
IOleCommandTarget
)创建的。基本命令包括箭头按键、CTRL+Z和退格。有关许多可能命令的示例,请参阅 VSConstants.VSStd97CmdID。 有了这些信息,我们可以想象类似获取解决方案资源管理器窗口的
(它实现
IOleCommandTarget
接口)并尝试更改其命令目标。经过一番搜索,我得到了这个:
// get the solution explorer window, note that you need to make sure it is loaded at the time
var solutionExplorerWindow = await VS.Windows.GetSolutionExplorerWindowAsync();
solutionExplorerWindow.Frame.GetProperty((int)__VSFPROPID.VSFPROPID_DocView, out object property);
// note that it is not actually `ToolWindowPane`, but an internal derived class
ToolWindowPane solutionExplorerPane = property as ToolWindowPane;
之后,我设置了一个断点来检查它有哪些字段,发现它有一个
IOleCommandTarget _commandTarget
字段,这是非常不言自明的。现在我们只需要将其更改为我们自己的
IOleCommandTarget
。棘手的是,字段是私有的,而类是内部的。所以我们需要做一些反射魔法:
FieldInfo commandTargetField = solutionExplorerPane.GetType().BaseType.GetField("_commandTarget", BindingFlags.Instance | BindingFlags.NonPublic);
// get the original command target
var originalCommandTarget = commandTargetField.GetValue(solutionExplorerPane) as IOleCommandTarget;
// create out command handler, with the orignal command target
CommandHandler commandHandler = new(originalCommandTarget);
// "hook" its command target
commandTargetField.SetValue(solutionExplorerPane, commandHandler);
在上面的代码片段中,我们得到它的
_commandTarget
,并构造我们自己的
CommandHandler
来覆盖原来的,基本上是一个钩子。以下是 CommandHandler
的实施方式:internal class CommandHandler : IOleCommandTarget
{
private readonly IOleCommandTarget _commandTarget;
internal CommandHandler(IOleCommandTarget commandTarget)
{
_commandTarget = commandTarget;
}
public DesignerVerbCollection Verbs => throw new NotImplementedException();
// Disable this warning because this will always be executed on the main thread
#pragma warning disable VSTHRD010 // Invoke single-threaded types on Main thread
// This method will be called to check if we can handle the command or not
public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText)
{
// do this if you want to hide the button on the "Edit" tool bar
if (pguidCmdGroup == VSConstants.CMDSETID.StandardCommandSet97_guid)
{
for (uint i = 0; i < prgCmds.Length; i++)
{
if (prgCmds[i].cmdID == (uint)VSConstants.VSStd97CmdID.Cut ||
prgCmds[i].cmdID == (uint)VSConstants.VSStd97CmdID.Delete)
{
prgCmds[i].cmdf = (uint)(OLECMDF.OLECMDF_INVISIBLE | OLECMDF.OLECMDF_INVISIBLE);
}
}
}
return _commandTarget.QueryStatus(pguidCmdGroup, cCmds, prgCmds, pCmdText);
}
// This method will be called to actually execute the command
public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
{
if (pguidCmdGroup == VSConstants.CMDSETID.StandardCommandSet97_guid)
{
if (nCmdID == (uint)VSConstants.VSStd97CmdID.Cut ||
nCmdID == (uint)VSConstants.VSStd97CmdID.Delete)
{
return VSConstants.S_OK;
}
}
return _commandTarget.Exec(pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut);
}
#pragma warning restore VSTHRD010 // Invoke single-threaded types on Main thread
}
请注意,您可能还想检查其他命令集,例如
。