(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

Неиспользуемые параметры, расширение контекстного меню для кнопок на панели задач и др.

Источник: codingclub

Вопрос Мне попадался C++-код, где для неиспользуемых параметров применяется UNREFERENCED_PARAMETER, например:

int SomeFunction(int arg1, int arg2){ UNREFERENCED_PARAMETER(arg2) ...}

Но встречался и такой код:

int SomeFunction(int arg1, int /* arg2 */){ ...}

Не могли бы вы пояснить, в чем тут разница и что лучше?

Джуди Макгео (Judy McGeough)

Ответ Ну конечно. Начнем с UNREFERENCED_PARAMETER. Это макрос, определенный в winnt.h:

#define UNREFERENCED_PARAMETER(P) (P)

Иначе говоря, UNREFERENCED_PARAMETER раскрывается в передаваемый параметр или выражение. Его предназначение - избежать предупреждений компилятора о наличии параметров, на которые нет ссылок (неиспользуемых параметров). Многие программисты, включая вашего верного слугу, предпочитают компилировать при наивысшем уровне предупреждений, Level 4 (/W4). Предупреждения Level 4 попадают в категорию того, что можно спокойно игнорировать. Небольшие погрешности не повредят вашему коду, но могут создать о вас плохое впечатление. Например, вы написали где-то в своей программе строку вроде:

int x=1;

Однако вы нигде не используете x. Возможно, эта строка осталась с тех пор, когда вы действительно использовали x, но потом удалили часть кода, а об этой переменной забыли. Предупреждения Level 4 помогают обнаруживать такие мелкие промахи. Так почему бы не разрешить компилятору помочь вам в достижении выс-шего уровня профессионализма? Успешная компиляция с предупреждениями Level 4 позволяет гордиться своей работой. Проблема в том, что при Level 4 компилятор жалуется на совсем безобидные вещи, например на неиспользуемые параметры (конечно, они безобидны, только если вы действительно ими не пользуетесь). Допустим, у вас есть функция с двумя аргументами, но вы используете лишь один из них:

int SomeFunction(int arg1, int arg2)
{
return arg1+5;
}

При /W4 компилятор сообщит: "warning C4100: 'arg2': unreferenced formal parameter". Чтобы обмануть компилятор, можно добавить UNREFERENCED_PARAMETER(arg2). Теперь ваша функция ссылается на arg2, и компилятор заткнется. А поскольку выражение:

arg2;

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

Острые умы могут поинтересоваться: если arg2 не используется, то зачем вообще объявлять его? Обычно так делают, когда реализуют функцию, которая должна отвечать определенной сигнатуре API, спущенной свыше. Например, у MFC-обработчика OnSize должна быть следующая сигнатура:

void OnSize(UINT nType, int cx, int cy);

Здесь cx и cy - новые ширина и высота окна, а nType - некий код наподобие SIZE_MAXIMIZED, если окно должно быть полностью развернуто, или SIZE_RESTORED при нормальном размере. Как правило, nType вас не волнует - вы заботитесь только о cx и cy. Поэтому при компиляции с ключом /W4 вам понадобится UNREFERENCED_PARAMETER(nType). А ведь OnSize - лишь одна из тысяч функций в MFC и Windows. Так что довольно трудно написать Windows-программу без параметров, на которые нет ссылок.

Ну и хватит о UNREFERENCED_PARAMETER. Как Джуди заметила, задавая свой вопрос, еще один часто применяемый C++-программистами трюк, который позволяет добиться того же результата, - заключение лишнего для них параметра в сигнатуре функции в признаки комментария:

void CMyWnd::OnSize(UINT /* nType */,
int cx, int cy)
{
}

Теперь nType является безымянным параметром; то же самое вы получили бы, набрав OnSize(UINT, int cx, int cy). А сейчас вопрос за 64 000 долларов: каким способом вы бы воспользовались - безымянным параметром или UNREFERENCED_PARAMETER?

Обычно это не имеет никакого значения; выбор определяется стилем. (Вы любите кофе черный или со сливками?) Но я могу придумать по меньшей мере одну ситуацию, где нужен именно UNREFERENCED_PARAMETER. Допустим, вы решили запретить развертывание вашего окна на весь рабочий стол. Вы отключаете кнопку Maximize, удаляете команду Maximize из системного меню и блокируете любые другие элементы управления, которые позволили бы полностью развернуть окно. Поскольку вы параноик (а таково большинство хороших программистов), вы добавляете выражение ASSERT, чтобы быть уверенным в том, что ваш код работает так, как было задумано:

void CMyWnd::OnSize(UINT nType, int cx, int cy)
{
ASSERT(nType != SIZE_MAXIMIZE);
... // используем cx, cy
}

Команда тестировщиков прогоняет вашу программу 87 способами, ASSERT ни разу не срабатывает, и вы решаете, что можно спокойно компилировать окончательную версию. Но без _DEBUG выражение ASSERT(nType!=SIZE_MAXIMIZE) раскрывается в ((void)0), и неожиданно nType становится неиспользуемым параметром! Вот вам и окончательная компиляция. Вы не можете закомментировать nType из списка параметров, потому что он нужен для ASSERT. Поэтому в такой ситуации, где параметр используется только в ASSERT или другом _DEBUG-коде, лишь UNREFERENCED_PARAMETER сделает компилятор счастливым как при отладочной сборке, так и при окончательной. Уловили суть?

Прежде чем закруглиться, не могу не упомянуть, что индивидуальные предупреждения компилятора можно подавлять директивой pragma warning:

#pragma warning( disable : 4100 )

4100 - код ошибки для неиспользуемого параметра. Эта директива действует на оставшуюся часть файла или модуля. Чтобы повторно включить данное преду-преждение:

#pragma warning( default : 4100 )

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

Вы могли бы подавить предупреждения о неиспользуемых параметрах в рамках одной функции, окружив ее директивами pragma warning:

#pragma warning( push )
#pragma warning( disable : 4100 )
void SomeFunction(...)
{
}
#pragma warning( pop )

Конечно, это было бы слишком нудно для неиспользуемых параметров, но, вероятно, понадобится для предупреждений других видов. Разработчики библиотек постоянно используют #pragma warning для блокировки предупреждений, чтобы их код можно было без проблем компилировать с ключом /W4. В MFC полно таких pragma. Параметров у директив #pragma warning гораздо больше, чем я упомянул здесь. Проверьте их в документации.

Вопрос Я заметил, что в некоторых приложениях есть специальные команды, которые появляются в контекстном меню для кнопки таких приложений на панели задач. Например, WinAmp (популярный медиа проигрыватель) добавляет подменю "WinAmp" с командами, специфическими для WinAmp. Как мне добавить собственные команды для кнопки приложения на панели задач?

Жирар Осигян (Jirair Osygian)

Вопрос Я создала простое MFC SDI-приложение с формой, на которой отображается счетчик. Мне нужно запускать и останавливать счетчик щелчком правой кнопки мыши на значке приложения, свернутого в кнопку на панели задач. Функции запуска и остановки прекрасно работают как кнопки на моей форме, и мне удалось добавить аналогичные команды в системное меню. Но когда я выбираю их в системном меню, ничего не происходит. Как обрабатывать сообщения от модифицированного системного меню?

Моник Шарман (Monicque Sharman)

Ответ Я отвечу на оба вопроса одним махом. Ответ на вопрос Жирара прост: меню, которое видит пользователь, щелкнув правой кнопкой мыши свернутое в кнопку на панели задач приложение, идентично меню, отображаемому при щелчке значка приложения в левом верхнем углу строки заголовка или при нажатии Alt+Space. На рис. 1 показано, что я имею в виду. Это меню называется системным, и в нем есть команды вроде Restore, Minimize, Maximize и Close.

Рис. 1. Системное меню

Вызовите ::GetSystemMenu, чтобы получить системное меню, а затем модифицируйте его, добавив, удалив или изменив нужные элементы. Вы можете или изменив нужные элементы. Вы можете даже полностью блокировать системное меню, отключив стиль WS_SYSMENU в флагах создания своего окна или в виртуальной функции PreCreateWindow. Но что бы вы ни делали, системное меню - это и то меню, которое отображается при щелч-ке правой кнопкой мыши свернутого в кнопку приложения на панели задач.

А это подводит нас к вопросу Моник: если в системное меню добавляются собственные команды, как их обрабатывать в MFC? Если вы поступите как обычно - напишете где-то обработчик ON_COMMAND и добавите его в одну из своих карт сообщений (message maps), - то обнаружите, что этот обработчик никогда не вызывается. В чем дело?

А дело в том, что Windows и MFC обрабатывают системные команды иначе, чем команды обычного меню. Когда пользователь щелкает команду обычного меню или кнопку на форме, Windows посылает вашему основному окну сообщение WM_COMMAND. Если вы пользуетесь MFC, ее механизм диспетчеризации команд перехватывает это сообщение и направляет его через систему любому объекту в карте команд, у которого имеется обработчик ON_COMMAND для этой команды. (Подробнее о диспетчеризации команд в MFC см. мою статью "Meandering Through the Maze of MFC Message and Command Routing" в "MSJ" за июль 1995 г. по ссылке www.microsoft.com/msj/0795/dilascia/dilascia.aspx.)

Но системные команды не идут через WM_COMMAND. Они поступают через другое сообщение, которое называется - а как же еще? - WM_SYSCOMMAND. Это относится как к истинно системным командам типа SC_MINIMIZE или SC_CLOSE, так и к добавляемым вами. Для обслуживания команд системного меню вы должны явно обрабатывать WM_SYSCOMMAND и выполнять проверку на идентификаторы своих команд. Это требует добавления ON_WM_SYSCOMMAND в карту сообщений основного окна, причем функция-обработчик должна выглядеть примерно так:

CMainFrame::OnSysCommand(UINT nID, LPARAM lp)
{
if (nID==ID_MY_COMMAND) {
... // обрабатываем
return 0;
}
// Передаем базовому классу - это важно!
return CFrameWnd::OnSysCommand(nID, lp);
}

Если команда не ваша, не забудьте передать ее базовому классу, - обычно это CFrameWnd или CMDIFrameWnd. Иначе Windows не получит сообщение и вы нарушите работу встроенных команд.

Обработка WM_SYSCOMMAND в основной рамке действует нормально, но какая-то она неуклюжая. Зачем использовать специальный механизм для обработки команд только потому, что они приходят через системное меню? А что если вы захотите обрабатывать системную команду в каком-то другом объекте, например в документе? Одна распространенная команда, помещаемая в системное меню, - About (ID_APP_ABOUT), и большинство MFC-программ обрабатывают ID_APP_ABOUT в объекте "приложение":

void CMyApp::OnAppAbout()
{
static CAboutDialog dlg;
dlg.DoModal();
}

Одно из по-настоящему замечательных средств MFC - ее система диспетчеризации команд (command-routing system), которая позволяет объектам, отличным от окон, например CMyApp, обрабатывать команды меню. Многие программисты даже не задумываются, насколько это необычно. Если вы уже обрабатываете ID_APP_ABOUT в своем объекте "приложение", то зачем вам реализовать отдельный механизм для поддержки ID_APP_ABOUT в системном меню?

Более эффективный способ (и в большей мере соответствующий стилю MFC) обработки дополнительных системных команд - передача их через обычный механизм диспетчеризации команд. Тогда вы смогли бы обрабатывать системные команды стандартным для MFC образом - с помощью обработчиков ON_COMMAND. Вы даже смогли бы использовать ON_UPDATE_COMMAND_UI для обновления элементов своего системного меню, например для отключения какого-то элемента или отображения галочки рядом с элементом.

На рис. 2 показан написанный мной небольшой класс CSysCmdRouter, который превращает системные команды в обычные. Для его использования вы должны создать экземпляр CSysCmdRouter в своей основной рамке и вызвать его метод Init из OnCreate:

int CMainFrame::OnCreate(...)
{
// Добавляем элементы в системное меню
CMenu* pMenu = GetSystemMenu(FALSE);
pMenu>AppendMenu(..ID_MYCMD1..);
pMenu>AppendMenu(..ID_MYCMD2..);
// Направляем системные команды через MFC
m_sysCmdHook.Init(this);
return 0;
}

Вызвав CSysCmdRouter::Init, вы можете обрабатывать ID_MYCMD1 и ID_MYCMD2 стандартным способом, с помощью обработчиков ON_COMMAND для любого объекта в MFC-схеме распределения команд - представления (view), документа, рамки (frame), приложения или другой мишени, добавленной переопределением OnCmdMsg. CSysCmdRouter также позволяет обновлять системное меню, используя обработчики ON_UPDATE_COMMAND_UI. Единственный подвох - идентификаторы ваших команд не должны конфликтовать с идентификаторами любых других команд меню (если только они не представляют ту же команду) или встроенных системных команд, которые начинаются с SC_SIZE = 0xF000. Visual Studio .NET присваивает идентификаторы команд, начиная с 0x8000 = 32768, так что, если вы позволите Visual Studio самостоятельно назначать идентификаторы, то все будет в порядке при условии, что у вас не более 0xF000-0x8000 = 0x7000 команд. В десятичном счислении это 28 672 команды. Ну а если в вашем приложении более 28 000 команд, вам нужно сходить на консультацию к психиатру в области программирования.

Как работает CSysCmdRouter? Просто: он использует мой универсальный класс CSubclassWnd, который я описывал во многих колонках. CSubclassWnd позволяет создавать подклассы оконных объектов MFC, не наследуя от них. CSysCmdRouter наследует от CSubclassWnd и с его помощью создает подкласс основной рамки. Он перехватывает сообщения WM_SYSCOMMAND, посылаемые рамке. Если идентификатор команды укладывается в диапазон, выделенный системным командам (больше, чем SC_SIZE = 0xF000), CSysCmdRouter пересылает его в Windows; в ином случае он глотает WM_SYSCOMMAND и заменяет его на WM_COMMAND, после чего MFC следует обычным процедурам, вызывая ваши обработчики ON_COMMAND. Ловко, да?

А как насчет обработчиков ON_UPDATE_COMMAND_UI? Как CSysCmdRouter заставляет их срабатывать для команд системного меню? Элементарно. Перед выводом меню Windows посылает вашему основному окну сообщение WM_INITMENUPOPUP. Это ваш шанс обновить элементы меню - включить или отключить их, добавить галочки и т. д. MFC захватывает WM_INITMENUPOPUP в CFrameWnd::OnInitMenuPopup и выполняет все операции, связанные с обновлением UI. MFC создает объект CCmdUI для каждого элемента меню и передает его соответствующим обработчикам ON_UPDATE_COMMAND_UI в ваших картах сообщений. MFC-функция, отвечающая за эту работу, - CFrameWnd::OnInitMenuPopup, которая начинается так:

void CFrameWnd::OnInitMenuPopup(CMenu* pMenu,
UINT nIndex, BOOL bSysMenu)
{
    if (bSysMenu)
    return; // не поддерживает системное меню
     ...
}

MFC ничего не делает для инициализации системного меню. Это берет на себя CSysCmdRouter. Он перехватывает WM_INITMENUPOPUP и сбрасывает флаг bSysMenu, который является старшим словом в LPARAM:

if (msg==WM_INITMENUPOPUP) {
lp = LOWORD(lp); // (присваивает HIWORD = 0)
}

Теперь, когда MFC получает WM_INITMENUPOPUP, она считает, что меню обычное. Все это прекрасно ралив CWnd::WindowProc, или сравнивать HMENU, если вам ботает, пока идентификаторы ваших команд не конфликтуют с настоящими системными командами. Единственное, что вы теряете, переопределяя OnInitMenuPopup, - возможность отличать системное меню от меню в основном окне. Но нельзя же получить все и сразу! Вы всегда можете обрабатывать WM_INIT-MENUPOPUP, переопреденужно различать системное и обычное меню. Но на самом деле вас не должно волновать, откуда поступает команда.

Чтобы показать, как все это действует на практике, я написал тестовую программу TBMenu. На рис. 3 представлено меню, выводимое "правым щелчком" кнопки программы TBMenu на панели задач. Как видите, в нижней части меню есть две дополнительные команды. Исходный код CMainFrame в TBMenu приведен на рис. 4. В OnCreate добавляются команды, которые обслуживаются обработчиками ON_COMMAND и ON_UPDATE_COMMAND_UI в карте сообщений CMainFrame. TBMenu обрабатывает ID_APP_ABOUT внутри своего класса "приложение" (не показан). CSysCmdRouter превращает системные команды в обычные.

 

Удачи в программировании!

Ссылки по теме


 Распечатать »
 Правила публикации »
  Написать редактору 
 Рекомендовать » Дата публикации: 30.12.2009 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
Microsoft Office 365 Профессиональный Плюс. Подписка на 1 рабочее место на 1 год
Microsoft 365 Business Basic (corporate)
Microsoft 365 Apps for business (corporate)
Microsoft 365 Business Standard (corporate)
Microsoft Windows Professional 10, Электронный ключ
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Безопасность компьютерных сетей и защита информации
Новости ITShop.ru - ПО, книги, документация, курсы обучения
Программирование на Microsoft Access
CASE-технологии
3D и виртуальная реальность. Все о Macromedia Flash MX.
Windows и Office: новости и советы
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100