Сообщений 6    Оценка 55        Оценить  
Система Orphus

Метод WndProc в Visual Basic .NET

Автор: Александр Климов
Источник: RSDN Magazine #2-2005
Опубликовано: 11.07.2005
Исправлено: 10.12.2016
Версия текста: 1.0
Предисловие
О мастерстве программиста и функциях Windows API
Метод WndProc и с чем его едят
Метод Control.WndProc
Заключение

wndproc.zip – пример для статьи

Предисловие

Для начала хочу сразу очертить круг читателей, для которых в первую очередь предназначена эта статья. На форумах, где «тусуются» программисты, пишущие на Visual Basic 6.0, нетрудно найти сообщения, авторы которых выражают свои опасения и нежелание переходить на Visual Basic .NET. Свои страхи они объясняет тем, что наслышаны о революционных изменениях в языке, а значит, необходимостью заново учить язык с нуля. На мой взгляд, подобные страхи безосновательны и ошибочны. Трудности возможны в первые две недели, которые уйдут на понимание новой модели программирования. Но затем VB-программист со стажем, несомненно, увидит, что легко может читать новый код в примерах, так как синтаксис языка практически сохранился, а может, даже и улучшился. Затем программист с удивлением увидит, что вещи, которые раньше можно было реализовать с помощью компонентов сторонних разработчиков, теперь легко реализуются встроенными средствами языка. Кстати, некоторое время назад на Западе возникло движение, организаторы которого призывали всех VB-программистов подписаться под воззванием к Microsoft с просьбой продолжить поддержку шестой версии Visual Basic. Я думаю, если компания пойдет навстречу данной просьбе, это будет ошибочным шагом. Хотя для меня Visual Basic 6.0 и является любимым языком, тем не менее, платформа .NET Framework предоставляет разработчику гораздо более широкие возможности для реализации своих задач. В данной статье я затрону для сравнения лишь одну тему – метод WndProc. На данном примере можно увидеть, как легко решаются задачи, доступные раньше только очень опытным программистам.

О мастерстве программиста и функциях Windows API

Начну издалека. Условно, VB6-программистов можно разделить на несколько групп. Поначалу, разработчик осваивает встроенные возможности языка и пишет свои программы, задействуя всю мощь средства разработки. Язык Visual Basic – очень мощный язык для реализации очень многих задач прикладного характера. Но в какой-то момент кодер замечает, что существуют возможности, которые предоставляются операционной системой, но ему недоступны. Например, VB-программист не может поменять местами кнопки мыши или открыть лоток привода CD-ROM. Программист узнает о существовании функций Windows API SwapMouseButton и mciSendString и достигает второго уровня мастерства. Замирая от восторга при виде открывшихся возможностей, программист начинает активно изучать новые функции. Процесс этот бесконечен, так как число функций Windows API исчисляется десятками тысяч. Во время изучения этих функций программист замечает существование особых функций обратного вызова (callback function). Начав активно использовать эти функции в своей практике, в глазах окружающих товарищей программист получает статус VB-гуру и достигает третьей ступени мастерства. Но все течет, все меняется. В Visual Basic .NET появился метод WndProc, который является аналогом функции обратного вызова WindowProc и доступен для понимания даже зеленому новичку.

Метод WndProc и с чем его едят

Условно можно утверждать, что Windows-программирование на C++ сводится к обработке сообщений Windows. Отлавливая то или иное сообщение Windows, С-программист пишет свой код, который должен выполниться в этот момент. В Visual Basic.NET организовать наблюдение за потоком сообщений Windows весьма просто. Для этого существует метод WndProc. Для начала обратимся к официальной документации и почитаем, что там пишут об этом методе.

Метод Control.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!


Эта статья опубликована в журнале RSDN Magazine #2-2005. Информацию о журнале можно найти здесь
    Сообщений 6    Оценка 55        Оценить