如何为Windows“默认”上下文菜单项的“复制/剪切/粘贴/删除”分配图标?


12

在Windows 8 / 8.1 x64下,我想为默认的Windows上下文菜单项(如CopyCutPasteDeleteUndoRedoSend To项)分配一个自定义图标,默认情况下具有任何图标:

在此处输入图片说明

在哪里可以找到注册表中这些上下文菜单项的“引用”,然后为它们添加“图标”注册表值?

或者换句话说,如何将图标分配给Shell扩展菜单(例如SendTo shellex)?

研究


如@ Sk8erPeter所评论,似乎:

“将Icon字符串值添加到不同的上下文菜单处理程序时,无法像将其添加到例如 HKEY_CLASSES_ROOT\*\shell\MYCUSTOMKEY


您指的是什么图标?你有截图吗?
雷斯塔法里安(Raystafarian)2015年

@Raystafarian我已经用图像更新了问题。
ElektroStudios 2015年

1
@Raystafarian:问题是如何在现有的基本上下文菜单项(例如“剪切”“复制”“删除”“重命名”等)中添加自定义图标。顺便说一句,在向上下文菜单中添加新的自定义项时,非常简单,因为您只需要Icon在键之类的字符串中添加字符串值HKEY_CLASSES_ROOT\*\shell\MYCUSTOMITEM(而键的值Icon将例如eg %SystemRoot%\System32\shell32.dll,-133或sg。else)。但是,增加的Icon字符串值,以不同的上下文菜单的处理程序不起作用将其添加到这些自定义项目时一样。
Sk8erPeter

这是另一个清晰的屏幕截图(有趣的部分是红色边框):i.imgur.com/fmewg6L.png。顺便说一句,如您所见,我在上下文菜单中有一些带有自定义图标的自定义项(例如“使用Notepad ++打开”)-这正是我们希望通过现有系统上下文菜单项实现的目标!
Sk8erPeter

1
@ Sk8erPeter目前,我最好的方法是创建一个用于SetMenuItemInfo响应的shell上下文菜单处理程序QueryContextMenu
本N

Answers:


10

会员通知:我是此答案中提到的软件的作者。

首先,我会让您知道我只是为了这个问题而学习了C ++和Win32 。

我已经开发了一个64位的shell扩展,该扩展被注册为上下文菜单处理程序。调用它时,它会遍历现有菜单项,寻找有趣的条目。如果找到一个,它将在其上粘贴一个图标(必须早先加载)。目前,它会查找“ 复制”,“ 剪切”,“ 删除”,“ 粘贴”,“ 重做”,“ 发送到 ”和“ 撤消”。您可以通过修改代码来添加自己的代码。程序说明如下。(对不起,我对C ++的了解不足,无法对其进行配置。)

实际运行时的屏幕截图,其中包含人类已知的最丑陋的图标:

在行动

如果您确实愿意,可以下载这些图标

设定

从我的Dropbox 下载注意一台VirusTotal扫描仪将该文件检测为某种形式的恶意软件。鉴于必须对现有条目进行修改,这是可以理解的。我对您说,它不会对您的计算机造成故意伤害。如果您怀疑和/或想要修改和扩展它,请参见GitHub上的代码!

在C驱动器中创建一个文件夹:C:\shellicon。具有下列标题创建BMP文件:copycutdeletepasteredosendtoundo。(希望很明显,哪个做某事。)这些图像可能应该是16 x 16像素(或者DPI设置太大会增加菜单边距),但是我也成功使用了较大的图像。如果希望图标看起来透明,则只需使其背景与上下文菜单具有相同的颜色。(Dropbox也使用了这个技巧。)我使用MS Paint制作了可怕的图标;其他程序可能或可能不与兼容地保存LoadImageA每英寸96像素的24位色深下的16 x 16似乎是最可靠的图像属性集。

将DLL放置在所有用户均可访问的位置,您刚刚创建的文件夹是一个不错的选择。在包含DLL的文件夹中打开一个管理提示,然后执行do regsvr32 ContextIcons.dll。这对shell类型创建注册信息*DriveDirectory,和Directory\Background。如果要删除外壳扩展名,请执行regsvr32 /u ContextIcons.dll

相关代码

基本上,扩展名仅使用来查询每个上下文菜单项的文本,GetMenuItemInfo并在适当时使用来调整图标SetMenuItemInfo

Visual Studio 为ATL项目生成了许多神奇的神秘代码,但这是的内容IconInjector.cpp,它实现了上下文菜单处理程序:

// IconInjector.cpp : Implementation of CIconInjector

#include "stdafx.h"
#include "IconInjector.h"
#include <string>

// CIconInjector

HBITMAP bmpCopy = NULL;
HBITMAP bmpCut = NULL;
HBITMAP bmpUndo = NULL;
HBITMAP bmpRedo = NULL;
HBITMAP bmpSendto = NULL;
HBITMAP bmpDel = NULL;
HBITMAP bmpPaste = NULL;
STDMETHODIMP CIconInjector::Initialize(LPCITEMIDLIST pidlFolder, LPDATAOBJECT pDataObj, HKEY hProgID) {
    // Load the images
    bmpCopy = (HBITMAP)LoadImageA(NULL, "C:\\shellicon\\copy.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE | LR_DEFAULTSIZE);
    bmpCut = (HBITMAP)LoadImageA(NULL, "C:\\shellicon\\cut.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE | LR_DEFAULTSIZE);
    bmpUndo = (HBITMAP)LoadImageA(NULL, "C:\\shellicon\\undo.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE | LR_DEFAULTSIZE);
    bmpRedo = (HBITMAP)LoadImageA(NULL, "C:\\shellicon\\redo.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE | LR_DEFAULTSIZE);
    bmpSendto = (HBITMAP)LoadImageA(NULL, "C:\\shellicon\\sendto.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE | LR_DEFAULTSIZE);
    bmpDel = (HBITMAP)LoadImageA(NULL, "C:\\shellicon\\delete.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE | LR_DEFAULTSIZE);
    bmpPaste = (HBITMAP)LoadImageA(NULL, "C:\\shellicon\\paste.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE | LR_DEFAULTSIZE);
    int err = GetLastError();
    return S_OK;
}
STDMETHODIMP CIconInjector::QueryContextMenu(HMENU hmenu, UINT uMenuIndex, UINT uidFirst, UINT uidLast, UINT flags) {
    using namespace std;
    if (flags & CMF_DEFAULTONLY) return S_OK; // Don't do anything if it's just a double-click
    int itemsCount = GetMenuItemCount(hmenu);
    for (int i = 0; i < itemsCount; i++) { // Iterate over the menu items
        MENUITEMINFO mii;
        ZeroMemory(&mii, sizeof(mii));
        mii.cbSize = sizeof(mii);
        mii.fMask = MIIM_FTYPE | MIIM_STRING;
        mii.dwTypeData = NULL;
        BOOL ok = GetMenuItemInfo(hmenu, i, TRUE, &mii); // Get the string length
        if (mii.fType != MFT_STRING) continue;
        UINT size = (mii.cch + 1) * 2; // Allocate enough space
        LPWSTR menuTitle = (LPWSTR)malloc(size);
        mii.cch = size;
        mii.fMask = MIIM_TYPE;
        mii.dwTypeData = menuTitle;
        ok = GetMenuItemInfo(hmenu, i, TRUE, &mii); // Get the actual string data
        mii.fMask = MIIM_BITMAP;
        bool chIcon = true;
        if (wcscmp(menuTitle, L"&Copy") == 0) {
            mii.hbmpItem = bmpCopy;
        }
        else if (wcscmp(menuTitle, L"Cu&t") == 0) {
            mii.hbmpItem = bmpCut;
        }
        else if (wcscmp(menuTitle, L"&Paste") == 0) {
            mii.hbmpItem = bmpPaste;
        } 
        else if (wcscmp(menuTitle, L"Se&nd to") == 0) {
            mii.hbmpItem = bmpSendto;
        }
        else if (wcsstr(menuTitle, L"&Undo") != NULL) {
            mii.hbmpItem = bmpUndo;
        }
        else if (wcsstr(menuTitle, L"&Redo") != NULL) {
            mii.hbmpItem = bmpRedo;
        }
        else if (wcscmp(menuTitle, L"&Delete") == 0) {
            mii.hbmpItem = bmpDel;
        }
        else {
            chIcon = false;
        }
        if (chIcon) SetMenuItemInfo(hmenu, i, TRUE, &mii);
        free(menuTitle);
    }
    return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 0); // Same as S_OK (= 0) but is The Right Thing To Do [TM]
}
STDMETHODIMP CIconInjector::InvokeCommand(LPCMINVOKECOMMANDINFO info) {
    return S_OK;
}
STDMETHODIMP CIconInjector::GetCommandString(UINT_PTR, UINT, UINT*, LPSTR, UINT) {
    return S_OK;
}

请注意,HBITMAP永远不会清除s,但这并没有太大关系,因为当Explorer关闭时,DLL的内容将消失。这些图标几乎不会占用任何内存。

如果您要针对32位进行编译,则第一个参数to GetCommandString只是a UINT而不是a UINT_PTR

如果您真的想要透明的图标,则必须创建一个带有所需图标的窗口,然后将其设置mii.hBmpItemHBMMENU_SYSTEM,然后将其置于其中mii.dwItemData,如MSDN文章MENUITEMINFO底部的介绍。我无法弄清楚如何从shell扩展创建窗口。LR_LOADTRANSPARENT作为的标志LoadImageA,它看起来很有希望,但它有其自身的陷阱-特别是,除非您使用256色位图,否则它不起作用。

如果图像加载遇到问题,请尝试LR_DEFAULTSIZELoadImageA呼叫中删除该标志。

精通C ++的人可能会从其他DLL中获取资源并将其转换为HBITMAPs,但那不是我。

修改它

我是在Visual Studio中编写的,我认为它是Windows C ++的最佳编辑器。

安装C ++工具后,将SLN文件加载到Visual Studio 2015中。在中IconInjector.cpp,您可以HBITMAP在顶部添加条目,并LoadImageA调用Initialize以添加新图标。在本else if节的下面,使用wcscmp调用查找完全匹配,或使用wcsstr调用查找子字符串的存在。在这两种情况下,&代表使用Shift + F10时下划线/加速器的位置。将模式设置为Release,将架构设置为x64,然后执行BuildBuild Solution。您将收到有关未能注册输出的错误,但请不要担心;您还是想手动执行此操作。结束资源管理器,将新的DLL(\x64\Release\ContextIcons.dll在solution文件夹中)复制到该位置,然后进行regsvr32跳舞。

归因

非常感谢MSDN的编写者,以及我引用过多的完整的傻瓜编写Shell扩展指南 ”的创建者。

对于在此shell扩展的生产中被杀死的许多Explorer实例:您之所以死是因为很大的原因,Internet上的某些人可以在其字词旁边带有图标。


哇!我非常感谢您的努力,非常感谢!(+1)我已尽力而为,但无法在Windows 10(内部版本10240)上使编译后的版本正常工作。我不知道问题出在哪里,所有的bmp图像都存在于正确的路径中(C:\shellicon\copy.bmp等等-这些是BMP格式的20x20像素的图标),我在命令提示符下将dll注册为admin,regsvr32 ContextIcons.dll并且运行成功,但是我没有看到上下文菜单中的任何更改。我什至重新启动了计算机,注销并再次重新注册了dll,但没有任何更改。我正在尝试在VS2015中编译源代码!
Sk8erPeter

@ Sk8erPeter MSDN说图标必须为16x16,但20x20对我有用。也许Windows 10需要16x16?请注意,您必须重新启动资源管理器才能使更改生效。
本N

2
@ Sk8erPeter当然,你去了。我将看到有关将代码放在GitHub上的信息。正在努力下载Windows 10 ...
Ben N

2
您不会相信...它可以与您的图像配合使用!:D:D这意味着我有一些Windows无法处理的bmp文件,不知道为什么(稍后我也会进行检查)。无论如何,非常感谢,您的代码确实可以解决问题!:)
Sk8erPeter

1
@BenN:好的,谢谢!:)会更方便一些。同时,我注意到BTW,如果我在传奇的 Paint中打开以前无法使用的图像,然后执行“另存为”>“ 24位位图(.bmp; .dip)”(因此将其保存到BMP文件中)再次),并且我将此新文件用作源图像,它很有效。当然,位图的大小必须恰好为16x16像素。因此,Paint创建了预期的位图格式,该格式为每像素24位(1670万种颜色),96x96 DPI和16x16像素。以前,我将IrfanView中的.png文件转换为.bmp文件并调整了大小,这些图标不起作用。
Sk8erPeter '16

1

我没有足够的代表留下评论,但看来此信息包含在shell32.dll中。这些文件已编译,因此很难看到其中包含什么功能,但似乎只是其中之一。

感兴趣的(注册表输出):

HKEY_CLASSES_ROOT \ CLSID {3ad05575-8857-4850-9277-11b85bdb8e09}

(默认) REG_SZ 复制/移动/重命名/删除/链接对象

AppID REG_SZ {3ad05575-8857-4850-9277-11b85bdb8e09}

LocalizedString REG_EXPAND_SZ @%SystemRoot%\ system32 \ shell32.dll,-50176

在InProcServer32项下,它引用shell32.dll。还有其他一些相关的冠冕堂皇的名字。可能还会感兴趣的是windows.storage.dll


1
有趣的信息。但是,这似乎是评论而不是答案。您现在有足够的代表在任何地方发表评论了:)
Ben N
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.