понедельник, 17 мая 2010 г.

Переформулирование очевидностей о сообщении WM_COMMAND

Это перевод Restating the obvious about the WM_COMMAND message. Автор: Реймонд Чен.

Я вполне удовлетворён документацией MSDN по сообщению WM_COMMAND, но ради дурманящей полноты я собираюсь повторить очевидные вещи в надежде, что вы, дорогие читатели, сможете использовать эту технику для обнаружения других очевидных вещей в MSDN.

Однострочное резюме сообщения WM_COMMAND говорит: "Сообщение WM_COMMAND отправляется, когда пользователь выбирает команду из меню, когда элемент управления отправляет уведомляющее сообщение своему родителю или когда нажимается клавиша-акселератор". В двух словах, есть три сценария, которые генерируют сообщение WM_COMMAND, а именно - те три, что указаны выше. Вы захотите думать о сценариях с меню и акселератором, как частных случаях второго сценария с элементом управления.

Источник сообщенияwParam (старшее слово)wParam (младшее слово)lParam
Меню0Идентификатор меню (IDM_*)0
Акселератор1Идентификатор акселератора (IDM_*)0
Элемент управленияКод уведомления элемента управленияИдентификатор элемента управленияОписатель окна элемента управления

Старшее слово параметра wParam "означает код уведомления, если сообщение пришло от элемента управления". Что здесь означает "элемент управления"? Вспомните, что вам нужно учесть контекст вопроса. Сообщение WM_COMMAND представлено в контексте Win32 в общем, и в контексте оконного менеджера в частности. Окна вроде однострочных редакторов (edit boxes), кнопок (push buttons) и списков (list boxes) часто называются "элементами управления" или "контролами" (controls), поскольку все они являются оконными классами в "common controls library" - библиотеке общих элементов управления. В мире оконного менеджера, "элемент управления" - это окно, чьей целью является предоставление некоторого уровня интерактивности (который, в случае статического элемента управления, может заключаться и в отсутствии интерактивности как таковой) при обслуживании своего родительского окна (parent window - в терминах системы). Тот факт, что сообщение WM_COMMAND обычно используется в контексте диалоговых окон, ещё больше подчёркивает, что термин "элемент управления" здесь является просто синонимом "дочернего окна".

Что означает здесь "код уведомления"? Коды уведомления элементов управления - это произвольные 16-ти битные значения, определяемые самим элементом управления. По соглашению их обычно именуют xxN_xxxx, где "N" означает "notification" ("уведомление"). Однако, будьте осторожны и не перепутайте эти коды с кодами уведомления, ассоциированными с сообщением WM_NOTIFY. К счастью, каждый код уведомления указывает в своём описании, приходит ли он как уведомление от WM_COMMAND или WM_NOTIFY. Современный проектировщик элементов управления скорее предпочтёт выбрать уведомления WM_NOTIFY, потому что они позволяют передать дополнительную информацию вместе с уведомлением. Сообщение же WM_COMMAND, напротив, передаёт только уведомление; остальные параметры WM_COMMAND зарезервированы, как мы увидим ниже. Если WM_NOTIFY лучше WM_COMMAND, то почему тогда некоторые элементы управления используют WM_COMMAND? Потому что сообщения WM_NOTIFY не существовало, пока не вышла Windows 95. Поэтому элементы управления, которые были написаны до выхода Windows 95, используют сообщение WM_COMMAND.

"Если сообщение пришло от акселератора, то это значение [старшее слово параметра wParam] равно 1". Напомню, что мы всё ещё в контексте оконного менеджера. В частности, в контексте сообщения WM_COMMAND. Здесь, "акселератор" ссылается на сообщения, генерируемые вызовом TranslateAccelerator в цикле выборки сообщений.

"Если сообщение пришло от меню, то это значение равно 0". Если сообщение WM_COMMAND было вызвано тем, что пользователь выбрал элемент меню, то старшее слово параметра wParam будет равно нулю.

Нижнее слово параметра wParam "обозначает идентификатор команды меню, элемента управления или акселератора". Идентификатор элемента меню - это код команды, который вы присвоили своему элементу или акселератору в шаблоне диалога или (в случае динамического создания) создали вручную при вызове функции типа InsertMenuItem (вы, вероятно, назвали идентификаторы своих элементов меню и акселераторов как IDM_что-то-там). Идентификатор элемента управления определяется создателем элемента управления; вспомните, что параметр hMenu в функциях CreateWindow и CreateWindowEx рассматривается как идентификатор дочернего окна (child window), если вы создаёте дочернее окно. Это и есть тот самый идентификатор (вы можете получить идентификатор элемента управления вызовом функции GetDlgCtrlID).

Наконец, параметр lParam является "описателем (handle) элемента управления, отправившего сообщение, если оно пришло от элемента управления. В противном случае, этот параметр равен 0". Если уведомление было создано дочерним окном (с кодом уведомления соответствующим этому окну, очевидно), то описатель этого окна передаётся как lParam. Если уведомление было создано акселератором или меню, то lParam равно нулю.

Заметьте, что почти все параметры сообщения WM_COMMAND заданы, как только вы определяетесь с типом уведомления.

Если вы создаёте уведомление от элемента управления, то вы должны передать код уведомления в старшем слове wParam, идентификатор элемента управления - в младшем, а его описатель - в lParam. Другими словами, если вы решили, что окно hwndC хочет отправить сообщение CN_READY, у вас нет другого выбора, кроме как написать:
SendMessage(
            GetParent(hwndC), 
            WM_COMMAND,
            MAKEWPARAM(GetDlgCtrlID(hwndC), CN_READY),
            hwndC);
Другими словами, все уведомления от элементов управления имеют форму:
SendMessage(
            GetParent(окно), 
            WM_COMMAND,
            MAKEWPARAM(GetDlgCtrlID(окно), код-уведомления),
            hwndC);
где "окно" - элемент управления, создающий уведомление, а "код-уведомления" - код уведомления. Конечно же, вы можете использовать PostMessage или любую другую функцию вместо SendMessage.

Два других случая (акселераторы и меню) не являются случаями, для которых вы обычно будете писать код отправки, поскольку вы просто позволяете функции TranslateAccelerator обрабатывать акселераторы, а системе меню управлять идентификаторами меню. Но если, по какой-то причине, вы захотите притвориться, что пользователь нажал акселератор или выбрал пункт меню, то вы можете создать такое уведомление руками, следуя правилам, которые мы вынесли из документации:
// эмулировать акселератор IDM_WHATEVER
SendMessage(hwnd, WM_COMMAND,
            MAKEWPARAM(IDM_WHATEVER, 1),
            0);
Тут, hwnd - это окно, которое вы "как-бы передали" в TranslateAccelerator, а IDM_WHATEVER - идентификатор акселератора.

Эмуляция выбора меню будет выглядеть так же, кроме (в соответствии с правилами выше), вы устанавливаете старшее слово wParam в ноль:
// эмулировать элемент меню IDM_WHATEVER
SendMessage(hwnd, WM_COMMAND,
            MAKEWPARAM(IDM_WHATEVER, 0),
            0);
Здесь, hwnd - это окно, ассоциированное с меню. Окно может быть ассоциировано с меню либо созданием вместе с меню (явной передачей описателя меню в функции CreateWindow или CreateWindowEx, или неявно - указанием описателя меню при регистрации класса окна) или передачей окна в функции вида TrackPopupMenu.

Одним важным отличием между акселераторами/меню и элементом управления является то, что идентификаторы акселераторов и элементов меню определяются вызывающим приложением, в то время как для элементов управления идентификаторы определяются им самим, а не приложением.

Вы могли заметить возможность "пошутить" с кодами уведомления элемента управления. Если элемент управления определяет код уведомления равный нулю, то он будет "выглядеть как" выбор элемента меню, поскольку старшее слово wParam в случае с выбором элемента меню равно 0. На самом деле, кнопка (button control) использует это:
const
  BN_CLICKED         = 0;
Это означает, что если пользователь щёлкает по кнопке, то создаваемое сообщение WM_COMMAND "пахнет как" уведомление о выборе меню. Вы, вероятно, могли использовать это в своей диалоговой процедуре, даже не осознавая это.

В самом начале я упомянул, что сценарии с акселераторами и меню являются частными случаями сценария с элементами управления. Если вы разделите сообщение WM_COMMAND на части, то увидите, что эти кусочки попадают в две категории:
  • Что случилось? (код уведомления)
  • С кем случилось? (идентификаторы и описатели)
В случае меню или акселератора, "Что случилось?" - это "пользователь щёлкнул на меню (0)" или "пользователь нажал на акселератор (1)". "С кем случилось" = "с этим элементом меню" или "с этим акселератором". Поскольку уведомления идут не от элемента управления, то описатель элемента управления будет равен 0.

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

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

Примечание переводчика: вы также можете использовать сообщение WM_MENUCOMMAND для обработки команд меню, если WM_COMMAND вам недостаточно.

Комментариев нет:

Отправить комментарий

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку (поддерживается OpenID).

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.