Поделиться через


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

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

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

О статических и динамических глаголах

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

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

Заметка

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

 

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

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

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

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

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

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

Предотвращение столкновений из-за неуточнённых имен глаголов

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

ISV_Name.verb

Всегда используйте конкретный progID приложения. Принятие соглашения о сопоставлении расширения имени файла с ProgID, предоставленным ISV, позволяет избежать потенциальных конфликтов. Тем не менее, поскольку некоторые типы элементов не используют это сопоставление, существует необходимость в уникальных именах поставщиков. При добавлении глагола в существующий 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, на практике pici часто указывает на структуру 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, которые относятся к обработчикам контекстного меню. Они описаны в следующей таблице.

Флаг Описание
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));
}

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

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

Глаголы и сопоставления файлов

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

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

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

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