¿Cómo asignar un ícono para los elementos predeterminados del menú contextual de Windows "Copiar / Cortar / Pegar / Eliminar"?

12

En Windows 8 / 8.1 x64, me gustaría asignar un ícono personalizado para los elementos predeterminados del menú contextual de Windows, como Copiar , Cortar , Pegar , Eliminar , Deshacer , Rehacer y Enviar a elementos, que por defecto tiene cualquier ícono:

ingrese la descripción de la imagen aquí

¿Dónde puedo ubicar la "referencia" a esos elementos del menú contextual en el registro y luego agregarles un valor de registro "icono"?

O en otras palabras, cómo asignar un icono a un menú de extensión de la cáscara como el SendTo shellex ?.

Investigación


Como comentó @ Sk8erPeter , parece que:

"Agregar el Iconvalor de cadena a diferentes controladores de menú contextual no funciona como cuando se agrega a un elemento personalizado como, por ejemplo, HKEY_CLASSES_ROOT\*\shell\MYCUSTOMKEY"

ElektroStudios
fuente
¿A qué icono te refieres? ¿Tienes una captura de pantalla?
Raystafarian
@ Raystafarian He actualizado la pregunta con una imagen.
ElektroStudios
1
@Raystafarian: la pregunta es cómo agregar un ícono personalizado a elementos de menú contextual básicos existentes como "Cortar" , "Copiar" , "Eliminar" , "Cambiar nombre" , etc. Por cierto, al agregar un nuevo elemento personalizado al menú contextual, es muy fácil, porque solo tiene que agregar el Iconvalor de cadena en una clave como HKEY_CLASSES_ROOT\*\shell\MYCUSTOMITEM(y el valor de Iconsería como eg %SystemRoot%\System32\shell32.dll,-133o sg. else). PERO agregar el Iconvalor de cadena a diferentes controladores de menú contextual no funciona como cuando se agrega a estos elementos personalizados.
Sk8erPeter
Aquí hay otra captura de pantalla para que quede claro (la parte interesante está en los bordes rojos): i.imgur.com/fmewg6L.png . Por cierto, como puede ver, tengo algunos elementos personalizados en el menú contextual con iconos personalizados (como "Abrir con Notepad ++" ): ¡esto es exactamente lo que nos gustaría lograr con los elementos del menú contextual del sistema existente!
Sk8erPeter
1
@ Sk8erPeter Mi mejor ventaja en este momento es la posibilidad de crear un controlador de menú contextual de shell que utilice SetMenuItemInfoen respuesta a QueryContextMenu.
Ben N

Respuestas:

10

Aviso de afiliación: soy el autor del software mencionado en esta respuesta.

Primero, quiero que sepa que aprendí C ++ y Win32 solo por esta pregunta .

He desarrollado una extensión de shell de 64 bits que se registra como un controlador de menú contextual. Cuando se invoca, revisa los elementos de menú existentes, buscando entradas interesantes. Si encuentra uno, le pega un ícono (que debe haberse cargado antes). Por el momento, busca Copiar , Cortar , Eliminar , Pegar , Rehacer , Enviar a y Deshacer . Puede agregar el suyo modificando el código; El procedimiento para esto se describe a continuación. (Lo siento, no soy lo suficientemente bueno en C ++ para hacerlo configurable).

Una captura de pantalla en acción, con los iconos más feos conocidos por el hombre:

en acción

Puede descargar estos iconos si realmente lo desea.

Configurarlo

Descárgalo (desde mi Dropbox). Aviso : un escáner de VirusTotal detecta este archivo como una forma de malware. Esto es comprensible, dado el tipo de cosas que tiene que hacer para bloquear las entradas existentes. Te doy mi palabra de que no hace daño intencional a tu computadora. Si sospecha y / o quiere modificarlo y extenderlo, ¡vea el código en GitHub !

Crear una carpeta en la unidad C: C:\shellicon. Crear archivos BMP con los siguientes títulos: copy, cut, delete, paste, redo, sendto, undo. (Espero que sea obvio cuál hace qué.) Estas imágenes probablemente deberían ser de 16 por 16 píxeles (o por grande que sea su configuración de DPI que haga el margen del menú), pero también he tenido éxito con las más grandes. Si desea que los iconos se vean transparentes, deberá hacer que su fondo sea del mismo color que el menú contextual. (Este truco también lo emplea Dropbox). Hice mis terribles iconos con MS Paint; otros programas pueden o no guardar de una manera compatible con LoadImageA. 16 por 16 a una profundidad de color de 24 bits a 96 píxeles por pulgada parece ser el conjunto más confiable de propiedades de imagen.

Ponga la DLL en un lugar accesible para todos los usuarios, esa carpeta que acaba de hacer es una buena opción. Abra un indicador de administrador en la carpeta que contiene la DLL y haga regsvr32 ContextIcons.dll. Esto crea la información de registro para los tipos de concha *, Drive, Directory, y Directory\Background. Si alguna vez desea eliminar la extensión de shell, hágalo regsvr32 /u ContextIcons.dll.

Código relevante

Básicamente, la extensión solo consulta el texto de cada elemento del menú contextual GetMenuItemInfoy, si corresponde, ajusta el icono con SetMenuItemInfo.

Visual Studio genera una gran cantidad de código misterioso mágico para proyectos ATL, pero este es el contenido de IconInjector.cpp, que implementa el controlador del menú contextual:

// 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;
}

Tenga en cuenta que los HBITMAPs nunca se limpian, pero esto no importa demasiado dado que las cosas de la DLL desaparecerán cuando Explorer se apague. Los íconos apenas toman memoria de todos modos.

Si está compilando para 32 bits, el primer parámetro para GetCommandStringes solo a en UINTlugar de a UINT_PTR.

Si realmente quiere iconos transparentes, que tendrá que crear una ventana con el icono deseado y luego fijar mii.hBmpItema HBMMENU_SYSTEMy poner el identificador de la ventana en mii.dwItemData, como se describe en la parte inferior de del artículo de MSDN sobreMENUITEMINFO . No pude descubrir cómo crear ventanas a partir de extensiones de shell. LR_LOADTRANSPARENTparece prometedor como una bandera de LoadImageA, pero tiene sus propios riesgos, específicamente, no funciona a menos que use mapas de bits de 256 colores.

Si tiene problemas con la carga de imágenes, intente eliminar el LR_DEFAULTSIZEindicador de las LoadImageAllamadas.

Alguien suficientemente capacitado en C ++ probablemente podría tomar recursos de otras DLL y convertirlos a HBITMAP s, pero ese alguien no soy yo.

Modificándolo

Escribí esto en Visual Studio, que creo que es el mejor editor para Windows C ++.

Cargue el archivo SLN en Visual Studio 2015 después de instalar las herramientas de C ++. En IconInjector.cpp, puede agregar HBITMAPentradas en la parte superior y LoadImageAllamadas Initializepara agregar nuevos íconos. Abajo en la else ifsección, use una wcscmpllamada para buscar una coincidencia exacta o una wcsstrllamada para buscar la presencia de una subcadena. En ambos casos, &representa la posición del subrayado / acelerador cuando se usa Shift + F10. Establezca su modo en Release y su arquitectura en x64, y haga BuildBuild Solution . Obtendrá un error acerca de no registrar la salida, pero no se preocupe; de todas formas querrás hacer esto manualmente. Finalice Explorer, copie la nueva DLL ( \x64\Release\ContextIcons.dllen la carpeta de la solución) en el lugar, luego haga elregsvr32 baile.

Atribuciones

Muchas gracias a los escritores de MSDN y al creador de " The Complete Idiot's Guide to Writing Shell Extensions". ", a la que hice referencia en gran medida.

Elogio

Para las muchas instancias de Explorer que murieron en la producción de esta extensión de shell: usted murió por una gran causa, que algunas personas en Internet pueden tener iconos al lado de sus palabras.

Ben N
fuente
¡Guauu! Realmente aprecio sus esfuerzos, muchas gracias! (+1) Hice mi mejor esfuerzo, pero no pude hacer que la versión compilada funcionara en Windows 10 (Build 10240). No sé cuál es el problema, todas las imágenes bmp existen en la ruta correcta ( C:\shellicon\copy.bmp, etc., estos son iconos de 20x20 píxeles en formato BMP) y registré el dll como administrador en el símbolo del sistema con el regsvr32 ContextIcons.dllque se ejecutó correctamente, pero No veo cambios en el menú contextual. Incluso reinicié la computadora, no registré y volví a registrar el dll, pero no hubo cambios. Estoy tratando de compilar la fuente en VS2015!
Sk8erPeter
@ Sk8erPeter MSDN dijo que los íconos deben ser 16x16, pero 20x20 funciona para mí. ¿Quizás Windows 10 requiere 16x16? Tenga en cuenta que debe reiniciar Explorer para que los cambios surtan efecto.
Ben N
2
@ Sk8erPeter Ciertamente, aquí tienes . Veré sobre poner el código en GitHub. Trabajando en la descarga de Windows 10 ahora ...
Ben N
2
No lo creerás ... ¡FUNCIONA con tus imágenes! : D: D Significa que tengo algunos archivos bmp que Windows no pudo manejar, no sé por qué (más tarde lo comprobaré también). De todos modos, muchas gracias, ¡tu código realmente resuelve el problema! :)
Sk8erPeter
1
@BenN: OK, gracias! :) Hubiera sido un poco más conveniente. Por cierto, mientras tanto, me di cuenta de que si abro mis imágenes que no funcionaban anteriormente en el legendario Paint, y hago un "Guardar como"> "Mapa de bits de 24 bits (.bmp; .dip)" (así que guárdelo en un archivo BMP nuevamente), y uso este nuevo archivo como imagen de origen, FUNCIONA. Por supuesto, el tamaño del mapa de bits debe ser exactamente 16x16 píxeles. Por lo tanto, Paint crea el formato de mapa de bits esperado que es de 24 bits por píxel (16,7 millones de colores), 96x96 DPI y 16x16 píxeles de tamaño. Anteriormente convertí y redimensioné archivos .png en IrfanView a archivos .bmp, estos íconos no funcionaron.
Sk8erPeter
1

No tengo suficiente representante para dejar un comentario, pero parece que esta información está contenida dentro de shell32.dll. Los archivos se han compilado, por lo que es difícil ver qué funciones contiene, pero parece ser la indicada.

De interés (registro de exportación):

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

(Predeterminado) REG_SZ Copiar / Mover / Cambiar nombre / Eliminar / Vincular objeto

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

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

Bajo la clave InProcServer32, hace referencia a shell32.dll. También hay un par de otros con nombres que suenan relevantes. Posiblemente también de interés es windows.storage.dll

nijave
fuente
1
Información interesante. Sin embargo, parece ser un comentario más que una respuesta. Ahora tienes suficiente representante para comentar en todas partes :)
Ben N