Как создать окно в c

от admin

2.2 Создание первого оконного приложения в .NET. "Убегающее окно".

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

Создайте новый проект, в качестве типа шаблона установите приложение Windows Forms, как показано на рисунке 1:
Уроки OpenGL + C#: Создание нового оконного проекта в MS Visual StudioРисунок 1. Создание нового проекта.
Назовите проект RandWindow и нажмите кнопку ОК.

  1. Окно Toolbox (Панель элементов управления) — элементы управления вы можете разместить на создаваемой форме.
  2. Окно Solution Explorer (Обозреватель решений) — здесь вы сможете увидеть следующие узлы: Properties — настройки проекта, Links (Ссылки) — подключенные к проекту библиотеки, а также созданные и подключенные к проекту файлы исходных кодов (с расширением .cs) и подключенные к проекту формы (например, Form1).
  3. Окно Class View (Окно классов) — здесь представлены все созданные в программе классы.
  4. Окно Properties (Свойства) — выбрав любой элемент управления или даже форму, вы сможете увидеть все параметры данного объекта, а также изменить значения, установленные в них по умолчанию.
Добавление новых элементов управления на форму

Итак, после того как вы ввели имя проекта, установили необходимый шаблон и нажали кнопку ОК, MS Visual Studio автоматически создаст каркас оконного приложения, после чего мы сможем добавить на него новые оконные элементы.

Для этого необходимо перетащить необходимый оконный элемент из окна инструментов (ToolBox).

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

В центре окна будет находиться надпись, которую мы создадим с помощью элемента Label.
Снизу будут расположены 2 кнопки.

Немного растяните заготовку окна. Если вы нажмете на него правой кнопкой, то откроется контекстное меню. В нем нажмите на пункте свойства, после чего вы сможете изучить различные параметры окна, которые вы можете изменить. На рисунке 3 изображены самые (как правило) необходимые свойства:
Уроки OpenGL + C#: Различные свойства окна (формы) в C# .NETРисунок 3. Различные свойства окна (формы) в C# .NET.
Немного растяните заготовку окна и добавьте все необходимые элементы. На рисунке 4 вы можете увидеть их в окне ToolBox:
Уроки OpenGL + C#: Перетаскивайте необходимые элементы из окна Toolbox на создаваемую формуРисунок 4. Перетаскивайте необходимые элементы из окна Toolbox на создаваемую форму.
Перейдите в свойства строки Label1, где измените текст на «Вы стремитесь сделать мир лучше?». Также измените тип шрифта, для этого найдите свойство Font (рис. 5).
Уроки OpenGL + C#: Свойство Font элемента LabelРисунок 5. Свойство Font элемента Label.
После чего установите тип шрифта Tahoma, ширину шрифта Bold и размер равный 16 (рис. 6).
Уроки OpenGL + C#: Установки шрифтаРисунок 6. Установки шрифта.
Далее измените текст на кнопках, используя свойство Text.

Полученная заготовка окна программы будет выглядеть следующим образом (рис. 7).
Уроки OpenGL + C#: Форма будет выглядеть следующим образомРисунок 7. Форма будет выглядеть следующим образом.

Техническая часть работы программы
  1. Сначала мы добавим обработчик события перемещения мыши и реализуем вывод ее координат x и y в два созданных поля ввода.
  2. Далее мы создадим функции обработчики щелчка по каждой из клавиш мыши (особенно усердные пользователи все же смогут попасть по кнопке «Да, конечно!»).
  3. Далее мы добавим код, реализующий случайное перемещение окна в том случае, если курсор приблизиться к кнопке «Да, конечно!».

Определение перемещения указателя мыши по форме

Щелкните непосредственно на части формы создаваемого приложения (НЕ на одном из элементов).

Теперь перейдите к свойствам формы с помощью щелчка правой кнопки мыши -> контекстное меню свойства.

Теперь необходимо перейти к списку возможных событий, которые может получать данное окно. Для этого щелкните на кнопке «Event» (события), как показано на рисунке 8:
Уроки OpenGL + C#: Переход к списку возможных событийРисунок 8. Переход к списку возможных событий.
Когда пользователь передвигает указатель мыши по нашему окну, операционная система посылает сообщение программе с текущими координатами указателя. Они-то нам и нужны.

Чтобы назначить обработчик данного события, найдите строку MouseMove (рис. 9), после чего сделайте двойной щелчок в поле справа от нее – автоматически добавится обработчик события движения мыши и добавится функция Form1_MouseMove в коде нашей программы.
Уроки OpenGL + C#: После двойного щелчка справа от MouseMove появиться данная строка, и автоматически совершится переход к коду функции-обработчика данного событияРисунок 9. После двойного щелчка справа от MouseMove появиться данная строка, и автоматически совершится переход к коду функции-обработчика данного события.
Добавьте в эту функцию 2 строки, чтобы ее код стал выглядеть следующим образом.

Данная функция, обрабатывающая событие перемещения указателя мыши над формой, получает 2 параметра: объект отправитель и экземпляр класса MouseEventsArgs, содержащий информацию о координатах указателя мыши и других текущих свойствах.

textBox1 и textBox2 это экземпляры класса textbox, реализующие управление нашими элементами поля для ввода.

Член данных экземпляров Text позволяет установить текст в данных полях.

Таким образом, если теперь откомпилировать программу (F5), при перемещении указателя мыши по форме окна мы будем видеть координаты указателя (внутри формы), которые будут непрерывно изменяться.

Теперь вернемся к заготовке нашей формы. Для это щелкните на соответствующей закладке (Form1.cs [Конструктор]), как показано на рисунке 10:
Уроки OpenGL + C#: Переход к конструктору форм C#Рисунок 10. Переход к конструктору форм C#.
Сделайте двойной щелчок по первой кнопке: Visual Studio автоматически добавит код обработки данной кнопки при нажатии.

Добавьте следующие строки кода:

Теперь снова вернитесь к конструктору и добавьте вторую кнопку, также с помощью двойного щелчка по ней.

Она будет содержать следующий код:

Как видите, здесь мы немного усложнили код вызова окна-сообщения, чтобы продемонстрировать то, как оно работает, более подробно. Все параметры передаваемые в функцию Show закомментированны в исходном коде.

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

Для этого мы добавим код в функцию:

private void Form1_MouseMove(object sender, MouseEventArgs)

Принцип очень прост: получая координаты движения мыши, мы проверяем не входят ли они в квадрат, очерчивающий нашу кнопку с небольшим запасом. Если да, то мы генерируем 2 случайных числа, которые будут использованы для перемещения окна.

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

Также нам понадобится объявить несколько "рабочих" переменных, которые мы будем в дальнейшем использовать.

Генерация случайных чисел

Класс Random в C# представляет собой генератор псевдослучайных чисел — т.е. данный класс отвечает за выдачу последовательности чисел, отвечающую определенным статистическим критериям случайности.

Random rnd = new Random();

Здесь мы объявили экземпляр класса Random (rnd), с помощью которого мы будем генерировать случайные числа. В дальнейшем мы будем использовать код, вида rnd.Next (диапазон_генерации) или rnd.Next (от, до -1) для генерации случайного числа.

Также мы объявим еще несколько переменных, часть из которых сразу будет инициализирована.

tmp_location объявляется для того, чтобы в будущем временно хранить текущее положение окна.

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

Чтобы определить разрешение экрана в C# .NET, мы будем использовать:

_h и _w будут хранить в себе размеры экрана пользователя, которые определяются при их инициализации.

Теперь код этой функции будет выглядеть следующим образом:

Вот, собственно, и все. Откомпеллировав приложение, можете попробовать нажать на кнопку "Да, конечно". Это будет крайне трудно.

Как создать окно в c

Ключевым элементом в системе графического интерфейса в WPF является окно, которое содержит все необходимые элементы управления. Окно в WPF представлено классом Window , который является производным от класса ContentControl. Поэтому окно является элементом управления содержимым, и как, к примеру, кнопка, может содержать в себе один дочерний элемент. Как правило, в его качестве выступает один из элементов компоновки, например, Grid.

Класс Window привносит ряд свойств, которые позволяют настроить окно приложения:

AllowsTransparency : при значении true позволяет установить прозрачный фон окна

Icon : представляет иконку, которая отображается в левом верхнем углу окна и в панели задач. Если иконка не установлена, то система будет использовать стандартную иконку по умолчанию.

Top : устанавливает отступ окна приложения от верхней границы экрана

Left : устанавливает отступ окна приложения от левой границы экрана

ResizeMode : задает режим изменения размеров окна. Может принимать следующие значения:

CanMinimize : окно можно только свернуть

NoResize : у окна нельзя изменить начальные размеры

CanResize : у окна можно изменять размеры

CanResizeWithGrip : в правом нижнем углу окна появляется визуализация того, что у окна можно изменять размеры

RestoreBounds : возвращает границы окна

ShowInTaskbar : при значении true иконка окна отображается на панели задач

SizeToContent : позволяет автоматически масштабировать размеры окна в зависимости от содержимого. Может принимать следующие значения:

Width : автоматически масштабируется только ширина

Height : автоматически масштабируется только высота

WidthAndHeight : автоматически масштабируются высота и ширина

Manual : автоматическое масштабирование отсутствует

Title : заголовок окна

Topmost : при значении true окно устанавливается поверх других окон приложения

WindowStartupLocation : устанавливает стартовую позицию окна. Может принимать следующие значения:

CenterOwner : если данное окно было запущено другим окном, то данное окно позиционируется относительно центра запустившего его окна

CenterScreen : окно помещается в центре экрана

Manual : позиция устанавливается вручную с помощью свойств Top и Left

WindowState : состояние окна. Возможные значения:

Maximized : раскрыто на весь экран

Normal : стандартное состояние

Жизненный цикл

В процессе работы окно в WPF проходит ряд этапов жизненного цикла, которые доступны нам через обработку событий класса Window:

Initialized : это событие возникает при инициализации окна, когда у него устанавливаются все свойства, но до применения к нему стилей и привязки данных. Это общее событие для всех элементов управления в WPF, поэтому следует учитывать, что сначала возникают события вложенных элементов, а затем их контейнеров. То есть событие Initialized окна приложения генерируется только после того, как отработает событие Initialized для всех вложенных элементов.

Loaded : возникает после полной инициализации окна и применения к нему стилей и привязки данных. После генерации этого события происходит визуализация элемента, и окно отображается на экране и становится видимым для пользователя

Closing : возникает при закрытии окна

Closed : возникает, когда окно становится закрытым

Unloaded : возникает после закрытия окна при выгрузке всех связанных ресурсов из памяти

Соответственно, если нам надо выполнить некоторые действия при загрузке или при закрытии окна, мы можем обработать события Loaded и Closing/Closed. Например, запишем в текстовый лог события жизненного цикла:

Окна на чистом WinAPI. Или просто о сложном

Казалось бы, что WinAPI уходит в прошлое. Давно уже существует огромное количество кросс-платформенных фреймфорков, Windows не только на десктопах, да и сами Microsoft в свой магазин не жалуют приложения, которые используют этого монстра. Помимо этого статей о том, как создать окошки на WinAPI, не только здесь, но и по всему интернету, исчисляется тысячами по уровню от дошколят и выше. Весь этот процесс разобран уже даже не по атомам, а по субатомным частицам. Что может быть проще и понятнее? А тут я еще…

Но не все так просто, как кажется.

Почему о WinAPI сейчас?

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

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

О чем это я? А вот об этом кусочке кода:

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

Ответ такой: так делать нельзя!

И, возвращаясь, к изначальному вопросу о WinAPI: очень много популярных, и не очень, проектов продолжают его использовать и в настоящее время, т.к. лучше, чем на чистом API многие вещи не сделать (тут можно бесконечно приводить аналогии вроде сравнения высокоуровневых языков и ассемблера, но сейчас не об этом). Да и мало ли почему? Просто используют и все тут.

О проблеме

Диалоговые окна упрощают работу с GUI, одновременно лишая нас возможности сделать что-то самостоятельно. Например, сообщения WM_KEYDOWN/WM_KEYUP, приходящие в оконную процедуру, «съедаются» в недрах DefDlgProc, беря на себя такие вещи, как: Навигация по Tab, обработка клавиш Esc, Enter, и т.д. Кроме того, диалоги не нужно создавать вручную: проще, ведь, набросать кнопок, списков, в редакторе ресурсов, вызвать в WinMain CreateDialog/DialogBox и все готово.

Обойти такие мелкие неприятности просто. Есть, как минимум, два вполне легальных способа:

  1. Создать свой собственный класс через RegisterClassEx и в процедуре обработки класса схватывать WM_KEYDOWN, перенаправлять в процедуру обработки самого диалога. Да-да! Можно создавать диалоги со своим собственным классом, и встроенный в VS редактор даже позволяет задавать имя класса для диалога. Вот только кто об этом знает и этим пользуется?
    Минус очевиден: Нужно регистрировать еще один класс, иметь на 1 CALLBACK процедуру больше, суть которой будет всего-навсего в трансляции пары сообщений. Кроме того, мы не будем знать куда их транслировать, и придется городить костыли.
  2. Использовать встроенный механизм акселераторов. И нам даже не придется менять код диалоговой процедуры! Ну, разве что, добавить одну строчку в switch/case, но об этом ниже.
Tutorials?

Не побоюсь сказать, что все туториалы по созданию окон через WinAPI начинаются с такого незамысловатого кода, обозначая его, как «цикл обработки сообщений» (опущу детали по подготовке класса окна и прочую обвязку):

Здесь действительно все просто:

  1. GetMessage() выхватывает очередное сообщение из очереди, и ключевой момент: блокирует поток, если в очереди пусто.
  2. TranslateMessage() из WM_KEYDOWN/WM_KEYUP формирует сообщения WM_CHAR/WM_SYSCHAR (они нужны, если кто-то хочет сделать свой редактор текста).
  3. DispatchMessage() отправляет сообщение в оконную процедуру (если таковая существует).

И ниже приводится пример правильного цикла.

Стоит сказать, что в шаблонах VS для Win32 приложений, написан именно такой неправильный цикл. И это очень печально. Ведь мало кто будет вникать в то, что сделали сами авторы, ведь это априори правильно. И неправильный код множится вместе с багами, которые очень сложно отловить.

После этого фрагмента кода, как правило, следует рассказ про акселераторы, и добавляется пара новых строчек (учитывая замечание в MSDN, предлагаю сразу писать правильный цикл):

Этот вариант я видел чаще всего. И он (та-дам) снова неправильный!

Сперва о том, что изменилось (потом о проблемах этого кода):

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

Собственно TranslateAccelerator этим и занимается: если видит WM_KEYDOWN и код клавиши, которые есть в этом списке, то (опять же ключевой момент) будет формировать сообщение WM_COMMAND (MAKEWPARAM(id, 1)) и отправлять в соответствующую для дескриптора окна, указанного в первом аргументе, процедуру обработки.

Из последней фразы, думаю, стало понятно, в чем проблема предыдущего кода.
Поясню: GetMessage выхватывает сообщения для ВСЕХ объектов типа «окно» (в число которых входят и дочерние: кнопки, списки и прочее), а TranslateAccelerator будет отправлять сформированную WM_COMMAND куда? Правильно: обратно в кнопку/список и т.д. Но мы обрабатываем WM_COMMAND в своей процедуре, а значит нам интересно ее получать в ней же.

Ясно, что TranslateAccelerator надо вызывать для нашего созданного окна:

И вроде все хорошо и замечательно теперь: мы разобрали все детально и все должно работать идеально.

И снова нет. 🙂 Это будет работать правильно, пока у нас ровно одно окно — наше. Как только появится немодальное новое окно (диалог), все клавиши, которые будут в нем нажаты оттранслируются в WM_COMMAND и отправляться куда? И опять же правильно: в наше главное окно.

На этом этапе предлагаю не городить костылей по решению этой тупиковой ситуации, а предлагаю рассмотреть вещи, которые уже реже (или почти не встречаются) в туториалах.

IsDialogMessage

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

На самом деле, делает она чуть больше, чем следует из названия. А именно:

  • Осуществляет навигацию по дочерним контролам кнопками Tab/Shift+Tab/вверх/вниз/вправо/влево. Плюс еще кое-что, но этого нам достаточно
  • По нажатии на ESC формирует WM_COMMAND( IDCANCEL )
  • По нажатии на Enter формирует WM_COMMAND( IDOK ) или нажатие на текущую кнопку по умолчанию
  • Переключает кнопки по умолчанию (рамочка у таких кнопок чуть ярче остальных)
  • Ну и еще разные штуки, которые облегчают пользователю работу с диалогом

Во-вторых, она нам облегчит жизнь по всем остальным пунктам, перечисленным в списке (и даже немного больше).

Вообще, она используется где-то в недрах Windows для обеспечения работы модальных диалоговых окон, а программистам дана, чтобы вызывать ее для немодальных диалогов. Однако мы ее можем использовать где угодно:

Т.е. теперь, если мы оформим цикл так:

То наше окошко будет иметь навигацию, как в родном диалоге Windows. Но теперь мы получили два недостатка:

  1. Этот код также будет хорошо работать только с одним (немодальным) окном;
  2. Получив все достоинства диалоговой навигации, мы лишаемся прелестей в виде сообщений WM_KEYDOWN/WM_KEYUP (только для самого окна, а не для дочерних контролов);

Пора поговорить о том, чего нет в туториалах и ответах.

Как правило (как правило! Если кому-то захочется большего, то можно регистрировать свой класс для диалогов и работать так. И, если же, кому-то это интересно, я могу дополнить этим статью) WM_KEYDOWN хотят тогда, когда хотят обработать нажатие на клавишу, которая выполнит функцию в независимости от выбранного контрола в окне — т.е. некая общая функция для всего данного конкретного диалога. А раз так, то почему бы не воспользоваться богатыми возможностями, которые нам сама WinAPI и предлагает: TranslateAccelerator.

Читать:
Куда сохраняются скачанные через web agent файлы

Везде используют ровно одну таблицу акселераторов, и только для главного окна. Ну действительно: цикл GetMessage-loop один, значит и таблица одна. Куда еще их девать?

На самом деле, циклы GetMessage-loop могут быть вложенными. Давайте еще раз посмотрим описание PostQuitMessage:

Таким образом, выход из GetMessage-loop осуществится, если мы вызовем PostQuitMessage в процедуре окна. Что это значит?

Мы можем для каждого немодального окна в нашей программе создавать свой собственный подобный цикл. В данном случае DialogBoxParam нам не подходит, т.к. оно крутит свой собственный цикл и повлиять мы на него не можем. Однако если создадим диалог через CreateDialogBoxParam или окно через CreateWindow, то можно закрутить еще один цикл. При этом в каждом таком окне и диалоге мы должны вызывать PostQuitMessage:

Обратите внимание: теперь для каждого нового окна в нашей программе мы можем добавить в обработку собственную таблицу акселераторов. WM_QUIT будет выхватывать GetMessage из цикла для диалога, а внешний цикл его даже не увидит. Почему так происходит?

Дело в том, что внешний цикл «встал» на вызове DispatchMessage, который вызвал нашу процедуру, которая крутит свой собственный внутренний цикл GetMessage с таким же DispatchMessage. Классический вложенный вызов (в данном случае DispatchMessage). Посему внешний цикл не получит WM_QUIT и не завершится на этом этапе. Все будет работать стройно.

Но и тут есть свои недостатки:
Каждый такой цикл будет обрабатывать сообщения только для «своего» окна. Про другие-то мы здесь не знаем. А значит, если где-то объявится еще один цикл, то все остальные окна не будут получать нужной обработки своих сообщений парой TranslateAccelerator/IsDialogMessage.

Что ж, пора учесть все эти замечание и написать наконец правильную обработку всех сообщений от всех окон нашей программы. Хочу заметить, что ниже рассматривается случай для одного потока. Т.к. каждый поток имеет свою очередь сообщений, то для каждого потока придется создавать свои структуры. Делается это весьма тривиальными изменениями в коде.

Делаем красиво

Т.к. правильная постановка задачи является половиной решения, то сперва надо эту самую задачу правильно же и поставить.

Во-первых, было бы логично, что только активное окно принимает сообщения. Т.е. для неактивного окна мы не будем транслировать акселераторы и передавать сообщения в IsDialogMessage.

Во-вторых, если для окна не задана таблица акселераторов, то транслировать нечего, будем просто отдавать сообщение в IsDialogMessage.

Создадим простой std::map, который будет мапить дескриптор окна в дескриптор таблицы акселераторов. Вот так:

И по мере создания окон будем в него добавлять новые окна с дескриптором на свою любимую таблицу (или нуль, если такая обработка не требуется).

Ну и после закрытия окна удалять. Вот так:

Теперь, как создаем новый диалог/окно, вызываем AddAccelerators( hNewDialog, IDR_MY_ACCEL_TABLE ). Как закрываем: DelAccel( hNewDialog ).

Список окон с нужными дескрипторами у нас есть. Немного модифицируем наш основной цикл обработки сообщений:

Значительно лучше! Что же там в HandleAccelArray и зачем там GetActiveWindow()?

Есть две функции, возвращающих дескриптор активного окна GetForegroundWindow и GetActiveWindow. Отличие первой от второй вполне доходчиво описано в описании второй:

Если первая будет возвращать дескриптор любого окна в системе, то последняя только того, которое использует очередь сообщений нашего потока. Т.к. нас интересуют окна только нашего потока (а значит те, которые будут попадать в нашу очередь сообщений), то и возьмем последнюю.

Так вот HandleAccelArray, руководствуясь переданным ей дескриптором на активное окно, ищет это самое окно в нашей мапе, и если оно там есть, отдает это сообщение на трансляцию в TranslateAccelerator, а затем (если первый не увидел нужного) в IsDialogMessage. Если и последняя не обработала сообщение, то возвращаем FALSE, чтобы пройти по стандартной процедуре TranslateMessage/DispatchMessage.

Теперь каждое дочернее окно вправе добавить себе любимую таблицу акселераторов и спокойно ловить и обрабатывать WM_COMMAND с нужным кодом.

А что там еще об одной строчке в коде обработчика WM_COMMAND?

Обычно код обработки WM_COMMAND выглядит так:

Теперь можно написать так:

И теперь, возвращаясь к тому же fceux, добавив всего одну строчку в код обработки команд от кнопок, мы получим желаемое: управлять дебагером с клавиатуры. Достаточно добавить небольшую обертку вокруг главного цикла сообщений и новую таблицу акселераторов с нужными соответствиями VK_KEY => IDC_DEBUGGER_BUTTON.

P.S.: Мало кто знает, но можно создавать свою собственную таблицу акселераторов, а теперь и применять ее прямо налету.

P.P.S.: Т.к. DialogBox/DialogBoxParam крутит собственный цикл, то от при вызове диалога через них акселераторы работать не будут и наш цикл (или циклы) будет «простаивать».

P.P.P.S.: После вызова HandleAccelWindow мап l_mAccelTable может измениться, т.к. TranslateAccelerator или IsDialogMessage вызывают DispatchMessage, а там может встретиться AddAccelerators или DelAccel в наших обработчиках! Поэтому лучше его после этой функции не трогать.

Пощупать код можно здесь. За основу был взят код, генерируемый из стандартного шаблона MS VS 2017.

Структура оконного приложения

Оконные приложения строятся по принципам событийно-управляемого программирования (event-driven programming) — стиля программирования, при котором поведение компонента системы определяется набором возможных внешних событий и ответных реакций компонента на них. Такими компонентами в Windows являются окна.

С каждым окном в Windows связана определенная функция обработки событий – оконная функция . События для окон называются сообщениями . Сообщение относится к тому или иному типу, идентифицируемому определенным кодом (32-битным целым числом), и сопровождается парой 32-битных параметров ( WPARAM и LPARAM ), интерпретация которых зависит от типа сообщения.

Задача любого оконного приложения — создать главное окно и сообщить Windows функцию обработки событий для этого окна. Все самое интересное для приложения будет происходить именно в функции обработки событий главного окна.

В Windows программа пассивна. После запуска она ждет, когда ей уделит внимание операционная система. Операционная система делает это посылкой сообщений. Сообщения могут быть разного типа, они функционируют в системе достаточно хаотично, и приложение не знает, какого типа сообщение придет следующим. Логика построения Windows-приложения должна обеспечивать корректную и предсказуемую работу при поступлении сообщений любого типа.

Классическое оконное приложение, как правило, состоит по крайней мере из двух функций:

Стартовая функция WinMain

В консольной программе на С точкой входа является функция main() . С этого места программа начинает выполняться.
Точкой входа программы для Windows является функция WinMain() .

Эта функция использует последовательность вызовов API и при завершении возвращает операционной системе целое число.
Аргументы функции:

  • hInstance – дескриптор процесса (instance handle) – число, идентифицирующее программу, когда она работает под Windows. Если одновременно работают несколько копий одной программы, каждая копия имеет свое значение hInstance .
  • hPrevInstance — предыдущий дескриптор процесса (previous instance) — в настоящее время устарел, всегда равен NULL.
  • szCmdLine — указатель на оканчивающуюся нулем строку, в которой содержатся параметры, переданные в программу из командной строки. Можно запустить программу с параметром командной строки, вставив этот параметр после имени программы в командной строке.
  • iCmdShow — целое константное значение, показывающее, каким должно быть выведено на экран окно в начальный момент. Задается при запуске программы другой программой. В большинстве случаев число равно 1 ( SW_SHOWNRMAL ).
Имя Значение Описание
SW_HIDE 0 Скрывает окно и делает активным другое окно
SW_SHOWNORMAL 1 Отображает и делает активным окно в его первоначальном размере и положении.
SW_SHOWMINIMIZED 2 Активизирует окно и отображает его в свернутом виде
SW_SHOWMAXIMIZED 3 Активизирует окно и отображает его в полноэкранном виде
SW_SHOWNOACTIVATE 4 Отображает окно аналогично SW_SHOWNORMAL , но не активизирует его
SW_SHOW 5 Отображает и делает активным окно с текущим размером и положением.
SW_MINIMIZE 6 Сворачивает текущее окно и делает активным следующее окно в порядке очереди.
SW_SHOWMINNOACTIVE 7 Сворачивает окно аналогично SW_SHOWMINIMIZED , но не активизирует его.
SW_SHOWNA 8 Отображает окно в текущей позиции аналогично SW_SHOW , но не активизирует его.
SW_RESTORE 9 Отображает и активизирует окно. Если окно было свернуто или развернуто во весь экран, оно отображается в своем первоначальном положении и размере.
SW_SHOWDEFAULT 10 Отображает окно способом, заданным по умолчанию.
SW_FORCEMINIMIZE 11 Применяется для минимизации окон, связанных с различными потоками.

В структуре стартовой функции Windows можно выделить следующие операции, образующие «скелет» программы:

Регистрация класса окна

Регистрация класса окна осуществляется функцией

Члены структуры
style — устанавливает стиль(и) класса. Этот член структуры может быть любой комбинацией стилей класса.

Имя Значение Описание
CS_VREDRAW 0x01 Вертикальная перерисовка: осуществлять перерисовку окна при перемещении или изменении высоты окна.
CS_HREDRAW 0x02 Горизонтальная перерисовка: осуществлять перерисовку окна при перемещении или изменении ширины окна.
CS_KEYCVTWINDOW 0x04 В окне будет выполняться преобразование виртуальных клавиш.
CS_DBLCLKS 0x08 Окну будут посылаться сообщения о двойном щелчке кнопки мыши.
CS_OWNDC 0x20 Каждому экземпляру окна присваивается собственный контекст изображения.
CS_CLASSDC 0x40 Классу окна присваивается собственный контекст изображения,который можно разделить между копиями.
CS_PARENTDC 0x80 Классу окна передается контекст изображения родительского окна.
CS_NOKEYCVT 0x100 Отключается преобразование виртуальных клавиш.
CS_NOCLOSE 0x200 Незакрываемое окно: в системном меню блокируется выбор пункта закрытия окна.
CS_SAVEBITS 0x800 Часть изображения на экране, закрытая окном, сохраняется.
CS_BYTEALIGNCLIENT 0x1000 Выравнивание клиентской области окна: использование границы по байту по оси x.
CS_BYTEALIGNWINDOW 0x2000 Выравнивание окна: bспользование границы по байту по оси x.
CS_PUBLICCLASS CS_GLOBALCLASS 0x4000 Определяется глобальный класс окон.

lpfnWndProc — указатель на оконную процедуру.

cbClsExtra — устанавливает число дополнительных байт, которые размещаются вслед за структурой класса окна. Система инициализирует эти байты нулями, в большинстве случаев равен 0.

cbWndExtra — устанавливает число дополнительных байтов, которые размещаются вслед за экземпляром окна. Система инициализирует байты нулями.

hInstance — дескриптор экземпляра, который содержит оконную процедуру для класса.

hIcon — дескриптор значка класса, дескриптор ресурса значка. Если этот член структуры — NULL, система предоставляет заданный по умолчанию значок.

hCursor — дескриптор курсора класса, дескриптор ресурса курсора. Если этот член структуры — NULL, приложение устанавливает форму курсора всякий раз, когда мышь перемещается в окно прикладной программы.

hbrBackground — дескриптор кисти фона класса, дескриптор физической кисти, которая используется, чтобы красить цветом фона, или код цвета, преобразованный к типу HBRUSH .

lpszMenuName — указатель на символьную строку с символом конца строки ( ‘\0’ ), которая устанавливает имя ресурса меню класса. Можно использовать целое число, чтобы идентифицировать меню с помощью макроса MAKEINTRESOURCE( int ) . Если этот член структуры — NULL , окна, принадлежащие этому классу, не имеют заданного по умолчанию меню.

lpszClassName — указатель на символьную строку с именем класса, оканчивающуюся ‘\0’ .

Создание окна

Создание окна осуществляется функцией

Прототип функции находится в файле библиотеки user32.dll.
Возвращаемое значение – дескриптор создаваемого окна. В случае невозможности создать окно возвращается NULL.

Аргументы функции :
lpClassName – указывает на строку с ‘\0’ в конце, которая определяет имя класса окна. Имя класса может быть зарегистрированным функцией RegisterClass или любым из предопределенных имен класса элементов управления.

lpWindowName — указывает на строку с ‘\0’ в конце, которая определяет имя окна.

dwStyle — определяет стиль создаваемого окна.

Имя Значение Описание
WS_BORDER 0x00800000 Окно имеет тонкую границу в виде линии.
WS_CAPTION 0x00C00000 Окно имеет строку заголовка.
WS_CHILD 0x40000000 Окно является дочерним.
WS_DISABLED 0x08000000 Окно является изначально неактивным.
WS_GROUP 0x00020000 Окно группирует другие управляющие элементы.
WS_HSCROLL 0x00100000 Окно содержит горизонтальную полосу прокрутки.
WS_MAXIMIZE 0x01000000 Исходный размер окна – во весь экран.
WS_MINIMIZE 0x20000000 Исходно окно свернуто.
WS_OVERLAPPED 0x00000000 Окно может быть перекрыто другими окнами.
WS_POPUP 0x80000000 Всплывающее окно.
WS_SYSMENU 0x00080000 Окно имеет системное меню в строке заголовка.
WS_VISIBLE 0x10000000 Окно изначально видимое.
WS_VSCROLL 0x00200000 Окно имеет вертикальную полосу прокрутки.

x — определяет координату левой стороны окна относительно левой стороны экрана. Измеряется в единицах измерения устройства, чаще всего в точках (pt). Для дочернего окна определяет координату левой стороны относительно начальной координаты родительского окна. Если установлен как CW_USEDEFAULT , Windows выбирает заданную по умолчанию позицию окна.

у – определяет координату верхней стороны окна относительно верхней стороны экрана. Измеряется в единицах измерения устройства, чаще всего в точках (pt). Для дочернего окна определяет координату верхней стороны относительно начальной координаты родительского окна.

nWidth – определяет ширину окна в единицах измерения устройства. Если параметр соответствует CW_USEDEFAULT , Windows выбирает заданную по умолчанию ширину и высоту для окна.

nHeight – определяет высоту окна в единицах измерения устройства.

hWndParent – дескриптор родительского окна.

hMenu – идентифицирует меню, которое будет использоваться окном. Этот параметр может быть NULL , если меню класса будет использовано.

hInstance — идентифицирует экземпляр модуля, который будет связан с окном.

lpParam — указывает на значение, переданное окну при создании.

Отображение и перерисовка окна

Отображение окна осуществляется функцией

Прототип функции находится в файле библиотеки user32.dll.
Возвращаемое значение: 1 – успешное отображение окна, 0 – ошибка.

Аргументы функции :
hWnd – дескриптор отображаемого окна.

nCmdShow – константа, определяющая, как будет отображаться окно согласно таблице.

Перерисовка окна осуществляется функцией

Прототип функции находится в файле библиотеки user32.dll.
Возвращаемое значение: 1 – успешная перерисовка окна, 0 – ошибка.
Аргумент функции hWnd – дескриптор окна.

Цикл обработки сообщений

После вызова функции UpdateWindow , окно окончательно выведено на экран. Теперь программа должна подготовить себя для получения информации от пользователя через клавиатуру и мышь. Windows поддерживает «очередь сообщений» (message queue) для каждой программы, работающей в данный момент в системе Windows. Когда происходит ввод информации, Windows преобразует ее в «сообщение», которое помещается в очередь сообщений программы. Программа извлекает сообщения из очереди сообщений, выполняя блок команд, известный как «цикл обработки сообщений» (message loop):

Для получения сообщения из очереди используется функция:

Прототип функции находится в файле библиотеки user32.dll.
В случае получения из очереди сообщения, отличного от WM_QUIT , возвращает ненулевое значение.

Аргументы функции :
lpMsg — указатель на структуру сообщения.

Структура POINT имеет вид

hWnd — дескриптор окна, очередь для которого просматривается.

wMsgFilterMin — нижняя граница фильтра идентификаторов сообщений.

wMsgFilterMax — верхняя граница фильтра идентификаторов сообщений.

передает аргумент — структуру msg обратно в Windows для преобразования какого-либо сообщения с клавиатуры. Возвращает ненулевое значение в случае успешной расшифровки сообщения, 0 – ошибка.

передает аргумент — структуру msg обратно в Windows. Windows отправляет сообщение для его обработки соответствующей оконной процедуре — таким образом, Windows вызывает соответствующую оконную функцию, указанную при регистрации класса окна.

После того, как оконная функция обработает сообщение, оно возвращается в Windows, которая все еще обслуживает вызов функции DispatchMessage . Когда Windows возвращает управление в стартовую функцию WinMain() к следующему за вызовом DispatchMessage коду, цикл обработки сообщений в очередной раз возобновляет работу, вызывая GetMessage .
Возвращает значение, определяемое оконной функцией, которое чаще всего игнорируется.
Прототипы функций находятся в файле библиотеки user32.dll.

Пример стартовой функции, создающей и выводящей окно размером 500х300 точек:

Примечание : Для корректной сборки приложения используется многобайтовая кодировка.

Оконная функция — обработка сообщений окна

Оконная функция предназначена для обработки сообщений окна. Функция обработки сообщений окна организована по принципу ветвления, состоящего из последовательной проверки типа сообщения. При совпадении типа сообщения, переданного в структуре Message с соответствующей веткой, осуществляется его обработка. Минимальный вид оконной функции представлен ниже.

4 аргумента оконной функции идентичны первым четырем полям структуры сообщения MSG .

В примере обрабатывается только один тип сообщения WM_DESTROY , которое передается оконной функции при закрытии окна.

Вызов функции DefWindowProc() обрабатывает по умолчанию все сообщения, которые не обрабатывает оконная процедура.
Функция PostQuitMessage() сообщает Windows, что данный поток запрашивает завершение. Аргументом является целочисленное значение, которое функция вернет операционной системе.

Результат выполнения программы, выводящей окно:
Окно

Комментариев к записи: 13

/*WinAPI приложение. Минимальный
набор функций для отображения окна.
Эта программа станет базовой заготовкой для всех последующих программ*/
#include <windows.h>
//Создаём прототип функции окна
LRESULT CALLBACK WndProc( HWND , UINT , WPARAM , LPARAM );
//объявляем имя программы
char szProgName[]=&qout;Имя программы&qout;;
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow)
<
HWND hWnd; //идентификатор окна
MSG lpMsg;
WNDCLASS w; //создаём экземпляр структуры WNDCLASS и начинаем её заполнять
w.lpszClassName=szProgName; //имя программы
w.hInstance=hInstance; //идентификатор текущего приложения
w.lpfnWndProc=WndProc; //указатель на функцию окна
w.hCursor=LoadCursor( NULL , IDC_ARROW); //загружаем курсор в виде стрелки
w.hIcon=0; //иконки у нас не будет пока
w.lpszMenuName=0; //и меню пока не будет
w.hbrBackground=( HBRUSH )GetStockObject(WHITE_BRUSH); //цвет фона окна — белый
w.style= CS_HREDRAW | CS_VREDRAW ; //стиль окна — перерисовываемое по х и по у
w.cbClsExtra=0;
w.cbWndExtra=0;
//Если не удалось зарегистрировать класс окна — выходим
if (! RegisterClass (&w))
return 0;
//Создадим окно в памяти, заполнив аргументы CreateWindow
hWnd= CreateWindow (szProgName, //Имя программы
"Моя первая программа!" , //Заголовок окна
WS_OVERLAPPEDWINDOW , //Стиль окна — перекрывающееся
100, //положение окна на экране по х
100, //по у
500, //размеры по х
400, //по у
( HWND ) NULL , //идентификатор родительского окна
( HMENU ) NULL , //идентификатор меню
( HINSTANCE )hInstance, //идентификатор экземпляра программы
( HINSTANCE ) NULL ); //отсутствие дополнительных параметров
//Выводим окно из памяти на экран
ShowWindow(hWnd, nCmdShow);
//Обновим содержимое окна
UpdateWindow(hWnd);
//Цикл обработки сообщений
while ( GetMessage (&lpMsg, NULL , 0, 0)) < //Получаем сообщение из очереди
TranslateMessage(&lpMsg); //Преобразуем сообщения клавиш в символы
DispatchMessage (&lpMsg); //Передаём сообщение соответствующей функции окна
>
return (lpMsg.wParam);
>

//Функция окна
LRESULT CALLBACK WndProc( HWND hWnd, UINT messg,
WPARAM wParam, LPARAM lParam)
<
HDC hdc; //создаём контекст устройства
PAINTSTRUCT ps; //создаём экземпляр структуры графического вывода
//Цикл обработки сообщений
switch (messg)
<
//сообщение рисования
case WM_PAINT :
//начинаем рисовать
hdc=BeginPaint(hWnd, &ps);
//здесь вы обычно вставляете свой текст:
TextOut(hdc, 150,150, "Здравствуй, WIN 32 API. " , 26);
//закругляемся
//обновляем окно
ValidateRect(hWnd, NULL );
//заканчиваем рисовать
EndPaint(hWnd, &ps);
break ;
//сообщение выхода — разрушение окна
case WM_DESTROY :
PostQuitMessage(0); //Посылаем сообщение выхода с кодом 0 — нормальное завершение
break ;
default:
return ( DefWindowProc (hWnd, messg, wParam, lParam));
//освобождаем очередь приложения от нераспознаных
>
return 0;
>

Related Posts