Сообщений 6 Оценка 55 Оценить |
Предисловие О мастерстве программиста и функциях Windows API Метод WndProc и с чем его едят Заключение |
wndproc.zip – пример для статьи
Для начала хочу сразу очертить круг читателей, для которых в первую очередь предназначена эта статья. На форумах, где «тусуются» программисты, пишущие на Visual Basic 6.0, нетрудно найти сообщения, авторы которых выражают свои опасения и нежелание переходить на Visual Basic .NET. Свои страхи они объясняет тем, что наслышаны о революционных изменениях в языке, а значит, необходимостью заново учить язык с нуля. На мой взгляд, подобные страхи безосновательны и ошибочны. Трудности возможны в первые две недели, которые уйдут на понимание новой модели программирования. Но затем VB-программист со стажем, несомненно, увидит, что легко может читать новый код в примерах, так как синтаксис языка практически сохранился, а может, даже и улучшился. Затем программист с удивлением увидит, что вещи, которые раньше можно было реализовать с помощью компонентов сторонних разработчиков, теперь легко реализуются встроенными средствами языка. Кстати, некоторое время назад на Западе возникло движение, организаторы которого призывали всех VB-программистов подписаться под воззванием к Microsoft с просьбой продолжить поддержку шестой версии Visual Basic. Я думаю, если компания пойдет навстречу данной просьбе, это будет ошибочным шагом. Хотя для меня Visual Basic 6.0 и является любимым языком, тем не менее, платформа .NET Framework предоставляет разработчику гораздо более широкие возможности для реализации своих задач. В данной статье я затрону для сравнения лишь одну тему – метод WndProc. На данном примере можно увидеть, как легко решаются задачи, доступные раньше только очень опытным программистам.
Начну издалека. Условно, VB6-программистов можно разделить на несколько групп. Поначалу, разработчик осваивает встроенные возможности языка и пишет свои программы, задействуя всю мощь средства разработки. Язык Visual Basic – очень мощный язык для реализации очень многих задач прикладного характера. Но в какой-то момент кодер замечает, что существуют возможности, которые предоставляются операционной системой, но ему недоступны. Например, VB-программист не может поменять местами кнопки мыши или открыть лоток привода CD-ROM. Программист узнает о существовании функций Windows API SwapMouseButton и mciSendString и достигает второго уровня мастерства. Замирая от восторга при виде открывшихся возможностей, программист начинает активно изучать новые функции. Процесс этот бесконечен, так как число функций Windows API исчисляется десятками тысяч. Во время изучения этих функций программист замечает существование особых функций обратного вызова (callback function). Начав активно использовать эти функции в своей практике, в глазах окружающих товарищей программист получает статус VB-гуру и достигает третьей ступени мастерства. Но все течет, все меняется. В Visual Basic .NET появился метод WndProc, который является аналогом функции обратного вызова WindowProc и доступен для понимания даже зеленому новичку.
Условно можно утверждать, что Windows-программирование на C++ сводится к обработке сообщений Windows. Отлавливая то или иное сообщение Windows, С-программист пишет свой код, который должен выполниться в этот момент. В Visual Basic.NET организовать наблюдение за потоком сообщений Windows весьма просто. Для этого существует метод WndProc. Для начала обратимся к официальной документации и почитаем, что там пишут об этом методе.
Метод Control.WndProc обрабатывает сообщения Windows. Все сообщения отсылаются методу WndProc после фильтрации с помощью метода PreProcessMessage. Метод WndProc точно соответствует функции Windows API WindowProc.
ПРИМЕЧАНИЕ Элементы управления-наследники должны вызывать метод WndProc базового класса для обработки тех сообщений, которые они не обрабатывают. |
СОВЕТ Наверное, не все еще знают, что существует русская официальная документация по .NET Framework, расположенная по адресу http://www.microsoft.com/rus/msdn/. Рекомендую обязательно ознакомиться с данным ресурсом. |
От теории перейду к практике и приведу несколько простых примеров. Для начала посмотрите, как получить доступ к методу WndProc. Запустите Visual Studio .NET (или Visual Basic .NET) и создайте простейшее приложение WindowsApplication. По умолчанию будет создана пустая форма Form1. Перейдите в редактор кода (F7). В левом выпадающем списке нужно выбрать (Overrides). После этого правый выпадающий список будет показывать все доступные для переопределения свойства и методы. Последним в этом списке будет метод WndProc. Щелкните мышкой на данном элементе списка, чтобы среда разработки создала заготовку кода для данного метода. Как видите, пока ничего сложного, точно такие же действия приходилось выполнять и в старой версии Visual Basic. В созданной заготовке нужно написать следующее:
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message) Const WM_LBUTTONDBLCLK AsInteger = &H203 If m.Msg = WM_LBUTTONDBLCLK Then MsgBox("Вы дважды щелкнули на форме") EndIfMyBase.WndProc(m) EndSub |
Запустите проект и выполните двойной щелчок на форме. У вас должно появиться сообщение о двойном нажатии левой кнопки мыши. Здесь ветеран VB может сильно удивиться и спросить: "А зачем такие сложности, когда есть стандартная процедура Form1_DoubleClick"? Совершенно верно, но сколько существует сообщений Windows, а сколько – встроенных стандартных процедур? Я специально привел простейший пример для первого знакомства. Теперь перейдем к задачам, которые уже нельзя решить с помощью обычных процедур. Например, в Windows XP появились новые визуальные стили: красивые разноцветные кнопочки, индикаторы прогресса и т.д. Но не всем нравится такая красота, и пользователь может переключиться в классический вид. С помощью сообщения WM_THEMECHANGED можно отловить этот момент следующим образом:
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message) Const WM_THEMECHANGED As Int32 = &H31A SelectCase m.Msg Case WM_THEMECHANGED MessageBox.Show("Визуальные стили поменялись") EndSelectMyBase.WndProc(m) EndSub |
Вот еще несколько примеров, которые могут пригодиться на практике. Например, нужно разрешить пользователю сворачивать, разворачивать и восстанавливать форму, но запретить изменять ее размеры. В этом случае надо перехватывать сообщение WM_SYSCOMMAND с параметром WParam, равным SC_SIZE:
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message) ' Разрешаем пользователю свернуть, развернуть и восстановить окно,' но не разрешаем менять размеры окна.' Ловим сообщение WM_SYSCOMMAND. Если параметр wParam равен SC_SIZE,' игнорируем это сообщение. В других случаях вызываем базовый ' метод WndProc для обработки других сообщенийConst WM_SYSCOMMAND AsLong = &H112 Const SC_SIZE AsLong = &HF000 ' Если поступило сообщение WM_SYSCOMMAND.If m.Msg = WM_SYSCOMMAND Then' Игнорируем команду SC_SIZEIf (m.WParam.ToInt32 And &HFFF0&) = SC_SIZE Then _ ExitSubEndIfMyBase.WndProc(m) EndSub |
Задержусь на сообщении WM_SYSCOMMAND. Если нужно добавить собственный пункт в системное меню приложения, то данное сообщение поможет обработать щелчок мыши на созданном пункте. Вот как это делается:
' Функции Windows API Private Declare Function GetSystemMenu Lib"user32" _ ByVal hwnd As IntPtr, _ ByVal bRevert AsBoolean) As IntPtr ' Константы <Flags()> _ PublicEnum AppendMenuFlags AsInteger MF_BYPOSITION = 1024 MF_REMOVE = 4096 MF_SEPARATOR = 2048 MF_STRING = 0 EndEnumPrivateDeclareAutoFunction AppendMenu Lib"user32" _ (ByVal hMenu As IntPtr, _ ByVal wFlags As AppendMenuFlags, _ ByVal wIDNewItem As Int32, _ ByVal lpNewItem AsString) AsBooleanPrivateDeclareFunction DrawMenuBar Lib"user32" _ (ByVal hwnd As IntPtr) AsBoolean' Сообщение WindowsPrivateConst WM_SYSCOMMAND As Int32 = &H112 ' Наш новый идентификатор для системного менюPrivateConst ID_ABOUT As Int32 = 1000 ' Дескриптор системного менюPrivate hSystemMenu As IntPtr PrivateSub Form1_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) HandlesMyBase.Load ' Получим дескриптор системного меню hSystemMenu = GetSystemMenu(Me.Handle, False) ' Добавляем снова разделитель AppendMenu (hSystemMenu, AppendMenuFlags.MF_SEPARATOR, 0, String.Empty) ' Теперь добавляем свою строчку AppendMenu(hSystemMenu, AppendMenuFlags.MF_STRING, ID_ABOUT, _ "Новый пункт меню") EndSubProtectedOverridesSub WndProc(ByRef m As System.Windows.Forms.Message) SelectCase m.Msg Case WM_SYSCOMMAND MyBase.WndProc(m) If m.WParam.ToInt32 = ID_ABOUT ThenIf hSystemMenu <> IntPtr.Zero Then' Обработка щелчка на созданном пункте меню MsgBox( _ "Вы выбрали динамически добавленный в " _ & "системное меню пункт!") EndIfEndIfCaseElseMyBase.WndProc(m) EndSelectEndSub |
Рисунок 1. Новый пункт в системном меню приложения.
В одном из обзоров, посвященном нововведениям в будущей версии Windows (Longhorn), я прочитал, что будет реализована следующая модель поведения приложений – при потере фокуса окном программы оно становится полупрозрачным. Вы можете не дожидаться новой версии операционной системы, а уже сейчас предложить своим потенциальным заказчикам подобную модель:
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message) MyBase.WndProc(m) Const WM_ACTIVATEAPP AsInteger = &H1C If m.Msg = WM_ACTIVATEAPP ThenIf m.WParam.ToInt32() <> 0 Then' Окно стало активнымIfMe.Opacity <> 1.0 ThenMe.Opacity = 1.0 Else' Окно стало неактивным' Устанавливаем степень прозрачности формыIfMe.Opacity <> 0.5 ThenMe.Opacity = 0.5 EndIfEndIfEndSub |
Рисунок 2. Создание полупрозрачного окна при потере фокуса.
На рисунке 2 показан момент, когда активным является приложение Калькулятор. При этом наша форма стала полупрозрачной и через нее стали видны значки Рабочего стола. Не буду больше утомлять читателя своими примерами, и перейду от частного к общему. Здесь я не стал изобретать велосипед, а решил воспользоваться примером из книги Джона Коннелла "Разработка элементов управления Microsoft .NET на Microsoft Visual Basic .NET". Вот как сам автор книги пишет об использовании сообщений Windows:
В классической версии Visual Basic требовались элементы управления от сторонних производителей для наблюдения за потоком сообщений Windows. В .NET эта задача тривиальна.
Далее автор приводит пример, позволяющий наглядно представить, как работают сообщения Windows. Для примера понадобится список ListBox, в котором будут выводиться все сообщения, генерируемые Windows при работе приложения. Сначала нужно объявить переменную wmMessage как Hashtable (хэш-таблица), в которой будут храниться определения сообщений Windows.
Dim wmMessage AsNew Hashtable |
Объект HashTable удобен тем, что позволяет хранить пары значений (ключ+значение). Если объекту передать ключ 0x0010, то объект вернет значение WM_CLOSE. Поскольку сообщений слишком много, ограничусь самыми популярными:
Private Sub BuildHashTable() wmMessage.Add("0x0010", "WM_CLOSE") wmMessage.Add("0x0011", "WM_QUERYENDSESSION") wmMessage.Add("0x0012", "WM_QUIT") wmMessage.Add("0x0013", "WM_QUERYOPEN") wmMessage.Add("0x0014", "WM_ERASEBKGND") wmMessage.Add("0x0015", "WM_SYSCOLORCHANGE") wmMessage.Add("0x0016", "WM_ENDSESSION") wmMessage.Add("0x0018", "WM_SHOWWINDOW") wmMessage.Add("0x001A", "WM_WININICHANGE") wmMessage.Add("0x001B", "WM_DEVMODECHANGE") wmMessage.Add("0x001C", "WM_ACTIVATEAPP") wmMessage.Add("0x001D", "WM_FONTCHANGE") wmMessage.Add("0x001E", "WM_TIMECHANGE") wmMessage.Add("0x001F", "WM_CANCELMODE") wmMessage.Add("0x0020", "WM_SETCURSOR") wmMessage.Add("0x0021", "WM_MOUSEACTIVATE") wmMessage.Add("0x0022", "WM_CHILDACTIVATE") wmMessage.Add("0x0023", "WM_QUEUESYNC") wmMessage.Add("0x0024", "WM_GETMINMAXINFO") wmMessage.Add("0x0081", "WM_NCCREATE") wmMessage.Add("0x0082", "WM_NCDESTROY") wmMessage.Add("0x0083", "WM_NCCALCSIZE") wmMessage.Add("0x0084", "WM_NCHITTEST") wmMessage.Add("0x0085", "WM_NCPAINT") wmMessage.Add("0x0086", "WM_NCACTIVATE") wmMessage.Add("0x0087", "WM_GETDLGCODE") wmMessage.Add("0x0088", "WM_SYNCPAINT") wmMessage.Add("0x00A1", "WM_NCLBUTTONDOWN") wmMessage.Add("0x00A2", "WM_NCLBUTTONUP") wmMessage.Add("0x00A3", "WM_NCLBUTTONDBLCLK") wmMessage.Add("0x00A4", "WM_NCRBUTTONDOWN") wmMessage.Add("0x00A5", "WM_NCRBUTTONUP") wmMessage.Add("0x00A6", "WM_NCRBUTTONDBLCLK") wmMessage.Add("0x00A7", "WM_NCMBUTTONDOWN") wmMessage.Add("0x00A8", "WM.NCMBUTTONUP") wmMessage.Add("0x00A9", "WM_NCMBUTTONDBLCLK") wmMessage.Add("0x0100", "WM_KEYDOWN") wmMessage.Add("0x0101", "WM_KEYUP") wmMessage.Add("0x0200", "WM_MOUSEMOVE") wmMessage.Add("0x0201", "WM_LBUTTONDOWN") wmMessage.Add("0x0202", "WM_LBUTTONUP") wmMessage.Add("0x0203", "WM_LBUTTONDBLCLK") wmMessage.Add("0x0204", "WM_RBUTTONDOWN") wmMessage.Add("0x0205", "WM_RBUTTONUP") wmMessage.Add("0x0206", "WM_RBUTTONDBLCLK") wmMessage.Add("0x0207", "WM_MBUTTONDOWN") wmMessage.Add("0x0208", "WM_MBUTTONUP") wmMessage.Add("0x0209", "WM_MBUTTONDBLCLK") wmMessage.Add("0x0134", "WM_CTLCOLORLISTBOX") wmMessage.Add("0x0111", "WM_COMMAND") End Sub |
Все готово для приема сообщений. В знакомой уже процедуре WndProc нужно написать следующий код:
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message) Dim sDescription AsStringDim sValue AsString sValue = "0x" & String.Format("{0:X4}", m.Msg) sDescription = wmMessage(sValue) If (Len(sDescription)) Then sValue += " " & sDescription EndIf ListBox1.Items.Add(sValue) MyBase.WndProc(m) EndSub |
В процедуру WndProc передается структура m типа System.Windows.Forms.Message, которая форматируется особым образом. Отформатированное значение передается в хэш-таблицу, чтобы получить соответствующее описание. За более подробными объяснениями я отсылаю читателя к указанной выше книге. Кстати, автор книги приводит еще один пример фильтрации сообщений Windows. Например, если вы хотите показать только сообщения, направленные списку ListBox, то нужно немного подправить приведенный код.
Рисунок 3. Программа для приема сообщений Windows.
В этой статье я попытался в меру своих сил показать, как просто теперь получить доступ к возможностям, предоставляемым Windows. А ведь раньше подобные операции были доступны только C++-программистам, которые свысока смотрели на VB-кодеров. Таким образом, сделав свой выбор в пользу языка Visual Basic .NET, вы подниметесь на еще одну ступеньку мастерства. Happy coding!
Сообщений 6 Оценка 55 Оценить |