Найти в Дзене
Универ на диване

Пишем оконное приложение для Windows на C++ WinApi #1

Здравствуйте, Дорогие друзья! Давненько я не писал на Дзене и разумеется подписчики начали понемногу «тикать» с канала. Где я был? Занимался книгой. И вот, когда «Гончие богов» приблизились к заключительной седьмой части я решил подкинуть пару статеек и сюда. Как говорится не корысти ради, не потому, что человек я абсолютно бескорыстный, а по той простой причине, что статьи тут если и приносят доход, то точно не каналам с образовательным материалом. Начнем! Сегодня я решил немного погонять Вас по Си и им подобным. А заодно и сделать заготовку приложения. Если вы искали руководство о том, как создавать ПОДОБИЯ приложений, то бишь неприглядные консольные программки, то можете смело закрывать эту страницу. Здесь мы будем писать НАСТОЯЩЕЕ ОКОННОЕ ПРИЛОЖЕНИЕ для Windows. Работать мы будем в Dev-C++. На мой взгляд программка эта вполне хороша для программистов-новичков, да и не только для них. Почему? Расскажу и покажу по ходу написания статьи, но в основном потому, что программка весит мало

Здравствуйте, Дорогие друзья! Давненько я не писал на Дзене и разумеется подписчики начали понемногу «тикать» с канала. Где я был? Занимался книгой. И вот, когда «Гончие богов» приблизились к заключительной седьмой части я решил подкинуть пару статеек и сюда. Как говорится не корысти ради, не потому, что человек я абсолютно бескорыстный, а по той простой причине, что статьи тут если и приносят доход, то точно не каналам с образовательным материалом. Начнем! Сегодня я решил немного погонять Вас по Си и им подобным. А заодно и сделать заготовку приложения. Если вы искали руководство о том, как создавать ПОДОБИЯ приложений, то бишь неприглядные консольные программки, то можете смело закрывать эту страницу. Здесь мы будем писать НАСТОЯЩЕЕ ОКОННОЕ ПРИЛОЖЕНИЕ для Windows. Работать мы будем в Dev-C++. На мой взгляд программка эта вполне хороша для программистов-новичков, да и не только для них. Почему? Расскажу и покажу по ходу написания статьи, но в основном потому, что программка весит мало и не требует запредельных ресурсов от ПК для своей нормальной работы (ну вот: заспойлерил!). Не буду рассказывать как устанавливать и откуда брать «Дэйву»: мы не на необитаемом острове живем, и можем без особых проблем отыскать все что нам нужно в «Ынтарнэтах». Итак, погнали:

Шаг 1: открываем программу.

Рисунок 1 – Открываем Dev-C++
Рисунок 1 – Открываем Dev-C++

Интерфейс, конечно, у программки… ну прямо скажем, да, простенький. Но как раз этим мне и нравится Dev! Нет ничего лишнего.

Шаг 2: Файл -> Создать -> Проект

Рисунок 2 – Начинаем новый проект
Рисунок 2 – Начинаем новый проект

А теперь начнется предварительная настройка. В открывшемся окне выбираем тип приложения, которое мы хотим создать:Windows Application и вводим имя проекта:

Рисунок 3 – «Обзываем» наш проект
Рисунок 3 – «Обзываем» наш проект

А теперь внимание: в названии проекта не используем кириллицу, иначе наше приложение мы не откроем никогда в жизни!

После того, как Вы нажмете ОК откроется окошко «Сохранить», где Вам необходимо выбрать директорию (папку), в которой мы будем сохранять файлы нашего проекта. Можно создать папку заранее, а можно прямо во время сохранения проекта: просто введите в строчку название новой папки и нажмите «Открыть» -> «Сохранить» .

Шаг 3: Открылся код нашего приложения

Рисунок 4 – Открылся программный код нашего приложения
Рисунок 4 – Открылся программный код нашего приложения

Та-да-ам!.. Мы ничего не писали, а программа уже есть: чудеса!.. Никакого чуда, мошенничества или ловкости рук, просто мы сейчас видим обычную «обертку», "шаблон" - заготовку программы. Давайте посмотрим что имеем. Сначала смотрим на строчку 21, с которой начинает существование функция WinMain:

Рисунок 5 – Смотрим на строчку 21
Рисунок 5 – Смотрим на строчку 21

WinMain– это по сути и есть наше приложение. Именно в этой функции создается окно, выполняется настройка его основных параметров и т.п. Кто-то называет эту функцию точкой входа в программу, кто-то главной функцией. Я же называю ее просто: main. Функция mainтут, как и везде незаменима. Без нее не будет вообще ничего (ну кроме ошибок компилятора разве что). Сильно погружаться в изучение «внешнего вида» участков кода мы не будем, вместо этого изучим саму их суть.

Смотрим на строчку 22. Здесь мы видим два непонятных слова: «WNDCLASSEX» и «wc». Первое слово не что иное, как структура. Нда… сразу огорошил человека непонятным словом! Не пугайтесь! Структура – совокупность параметров. Ну или если говорить вообще просто, структура – это то, с помощью чего мы описываем объект. Для примера поясню. Перед нами лежит книга и нам нужно ее описать. Делаем это следующим образом:

Объект (структура) – книга;

Переплет – твердый;

Количество страниц – 1000;

Цвет обложки – черный;

Высота – 40 мм;

Длина – 22 см;

Ширина – 18 см;

Это по сути и есть структура. Мы указали все параметры объекта, которые потребуются нам для простого взаимодействия с ним. Обратите внимание: я не указал в этом списке «Автор – такой-то» и «Издательство – такое-то». Сделал я это нарочно, потому как об объекте информации нам это не даст никакой. Автор и Издательство – не параметры книги, а параметры СОДЕРЖАНИЯ книги, т.е. не самого объекта, а его содержимого.

И что же тогда значат эти два слова в 22 строке? Простая аналогия:

WNDCLASSEX wc; = Объект – книга;

wc – название структуры. Назвать структуру мы можем по-разному. Вообще как захотим. Это обыкновенное слово, которое придет нам в голову. Не думаю, что Вам вдруг стало все понятно в мгновение ока. И это хорошо. Потому как если всё понятно сразу – значит впоследствии не будет понятно ничего. Программирование – одна из наук, где понимание приходит не при чтении теории, а в процессе написания программы.

Смотрим на 23 строчку: HWND hwnd.Ну и что? Два одинаковых слова, одно из которых написано капсом, а второе – прописью. Вообще ничего не понятно. А если скажу, что HWND – это такой тип данных, которым принято называть окно? Написал, перечитал, ужаснулся тому, что подумает новичок: «тип данных, которым принято называть окно»… Лучше поясню простыми словами: Вы сейчас читаете мою статью. Открыта она у Вас в ОКНЕ браузера. Уловили суть? ОКНО! Любое приложение открывается в виде ОКНА. При написании кода окно принимается за огромную совокупность параметров и эту совокупность мы называем hwnd(можем назвать как захотим, ни на что не влияет). А тип данных у нее HWND. Если не понятно, повторюсь: это нормально!

Строчка 24: MSG msg. Читаем, а в голове сразу же растягивается в «Message». Верно: это тип данных, который отвечает за сообщения. И это не те сообщения, которые всплывают в диалоговых окнах. Вообще, при работе программы, внутри нее непрестанно циркулируют тысячи сообщений: ее компоненты обмениваются ими между собой. Нам это пока что не важно. А важно то, что все эти сообщения в окне нашей программы попадают в переменную с именем msg.

Пропустим 27-ю строчку со страшным незнакомым словом memset:

-6

Пока нам не до него. Но вскользь упомяну, что это функция для очистки массивов (куска памяти). Смотрим на строчки с 28 по 31 и удивляемся: зачем кто-то написал что-то через точку? А почему не с заглавной буквы?! Хех, может с точки зрения русского языка (да и любого другого… ну за исключением Азбуки Морзе, где точки в изобилии) тут ошибка на ошибке, но с точки зрения языков программирования здесь все правильно: с помощью точки мы обращаемся к параметру внутри структуры. То есть:

книга.переплет = твердый;

книга.кол-во страниц = 1000;

книга.цвет обложки = черный;

книга.высота = 40;

и т.д.

В строчке 28 мы указываем какой размер в памяти нужно отдать под создаваемую нами структуру. По умолчанию задан параметр sizeof(WNDCLASSEX). Функция sizeof() используется для того, чтобы показывать размер в байтах, который что-либо занимает в памяти. В данном случае как бы это банально не звучало, структуре присваивается размер структуры. Проще говоря:

книга.кол-во страниц = 1000; => книга.кол-во страниц = sizeof(книга);

Если мы откроем книгу и начиркаем в ней, то страниц в ней от этого больше не станет, как впрочем и меньше. То же самое со структурой в программе.

29 строчка не менее важна, чем все предыдущие. Тут мы даем имя тому, что будет работать с сообщениями внутри программы. То есть lpfnWndProc – это та штука, которая будет читать msg, которые нашему окну будут присылать разные кнопочки, крутяшки и прочее (под прочим понимается текст, который мы вводим с клавиатуры, движения и нажатия кнопок мыши) и в зависимости от содержания msg как-то изменять . По умолчанию она названа WndProc. Не сложно догадаться, что «разверблюживание» этого имени приведет к тому, что мы увидим нечто вроде «Windows Procedure», то есть процедура окна. Само слово «процедура» говорит о выполнении каких-либо действий. Просто и понятно.

Тридцатую строчку объяснить будет сложнее. По сути в ней написано «hInstance = hInstance». Снова два одинаковых слова… Хмм… Вы когда-нибудь программировали железо? Даже простая Ардуинка Уно сгодится (выбрал самое распространенное и популярное). Так вот, вспомните как в самом начале скетча Вы иногда писали что-то вроде «#define LED 5». Этим мы присваивали пину 5 название LED. Параметр hInstance используется как раз для этого: с помощью него мы присваиваем нашему окну идентификатор - числовое значение, с помощью которого система может понять что в данный момент работает именно наша программа и при необходимости имеет возможность обратиться к ней (как, догадываетесь? Подсказка: msg).

В тридцать первой строчке нет ничего запредельно непонятного: h.Cursor = LoadCursor(NULL, IDC_ARROW).Тут мы выбираем какой курсор у нас в окне будет. Думаю, по названию «IDC_ARROW» Вы догадались, что это стрелка. Какие еще виды бывают? Любые! Вы сами можете нарисовать курсор, сохранить его в файл с определенным расширением и загрузить в свою программу через функцию LoadCursor().

Идем дальше, строки 34-37:

Рисунок 6 – Продолжаем разбираться с заготовкой
Рисунок 6 – Продолжаем разбираться с заготовкой

Если Вы – начинающий программист (в эту же категорию попадают студенты), английский знаете на уровне словарика, как к слову и я, то словечко «Background» никаких ассоциаций у Вас не вызовет. Не буду мучит Вас своей бессмысленно болтавней, в 34 строчке мы задаем цвет нашего окна. Видите там, в скобочках, такое интересное значение «COLOR_WINDOW+1»? Возможно, Вы мне не поверите, но это и есть цвет. Это один из базовых цветов для окна приложения. Мы можем задавать его оттенки изменяя «+1» на «+2», «+3» и т.д. Помимо этого мы можем:

а) выбрать собственный цвет из палитры (количество стандартных цветов ограниченно и мы не сможем получить требуемый оттенок играясь с этим параметром и меняя его от нуля до миллиона);

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

Ну-у-у, с 35 строкой вообще проблем быть не должно: в самом названии параметра скрыта его суть: имя класса. Его мы можем задавать любым. Но все же лучше присваивать такие имена классам, чтобы по ним было понятно что за окно описывается в классе. Спросите зачем классу из нашей структуры имя? Затем, что мы можем создать два одинаковых окна, три… десять. При этом мы не будем для каждого создавать отдельный класс, а воспользуемся одним. Удобно? Очень! С чем это можно сравнить… Пожалуй с типографией. Печатаем одну книгу, потом еще одну, еще и еще. При этом сколько раз в память принтера файлик нашей книги подгружается? Верно: один! То есть файл, который компьютер переслал в память принтера – своего рода класс, а книги, которые мы печатаем – окна приложения. Все они одинаковые, все напечатаны с одного исходника. И тут пора бы сказать пару слов о наследовании, но-о… мы не будем. Чисто из вредности. Ну а если честно, то незачем сейчас ворошить эту тему. Переходим к 36 строке. Тут тоже всё понятно: мы загружаем иконку. Ну или не загружаем, если не хотим. 36 строка – иконка, которая отобразится в левом верхнем углу окна, 37 строка – иконка, которая отображается в списке программ и на панели задач. Я лично не встречал еще людей, которые устанавливали бы одну иконку и забывали про другую, учитывая, что файл для этого используется один и тот же.

Далее опять страшное: с 39 по 41 строчку кода мы видим условие, которое дословно можно интерпретировать как «если не выполняется неизвестная нам функция RegisterClassEx(), когда мы закидываем в нее в качестве параметра нашу структуру wc, то вывести на экран диалоговое окно с заголовком “Не удалось зарегистрировать окно!” и с содержанием “Ошибка!”». Проще говоря, это окошко Вы увидите, если накосячите с параметрами класса.

И вот мы приблизились к сути WinMain(): все что нужно объявлено, параметры присвоены, пора бы уже что-то да создать:

Рисунок 7 – Строчка кода, создающая окно
Рисунок 7 – Строчка кода, создающая окно

Итак, мы помним (а если нет, то прямо сейчас я напомню), что ранее в самом начале мы создали переменную типа HWNDи по умолчанию назвали ее hwnd. Теперь мы присваиваем этой переменной значение. Не удивляйтесь, при создании оконных приложений это норма. Для того, чтобы наш код не выглядел так:

Окно hwnd= куча параметров через запятую

мы должны использовать специальную функцию, которая соберет эту кучу параметров в вид переменной с типом ОКНО (ну или если быть более точным HWND). И функция эта CreateWindowEx. С помощью нее мы приводим набор параметров к такому виду:

ОКНО hwnd= ОКНО (куча параметров через запятую, но при этом в строго определенном порядке)

Первый параметр в функции – это стиль окна, которое мы создаем. Под стилем понимается не только внешний вид интерфейса, но и его функциональность. По умолчанию задан параметр WS_EX_CLIENTEDGE (окно с углубленной рамкой). Второй параметр – это имя созданного нами (ну в данном конкретном случае ЗА НАС) класса. То есть мы указываем где искать параметры для настройки создаваемого окна. Третий параметр – по умолчанию «Caption» - заголовок окна. То, что написано в рамке приложения сверху – его название. Четвертый параметр включает в себя свойства окна. Их мы пока подробно рассматривать не будем. Скажу только что их может быть много и указываются они все как один параметр через знак ИЛИ: «|». Следующие два параметра с пометками икс и игрек в комментариях – это координаты окна на экране. Затем следуют ширина и высота в пикселах. На пустые параметры (имеются ввиду NULL) пока внимания не обращаем и переходим к hInstance. Что это мы с вами уже знаем: идентификатор нашего окна.

На этом в изучении шаблонного кода остановимся пока что и поиграемся с параметрами, а именно: создадим окно, загрузим свою иконку, загрузим на окошко свой фон. Для того, чтобы создать свое первое приложение жмем на кнопку «Скомпилировать и выполнить»:

Рисунок 8 – Запускаем компиляцию
Рисунок 8 – Запускаем компиляцию

Программа перенаправит нас в окно сохранения файла. Это исходный файл проекта. По умолчанию он носит название «main» и без него работать ничего не будет от слова совсем. Сохраняем.

Рисунок 9 – Сохраняем файл
Рисунок 9 – Сохраняем файл

Немного ждем и-и-и, вот она, наша первая программа:

Рисунок 10 – Окно приложения
Рисунок 10 – Окно приложения

Если кто не понял, вот эта белая штука с надписью «Caption» и со значками «Свернуть», «Развернуть» и «Закрыть» и есть наше приложение. Давайте немного поэкспериментируем: попытаемся изменить цвет фона окна. Для этого закроем наше «пустое приложение» и обратимся к строчке 34 программного кода. Насколько помните именно тут мы можем оперировать настройками фона окна. Давайте заменим единицу в параметрах на двойку:

Рисунок 11 – Изменяем значение параметра в hbrBackground
Рисунок 11 – Изменяем значение параметра в hbrBackground

Запускаем компиляцию и после десяти секунд ожидания получаем:

Рисунок 12 – Фон окна приложения нами успешно изменен
Рисунок 12 – Фон окна приложения нами успешно изменен

Ну что, попробуем еще? Я не буду писать, что "меняю цифру в параметре", а просто сделаю несколько скринов подряд с разными значениями:

Рисунок 13 – Окно программы с фоном, заданным в hbrBackground неявно
Рисунок 13 – Окно программы с фоном, заданным в hbrBackground неявно

Как видите не густо тут с выбором. Давайте сделаем свой фон. Для этого заглянем под капот параметру hbrBackground. Просто загуглим ее. Ну точнее заяндексим. Нам нужен сайт – справочник. И я вас обрадую, такой есть. Точнее таких сайтов несколько. Один из самых популярных это https://learn.microsoft.com/ru-ru/windows/win32/, но только им при работе я Вам ограничиваться не советую, потому как там есть далеко не всё! Итак, открываем поисковик и ищем… не угадали, не hbrBackground , а HBRUSH. С чего бы вдруг? Внимательно смотрим на наш программный код:

wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);

Смотрите: у нас значение «COLOR_WINDOW+1» приводится к типу HBRUSH, чтобы мы могли использовать его для параметра hbrBackground. Поясняю зачем это делается. Где-то в файле библиотеки параметр hbrBackground объявлен следующим образом:

HBRUSH hbrBackground;

То есть операцией wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1) приводим всё к виду (HBRUSH) hbrBackground= (HBRUSH)(COLOR_WINDOW+1). Что такое HBRUSH? Не буду мучить Вас терминами. Простыми словами это кисть. Кисть, которой мы закрашиваем что-то. В данном конкретном случае – главное окно нашего приложения. И нам нужно понять как создать новую кисть и задать ей требуемый цвет. Забиваем в поисковую строчку «hbrush winapi»и переходим на сайт, о котором я говорил ранее:

Рисунок 14 – Функция создания кисти (Источник: https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-createsolidbrush, дата обращения: 20.02.2025)
Рисунок 14 – Функция создания кисти (Источник: https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-createsolidbrush, дата обращения: 20.02.2025)

И тут некоторые из Вас могут презрительно фыркнуть и сказать что-то вроде «Зачем мне что-то гуглить?! Я ж программистом становлюсь!». И тут кроется главное заблуждение людей о программистах. Кинематограф, как отечественный, так и зарубежный, учит нас тому, что программист – это такой худой мужик в роговых очках, который прямо из головы пишет код в какой-то непонятной среде разработки, отдаленно напоминающей командную строку. Огорчу фанатов подобных фильмов: Интернет при разработке любого приложения, даже самого простенького, просто необходим. В сети мы ищем не только описания функций, как, например, сейчас, но и ответы на то, почему вдруг компилятор отказывается компилировать наш код. Но… не будем отвлекаться от важного и интересного. Мы нашли то, что искали. Это функция CreateSolidBrush. При этом я не листал огромный справочник (а он огромный, не сомневайтесь), а перешел на первый попавшийся сайт. На этой странице нас интересует раздел Sintax, то есть синтаксис функции (не пугайтесь этого громкого слова, синтаксис – это всего лишь правило построения функции, заполнения ее параметров). Давайте детально рассмотрим то, что там написано:

HBRUSH CreateSolidBrush([in] COLORREF color);

Слева от функции мы видим, к какому типу она принадлежит: заветное слово HBRUSH. То есть в процессе работы функции будет создана именно кисть. Хорошо. Теперь смотрим на параметры: [in] COLORREF color. Очевидно, что COLORREF – это тип данных, а color – это переменная, с помощью которой можно задавать цвет. Для того, чтобы понять как это сделать листаем страницу справочника ниже и видим раздел Параметры:

Рисунок 15 – Ознакамливаемся с разделом Параметры функции
Рисунок 15 – Ознакамливаемся с разделом Параметры функции

Жмем на COLORREF и переходим на страничку справочника, где описан этот параметр. Листаем страницу вниз и видим раздел «Example»:

Рисунок 16 – Знакомимся с параметрами типа COLORREF (Источник: https://learn.microsoft.com/en-us/windows/win32/gdi/colorref, Дата обращения: 20.02.2025)
Рисунок 16 – Знакомимся с параметрами типа COLORREF (Источник: https://learn.microsoft.com/en-us/windows/win32/gdi/colorref, Дата обращения: 20.02.2025)

То есть данная переменная не что иное, как шестнадцатеричная запись цвета, его описание в виде порядкового номера в палитре цветов. И вновь не пугаемся! Для того, чтобы найти эту цифровую запись для конкретного цвета достаточно открыть Паинт и перейти в раздел Палитры «Изменение цветов»:

Рисунок 17 – Палитра цветов
Рисунок 17 – Палитра цветов

Здесь мы можем установить значение цвета в RGB формате. Для тех кто не знает: RGB – формат цвета, в котором в цифровом соотношении показано содержание в выбранном цвете элементарных, базовых, цветов – красного (R), зеленого (G), синего (B). При этом значение каждого элементарного цвета выражается десятичным числом от 0 до 255 (то есть число восьмиразрядное). И как это нам поможет с шестнадцатеричным видом? Давайте укрупним во-от этот фрагмент с рисунка 16:

Рисунок 18 – Укрупняем интересующий нас фрагмент
Рисунок 18 – Укрупняем интересующий нас фрагмент

Начнем с первой строчки: COLORREF rgbRed=0x000000FF. Здесь, как Вы уже возможно догадались по имени переменной, задается красный цвет. Чтобы Вам было проще понять что это за число такое запишу его в табличку, в которой подпишу в нем биты:

-20

Нумерация данных ведется справа-налево и начинается с нуля. Это я так, напоминаю, если вдруг кто забыл. Значит, нулевой и первый символы отвечают за концентрацию красного оттенка в цвете. Почему тогда указаны FF? Откроем калькулятор и осуществим перевод десятичного числа 255 в шестнадцатеричную систему счисления (HEX):

Рисунок 19 – Число 255 в различных системах счисления
Рисунок 19 – Число 255 в различных системах счисления

Проверим! Исправим строчку программного кода, отвечающую за установление фона окна приложения: вместо wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1) введем wc.hbrBackground = CreateSolidBrush(0x000000FF):

Рисунок 20 – Вносим исправления в строчку кода с hbrBackground
Рисунок 20 – Вносим исправления в строчку кода с hbrBackground

Запускаем. И что же мы видим?

Рисунок 21 – Окно приложения
Рисунок 21 – Окно приложения

Фон окна действительно стал красным. Поэкспериментируем: снизим концентрацию красного оттенка. То есть сделаем цвет более темным (чуть позже поясню почему станет темнее). Для этого зададим вместо 255, скажем… ну пусть будет 150. Заходим в калькулятор и конвертируем:

Рисунок 22 – Конвертируем значение 150 в другие системы счисления
Рисунок 22 – Конвертируем значение 150 в другие системы счисления

Получается, что нам нужно ввести вместо FFчисло 96:

Рисунок 23 – Изменяем оттенок красного цвета
Рисунок 23 – Изменяем оттенок красного цвета

Запускаем и смотрим:

Рисунок 24 – Цвет окна изменился
Рисунок 24 – Цвет окна изменился

Цвет окна действительно стал более темным. Думаю не лишним будет, если я поясню почему так произошло. Многие помнят, что все цвета в мире состоят из основных трёх: красного, зеленого и синего (RGB). В программировании все так же. На примере красного мы увидели, что если концентрации двух из трех основных цветов занулить, то есть вообще их в палитру не добавлять, то мы получим только оттенки одного основного цвета. Если же занулить значения всех основных цветов, то есть сделать 0x00000000, мы получим черный цвет (нет ничего – черный). И напротив: если мы смешаем все основные цвета в максимальной их концентрации, то получим белый: 0x00FFFFFF.

Рисунок 25 – Ух-ты, действительно белый…
Рисунок 25 – Ух-ты, действительно белый…

В этом числе нулевой и первый символы – красный цвет, второй и третий символы – зеленый цвет, четвертый и пятый символы – синий цвет:

-28

Поупражняемся. Зададим в качестве фона окна чисто зеленый цвет. Для этого нам нужно убрать из палитры синий и красный оттенки, а концентрацию зеленого, напротив, сделать максимальной. То есть числовой идентификатор цвета станет равен 0х0000FF00. Проверяем (я буду делать совместный скриншот, то есть вписываю строчку в код, запускаю компиляцию и делаю скрин после того, как запустится наше приложение. Так я сэкономлю место в статье не усложнив ее при этом):

Рисунок 26 – Задаем цвет окна 0x0000FF00
Рисунок 26 – Задаем цвет окна 0x0000FF00

Как видим, цвет окна действительно стал зеленым. Сделаем то же самое, но теперь для синего: зануляем значения для красного и зеленого цветов и получаем число 0x00FF0000.

Рисунок 27 – Заменяем цвет окна на 0x00FF0000
Рисунок 27 – Заменяем цвет окна на 0x00FF0000

Цвет окна стал синим. Отлично! Повторим: все цвета состоят из трех основных: красного (R), зеленого (G) и синего (B). У каждого цвета есть его числовой идентификатор (проще говоря цвет выражен в цифрах). Зачастую для этого используется шестнадцатеричное число. В этом числе – своеобразный префикс (показывает нам, что число шестнадцатеричное), 00 – два символа, которые мы не используем, и, наконец, последующие шесть символов, с помощью которых мы и задаем цвет. Давайте создадим в палитре Паинта собственный оттенок и запишем его идентификатор в шестнадцатеричном виде. Пусть это будет вот такой темно – желтый цвет:

Рисунок 28 – Цвет в палитре
Рисунок 28 – Цвет в палитре

Нас интересует какая у какого основного цвета концентрация: Красный – 236, Зеленый – 176, Синий – 19. Эту информацию мы смотрим в трех строках справа снизу окошка палитры (ну это я для тех, кто никогда вообще не открывал палитру в Паинте). Теперь мы открываем калькулятор (режим «Программист») и конвертируем эти числа из десятичных в шестнадцатеричные. Сначала красный:

-32

Теперь зеленый:

-33

Ну и, наконец, синий:

-34

А теперь составляем шестнадцатеричный идентификатор для цвета:

-35

Получаем число 0х0013В0ЕС. Записываем его как параметр в функцию:

Рисунок 29 – Получаем желтый фон
Рисунок 29 – Получаем желтый фон

И вот, мы передали в функцию CreateSolidBrush() параметр 0х0013В0ЕС и действительно получили наш темно-желтый цвет в качестве фона окна приложения. Если хотите, можете поупражняться еще. Я же перехожу к более интересному: к тому, как установить в качестве фона окна приложения не просто цветной холст, а настоящее изображение.

Начнем с того, что любая программа состоит не только из программного кода, но и из ресурсов. Ресурсы – это всё то, что мы не можем создать программно: картинки, иконки, курсоры и т.п. Поэтому мы создаем их в специальных редакторах и затем просто «подключаем» к нашему приложению. Покажу как это делается на примере фона. Создаем картинку с разрешением, которое указано при создании окна нашего приложения. В нашем случае это 640 на 480 пиксел (строчки 47 и 48 программного кода). Картинка по смыслу должна подходить к функциональному назначению приложения. То есть, если говорить простыми словами, показывать то, для чего приложение: для торговой организации – фон со стеллажами и товарами, для автозавода – фон с конвейером и т.п. Стилизовать под конкретную задачу приложение не так уж и важно, НО: чем круче выглядит Ваше приложение, тем лучшее о нем сложится первое впечатление у пользователя. Итак, просим нейросеть создать для нас картинку и дорабатываем ее напильником. Пусть наша задача состоит в том, что нам нужно создать приложение для… Хмм… так сложно придумать… Пусть это будет программка для учета клиентов СТО (ну раз уж за автозавод речь завел). Итак, нам нужна картинка с авто. Думаю такая картинка в качестве фона вполне сгодится:

Рисунок 30 – Подготавливаем фон для окна приложения
Рисунок 30 – Подготавливаем фон для окна приложения

Далее сохраняем изображение с расширением *.bmp в папке, где лежит наш с вами проект:

Рисунок 31 – Сохраняем картинку в директории проекта
Рисунок 31 – Сохраняем картинку в директории проекта

Теперь у нас есть приложение и картинка для его фона, остается только их подружить между собой. Для этого необходимо добавить файл с картинкой в ресурсы нашего приложения. Но сначала эти ресурсы еще нужно создать. Пока что в нашем проекте есть только один файл: main.cpp. То есть главный файл программы. Помимо него необходимо создать файл, в который мы поместим все переменные и константы (и многое-многое прочее) - .h файл или как его принято называть «header - файл» и файл ресурсов. Для этого заходим в меню Файл -> Создать -> Исходный файл. Откроется диалоговое окно с вопросом «Добавить новый файл в текущий проект?». Жмем Yes. Повторяем операцию. В нашем проекте появились два безымянных файла:

Рисунок 32 – Два новых исходных файла
Рисунок 32 – Два новых исходных файла

Теперь сохраняем один файл под именем resources.h, а второй – resources.rc. Для того чтобы переключиться с одного файла на другой жмем на соответствующую вкладку. Итак, сохраняем: Файл -> Сохранить как. В открывшемся окне вводим название файла и выбираем расширение:

Рисунок 33 – Сохраняем файл Безымянный1 под именем resources.h
Рисунок 33 – Сохраняем файл Безымянный1 под именем resources.h
Рисунок 34 - Сохраняем файл Безымянный2 под именем resources.rc
Рисунок 34 - Сохраняем файл Безымянный2 под именем resources.rc

Теперь мы должны добавить файл с картинкой в ресурсы проекта. Для этого нам нужно создать константу-идентификатор, к которой будет привязан файл. Заходим в файл resources.h и пишем: #define FonWindow 1:

Рисунок 35 – Создаем идентификатор
Рисунок 35 – Создаем идентификатор

Мы просто присвоили константе FonWindow(которую сами же и придумали) значение 1. Теперь переходим в файл resources.rc и здесь указываем где лежит наш файл с картинкой. Важно понимать, что путь к файлу указывается из текущей директории, то есть из папки, где сохранен проект. Если картинка лежит рядом с файлом проекта (нет вложенных директорий), то мы просто указываем в кавычках ее имя. Также важно «прикрепить» к этому файлу (как к слову и к main.cpp) библиотеку (header - файл) resources.h, которую мы создали. Для этого используем директиву #include:

Рисунок 36 – Добавляем картинку в ресурсы (не забываем про расширение файла!)
Рисунок 36 – Добавляем картинку в ресурсы (не забываем про расширение файла!)

Теперь переходим в файл main.cpp и правим значение параметра wc.hbrBackground. Функция CreateSolidBrush() создает кисть со сплошным цветом. Нам же нужно создать кисть, которая будет «рисовать картинками» из привязанного файла. Для этого заменяем функцию на соответствующую: CreatePatternBrush(). Найдем данную функцию в справочнике:

Рисунок 37 – Смотрим информацию по функции CreatePatternBrush() в справочнике (Источник: https://learn.microsoft.com/ru-ru/windows/win32/api/wingdi/nf-wingdi-createpatternbrush, Дата обращения: 20.02.2025)
Рисунок 37 – Смотрим информацию по функции CreatePatternBrush() в справочнике (Источник: https://learn.microsoft.com/ru-ru/windows/win32/api/wingdi/nf-wingdi-createpatternbrush, Дата обращения: 20.02.2025)

Как видим всё просто и понятно: берем наш идентификатор и подставляем его в функцию. Ну это если не думать. А если подумать… Нам нужно установить фон для конкретного окна, то есть функция должна знать идентификатор этого окна, а также загрузить картинку из ресурсов, значит нужно использовать какое-то ключевое слово. Оба эти условия реализуются с помощью функции LoadBitmap().Находим ее в справочнике и смотрим на синтаксис:

Рисунок 38 – Смотрим информацию по функции LoadBitmap() в справочнике (Источник; https://learn.microsoft.com/ru-ru/windows/win32/api/winuser/nf-winuser-loadbitmapa, Дата обращения: 20.02.2025)
Рисунок 38 – Смотрим информацию по функции LoadBitmap() в справочнике (Источник; https://learn.microsoft.com/ru-ru/windows/win32/api/winuser/nf-winuser-loadbitmapa, Дата обращения: 20.02.2025)

Пишем код:

Рисунок 39 – Подгружаем картинку на фон окна из ресурсов
Рисунок 39 – Подгружаем картинку на фон окна из ресурсов

А теперь КОЕ-ЧТО ВАЖНОЕ: ИНОГДА (повторяю: не всегда!) перед тем как приступать к компиляции нужно зайти в директорию проекта и удалить некоторые файлы, иначе вместо фона мы получим белый лист:

Рисунок 40 – Эти файлы удаляем если вместо картинки у нас выводится белый фон
Рисунок 40 – Эти файлы удаляем если вместо картинки у нас выводится белый фон

Запускаем компиляцию и получаем долгожданный результат:

Рисунок 41 – Устанавливаем картинку в качестве фона в окно приложения
Рисунок 41 – Устанавливаем картинку в качестве фона в окно приложения

Молодцы! Хорошо потрудились! Теперь давайте разберемся почему для загрузки файла из ресурсов нужна именно функция LoadBitmap(). Почему нельзя просто указать идентификатор изображения в функции CreatePatternBrush()? Давайте нарочно допустим такую ошибку в коде и посмотрим что нам на нее скажет компилятор:

Рисунок 42 – Смотрим на что ругается компилятор
Рисунок 42 – Смотрим на что ругается компилятор

Главная информация заключена во второй строчке сообщения об ошибке:

[Error] invalid conversion from ‘int’ to ‘HBITMAP’

Проще говоря, в функцию CreatePatternBrush() необходимо передавать параметр с типом данных HBITMAP, а мы попытались передать число (integer). Но что если мы создадим переменную типа HBITMAP вместо константы типа int? Заходим в файл resources.h, комментируем первую строку, в которой мы создавали константу и создаем переменную типа HBITMAP:

Рисунок 43 – Создаем переменную типа HBITMAP
Рисунок 43 – Создаем переменную типа HBITMAP

Как думаете, что нам скажет на это компилятор?

Рисунок 44 – Компилятор не ругается, но фона нет
Рисунок 44 – Компилятор не ругается, но фона нет

Как видим, компилятор не ругается, но фон у окна далеко не тот, который мы хотим. Почему так? Разбираемся: для нормальной работы функции CreatePatternBrush() ей необходимо передать параметр с типом данных HBITMAP – мы это сделали. Мы создали переменную такого типа в файле библиотеки (resources.h) и закинули ее в функцию. НО! При этом переменная как была пустой, так пустой и осталась. Не смотря на то, что в файле ресурсов по-прежнему указан путь к картинке в папке проекта, достать ее оттуда мы не можем по той простой причине, что компилятор не знает как до нее добраться: у файла с изображением нет идентификатора. Это всё равно что звонить человеку, номера телефона которого мы не знаем! То есть, по сути в файле ресурсов у нас должна быть вот такая запись:

Рисунок 45 – Как должен видеть компилятор ресурсы в файле *.rc
Рисунок 45 – Как должен видеть компилятор ресурсы в файле *.rc

Иносказательно, по адресу «1» лежит путь к файлу с расширением, соответствующим типу данных HBITMAP. Нарушений в записи, которую мы сделали нет, компилятору всё понятно. Но вот будет ли понятно программисту, который откроет наш код? Откуда взялась единица? Понять-то он поймет… Ну да ладно, речь сейчас не об этом, правилам хорошего кода поучимся позже, а пока что продолжим работать с изображениями.

Сделаем так, чтобы переменная FonWindow перестала быть пустой. Как? Вы уже знаете ответ! С помощью функции LoadBitmap(). Переходим в файл библиотеки и вкладываем в переменную HBITMAP FonWindow значение:

Рисунок 46 – Компилятор снова ругается
Рисунок 46 – Компилятор снова ругается

И опять ошибка? Почему? Ведь мы передали в функцию все необходимые параметры? Может быть дело в том, что мы напрямую присвоили «адрес» файлу ресурса, то есть путь к картинке? Нет. Тут дело в другом: компилятор не знает какой процесс (то есть что за программа) вызывает функцию LoadBitmap(). Но почему? Ведь мы указали hInstanceв первом его параметре… И да, и нет! Дело в том, что идентификатора hInstance не существует вне WinMain(), он может существовать только внутри нее:

Рисунок 47 – Фрагмент кода с объявлением hInstance
Рисунок 47 – Фрагмент кода с объявлением hInstance

Проще говоря, вот это:

-55

и вот это:

-56

абсолютно разные значения, причем та hInstance, которую мы передаем в функциюLoadBitmap() даже не объявлена, ее нет. Но ведь тогда мы можем передать это значение в функцию WinMain() извне, а объявить его в файле библиотеки. Можем… Только толку от этого не будет:

Рисунок 48 – Изменяем значение переменной типа HINSTANCE на hInst в файле resources.h
Рисунок 48 – Изменяем значение переменной типа HINSTANCE на hInst в файле resources.h
Рисунок 49 – Передаем глобальную переменную в функцию WinMain()
Рисунок 49 – Передаем глобальную переменную в функцию WinMain()

Ииии… Ничего не получаем в итоге:

Рисунок 50 – Пустое окно
Рисунок 50 – Пустое окно

Все потому, что по форме мы ошибку исправили: параметр в обеих функциях имеет одно и то же имя, переменная задекларирована, а по факту нет. Теперь компилятор видит нечто подобное в WinMain():

Рисунок 51 – То, что мы сделали
Рисунок 51 – То, что мы сделали

Как думаете, что будет использовать компилятор: то, что создастся при начале работы функции WinMain() или то, что мы ему посоветуем использовать? Конечно же первое! Отсюда получаем вывод: извне HINSTANCEв WinMain() лучше не задавать. Но как тогда быть, если нам прям ну очень-очень нужно сделать что-то с HINSTANCEвне WinMain(), скажем в библиотеке ресурсов? Выход есть: можно «вытащить наружу» ту HINSTANCE, что находится в WinMain(). Как? Это не очень сложно, но об этом расскажу позже: мы и так отвлеклись от основной темы статьи. Возвращаем код в изначальное состояние и переходим к иконкам.

Иконки – это маленькие картинки, сохраненные в файл с расширением *.ico.По сути в этом файле лежит не одна картинка, а несколько (одна и та же, но разных размеров). Специальных редакторов для сохранения в *.icoне нужно: конвертировать картинку в этот формат можно онлайн и бесплатно. Разработаем иконку. Лучше всего не использовать многоцветные изображения (картинки) в качестве иконки, потому как они могут быть деформированы в процессе сжатия до необходимых размеров. Но как быть, ведь хочется, чтобы приложение смотрелось солидно? Можно использовать текст. Напомню, мы делаем программку для учета клиентов СТО. Придумаем оригинальное название для приложения. Ну, допустим, «СТО рук». Звучит? По-моему неплохо. Открываем Паинт и сжимаем лист до 64 на 64 пиксел. Далее подключаем фантазию. И та-дам:

Рисунок 52 – Созданная на коленке иконка
Рисунок 52 – Созданная на коленке иконка

Сохраняем файл с расширением *.pngи отправляемся в браузер конвертировать его в .ico.

Теперь, после того, как иконка готова, закидываем ее в директорию проекта и возвращаемся в Dev. Здесь мы должны добавить иконку в ресурсы. Сделать это просто: сначала создаем константу – идентификатор, как мы делали это для картинки. Открываем файл resources.h и придумываем имя для константы. Пусть будет STOicon:

Рисунок 53 – Создаем идентификатор для ресурса – иконки
Рисунок 53 – Создаем идентификатор для ресурса – иконки

Переходим в файл resources.rc и указываем путь к иконке. Для этого используем ключевое слово ICON:

Рисунок 54 – Добавляем иконку в ресурсы
Рисунок 54 – Добавляем иконку в ресурсы

Отлично: полдела сделано! Теперь нужно указать компилятору, что у нашего приложения есть иконка и ее нужно «подцепить» к нему. Чтобы понять как это сделать перейдем в файл main.cpp и посмотрим на строчки с кодом, в которых описаны иконки. Вот они, 37-ая и 38-ая:

Рисунок 55 – Программный код для загрузки иконки из ресурсов
Рисунок 55 – Программный код для загрузки иконки из ресурсов

Что делаем дальше? Думаю Вы и сами догадались: идем к справочнику и ищем там функцию LoadIcon():

Рисунок 56 – Смотрим описание функции LoadIcon()в справочнике (Источник: https://learn.microsoft.com/ru-ru/windows/win32/api/winuser/nf-winuser-loadicona , Дата обращения: 21.02.2025)
Рисунок 56 – Смотрим описание функции LoadIcon()в справочнике (Источник: https://learn.microsoft.com/ru-ru/windows/win32/api/winuser/nf-winuser-loadicona , Дата обращения: 21.02.2025)

Ничего не напоминает? Я про функцию LoadPatternBrush(). Здесь всё то же самое: первый параметр функции – идентификатор нашего окна hInstance, второй – идентификатор ресурса. Правим код:

Рисунок 57 – Правим программный код
Рисунок 57 – Правим программный код

Запускаем компиляцию. И ура, всё получилось:

Рисунок 58 – Результат наших трудов
Рисунок 58 – Результат наших трудов

Теперь перейдем в директорию проекта и проверим изменилась ли иконка у нашего приложения:

Рисунок 59 – Директория проекта
Рисунок 59 – Директория проекта

Всё превосходно: иконка действительно изменилась. Но мне не нравится, что наше приложение называется Project_1. Имя это мы дали ему, когда еще не знали что именно будем разрабатывать, теперь же можно его переименовать. Точнее пересохранить. Идем в Файл ->Сохранить проект как. Назовем приложение STOruk. Сохраняем. После первого же запуска мы получим в директории вот такую мешанину:

Рисунок 60 – Директория проекта
Рисунок 60 – Директория проекта

Удалим все старые файлы с пометкой Project_1.

Теперь возвращаемся в Dev. Исправим заголовок в рамке нашего приложения. Для этого правим код в функции создания окна CreateWindowEx():

Рисунок 61 –Исправляем заголовок приложения
Рисунок 61 –Исправляем заголовок приложения

Компилируем и смотрим что получилось:

Рисунок 62 – Результат работы
Рисунок 62 – Результат работы

Как видим, всё получилось.

Что ж, на этом, пожалуй, закончим данную статью. Мы многое сделали: изучили как создать простое оконное приложение, как менять в нем фон и как создать для него иконку. Тренируйтесь, а я пока что приступлю к следующей статье, в которой мы с Вами рассмотрим как добавлять в приложение простые органы взаимодействия с программой: кнопки, графы для ввода текста и т.п. и как с ними работать.

Надеюсь, что моя статья была для Вас хоть чем-то полезна. Спасибо, что читаете. Удачи в учебе и труде!

К следующей статье цикла >

PS: Ах да, чуть не забыл! Жду в гости на странице моей книги: https://author.today/work/391996 . Надеюсь, она вам понравится!

-72

Теги: программирование, приложение, WinApi, C, C++, оконное приложение, программный код, фон в оконном приложении, как сделать иконку для приложения, самоучитель WinApi