Integración de una aplicación de escritorio empaquetada con el Explorador de archivos
Algunas aplicaciones de Windows definen extensiones del Explorador de archivos que agregan entradas del menú contextual que permiten a los clientes realizar opciones relacionadas con la aplicación. Las tecnologías de implementación de aplicaciones de Windows anteriores, como MSI y ClickOnce, definen extensiones del Explorador de archivos a través del Registro. El Registro tiene una serie de subárboles que controlan las extensiones del Explorador de archivos y otros tipos de extensiones de Shell. Normalmente, estos instaladores crean una serie de claves del Registro para configurar los distintos elementos que se van a incluir en el menú contextual.
Si empaqueta la aplicación de Windows mediante MSIX, el Registro se virtualiza y, por lo tanto, la aplicación no puede registrar extensiones del Explorador de archivos a través de dicho Registro. En su lugar, debe definir las extensiones del Explorador de archivos a través de las extensiones de paquete, que se definen en el manifiesto de paquete. En este artículo se describen varias maneras de hacerlo.
Puede buscar en GitHub el código de ejemplo completo usado en este artículo.
Adición de una entrada de menú contextual que admita parámetros de inicio
Una de las maneras más sencillas de realizar la integración en el Explorador de archivos es definir una extensión de paquete que agregue la aplicación a la lista de aplicaciones disponibles en el menú contextual cuando un usuario haga clic con el botón derecho en un tipo de archivo específico en el Explorador de archivos. Si el usuario abre la aplicación, la extensión puede pasar los parámetros a dicha aplicación.
Este escenario tiene varias limitaciones:
- Solo funciona en combinación con la característica de asociación de tipos de archivo. Puede mostrar opciones adicionales en el menú contextual solo para los tipos de archivo que estén asociados a la aplicación principal (por ejemplo, si la aplicación admite la apertura de un archivo al hacer doble clic en él en el Explorador de archivos).
- Las opciones del menú contextual se mostrarán solo si la aplicación está establecida como la opción predeterminada para ese tipo de archivo.
- La única acción admitida es iniciar el archivo ejecutable principal de la aplicación (es decir, el mismo ejecutable que está conectado a la entrada del menú Inicio). Sin embargo, cada acción puede especificar parámetros diferentes, que puede usar cuando las aplicaciones empiecen a comprender qué acción desencadenó la ejecución, así como a realizar diferentes tareas.
A pesar de estas limitaciones, este enfoque es suficiente para muchos escenarios. Por ejemplo, si va a compilar un editor de imágenes, puede agregar fácilmente una entrada en el menú contextual para cambiar el tamaño de una imagen, lo que iniciará el editor de imágenes directamente con un asistente para iniciar el proceso de cambio de tamaño.
Implementación de la entrada del menú contextual
Para admitir este escenario, agregue un elemento Extension con la categoría windows.fileTypeAssociation
al manifiesto de paquete. Este debe agregarse como elemento secundario del elemento Extensions en el elemento Application.
En el ejemplo siguiente se muestra un registro de una aplicación que habilita los menús contextuales para los archivos con la extensión .foo
. Este ejemplo especifica la extensión .foo
porque es falsa y normalmente no está registrada en otras aplicaciones de ningún equipo determinado. Si tiene que administrar un tipo de archivo que ya se use (por ejemplo, .txt o .jpg), recuerde que no podrá ver la opción hasta que la aplicación esté establecida como predeterminada para ese tipo de archivo. Este ejemplo es un extracto del archivo Package.appxmanifest del ejemplo relacionado en GitHub.
<Extensions>
<uap3:Extension Category="windows.fileTypeAssociation">
<uap3:FileTypeAssociation Name="foo" Parameters=""%1"">
<uap:SupportedFileTypes>
<uap:FileType>.foo</uap:FileType>
</uap:SupportedFileTypes>
<uap2:SupportedVerbs>
<uap3:Verb Id="Resize" Parameters=""%1" /p">Resize file</uap3:Verb>
</uap2:SupportedVerbs>
</uap3:FileTypeAssociation>
</uap3:Extension>
</Extensions>
En este ejemplo se da por supuesto que los siguientes espacios de nombres y alias están declarados en el elemento raíz <Package>
del manifiesto.
<Package
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:uap2="http://schemas.microsoft.com/appx/manifest/uap/windows10/2"
xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
IgnorableNamespaces="uap uap2 uap3 rescap">
...
</Package>
El elemento FileTypeAssociation asocia la aplicación a los tipos de archivo que quiere admitir. Para obtener más información, consulte Asociar la aplicación empaquetada con un conjunto de tipos de archivo. Estos son los elementos más importantes relacionados con este.
Atributo o elemento | Descripción |
---|---|
Atributo Name |
Coincide con el nombre de la extensión que quiere registrar menos el punto (en el ejemplo anterior, foo ). |
Atributo Parameters |
Contiene los parámetros que quiere pasar a la aplicación cuando el usuario hace doble clic en un archivo con dicha extensión. Normalmente, al menos, se pasa %1 , que es un parámetro especial que contiene la ruta de acceso del archivo seleccionado. De este modo, al hacer doble clic en un archivo, la aplicación conoce su ruta de acceso completa y puede cargarla. |
Elemento SupportedFileTypes | Especifica el nombre de la extensión que quiere registrar, incluido el punto (en este ejemplo, .foo ). Puede especificar varias entradas <FileType> si quiere admitir más tipos de archivo. |
Para definir la integración del menú contextual, también debe agregar el elemento secundario SupportedVerbs. Este elemento contiene uno o varios elementos Verb que definen las opciones que se mostrarán cuando un usuario haga clic con el botón derecho en un archivo con la extensión .foo en el Explorador de archivos. Para obtener más información, consulte Agregar opciones en los menús contextuales de los archivos que tienen un tipo de archivo específico. Estos son los elementos más importantes relacionados con el elemento Verb.
Atributo o elemento | Descripción |
---|---|
Atributo Id |
Especifica el identificador único para la acción. |
Atributo Parameters |
De forma similar al elemento FileTypeAssociation, este atributo para el elemento Verb contiene los parámetros que se pasan a la aplicación cuando el usuario hace clic en la entrada del menú contextual. Normalmente, aparte del parámetro especial %1 para obtener la ruta de acceso del archivo seleccionado, se pasan también uno o más parámetros para obtener el contexto. De este modo, la aplicación puede comprender que se abrió desde una entrada del menú contextual. |
Valor del elemento | El valor del elemento Verb contiene la etiqueta que se va a mostrar en la entrada del menú contextual (en este ejemplo, Resize file [Cambiar tamaño del archivo]). |
Acceso a los parámetros de inicio en el código de la aplicación
La manera en que la aplicación recibe los parámetros depende del tipo de aplicación que haya creado. Por ejemplo, una aplicación de WPF normalmente procesa los argumentos del evento de inicio en el método OnStartup
de la clase App
. Puede comprobar si hay parámetros de inicio y, en función del resultado, realizar la acción más adecuada (como abrir una ventana específica de la aplicación en lugar de la principal).
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
if (e.Args.Contains("Resize"))
{
// Open a specific window of the app.
}
else
{
MainWindow main = new MainWindow();
main.Show();
}
}
}
En la captura de pantalla siguiente se muestra la entrada de menú contextual Resize file (Cambiar tamaño del archivo) creada en el ejemplo anterior.
Compatibilidad con archivos o carpetas genéricos y realización de tareas complejas
Aunque el uso de la extensión FileTypeAssociation en el manifiesto de paquete es suficiente para muchos escenarios, tal como se describe en la sección anterior, es posible que le resulte restrictivo. Los dos desafíos más importantes son:
- Solo puede controlar los tipos de archivo a los que está asociado. Por ejemplo, no puede controlar una carpeta genérica.
- Solo puede iniciar la aplicación con una serie de parámetros. No puede realizar operaciones avanzadas, como iniciar otro ejecutable ni realizar una tarea sin abrir la aplicación principal.
Para lograr estos objetivos, debe crear una extensión de Shell, lo que proporciona maneras más eficaces de realizar la integración en el Explorador de archivos. En este escenario, se crea un archivo DLL que contiene todo lo necesario para administrar el menú contextual del archivo, incluida la etiqueta, el icono, el estado y las tareas que va a realizar. Dado que esta funcionalidad se implementa en un archivo DLL, puede hacer casi todo lo que hace con una aplicación normal. Después de implementar el archivo DLL, debe registrarlo a través de las extensiones que defina en el manifiesto de paquete.
Nota
El proceso descrito en esta sección tiene una limitación. Una vez que el paquete MSIX que contiene la extensión esté instalado en un equipo de destino, debe reiniciarse el Explorador de archivos para poder cargar la extensión de Shell. Para ello, el usuario puede reiniciar el equipo, o bien el proceso explorer.exe desde el Administrador de tareas.
Implementación de la extensión de Shell
Las extensiones de Shell se basan en COM (Modelo de objetos componentes). El archivo DLL expone uno o más objetos COM que están registrados en el Registro del sistema. Windows detecta estos objetos COM e integra la extensión en el Explorador de archivos. Dado que está integrando el código con Windows Shell, es importante el rendimiento y la superficie de memoria. Por lo tanto, estos tipos de extensiones se compilan normalmente con C++.
Para ver el código de ejemplo que muestra cómo implementar extensiones de Shell, consulte el proyecto ExplorerCommandVerb en el ejemplo relacionado de GitHub. Este proyecto se basa en este ejemplo en los ejemplos de escritorio de Windows, y tiene varias revisiones para que sea más fácil de usar con las versiones más recientes de Visual Studio.
Este proyecto contiene una gran cantidad de código reutilizable para distintas tareas, como los menús dinámicos frente a los menús estáticos y el registro manual del archivo DLL. No necesitará la mayor parte de este código si va a empaquetar la aplicación con MSIX, ya que la compatibilidad con el empaquetado se encargará de estas tareas automáticamente. El archivo ExplorerCommandVerb.cpp contiene la implementación del menú contextual, y este es el archivo de código principal de interés de este tutorial.
La función clave es CExplorerCommandVerb::Invoke
. Esta es la función que se invoca cuando un usuario hace clic en la entrada en el menú contextual. En el ejemplo, para minimizar el impacto en el rendimiento, la operación se realiza en otro subproceso, por lo que encontrará la implementación real en CExplorerCommandVerb::_ThreadProc
.
DWORD CExplorerCommandVerb::_ThreadProc()
{
IShellItemArray* psia;
HRESULT hr = CoGetInterfaceAndReleaseStream(_pstmShellItemArray, IID_PPV_ARGS(&psia));
_pstmShellItemArray = NULL;
if (SUCCEEDED(hr))
{
DWORD count;
psia->GetCount(&count);
IShellItem2* psi;
HRESULT hr = GetItemAt(psia, 0, IID_PPV_ARGS(&psi));
if (SUCCEEDED(hr))
{
PWSTR pszName;
hr = psi->GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, &pszName);
if (SUCCEEDED(hr))
{
WCHAR szMsg[128];
StringCchPrintf(szMsg, ARRAYSIZE(szMsg), L"%d item(s), first item is named %s", count, pszName);
MessageBox(_hwnd, szMsg, L"ExplorerCommand Sample Verb", MB_OK);
CoTaskMemFree(pszName);
}
psi->Release();
}
psia->Release();
}
return 0;
}
Cuando un usuario hace clic con el botón derecho en un archivo o carpeta, esta función muestra un cuadro de mensaje con la ruta de acceso completa del archivo o la carpeta seleccionados. Si quiere personalizar la extensión de Shell de otras maneras, puede extender las siguientes funciones en el ejemplo:
- Puede cambiar la función GetTitle para personalizar la etiqueta de la entrada en el menú contextual.
- Puede cambiar la función GetIcon para personalizar el icono que se muestra cerca de la entrada en el menú contextual.
- Puede cambiar la función GetTooltip para personalizar la información sobre herramientas que se muestra al mantener el mouse sobre la entrada en el menú contextual.
Registro de la extensión de Shell
Dado que la extensión de Shell se basa en COM, el archivo DLL de implementación debe exponerse como servidor COM para que Windows pueda integrarlo en el Explorador de archivos. Normalmente, para ello, se asigna un identificador único (denominado CLSID) al servidor COM y este se registra en un subárbol específico del Registro del sistema. En el proyecto ExplorerCommandVerb, el CLSID de la extensión CExplorerCommandVerb
se define en el archivo Dll.h.
class __declspec(uuid("CC19E147-7757-483C-B27F-3D81BCEB38FE")) CExplorerCommandVerb;
Cuando se empaqueta un archivo DLL de extensión de Shell en un paquete MSIX, se sigue un enfoque similar. Sin embargo, el GUID debe registrarse en el manifiesto de paquete en lugar de en el Registro, como se explica aquí.
En el manifiesto de paquete, empiece agregando los siguientes espacios de nombres al elemento Package.
<Package
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4"
xmlns:desktop5="http://schemas.microsoft.com/appx/manifest/desktop/windows10/5"
xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
IgnorableNamespaces="desktop desktop4 desktop5 com">
...
</Package>
Para registrar el CLSID, agregue un elemento com.Extension con la categoría windows.comServer
en el manifiesto de paquete. Este debe agregarse como elemento secundario del elemento Extensions en el elemento Application. Este ejemplo es un extracto del archivo Package.appxmanifest del ejemplo relacionado en GitHub.
<com:Extension Category="windows.comServer">
<com:ComServer>
<com:SurrogateServer DisplayName="ContextMenuSample">
<com:Class Id="CC19E147-7757-483C-B27F-3D81BCEB38FE" Path="ExplorerCommandVerb.dll" ThreadingModel="STA"/>
</com:SurrogateServer>
</com:ComServer>
</com:Extension>
Hay dos atributos críticos que se pueden configurar en el elemento com:Class.
Atributo | Descripción |
---|---|
Atributo Id |
Debe coincidir con el CLSID del objeto que quiere registrar. En este ejemplo, se trata del CLSID declarado en el archivo Dll.h asociado a la clase CExplorerCommandVerb . |
Atributo Path |
Debe contener el nombre del archivo DLL que expone el objeto COM. En este ejemplo se incluye el archivo DLL en la raíz del paquete, por lo que solo puede especificar el nombre del archivo DLL generado por el proyecto ExplorerCommandVerb . |
A continuación, agregue otra extensión que registre el menú contextual del archivo. Para ello, agregue un elemento desktop4:Extension con la categoría windows.fileExplorerContextMenus
al manifiesto de paquete. Este elemento también debe agregarse como elemento secundario del elemento Extensions en el elemento Application.
<desktop4:Extension Category="windows.fileExplorerContextMenus">
<desktop4:FileExplorerContextMenus>
<desktop5:ItemType Type="Directory">
<desktop5:Verb Id="Command1" Clsid="CC19E147-7757-483C-B27F-3D81BCEB38FE" />
</desktop5:ItemType>
</desktop4:FileExplorerContextMenus>
</desktop4:Extension>
Hay dos atributos críticos que se deben configurar en el elemento desktop4:Extension.
Atributo o elemento | Descripción |
---|---|
Atributo Type de desktop5:ItemType |
Define el tipo de los elementos que quiere asociar al menú contextual. Puede ser una estrella (* ), si quiere que se muestre para todos los archivos, o una extensión de archivo específica (.foo ), o bien puede estar disponible para carpetas (Directory ). |
Atributo Clsid de desktop5:Verb |
Debe coincidir con el valor de CLSID que ha registrado previamente como servidor COM en el archivo del manifiesto de paquete. |
Configuración del archivo DLL en el paquete
Incluya el archivo DLL que implementa la extensión de Shell (en este ejemplo, ExplorerCommandVerb.dll) en la raíz del paquete MSIX. Si usa el Proyecto de paquete de aplicación de Windows, la solución más sencilla es copiar el archivo DLL y pegarlo en el proyecto y asegurarse de que la opción Copiar en el directorio de salida para las propiedades del archivo DLL esté establecida en Copiar si es posterior.
Para asegurarse de que el paquete siempre incluya la versión más reciente del archivo DLL, puede agregar un evento posterior a la compilación al proyecto de extensión de Shell para que, cada vez que lo compile, el archivo DLL se copie en el Proyecto de paquete de aplicación de Windows.
Reinicio del Explorador de archivos
Después de instalar el paquete de extensión de Shell, debe reiniciar el Explorador de archivos para poder cargar la extensión de Shell. Se trata de una limitación de las extensiones de Shell que se implementan y registran a través de paquetes MSIX.
Para probar la extensión de Shell, reinicie el equipo o reinicie el proceso explorer.exe desde el Administrador de tareas. Después de hacerlo, debería poder ver la entrada en el menú contextual.
Si hace clic allí, se llamará a la función CExplorerCommandVerb::_ThreadProc
para mostrar el cuadro de mensaje con la ruta de acceso de la carpeta seleccionada.