Программирование на Visual C++. Архив рассылки - Алекс Jenter
Шрифт:
Интервал:
Закладка:
Итак, нам требуется функция, которая выводила бы контекстное меню для заданного файла (каталога), который передавался бы ей в качестве параметра. Эта функция должна выполнить следующие действия:
• Получить интерфейс IContextMenu для этого файла (каталога).
• Создать всплывающее меню (посредством CreatePopupMenu).
• Заполнить его элементами с помощью IContextMenu::QueryContextMenu.
• Показать меню пользователю (TrackPopupMenu).
• Выполнить выбранную команду посредством IContextMenu::InvokeCommand.
Основную сложность на самом деле представляет первый этап. Получить указатель на IContextMenu мы можем только, имея указатель на базовый интерфейс IShellFolder, но Windows не предоставляет простого способа получить этот указатель. Выполнение этой задачи в свою очередь распадается на несколько шагов:
– Получить интерфейс IShellFolder рабочего стола посредством SHGetDesktopFolder.
– Построить LPITEMIDLIST для заданного файла (каталога), используя IShellFolder::ParseDisplayName.
– Получить IShellFolder для этого файла вызовом IShellFolder::BindToObject.
Функция, которая отображает контекстное меню, может выглядеть примерно так (я снабдил её подробными комментариями).
void ShowContextMenu (CWnd *pWnd, LPCTSTR pszPath, CPoint point) {
// Строим полное имя.
TCHAR tchPath[MAX_PATH];
GetFullPathName(pszPath, sizeof(tchPath)/sizeof(TCHAR), tchPath, NULL);
// Если нужно, перекодируем ANSI в UNICODE.
WCHAR wchPath[MAX_PATH];
if(IsTextUnicode (tchPath, lstrlen (tchPath), NULL)) lstrcpy ((char *)wchPath, tchPath);
else MultiByteToWideChar(CP_ACP, 0, pszPath, -1, wchPath, sizeof(wchPath)/sizeof(WCHAR));
// Получаем интерфейс IShellFolder рабочего стола
IShellFolder *pDesktopFolder;
SHGetDesktopFolder(&pDesktopFolder);
// Преобразуем путь в LPITEMIDLIST
LPITEMIDLIST pidl;
pDesktopFolder->ParseDisplayName(pWnd->m_hWnd, NULL, wchPath, NULL, &pidl, NULL);
// Получаем интерфейс IShellFolder для заданного файла (папки)
IShellFolder *pFolder;
pDesktopFolder->BindToObject(pidl, NULL, IID_IShellFolder, (void**)&pFolder);
// Получаем интерфейс IContextMenu для заданного файла (папки)
IContextMenu *pContextMenu;
pFolder->GetUIObjectOf(pWnd->m_hWnd, 1, (LPCITEMIDLIST*)&pidl, IID_IContextMenu, NULL, (void**)&pContextMenu);
// Создаём меню
CMenu PopupMenu;
PopupMenu.CreatePopupMenu();
// Заполняем меню
pContextMenu->QueryContextMenu(PopupMenu.m_hMenu, 0, 1, 0x7FFF,CMF_EXPLORE);
// Отображаем меню
UINT nCmd = PopupMenu.TrackPopupMenu(TPM_LEFTALIGN|TPM_LEFTBUTTON|TPM_RIGHTBUTTON|TPM_RETURNCMD, point.x, point.y, pWnd);
// Выполняем команду (если она была выбрана)
if(nCmd) {
CMINVOKECOMMANDINFO ici;
ZeroMemory(&ici, sizeof(CMINVOKECOMMANDINFO));
ici.cbSize = sizeof(CMINVOKECOMMANDINFO);
ici.hwnd = pWnd->m_hWnd;
ici.lpVerb = MAKEINTRESOURCE(nCmd-1);
ici.nShow = SW_SHOWNORMAL;
ContextMenu->InvokeCommand(&ici);
}
// Получаем интерфейс IMalloc.
IMalloc *pMalloc;
SHGetMalloc(&pMalloc);
// Используем его для освобождения памяти, выделенной на ITEMIDLIST
pMalloc->Free(pidl);
// Освобождаем все полученные интерфейсы
pDesktopFolder->Release();
pFolder->Release();
pContextMenu->Release();
pMalloc->Release();
return;
}
Эту функцию можно вызывать, например, из обработчика OnContextMenu. Делается это так:
void CMyView::OnContextMenu(CWnd* pWnd, CPoint point) {
ShowContextMenu(pWnd, "C:\command.com", point);
}
За дополнительной информацией следует обратиться к следующим статьям в MSDN:
– Periodicals 1997, Microsoft Systems Journal, April, Wicked Code
– Knowledge Base, статья ID: Q198288
– Описание IShellFolder и IContextMenu
Что касается второго вопроса (о создании собственных пунктов меню), мы имеем полный контроль над процессом создания меню, а значит можем делать с ним всё, что угодно. Нужно только иметь в виду 2 момента.
Во-первых, поскольку функция TrackPopupMenu вызывается с флагом TPM_RETURNCMD, она на будет отправлять окну сообщение WM_COMMAND. Поэтому нужно анализировать значение nCmd, возвращённое функцией TrackPopupMenu и вызывать нужный обработчик вручную. Например:
UINT nCmd = PopupMenu.TrackPopupMenu(…);
if (nCmd) {
if (nCmd == 0x8000) {
AfxMessageBox("It works!!!");
} else {
// Используем IContextMenu::InvokeCommand
}
}
Во-вторых, функция IContextMenu::QueryContextMenu получает параметры idCmdFirst, idCmdLast (в примере выше они равны 1 и 0x7FFF соответственно). Идентификаторы для стандартных пунктов меню выбираются именно в диапазоне от idCmdFirst до idCmdLast. Поэтому нужно проследить, чтобы идентификаторы пользовательских пунктов меню в этот диапазон не попали.
Alexander Shargin ОБРАТНАЯ СВЯЗЬКогда начал читать вашу статью на тему мерцания, подумал было, что вы обязательно упомянете тот метод, который использовал я в своей программе. На мой взгляд, он достаточно известен, и, кажется, является самым самым лучшим.
Нужно просто создать обработчик события WM_ERASEBKGND с одной-единственной строчкой:
BOOL CSomeClass::OnEraseBkgnd(CDC* pDC) {
return FALSE;
}
Т.е., по-русски говоря, программа фон не очистила, рисуйтесь полностью.
VladНа ответ A1 из прошлого выпуска:
Теперь практика. Пусть имеется готовое SDI приложение (с технологией Документ/Представление). Создаем дополнительное Представление. Это делается в функции CFrameWnd::OnCreateClient примерно так:
BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext) {
// class CNewView – это наше новое представление
pContext->m_pNewViewClass = RUNTIME_CLASS(CNewView);
// обратите внимание на идентификатор нового Представления
// переменная m_pNewView описанна в CMainFrame как
CNewView* m_pNewView;
m_pNewView = STATIC_DOWNCAST(CNewView, CreateView(pContext, AFX_IDW_PANE_FIRST+1));
m_pNewView->ShowWindow(SW_HIDE); // для сброса флага WS_VISIBLE
return CFrameWnd::OnCreateClient(lpcs, pContext);
}
Этот код работает неправильно, причём это видно даже невооружённым взглядом. В последней строчке функции CMainFrame::OnCreateClient вызывается функция базового класса. Но ведь поле pContext->pNewViewClass уже изменилось! В результате вместо двух разных видов будет создано два одинаковых. Ошибка лечится переносом вызова функции из базового класса в начало переопределённой функции:
BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext) {
int nResult = CFrameWnd::OnCreateClient(lpcs, pContext);
…
return nResult;
}
Кроме того, неясно, как использовать функцию SwitchView. Указатель на созданный нами вид хранится в m_pNewView, но для получения указателя на вид, созданный самой MFC, не видно удобного способа. Вероятно, лучший вариант – также сохранить его в члене класса CMainFrame.
Alexander Shargin В ПОИСКАХ ИСТИНЫQ. У меня dialog-base приложение, живет в systray. Необходимо, чтобы приложение при повторном запуске находило уже запущеный экземпляр программы и активизировало его. Я пытался сделать это через FindWindow(), в которую передается имя зарегистрированного класса окна, и заголовок окна, которое разыскивается. По заголовку я искать не могу, так как он все время у меня меняется. Следовательно, нужно искать по зарегистрированному имени класса окна. Вот тут то и начинается проблема. Я его не знаю. MFC сама их раздает dialog-based приложениям. А переопределить это имя можно было бы в PreCreateWindow(), но этот метод CDialog не наследует из CWnd. Во всех остальных методах, имя класса уже зарегистрированно, т.е. менять его поздно. Как быть?
el-fЭто все на сегодня. До новых встреч!
Алекс Jenter [email protected] Красноярск, 2000.Программирование на Visual C++
Выпуск №26 от 3 декабря 2000 г.
Здравствуйте!
Итак, вот уж и зима на дворе… Время, когда отходить от компьютера не хочется даже ненадолго ;) Правда, это если у вас в комнате достаточно тепло. В другом случае не хочется вылезать из-под трех одеял;)
IDEВ прошлом выпуске мы с вами говорили о профилировании программ. Некоторые читатели просили также рассказать обо всех богатых возможностях отладки, которые предлагает Visual C++. Александр Шаргин, наш постоянный автор, любезно предложил написать в рассылку статью на эту тему. Думаю, что даже умеющие пользоваться отладчиком программисты найдут в ней для себя много интересного.
Использование отладчика в Visual C++В этой статье я очень кратко расскажу о возможностях встроенного отладчика Visual C++.
Запуск отладки
Чтобы запустить программу на отладку, нужно выбрать одну из команд меню Build->Start Debug. Обратите внимание, что команда Attach to Process позволяет подключиться к уже запущенному процессу.
Точки останова (Breakpoints)
Точки останова служат для прерывания программы, выполняемой в отладчике. Их можно привязывать к конкретной строке в коде программы, к переменной или к сообщению Windows. После того как программа прервана, её можно выполнять в пошаговом режиме или просто проанализировать значения переменных, после чего продолжить выполнение.