Приветствую, Дорогие друзья! И вновь Вы на моем канале. Сегодня мы с Вами продолжим работу над оконным приложением Windows. Напомню: в прошлый раз мы закончили на том, что создали окно приложения, установили в нем фон и добавили иконку. В этой статье мы будем работать с элементами управления, или вернее будет сказать «взаимодействия» с окном.
Можно считать, что приложение в целом состоит из окон. Я не опечатался. Помимо главного окна в приложении может быть еще много других окон: кнопки, текстовое поле, статические блоки – это всё окна. У всех этих элементов один тип данных: HWND. О нем мы уже говорили в прошлой статье. Но при этом функциональное назначение у названных окон разное: кнопка срабатывает при нажатии на нее левой кнопкой мыши, в текстовое поле можно вводить символы с клавиатуры или выводить из файла, статические объекты отлично подходят для отображения нередактируемой информации, картинок, надписей и т.п. Со всем этим мы сегодня и поработаем. Открываем наш программный код и попутно обдумываем какой функционал нам нужен. Напоминаю: мы создаем программу учета клиентов для СТО (автосервис звучит как-то слишком вычурно). Итак, нам нужны графы для ввода текста с клавиатуры. Их должно быть как минимум четыре: фамилия, имя, VIN-номер авто, неполадки. Также нужна кнопка «Сохранить». Также можно добавить статический блок, на котором будет отображаться текущая дата. Для начала, пожалуй, сойдет.
Первым делом добавим в приложение окошки для ввода информации. Будем создавать их постепенно. Для начала сделаем окно для ввода фамилии. Переходим в файл resources.h и создаем там новую переменную типа HWND. Назову я ее Surname.
Возвращаемся в main.cpp. Здесь нам нужно создать окно. Как это сделать? Аналогично главному окну. Его мы создавали с помощью функции CreateWindowEx(). Найдем ее в справочнике:
Нас посылают посмотреть что такое CreateWindow(). Переходим по ссылке:
Разбираемся с синтаксисом:
- первый параметр (lpClassName) – Имя класса. Помните, как в прошлой статье мы вскользь упоминали «WindowClass»? Здесь мы указываем нечто подобное.
- второй параметр (lpWindowName) – Здесь мы указываем имя (заголовок) окна. В случае кнопки это будет надпись, которую мы хотим отобразить на этой кнопке.
- третий параметр (swStyle) – Стиль окна. Рамка, притопленные края, наличие кнопок «свернуть», «развернуть», «закрыть». В одном параметре может быть несколько стилей. Записываются они через знак ИЛИ «|». Мы уже видели нечто подобное в прошлой статье.
- четвертый параметр (x) – позиция окна по оси Х. Если мы работаем с главным окном, как в прошлой статье, то это значение задает иксовую координату верхней левой точки. Если же создаем дочернее окно (окно внутри главного окна), то параметр указывает на координату относительно края главного окна. Позже сами увидите.
- пятый параметр (y) – ну если прошлая координата иксовая, то не сложно догадаться, что эта игрековая. Говорить, что точка левая верхняя, думаю не нужно.
- шестой параметр (nWidth) – Ширина окна в пикселах.
- седьмой параметр (nHeight) – Высота окна в пикселах.
- восьмой параметр (hWndParent) – Дескриптор (имя) окна – родителя.
- девятый параметр (hMenu) – Идентификатор окна. С помощью него программа понимает от какого именно элемента управления ей приходит сообщение.
- десятый параметр (hInstance) – ну про это, думаю даже говорить не надо: и так понятно.
- одиннадцатый параметр (lpParam) – об этом параметре поговорим как-нибудь позже. Не так уж и часто мы будем его использовать.
Теперь поговорим о данных параметрах подробнее.
Итак, параметр lpClassName может принимать следующие значения:
Есть еще парочка видов, но о них мы поговорим в других статьях: в простом приложении нам они совсем не пригодятся.
Параметр lpWindowName принимает в качестве значения строку из символов и букв. Допускается как латиница, так и кириллица. Также можно настроить стиль и кегль текста (об этом сейчас мы конечно же говорить не будем).
Параметр swStyle. С ним не всё так просто как с двумя предыдущими. Для каждого вида окна приоритетны свои стили. Поэтому, обычно, в справочниках не существует отдельного раздела со стилями: его помещают в конкретную главу, в которой описывается конкретный вид окна.
Про координаты, высоту и ширину сказать нечего… Ну как нечего: как продолжим написание кода для нашего приложения, тогда и скажу.
Параметр hWndParent. Имя окна – родителя. В качестве значения этого параметра мы будем передавать имя hwnd, которое было создано нами в прошлой статье. С помощью него определяется «привязанность» дочернего окна к родительскому, то есть к тому, внутри которого оно находится или от которого оно зависит.
Параметр hMenu представляет собой число. Но: тип данных этого числа преобразован в HMENU. По сути это такой идентификатор, который пересылается программе в сообщении от окна. Понимаю, что для многих сказанное мной сейчас – темный лес. Не отчаивайтесь, вы все поймете, но чуть позже. Главное не просто читайте про то, как писать код, но и сами тренируйтесь. Да, местами это утомительно, НО при этом очень интересно!
Ну что, добавим новое окно в наше приложение? Напомню, сейчас оно выглядит вот так:
Окно с текстовым полем сделать не очень сложно (я, кажется, это уже говорил). Для этого нужно в параметр lpClassName передать значение edit. При этом не стоит забывать, что тип данных этого параметра LPCSTR, то есть строка. Поэтому и записывать сюда значение нужно в виде строки, то есть в кавычках. Открываем main.cpp, пролистываем код до строки, в которой создаем главное окно приложения, отступаем немного и чуть ниже пишем (строка 58):
Видите, какую большую шпаргалку выдала нам среда проектирования на функцию CreateWindow()? Читать ее мы не будем, мы и так уже разобрались что и куда нужно писать. Указываем значение первого параметра функции:
А теперь покажу кое-что интересное. Если Вы внимательно читали, ранее мы говорили о том, что второй параметр функции CreateWindow() используется для того, чтобы выводить заголовок окна. У edit-окон заголовка нет, ведь это такой тип окон, данные в которых свободно редактируются пользователем. Однако это не значит, что параметр lpWindowNameбудет игнорироваться компилятором, напротив: строка, указанная здесь будет помещен в поле окна editкак текст по умолчанию. Мы создаем графу для ввода в программу фамилии клиента. Так поместим же в нее шаблон: например, Иванов. Не забываем, что это тоже строка, поэтому используем кавычки.
Теперь, наверное самая сложная часть создания окна: указать его стили. Чтобы сделать всё максимально правильно отправляемся в сеть и ищем в справочнике Window Styles. Соберу-ка я основные, те с которыми мы чаще всего буде работать, в табличку:
Есть еще несколько стилей, но сейчас нам о них говорить нет смысла: в написании простой программы они нам ни к чему. Вернемся в Dev, к файлу main.cpp. Напомню: нам нужно передать в функцию CreateWindow() третий параметр (swStyle). Выбираем из таблицы стили, которые необходимо здесь указать:
1. Окно должно быть видимым, значит применяем к нему стиль WS_VISIBLE;
2. Окно дочернее, оно расположено внутри окна hwnd, значит указываем стиль WS_CHILD.
Записываем стили в параметр swStyle через знак ИЛИ:
Теперь нужно расположить наш edit внутри окна hwnd и задать его ширину и высоту. Мы должны учесть, что фамилия может быть длинной, то есть строка будет довольно широкой. Также не стоит забывать о том, что нам еще нужно будет добавить надпись к окошку, чтобы было понятно что туда вводить, а значит позиционироваться оно будет точно не у самой левой границы окна приложения. Для начала создадим editвысотой 20 пиксел и шириной, скажем, 150 пиксел. Сдвинем его левый верхний угол от «начала координат» по иксу на 100 пиксел, по игреку – на 150 пиксел, то есть:
Теперь, в качестве параметра hWndParent укажем дескриптор окна – родителя, то есть hwnd. Параметр hMenuпока что указывать не будем: впишем сюда NULL, то есть «пусто». Чуть позже, когда создадим все необходимые окна, мы каждому из них присвоим hMenu. Пока что пусть будет NULL. В качестве параметра hInstance указываем hInstance, в качестве lpParam – NULL:
Запускаем компиляцию.
Поле редактируемое, как я и говорил. Мы можем просто кликнуть внутри него левой кнопкой мыши, стереть содержимое и ввести с клавиатуры любой текст:
Функциональное назначение окошко выполняет, но вот его расположение меня не устраивает: желательно сдвинуть его по иксу вправо. Отступим от правой границы окна hwnd на 100 пиксел и разместим окно с надписью там, то есть координата икс окошка edit станет Х=640-150-100=390. Здесь мы из ширины окна вычли ширину окошка edit и тот зазор, который мы оставляем между окошком и рамкой окна. Правим код и запускаем компиляцию:
Теперь нам нужно добавить к текстовому полю поясняющую надпись. Сделать это можно двумя способами: с помощью статического блока или же напрямую разместить надпись на фоновом изображении. В первом случае всё будет выглядеть красиво, но мы получаем высокую сложность реализации этого самого «красиво». Во втором случае всё будет не менее красиво, но мы получаем один существенный недостаток: чтобы сдвинуть надпись в программе, нам придется перемещать ее в графическом редакторе. Именно поэтому выберем первый вариант. Для его реализации нужно выполнить следующее:
1. Создать элемент типа static и расположить его немного левее элемента edit;
2. Вывести на static текст «Фамилия:»;
3. Сделать так, чтобы фон элемента static стал прозрачным (ну или казался нам прозрачным).
Всё, вот эти три действия нам и предстоит выполнить.
Первое действие: создаем статик. Для этого в библиотеке ресурсов (resources.h) создаем новую переменную типа HWND. Назовем ее StSurname (сокращенно от «static Surname»):
Обязательно добавляем комментарии к нашему коду, иначе пройдет пара месяцев, и Вы забудете что, где и для чего создавалось.
Возвращаемся в main.cpp и копипастим строчку кода, в которой создавали окошко Surname:
Заменяем в этой строке имя переменной с Surname на StSurname, значение параметра lpClassNameв функции CreateWindow() с «edit» на «static», а значение параметра lpWindowNameс «Иванов» на «Фамилия». Также уменьшаем ширину окошк: нам вполне хватит 80 пиксел. Сдвигаем статик по иксу на 80 пиксел влево, то есть вместо х = 390 мы указываем х = 390-80=310 пиксел:
Запускаем компиляцию и смотрим, что у нас получилось:
Выглядит неплохо, но фон у статика непрозрачный… Всё потому, что мы выполнили только два шага из трех намеченных при создании этого элемента. Теперь следует выполнить третий, самый сложный. Сложный не для Вас, а для меня, потому как придется рассказывать Вам много нового. Для начала давайте пролистаем наш код в самое начало, вот сюда:
То, что Вы видите здесь (строки 5 – 18) – это обработчик команд. Ну или проще говоря, сообщений, которые отправляют приложению составляющие его части либо устройства ввода (клавиатура, мышь и т.д.). Обратите внимание на имя этой процедуры: WndProc. С ним мы уже сталкивались в прошлой нашей статье, когда «заполняли» значениями структуру WNDCLASSEX wc. Как видите, у WndProc тоже есть свои параметры:
HWND hwnd– дескриптор окна, для которого выполняется обработка сообщений (где в принципе ведется работа);
UINT Message– сообщение, код, который элемент или программа присылают сообщению. Два других параметра содержат в себе дополнительные сведения сообщения. Для определенного сообщения они свои, поэтому описать абстрактно конкретно данные параметры не выйдет.
То есть LRESULT CALLBACK предназначен для того, чтобы принимать, «сортировать» и обрабатывать сообщения. Без него не будет работать ничего. Даже программа наша не запустится. Внутри обработчика лежит структура switch(). Что такое switch()? Если языки (я имею ввиду не иностранные, а языки программирования) Вам преподавали в Вашем учебном заведении из рук вон плохо, то с пониманием назначения этого простого в использовании и, без сомнения полезного, оператора могут возникнуть проблемы. Для тех, у кого преподаватели зря едят свой хлеб с икрой, поясню, те же, кому это не надо, могут листать дальше.
Предположим нам нужно создать программу с ветвлением. Чтобы было интереснее и понятнее придумаем «трехстрочное ТЗ», то есть техническое задание, состоящее только из пары параметров. Пусть от нас требуется написать программу для поста охраны на маленьком предприятии, которая будет выводить на монитор дежурного ФИО работника и его фото после того, как он приложит свой индивидуальный пропуск к считывающему устройству. Сам процесс считывания числового идентификатора с пропуска и вывод на экран информации мы затрагивать сейчас не будем. Рассмотрим только процедуру выбора ФИО из списка возможных в зависимости от того, какой идентификатор считался. Пусть идентификатор считывается в переменную Identтипа int, а ФИО заполняется в строку Name типа string. Саму же функцию, которая проделывает эту операцию, назовем IDinName(). Итак, ясно-понятно, что у функции должен быть параметр, с помощью которого в нее будет передаваться идентификатор (переменная Ident), внутри нее должно выполняться сравнение с заложенными в нее заранее значениями параметра и в результате своей работы она должна возвращать что-то (в данном случае строку Name). ФИО и номера пропусков придумаем по ходу написания кода:
Когда у нас всего пара значений подойдет и подобная реализация программы, но что если потребуется проверить тысячу значений? Или десять тысяч? Мы просто запутаемся в условиях if. И это я еще записал их в приглядном виде в одну строчку! Как раз для таких случаев, когда у нас множество условий, и нужен оператор switch(). В качестве параметра мы передаем в switch() значение, которое необходимо сравнить со всеми известными, а сравнение выполняем с помощью специального оператора case.Смотрите насколько короче стала запись нашего кода:
Таким образом, мы сократили объем программного кода почти в полтора раза, притом избавившись от условий if-else и дополнительной строки Name. Суть функции при этом не изменилась: на ее вход поступает число Ident, мы отправляем его в switch(), где оно поочередно сравнивается с каждым записанным в память значением и, при первом же совпадении осуществляется переход в соответствующий этому совпадению case, где вложена операция, которую необходимо выполнить и после этого выйти из switch(). Видите, ничего сложного! Те же самые ЕСЛИ и ИНАЧЕ, только записаны по-другому. Теперь для тех, до кого смысл не дошел до сих пор! Разжевываю посимвольно. Мы создали функцию IDinName(). При этом объявили, что тип данных у нее string, то есть строка. Это значит, что в результате своей работы она выдаст строку. Обычную строку из букв. То есть мы вызовем эту функцию внутри другой (например примитивной cout, которая печатает данные в консоль) и вместо ее названия выведется строка. Теперь дальше. Внутри функции лежит оператор switch(). И мы как бы говорим ему: «Смотри, вот мы дали тебе число, которое называется Ident. Сравнивай это число со всеми числами, которые лежат у тебя под операторами case.». И switch() берет число, которое мы ему передали и начинает сравнивать. Для примера возьмем число 58664830. Вызываем функцию, передавая в нее это значение:
Вот мы вызвали функцию, дали оператору switch() число и он начинает сравнивать его с указанными внутри себя. Идет к первому case и как бы спрашивает: «у тебя там случайно не 58664830?», тот отвечает «нет!», switch() переходит к следующему case и задает ему такой же вопрос. Тот также отвечает «нет». switch() идет дальше и тут один из case отвечает ему «Да! У меня именно такое число!». Switch() останавливается на этом caseи говорит ему «Выполняй то, что в тебе указано!» и case переходит к выполнению кода, который помещен после двоеточия. В нашем случае код такой: return ‘Сергеев Сергей Сергеевич’; То есть switch() говорит case: «приравняй функцию IDinName() к строчке ‘Сергеев Сергей Сергеевич’». После того, как case это делает, у функции IDinName() появляется значение ‘Сергеев Сергей Сергеевич’. Всё, switch() свою работу на этом завершает, он больше не нужен программе.
Теперь представим, что мы передали в функцию число, которого нет среди возможных вариантов. Допустим 11111111. Switch() принимает это число и отправляется опрашивать все case как и в прошлый раз. Только теперь все они отвечают ему «нет». switch() доходит до последнего case, он также говорит ему «нет» и switch() вынужден идти дальше. А дальше идти некуда: все варианты он уже перебрал, а по второму кругу без команды он пойти не может. Поэтому ему ничего не остается, как выполнить команду «return‘Это не наш сотрудник!’». То есть присвоить функции эту строчку как ее значение.
Вроде расписал дальше некуда: понятно должно стать даже последнему двоечнику.
Возвращаемся в Dev. Смотрим на тот switch(), который помещен в обработчике команд. Чтобы не листать вверх, дублирую рисунок:
Switch() здесь в качестве параметра принимает сообщение Message, которое присылают программе ее элементы или устройства ввода. В его теле уже есть один case. Это WM_DESTROY. Этот case отвечает за закрытие нашего приложения. То есть когда мы нажимаем на крестик в правом верхнем углу окна приложения (кнопка «Закрыть»), этот крестик отправляет окну hwnd сообщение с кодом «WM_DESTROY». Это сообщение попадает в обработчик команд (WndProc), где через switch() сверяется со всеми возможными кодами сообщений. Когда обнаруживается совпадение выполняется та операция, которая лежит внутри соответствующего case. В данном случае – это операция PostQuitMessage(). При ее выполнении наше приложение обращается к системе и просит его закрыть (завершить процесс, но об этом потом).
Обратите внимание, здесь, в отличие от примера, который мы рассматривали, в конце switch() не просто return, а default:return. То есть возвращаемое по умолчанию значение. В чем разница? По сути разницы особой нет: и то, и то значение соответствует тому, что мы получим, если применим условный оператор «ИНАЧЕ», просто здесь оно задано по умолчанию. И что же такого, интересно нужно сделать по умолчанию, в случае, если пришедшее приложению сообщение не соответствует ни одному case? А ничего! DefWindowProc() никак не меняет наше приложение. Зачем же тогда вообще нужно это значение по умолчанию? Затем, что мы получили в WndProc() какое-то сообщение и передали его на обработку в switch(). Значит, для того, чтобы выйти из switch() мы должны что-то вернуть в качестве нового значения WndProc(). Если этого не сделать наша программа просто зависнет в конце switch(). Вот мы и возвращаем команду «ничего не делать!».
Что ж, обработчик команд рассмотрели, теперь можно перейти и к более насущным проблемам. Напомню: нам нужно сделать окошко StSurnameпрозрачным. По сути стать прозрачным окно не может. Но зато оно может сделать свой фон таким, какой, извиняюсь за тавтологию, расположен на его фоне. То есть у нас есть static. За ним – фрагмент картинки, которая установлена в качестве фона для окна hwnd. Мы можем взять этот фрагмент и использовать его в качестве фона для static. Искажений и разрывов в картинке наблюдать мы не будем, а значит фон окошка StSurname будет казаться нам прозрачным. Для того, чтобы это сделать, нам нужно отправить приложению сообщение с просьбой изменить цвет кисти, которую мы используем для фона окошка StSurnameна пустой, то есть на тот, который находится ПОД окошком StSurname. Чтобы установить цвет окна подобным образом нужно отправить программе сообщение с кодом WM_CTLCOLORSTATIC. Найдем его в справочнике:
Добавим соответствующий case в обработчик команд:
Многие просто добавляют новые case в конец уже существующего списка. Я же стараюсь делать это в определенном порядке: выше всех – сообщения от мыши (если требуется), затем – работа с изображениями, ниже – сообщения от меню и элементов управления, в самом низу – сообщения, с помощью которых можно выполнять операции с окнами: сворачивать, разворачивать, удалять, ну и само-собой, разумеется, закрывать. Вам я так же не советую месить всё в одну кучу.
Теперь нам нужно разобраться, что делает обработчик, когда получает это сообщение. Для начала нужно понять КОГДА это сообщение вообще приходит. Отвечаю: static отправляет это сообщение при своем появлении в программе. Но почему же тогда мы не видим в WndProc() ничего, кроме функции закрытия приложения? Всё очень просто: если мы не задаем явно параметры, они выбираются из списка по умолчанию. То есть сообщение обработчику приходит и он с ним работает так, как должен работать по умолчанию. Лишний код нам видеть как бы и не нужно в программе. Когда мы прописываем case, обработчик вынужден считаться с нашим мнением, и выбирает при создании того или иного окна те параметры, которые мы задали, потому что они становятся для него более приоритетными, нежели параметры по умолчанию. Вернемся к тому, что должен сделать обработчик, когда получит сообщение WM_CTLCOLORSTATIC, чтобы фон всех объектов staticстал прозрачным. Он должен удалить цвет с кисти объекта, который к нему обращается (у окна, которое прислало ему сообщение). Для этого необходимо использовать функцию SetBkMode(). Смотрим в справочник:
Итак, у функции два параметра. Первый – непонятного нам типа HDC. Раньше мы с ним не сталкивались. Второй – обыкновенная константа, прописанная при объявлении функции где-то в ее библиотеке.
Давайте поясню что такое HDC. Представьте, что перед нами учебная доска. Обычная доска, на которой можно писать мелом. Так вот, чтобы стало понятно: сама доска – это HWND, а вот ПОВЕРХНОСТЬ, на которой мы пишем – это HDC. Думаю подобная интерпретация будет более понятной, чем «дескриптор контекста объекта, используемый для вывода на него графической информации». Получается, что функция SetBkMode() берет поверхность окна, которое присылает сообщение в обработчик и устанавливает для него фон, прописанный во втором параметре. Пролистываем справочник ниже и видим, что в качестве второго параметра можно указывать одну из двух констант:
То есть либо мы объект закрашиваем (OPAQUE), либо оставляем "нетронутым" (TRANSPARENT). С этим худо-бедно разобрались. Теперь поймем откуда функция SetBkMode() возьмет первый параметр, HDC. Для этого посмотрим на рисунок 21, где мы рассматривали синтаксис сообщения WM_CTLCOLORSTATIC. Видите описание wParam? «Обработка контекста устройства для статического окна управления». Именно отсюда мы и можем извлечь HDC создаваемого окошка. Всё что требовалось, мы с Вами изучили, значит смело можем переходить к реализации задуманного. Открываем файл main.cpp и в case WM_CTLCOLORSTATIC прописываем функцию SetBkMode():
Запускаем компиляцию и смотрим что получилось:
Как хотели у нас не получилось. Почему? Давайте разбираться. Мы отправили сообщение от окошка StSurname приложению в момент его создания. Обработчик это сообщение получил. Функция SetBkMode() отработала. Case CTLCOLORSTATIC закрылся. Что не так? Всё очень просто: мы указали что закрасить и каким цветом будем красить, но не указали при этом ЧЕМ будем красить. Отсюда следует простое решение нашей проблемы: нужно вернуть кисть, которой мы будем красить наш static. Сделать это можно с помощью функции GetStockObject(). Ищем ее в справочнике:
Существует 20 значений параметра I, передаваемого в функцию. Здесь я их указывать не буду, если нужно, переходите по ссылке под рисунком и смотрите сами. Скажу только, что сейчас нам нужен параметр i со значением HOLLOW_BRUSH, или же с эквивалентным ему NULL_BRUSH. Добавляем функцию GetStockObject() в case CTLCOLORSTATIC:
Обязательно приводим результат к типу LRESULT, то есть к тому типу, который установлен у WndProc. В противном случае компилятор заругается на несоответствие типов данных:
Ну и, конечно же, результат того, что мы получим, если запустим компиляцию кода, приведенного на рисунке 28:
Теперь каждый объект с классом static будет создаваться с прозрачным фоном. Это то, чего мы и добивались. Но что если помимо прозрачных нам потребуется непрозрачный static? Мы возьмем и сделаем его! Как? Об этом я расскажу, но точно не в этой статье. Пока что закончим с тем, что наметили в начале: добавим в приложение окна Имя, Отчество и VIN-номер.
Делаем графу «Имя». Начнем как и в прошлый раз с окошка edit. Переходим в файл resources.h и создаем там переменную типа HWND. Назовем ее просто: Name. Сразу же создадим переменную для элемента static, с помощью которого будем выводить надпись «Имя:». Эту переменную назовем StName. Думаю, почему так, объяснять не надо:
Возвращаемся в файл main.cpp. Здесь копипастим строки кода, в которых мы создавали окна Surname и StSurname. Заменяем имена в «клонах» на Name и StName соответственно:
В новых строках не меняем больше ничего, кроме параметров lpWindowName, то есть строк – заголовков окон и значений игрек: нам нужно сместить окна Name и StNameотносительно Surname и StSurnameна 25 пиксел (а может и не на 25 – в общем, на сколько нужно, на столько и смещаем! Я буду на 25. Поясняю, чтобы вы не подумали, что 25 – это какая-то волшебная константа). То есть вместо значения 150 помещаем в параметры игрек функций CreateWindow() значения 175 (не 125 потому, что ноль у нас вверху! То есть система координат расположена так, что по иксу значения увеличиваются слева – направо, а по игреку сверху-вниз).
Внимательно посмотрите на строки 66 и 69, а затем на строки 67 и 70 и Вы поймете чем они отличаются. Запускаем компиляцию и смотрим на то, что у нас получится:
Отлично! Всё получилось. Только вот промежуток между статиком с текстом «Имя:» и соответствующей строкой немного великоват. Давайте подвинем его немного ближе. Можно конечно сдвинуть вправо сам элемент, но тогда нам придется потратить много времени, чтобы определить на сколько именно пиксел его сдвигать. Можно поступить хитрее. Ширина элемента StName 80 пиксел, он вплотную «пододвинут» к окошку Name, при этом в статике текст выровнен по левому краю. У нас будет еще как минимум два таких элемента типа staticи не хотелось бы, чтобы текст в них ходил ходуном. Ведь мы договорились, что неотъемлемая часть любого приложения – его презентабельный вид. Поэтому мы просто подвинем все эти элементы на 5 пиксел влево и выровняем в них текст по правому краю. С первой операцией понятно: достаточно просто вычесть 5 пиксел из текущей координаты икс в элементах StSurname и StName. Но вот что делать с выравниванием текста? Здесь нам помогут специальные стили окна для static. Помните, я говорил, что для различных окон они индивидуальны? Так вот, существует три типа выравнивания текста в static: по левому краю, по правому краю и (никогда не угадаете) по центру. Соответствуют им стили SS_LEFT, SS_RIGHT и SS_CENTER. Нам нужно добавить в параметры swStyle функций CreateWindow() для StSurname и StNameстиль SS_RIGHT:
Запускаем компиляцию, смотрим, что у нас получилось:
Ну вот, другое дело! Теперь мы можем смело переходить к созданию графы «Отчество:». Идем в файл resources.h и зоздаем там две новые переменные типа HWND: Patronymic и StPatronymic:
Возвращаемся в main.cpp и клонируем строки кода, в которых создавали окошки Name и StName. Изменяем имена окошек в новых строчках на Patronymic и StPatronymic соответственно:
Заменяем заголовки окон. В Patronymic указываем «Иванович», а в StPatronymic«Отчество:». Также смещаем эти два окна вниз на 25 пиксел (в параметрах игрек заменяем 175 на 200):
Запускаем компиляцию и смотрим, что получится:
Графа «Отчество» успешно добавлена. И нас пока что всё устраивает. Переходим к следующему шагу. Сделаем еще одну графу: «VIN-номер». По уже отработанной схеме идем в файл resources.h и добавляем в проект две новые переменные. Назовем их VIN и StVIN:
Возвращаемся в main.cpp и копипастим строчки кода, в которых создавали окошки Patronymic и StPatronymic. Меняем в новых строчках имена переменных на VIN и StVIN:
Заменяем параметры lpWindowName функций CreateWindow(): в VINвписываем «*****************» (17 звездочек по количеству символов в VIN-номере автомобиля), в StVIN указываем «VIN-номер:». Смещаем строки относительно предыдущих на 25 пиксел вниз (вместо 200 в параметре игрек указываем 225):
Запускаем компиляцию:
Отлично! Всё как мы и задумывали. Идем дальше: все на сегодня нам осталось сделать – это добавить в окно приложения кнопку с надписью «Сохранить» и элемент static, на котором будет отображаться текущая дата. Приступаем. Чтобы создать кнопку мы будем использовать функцию CreateWindow(). Да-да, ту же, что и для блоков edit со статиками. Только теперь в качестве класса (первый параметр функции) мы будем указывать «button». В файле resources.h создадим новую переменную типа HWND. Назовем ее SaveBtn:
Возвращаемся в main.cpp. Здесь мы должны создать новое окно. Копипастить на этот раз мы ничего не будем. Пролистываем код и под строкой, в которой создавали окошко StVIN пишем SaveBtn = CreateWindow(); В качестве первого параметра передаем значение «button» (основные классы перечислены в табличке в начале статьи). Второй параметр – строка-заголовок. Сюда пишем «Сохранить». Переходим к стилям: кнопка должна быть изначально видимой и является дочерним окном для hwnd. Значит, как в случае с элементами edit ранее указываем значения WS_VISIBLE | WS_CHILD. Теперь нужно определиться с координатами. Пусть кнопка располагается в правом нижнем углу окна приложения, скажем в 100 пикселах от нижней границы по игреку и при этом пусть ее правая граница будет совпадать с правыми границами элементов edit. То есть по игреку координаты окошка составят 480-100-30=350. Здесь 30 – высота кнопки. По иксу все немного сложнее: 390+150-90=450. Здесь 390 – начальная координата всех элементов edit, 150 – ширина всех окошек edit, 90 – ширина кнопки, которую мы задаем. Параметр hwndParrentпо-прежнему оставляем hwnd, hMenu пока что NULL, hInstance – hInstance, lpParam – NULL:
Компилируем:
Да, то что нужно! Кнопку мы добавили, можем по ней покликать, но результата от этого никакого пока что нет. Все потому что наша программа еще не умеет с ней работать. Обучать ее этому мы будем в следующей статье, а пока что создадим static, в который выведем текущую дату, установленную на нашем компьютере. Сначала создадим пустой элемент static в левом нижнем углу программы. Идем в resources.h и создаем новую переменную типа HWND. Назовем ее StDate:
Возвращаемся в main.cpp, отступаем от строк, в которых создавали элементы edit и staticи пишем: StDate= CreateWindowA(); В качестве первого параметра функции задаем «static», В качестве второго пустую строку: «». Стили укажем следующие: WS_VISIBLE| WS_CHILD | SS_CENTER. То есть: окно изначально видимое, дочернее, выравнивание внутри окна – по центру. В качестве координат вполне сойдут (50, 400), то есть по иксу 50, по игреку 400. Ширина пусть будет 100, высота 20 пиксел. Родительское окно – hwnd, hMenu –NULL, hInstance –hInstznce, lpParam – NULL:
Компилируем и, разумеется, ничего не видим, ведь в качестве заливки для элементов static мы установили прозрачный фон:
Как говорится, Вы его не видите, а он там есть! Для того, чтобы добавить на наш static дату, нам сначала нужно «вытащить» ее в строчку. Параметры даты можно получить из структуры SYSTEMTIME. Создаем новую структуру типа SYSTEMTIMEс именем sdate. Делаем мы это в файле main.cpp ПЕРЕД строчкой, в которой создаем окошко StDate. Чтобы извлечь из созданной структуры локальную дату и время нужно вызвать функцию GetLocalTime(). Параметр у этой функции всего один: LPSYSTEMTIME lpSystemTime. Проще говоря это та структура sdate, которую мы создали (точнее не сама структура, а ее адрес: ставим перед именем sdate амперсанд). Теперь нам нужно создать строку (массив char-ов), в которую считаем дату. Назовем ее DateSys. В общем виде получим следующее:
Осталось только записать данные из sdate в DateSys и передать эту строку как параметр lpWindowNameв функцию создания окошка StDate. С этим нам поможет функция GetDateFormat(). Найдем ее в справочнике:
В качестве первого параметра функции необходимо указать языковой стандарт функции. Таких стандартов, конечно же, несколько (было бы удивительно, будь он один):
LOCALE_CUSTOM_DEFAULT, LOCALE_CUSTOM_UI_DEFAULT, LOCALE_CUSTOM_UNSPECIFIED, LOCALE_INVARIANT, LOCALE_SYSTEM_DEFAULT, LOCALE_USER_DEFAULT. Объяснить их назначение просто и понятно я пока что не смогу. Возможно позже. Из этого списка, мы будем использовать LOCALE_SYSTEM_DEFAULT, так как этот стандарт является стандартом по умолчанию операционной системы.
Второй параметр указывает флаги формата даты. Вариантов здесь также много. Мы используем NULL, то есть формат по умолчанию.
Третий параметр – указатель на структуру SYSTEMTIME. Сюда мы передаем sdateи конечно же не забываем про амперсанд.
Четвертый параметр – формат даты. Почитать подробно Вы можете в справочнике, там все расписано доступным и понятным русским языком. В функцию мы передадим следующее значение: «dd MMM yyyy», то есть день – два знака, цифровой формат, месяц – полное название месяца, год – полная, не сокращенная запись.
Пятый параметр функции – это указатель на буфер (нашу строку DateSys), куда мы будем «сгружать» данные.
Ну и, наконец, последний параметр – размер буфера, то есть количество символов, которое мы отдаем под данные. Отрядим под данные 32 символа (когда создавали массив мы указали столько же), этого вполне должно хватить. Что ж, пишем код:
Компилируем:
Получилось! Но не совсем… Дата целиком не помещается в наш static. Исправим это: сдвинем его по иксу немного влево, скажем на 30 и сделаем его шире на 100:
Снова компилируем и видим, что теперь все в порядке:
Отлично! На сегодня всё. Мы рассмотрели как создаются такие элементы оконного приложения как edit, static, button. Научились делать прозрачный фон для некоторых из них, а также выводить на них текст и даже дату! В следующей статье мы добавим в программу еще несколько компонентов и научим ее обрабатывать команды. Рассмотрим процесс создания текстового (ну или не совсем текстового) файла, а также сохранения в него данных из текстовых полей приложения.
Что ж, как всегда, спасибо, что читаете! Надеюсь моя статья Вам чем-нибудь да поможет. Удачи в учебе и труде!
< К предыдущей статье цикла К следующей статье цикла >
Теги: программирование, оконное приложение, приложение Windows, вывести дату WinApi, кнопка, статик, static, button, edit, текстовое поле WinApi, сделать элемент прозрачным WinApi, C++, стили окна, функция CreateWindow(), создание окна простыми словами, обработчик команд, обработчик сообщений, работа switch() простыми словами, что такое HDC, проектирование приложения простыми словами