Параметри, що не використовуються, розширення контекстного меню для кнопок на панелі завдань та ін.
Питання Мені траплявсяC++-код, де для невикористовуваних параметрів застосовується UNREFERENCED_PARAMETER, наприклад:
int SomeFunction(int arg1, int arg2)
Але зустрічався і такий код:
int SomeFunction(int arg1, int /* arg2 */)
Чи не могли б ви пояснити, у чому тут різниця і що краще?
Джуді Макгео (Judy McGeough)
Відповідь Ну звісно. Почнемо з UNREFERENCED_PARAMETER. Це макрос, визначений у winnt.h:
#define UNREFERENCED_PARAMETER(P) (P)
Однак, ви ніде не використовуєте 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 не використовується, то навіщо взагалі оголошувати його? Зазвичай так роблять, коли реалізують функцію, яка повинна відповідати певній сигнатурі 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-програму без параметрів, на які немає посилань.
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 >
Перш ніж заокруглитись, не можу не згадати, що індивідуальні попередження компілятора можна придушувати директивою 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 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), - то виявите, що цей обробник ніколи невикликається. В чому справа?
Але системні команди не йдуть через 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), програми або іншої мішені, доданої перевизначенням On. CSysCmdRouter також дозволяє оновлювати системне меню, використовуючи ON_UPDATE_COMMAND_UI. Єдиний підступ - ідентифікатори ваших команд не повинні конфліктувати з ідентифікаторами будь-яких інших команд меню (якщо вони не представляють ту ж команду) або вбудованих системних команд, які починаються з SC_SIZE =0xF000. Visual Studio .NET надає ідентифікатори команд, починаючи з 0x8000 = 32768, так що, якщо ви дозволите Visual Studio самостійно призначати ідентифікатори, то все буде гаразд за умови, що у вас не більше 0xF000-0x8000 = 0x7000 команд. У десятковому численні це 28672 команди. Ну а якщо у вашому додатку понад 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, яка починається так:
voidCFrameWnd::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) & gt;
Тепер, коли 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 перетворює системні команди на звичайні.