Сообщений 13    Оценка 81        Оценить  
Система Orphus

Избавляемся от контекстного меню

Автор: Тимофей Чадов
Опубликовано: 24.08.2001
Исправлено: 13.03.2005

Демонстрационная программа - 150 KB
Исходные тексты - 32 KB

Вместо вступления

Наверное, вам давно и порядком надоело контекстное меню, которое броузер выдает по умолчанию. На это есть весьма весомые причины. Забудем о таких практически безобидных командах, как Назад - Вперед. Мы переживем, хотя в нашем приложении они, в общем, и не нужны. Но предположим, что вы никому не хотите показывать исходный текст вашей странички (Показать в виде HTML), не говоря уж о такой вредной команде, как Показать в новом окне. Или, например, когда после (месяца, года) мучений ваш начальник (заказчик), случайно щелкнув правой клавишей и увидев эти знакомые словосочетания, восклицает: "Ха! Да чем же это вы занимались все это время? Это ведь не ваша разработка, а всем известный Internet Explorer"... Да, было бы гораздо лучше, если бы он произнес: "Эх! Да мои программисты, похоже, способны выиграть войну броузеров!" и даст вам хотя бы половину тех денег, которые вложило Microsoft в IE.

Словом, надеюсь, что я вас убедил. Настало время избавиться от этого ненавистного контекстного меню.

Способ 1. Обработчик oncontextmenu

Начиная с пятой версии Internet Explorer, DHTML для различных элементов страницы поддерживает событие oncontextmenu. Это событие возникает, когда пользователь нажимает правую клавишу мыши на объекте.

В статье Подключение к событиям DHTML мы обсуждали, как реализовывать подобные обработчики, поэтому не будем останавливаться на этом способе подробно. К тому же этот способ поддерживается только в IE 5.0 и выше.

Способ 2. Знакомьтесь, IDocHostUIHandler

Приложения, использующие WebBrowser, могут получить дополнительный контроль над его поведением, реализовав интерфейс IDocHostUIHandler. С помощью него можно переопределить стандартное контекстное меню, расширить объектную модель DHTML броузера на свое приложение (механизм window.external), обработать сочетание горячей клавиши или операцию drag-and-drop, поменять стандартные настройки по умолчанию и т.п.

Определение интерфейса можно посмотреть в MSDN. Несмотря на обилие методов, для реализации по умолчанию достаточно в каждом вернуть E_NOTIMPL. Сегодня мы так и сделаем, за исключением сферы наших интересов - ShowContextMenu. Вот им-то и займемся поподробнее.

Реализовав этот метод, мы получим контроль над контекстным меню, отображаемым при нажатии правой клавиши мыши. Для того чтобы отказаться от меню по умолчанию, достаточно из этого метода всего лишь вернуть S_OK. Иногда даже этого бывает достаточно. Но это слишком просто для наc. Посмотрим, что тут еще можно сделать. Итак, определение метода выглядит следующим образом:

HRESULT ShowContextMenu( 
    DWORD dwID,
    POINT *ppt,
    IUnknown *pcmdtReserved,
    IDispatch *pdispReserved

dwID - Идентификатор контекстного меню. Определяет, какое меню необходимо отобразить. В файле "mshtmhst.h" определены следующие константы:

#define CONTEXT_MENU_DEFAULT        0
#define CONTEXT_MENU_IMAGE          1
#define CONTEXT_MENU_CONTROL        2
#define CONTEXT_MENU_TABLE          3
#define CONTEXT_MENU_TEXTSELECT     4
#define CONTEXT_MENU_ANCHOR         5
#define CONTEXT_MENU_UNKNOWN        6

ppt - указатель на структуру POINT, содержащую координаты экрана для отображения контекстного меню

pcmdtReserved - указатель на интерфейс IOleCommandTarget, который может быть использован для выполнения некоторых команд.

pdispReserved (Internet Explorer 5 и выше) - указатель на интерфейс IDispatch объекта, чьи координаты содержаться в ppt. Этот указатель может быть использован для реализации различных меню у каждого элемента страницы. Например, следующим образом.

IHTMLElement *pElem;
HRESULT hr;
hr = pdispReserved->QueryInterface(IID_IHTMLElement, (void**)pElem);
if(SUCCEEDED (hr)) {
    BSTR bstr;
    pElem->get_tagName(bstr);
    USES_CONVERSION;
    TRACE("TagName:%s\n", OLE2T(bstr));
    SysFreeString(bstr);
    pElem->Release();
}

После того, как COM объект создан, его надо зарегистрировать. Для этой цели WebBrowser поддерживает специальный интерфейс ICustomDoc, который содержит всего один метод SetUIHandler(IDocHostUIHandler *pUIHandler). Ему-то и надо передать указатель на наш объект.

IHTMLDocument2* pHtmlDoc;
...
ICustomDoc* pCustomDoc;
hr = pHtmlDoc->QueryInterface( __uuidof( ICustomDoc ), (void**)&pCustomDoc );
...
hr = pCustomDoc->SetUIHandler( (IDocHostUIHandler*)m_pMyHost);

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

Небольшие вкусности напоследок

Итак, мы научились подменять стандартные меню Internet Explorer. Помню, как я радовался первый раз, когда все это заработало. Но остается еще одна тонкость. Если требуется полностью заменить меню, нет проблем, описанный выше способ позволяет это делать. Однако предположим, что требуется только внести небольшие изменения. В этом случае предыдущий вариант не очень подходит. Однажды мне потребовалось вставить пункт меню Вид кодировки. Попробуйте реализовать его вручную, и вы поймете, о чем речь.

WebBrowser загружает меню из файла shdoclc.dll. Зная это, и определив несколько констант, мы получим шанс вручную манипулировать содержимым стандартного меню броузера. В следующем примере отображается все меню кроме элемента "Просмотреть в виде HTML". Кстати, не забудьте включить файл mshtmcid.h. В нем определены константы подобные IDM_VIEWSOURCE.

STDMETHOD( ShowContextMenu )(  
    /* [in] */ DWORD dwID,
    /* [in] */ POINT *ppt,
    /* [in] */ IUnknown *pcmdTarget,
    /* [in] */ IDispatch *pdispReserved)
{ 
    #define IDR_BROWSE_CONTEXT_MENU  24641
    #define IDR_FORM_CONTEXT_MENU    24640
    #define SHDVID_GETMIMECSETMENU   27
    #define SHDVID_ADDMENUEXTENSIONS 53
    
    HRESULT hr;
    HINSTANCE hinstSHDOCLC;
    HWND hwnd;
    HMENU hMenu;
    CComPtr<IOleCommandTarget> spCT;
    CComPtr<IOleWindow> spWnd;
    MENUITEMINFO mii = {0};
    CComVariant var, var1, var2;
    
    hr = pcmdTarget->QueryInterface(IID_IOleCommandTarget, (void**)&spCT);
    hr = pcmdTarget->QueryInterface(IID_IOleWindow, (void**)&spWnd);
    hr = spWnd->GetWindow(&hwnd);
    
    hinstSHDOCLC = LoadLibrary(TEXT("SHDOCLC.DLL"));
    
    hMenu = LoadMenu(hinstSHDOCLC,
        MAKEINTRESOURCE(IDR_BROWSE_CONTEXT_MENU));
    
    hMenu = GetSubMenu(hMenu, dwID);
    
    // Получаем подменю для языков кодировки
    hr = spCT->Exec(&CGID_ShellDocView, SHDVID_GETMIMECSETMENU, 0, NULL, &var);
    
    mii.cbSize = sizeof(mii);
    mii.fMask  = MIIM_SUBMENU;
    mii.hSubMenu = (HMENU) var.byref;
    
    // Заполняем меню Вид кодировки
    SetMenuItemInfo(hMenu, IDM_LANGUAGE, FALSE, &mii);
    
    // Удаляем пункт меню Показать в виде HTML
    DeleteMenu(hMenu, IDM_VIEWSOURCE, MF_BYCOMMAND);        
    
    int iSelection = ::TrackPopupMenu(hMenu,
        TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD,  ppt->x, ppt->y, 0, hwnd, (RECT*)NULL);
    
    // Пересылаем выбранную команду окну броузера
    LRESULT lr = ::SendMessage(hwnd, WM_COMMAND, iSelection, NULL);
    
    FreeLibrary(hinstSHDOCLC);
    return S_OK;
}

Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав.
    Сообщений 13    Оценка 81        Оценить