Настройка контекстного меню с помощью динамических команд

Обработчики контекстного меню также называются обработчиками контекстного меню или обработчиками команд. Обработчик контекстного меню — это тип обработчика типов файлов.

Этот раздел организован следующим образом:

Сведения о статических и динамических командах

Настоятельно рекомендуется реализовать контекстное меню с помощью одного из методов статической команды. Рекомендуется следовать инструкциям, приведенным в разделе "Настройка контекстного меню с помощью статических команд" создания обработчиков контекстного меню. Сведения о динамическом поведении для статических команд в Windows 7 и более поздних версиях см. в разделе "Получение динамического поведения для статических команд" в разделе "Создание обработчиков контекстного меню". Дополнительные сведения о реализации статической команды и о том, какие динамические команды следует избегать, см. в разделе "Выбор статической или динамической команды" для контекстного меню.

Если необходимо расширить контекстное меню для типа файла, зарегистрируя динамическую команду для типа файла, следуйте инструкциям, приведенным далее в этом разделе.

Примечание.

При регистрации обработчиков, работающих в контексте 32-разрядных приложений, следует учитывать, что при вызове команд оболочки в контексте 32-разрядного приложения подсистема WOW64 перенаправляет доступ к некоторым путям. Если обработчик .exe хранится в одном из этих путей, он недоступен в этом контексте. Таким образом, в качестве работы либо сохраните exe-файл в пути, который не перенаправляется, либо сохраните заглушку вашей exe-версии, которая запускает реальную версию.

 

Как обработчики контекстного меню работают с динамическими командами

Помимо IUnknown, обработчики контекстного меню экспортируют следующие дополнительные интерфейсы для обработки сообщений, необходимых для реализации элементов меню, нарисованных владельцем:

Дополнительные сведения о элементах меню, нарисованных владельцем, см . в разделе "Создание элементов меню с рисованием владельца" в разделе "Использование меню".

Оболочка использует интерфейс IShellExtInit для инициализации обработчика. Когда оболочка вызывает IShellExtInit::Initialize, она передает объект данных с именем объекта и указателем на список идентификаторов элементов (PIDL) папки, содержащей файл. Параметр hkeyProgID — это расположение реестра, в котором зарегистрирован дескриптор контекстного меню. Метод IShellExtInit::Initialize должен извлечь имя файла из объекта данных и сохранить имя и указатель папки на список идентификаторов элементов (PIDL) для последующего использования. Дополнительные сведения об инициализации обработчика см. в разделе "Реализация IShellExtInit".

Когда команды представлены в контекстном меню, они сначала обнаруживаются, затем отображаются пользователю и, наконец, вызываются. В следующем списке подробно описаны три шага.

  1. Оболочка вызывает IContextMenu::QueryContextMenu, которая возвращает набор команд, которые могут быть основаны на состоянии элементов или системы.
  2. Система передает дескриптор HMENU , который метод может использовать для добавления элементов в контекстное меню.
  3. Если пользователь щелкает один из элементов обработчика, оболочка вызывает IContextMenu::InvokeCommand. Затем обработчик может выполнить соответствующую команду.

Предотвращение конфликтов из-за неквалифицированных имен глаголов

Так как команды регистрируются для каждого типа, то одно и то же имя команды можно использовать для глаголов в разных элементах. Это позволяет приложениям ссылаться на общие команды независимо от типа элемента. Хотя эта функция полезна, использование неквалифицированных имен может привести к столкновениям с несколькими независимыми поставщиками программного обеспечения (ISV), которые выбирают одно и то же имя команды. Чтобы избежать этого, всегда префиксные команды с именем поставщика программного обеспечения следующим образом:

ISV_Name.verb

Всегда используйте конкретный progID приложения. Принятие соглашения о сопоставлении расширения имени файла с isV, предоставленным ProgID, позволяет избежать потенциальных конфликтов. Тем не менее, поскольку некоторые типы элементов не используют это сопоставление, существует необходимость в уникальных именах поставщиков. При добавлении команды в существующий progID, который уже зарегистрирован, необходимо сначала удалить раздел реестра для старой команды перед добавлением собственной команды. Это необходимо сделать, чтобы избежать объединения сведений о команде из двух команд. Неспособность сделать это приводит к непредсказуемому поведению.

Регистрация обработчика контекстного меню с помощью динамической команды

Обработчики контекстного меню связаны либо с типом файла, либо с папкой. Для типов файлов обработчик регистрируется в следующем подразделе.

HKEY_CLASSES_ROOT
   Program ID
      shellex
         ContextMenuHandlers

Чтобы связать обработчик контекстного меню с типом файла или папкой, сначала создайте подраздел в подразделе ContextMenuHandlers . Присвойте подключу обработчику значение по умолчанию и задайте значение по умолчанию подраздела строковой форме ИДЕНТИФИКАТОРа класса обработчика (CLSID).

Затем чтобы связать обработчик контекстного меню с различными типами папок, зарегистрируйте обработчик так же, как и для типа файла, но в подразделе FolderType , как показано в следующем примере.

HKEY_CLASSES_ROOT
   FolderType
      shellex
         ContextMenuHandlers

Дополнительные сведения о типах папок, для которых можно зарегистрировать обработчики, см. в разделе "Регистрация обработчиков расширений оболочки".

Если тип файла связан с ним контекстным меню, то дважды щелкнув объект, обычно запускает команду по умолчанию, а метод IContextMenu::QueryContextMenu не вызывается. Чтобы указать, что метод IContextMenu::QueryContextMenu должен вызываться при двойном щелчке объекта, создайте подраздел подключ подключа обработчика clSID, как показано здесь.

HKEY_CLASSES_ROOT
   CLSID
      {00000000-1111-2222-3333-444444444444}
         shellex
            MayChangeDefaultMenu

При двойном щелчке объекта, связанного с обработчиком, вызывается IContextMenu::QueryContextMenu с флагом CMF_DEFAULTONLY, установленным в параметре uFlags.

Обработчики контекстного меню должны задать подраздел MayChangeDefaultMenu , только если им может потребоваться изменить команду контекстного меню по умолчанию. Установка этого подраздела заставляет систему загружать библиотеку DLL обработчика при двойном щелчке связанного элемента. Если обработчик не изменяет команду по умолчанию, этот подраздел не следует задавать, так как это приводит к тому, что система загружает библиотеку DLL ненужным образом.

В следующем примере показаны записи реестра, которые позволяют включить обработчик контекстного меню для типа файла MYP. Вложенный ключ CLSID обработчика включает вложенный ключ MayChangeDefaultMenu, чтобы гарантировать, что обработчик вызывается, когда пользователь дважды щелкает связанный объект.

HKEY_CLASSES_ROOT
   .myp
      (Default) = MyProgram.1
   CLSID
      {00000000-1111-2222-3333-444444444444}
         InProcServer32
            (Default) = C:\MyDir\MyCommand.dll
            ThreadingModel = Apartment
         shellex
            MayChangeDefaultMenu
   MyProgram.1
      (Default) = MyProgram Application
      shellex
         ContextMenuHandler
            MyCommand = {00000000-1111-2222-3333-444444444444}

Реализация интерфейса IContextMenu

IContextMenu является самым мощным, но и самым сложным методом для реализации. Настоятельно рекомендуется реализовать команду с помощью одного из методов статической команды. Дополнительные сведения см. в разделе "Выбор статической или динамической команды" для контекстного меню. IContextMenu имеет три метода, GetCommandString, InvokeCommand и QueryContextMenu, которые подробно рассматриваются здесь.

Метод IContextMenu::GetCommandString

Метод IContextMenu::GetCommandString обработчика используется для возврата канонического имени для команды. Этот метод является необязательным. В Windows XP и более ранних версиях Windows, если в Windows Обозреватель есть строка состояния, этот метод используется для получения текста справки, отображаемого в строке состояния для элемента меню.

Параметр idCmd содержит смещение идентификатора команды, определенной при вызове IContextMenu::QueryContextMenu . Если строка справки запрашивается, для uFlags будет задано значение GCS_HELPTEXTW. Скопируйте строку справки в буфер pszName , приведение его к PWSTR. Строка команды запрашивается путем задания uFlags для GCS_VERBW. Скопируйте соответствующую строку в pszName так же, как и со строкой справки. Флаги GCS_VALIDATEA и GCS_VALIDATEW не используются обработчиками контекстного меню.

В следующем примере показана простая реализация IContextMenu::GetCommandString, соответствующая примеру IContextMenu::QueryContextMenu, приведенному в разделе метода IContextMenu::QueryContextMenu. Так как обработчик добавляет только один пункт меню, существует только один набор строк, которые можно вернуть. Метод проверяет, является ли idCmd допустимым и, если это так, возвращает запрошенную строку.

Функция StringCchCopy используется для копирования запрошенной строки в pszName, чтобы убедиться, что скопированная строка не превышает размер буфера, указанного cchName. В этом примере реализована поддержка только значений Юникода uFlags, так как только те, которые использовались в Windows Обозреватель с Windows 2000.

IFACEMETHODIMP CMenuExtension::GetCommandString(UINT idCommand, 
                                                UINT uFlags, 
                                                UINT *pReserved, 
                                                PSTR pszName, 
                                                UINT cchName)
{
    HRESULT hr = E_INVALIDARG;

    if (idCommand == IDM_DISPLAY)
    {
        switch (uFlags)
        {
            case GCS_HELPTEXTW:
                // Only useful for pre-Vista versions of Windows that 
                // have a Status bar.
                hr = StringCchCopyW(reinterpret_cast<PWSTR>(pszName), 
                                    cchName, 
                                    L"Display File Name");
                break; 

            case GCS_VERBW:
                // GCS_VERBW is an optional feature that enables a caller
                // to discover the canonical name for the verb passed in
                // through idCommand.
                hr = StringCchCopyW(reinterpret_cast<PWSTR>(pszName), 
                                    cchName, 
                                    L"DisplayFileName");
                break; 
        }
    }
    return hr;
}

Метод IContextMenu::InvokeCommand

Этот метод вызывается, когда пользователь щелкает элемент меню, чтобы сообщить обработчику выполнить связанную команду. Параметр pici указывает на структуру, содержащую необходимые сведения.

Хотя pici объявлен в Shlobj.h как структура CMINVOKECOMMANDINFO, на практике она часто указывает на структуру CMINVOKECOMMANDINFOEX. Эта структура является расширенной версией CMINVOKECOMMANDINFO и содержит несколько дополнительных элементов, которые позволяют передавать строки Юникода.

Проверьте элемент cbSize pici, чтобы определить, какая структура была передана. Если это структура CMINVOKECOMMANDINFOEX, а элемент fMask имеет набор флагов CMIC_MASK_UNICODE, приведение pici к CMINVOKECOMMANDINFOEX. Это позволяет приложению использовать сведения Юникода, содержащиеся в последних пяти членах структуры.

Элемент lpVerb или lpVerbW структуры используется для идентификации выполняемой команды. Команды определяются одним из следующих двух способов:

  • По строке команды
  • По смещение идентификатора команды

Чтобы отличить эти два случая, проверка слово lpVerb для дела ANSI или lpVerbW для дела Юникода. Если слово высокого порядка ненулевое, lpVerb или lpVerbW содержит строку команды. Если слово с высоким порядком равно нулю, смещение команды находится в слове с низким порядком lpVerb.

В следующем примере показана простая реализация IContextMenu::InvokeCommand, соответствующая примерам IContextMenu::QueryContextMenu и IContextMenu::GetCommandString, заданным до и после этого раздела. Метод сначала определяет, в какой структуре передается. Затем он определяет, определяется ли команда смещением или его командой. Если lpVerb или lpVerbW содержит допустимую команду или смещение, метод отображает окно сообщения.

STDMETHODIMP CShellExtension::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)
{
    BOOL fEx = FALSE;
    BOOL fUnicode = FALSE;

    if(lpcmi->cbSize == sizeof(CMINVOKECOMMANDINFOEX))
    {
        fEx = TRUE;
        if((lpcmi->fMask & CMIC_MASK_UNICODE))
        {
            fUnicode = TRUE;
        }
    }

    if( !fUnicode && HIWORD(lpcmi->lpVerb))
    {
        if(StrCmpIA(lpcmi->lpVerb, m_pszVerb))
        {
            return E_FAIL;
        }
    }

    else if( fUnicode && HIWORD(((CMINVOKECOMMANDINFOEX *) lpcmi)->lpVerbW))
    {
        if(StrCmpIW(((CMINVOKECOMMANDINFOEX *)lpcmi)->lpVerbW, m_pwszVerb))
        {
            return E_FAIL;
        }
    }

    else if(LOWORD(lpcmi->lpVerb) != IDM_DISPLAY)
    {
        return E_FAIL;
    }

    else
    {
        MessageBox(lpcmi->hwnd,
                   "The File Name",
                   "File Name",
                   MB_OK|MB_ICONINFORMATION);
    }

    return S_OK;
}

Метод IContextMenu::QueryContextMenu

Оболочка вызывает IContextMenu::QueryContextMenu , чтобы включить обработчик контекстного меню для добавления элементов меню в меню. Он передает дескриптор HMENU в параметре hmenu . Параметр indexMenu присваивается индексу, который будет использоваться для первого элемента меню, который необходимо добавить.

Все элементы меню, добавленные обработчиком, должны иметь идентификаторы, которые попадают между значениями в параметрах idCmdFirst и idCmdLast . Как правило, первый идентификатор команды имеет значение idCmdFirst, которое увеличивается по одному (1) для каждой дополнительной команды. Эта практика позволяет избежать превышения идентификатора IdCmdLast и максимально увеличить количество доступных идентификаторов в случае вызова оболочки нескольких обработчиков.

Смещение команды идентификатора элемента — это разница между идентификатором и значением в idCmdFirst. Сохраните смещение каждого элемента, добавляемого обработчиком в контекстное меню, так как оболочка может использовать ее для идентификации элемента, если он впоследствии вызывает IContextMenu::GetCommandString или IContextMenu::InvokeCommand.

Вы также должны назначить команду каждой добавляемой команде. Команда — это строка, которую можно использовать вместо смещения для идентификации команды при вызове IContextMenu::InvokeCommand. Он также используется функциями, такими как ShellExecuteEx для выполнения команд контекстного меню.

Существует три флага, которые можно передать через параметр uFlags , который относится к обработчикам контекстного меню. Они описаны в следующей таблице.

Флаг Description
CMF_DEFAULTONLY Пользователь выбрал команду по умолчанию, как правило, дважды щелкнув объект. IContextMenu::QueryContextMenu должен возвращать элемент управления в оболочку без изменения меню.
CMF_NODEFAULT Элемент в меню не должен быть элементом по умолчанию. Метод должен добавить свои команды в меню.
CMF_NORMAL Контекстное меню будет отображаться обычно. Метод должен добавить свои команды в меню.

 

Используйте InsertMenu или InsertMenuItem, чтобы добавить элементы меню в список. Затем возвращает значение HRESULT с уровнем серьезности, заданным для SEVERITY_SUCCESS. Задайте для значения кода смещение наибольшего идентификатора команды, который был назначен, а также один (1). Например, предположим, что idCmdFirst имеет значение 5, а в меню добавляются три элемента с идентификаторами команд 5, 7 и 8. Возвращаемое значение должно быть MAKE_HRESULT(SEVERITY_SUCCESS, 0, 8 - 5 + 1).

В следующем примере показана простая реализация IContextMenu::QueryContextMenu , которая вставляет одну команду. Смещение идентификатора для команды равно IDM_DISPLAY, что равно нулю. Переменные m_pszVerb и m_pwszVerb — это частные переменные, используемые для хранения связанной строки глагола независимо от языка в форматах ANSI и Юникода.

#define IDM_DISPLAY 0

STDMETHODIMP CMenuExtension::QueryContextMenu(HMENU hMenu,
                                              UINT indexMenu,
                                              UINT idCmdFirst,
                                              UINT idCmdLast,
                                              UINT uFlags)
{
    HRESULT hr;
    
    if(!(CMF_DEFAULTONLY & uFlags))
    {
        InsertMenu(hMenu, 
                   indexMenu, 
                   MF_STRING | MF_BYPOSITION, 
                   idCmdFirst + IDM_DISPLAY, 
                   "&Display File Name");

    
        
        hr = StringCbCopyA(m_pszVerb, sizeof(m_pszVerb), "display");
        hr = StringCbCopyW(m_pwszVerb, sizeof(m_pwszVerb), L"display");

        return MAKE_HRESULT(SEVERITY_SUCCESS, 0, USHORT(IDM_DISPLAY + 1));
    }

    return MAKE_HRESULT(SEVERITY_SUCCESS, 0, USHORT(0));
}

Сведения о других задачах реализации команд см. в разделе "Создание обработчиков контекстного меню".

Контекстные меню и обработчики контекстного меню

Команды и сопоставления файлов

Выбор статической или динамической команды для контекстного меню

Рекомендации по обработчикам контекстного меню и нескольким командам выбора

Создание обработчиков контекстного меню

Справочник по контекстным меню