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

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

Здравствуйте, Дорогие друзья! И вот Вы вновь на моем канале. В этой статье мы с Вами продолжим формировать наше оконное приложение для Windows. Напомню, в прошлой статье мы добавили в него четыре текстовых поля для ввода информации, кнопку и пять статических элементов, один из которых использовали для отображения текущей даты. Сегодня мы закончим с интерфейсом приложения и начнем «обучать» его обрабатывать команды от элементов управления. Однако, первое, что мы сделаем, это приведем наш код к более читабельному виду. Говорю я сейчас о функции WinMain(). Посмотрите, что мы в ней наделали: В прошлой статье мы рассмотрели конструкцию switch() – case, расположенную в обработчике команд. Установили, что в некоторых случаях case «отрабатывают» в момент создания окна. Вспомните: мы перекрашивали элементы static в момент их создания. И мы говорили, что любая операция с окном, будь это его создание, изменение его параметров, начальных координат и прочее, сопровождается отправкой в обработчик со

Здравствуйте, Дорогие друзья! И вот Вы вновь на моем канале. В этой статье мы с Вами продолжим формировать наше оконное приложение для Windows. Напомню, в прошлой статье мы добавили в него четыре текстовых поля для ввода информации, кнопку и пять статических элементов, один из которых использовали для отображения текущей даты. Сегодня мы закончим с интерфейсом приложения и начнем «обучать» его обрабатывать команды от элементов управления. Однако, первое, что мы сделаем, это приведем наш код к более читабельному виду. Говорю я сейчас о функции WinMain(). Посмотрите, что мы в ней наделали:

Рисунок 1 – Содержимое файла main.cpp проекта STOruk
Рисунок 1 – Содержимое файла main.cpp проекта STOruk

В прошлой статье мы рассмотрели конструкцию switch() – case, расположенную в обработчике команд. Установили, что в некоторых случаях case «отрабатывают» в момент создания окна. Вспомните: мы перекрашивали элементы static в момент их создания. И мы говорили, что любая операция с окном, будь это его создание, изменение его параметров, начальных координат и прочее, сопровождается отправкой в обработчик сообщения. Если в программе явно не прописан case, это не значит, что по умолчанию его нет. К чему это я: в момент формирования окошка, оно отправляет приложению сообщение с кодом «WM_CREATE». Сообщение это отправляет функция CreateWindow() или CreateWindowEx() перед тем как вернуть значение, то есть прямо перед тем как создать окно. Чем для нас это выгодно? Смотрите: в WinMain() мы создаем собственный класс для главного окна (wc) и формируем на основе параметров, переданных в этот класс главное окно приложения:

Рисунок 2 – Основной код функции WinMain()
Рисунок 2 – Основной код функции WinMain()

В момент своего создания окно hwndотправит приложению сообщение WM_CREATE. По умолчанию процедура WndProc() обработает это сообщение и ничего с нашим приложением не сделает (вернее будет сказать никак его не изменит): окно уже будет создано, а большего в программе и не прописано. А что если мы захотим, чтобы сделала? Для этого достаточно просто явно прописать case WM_CREATE. Но для чего это нам? Всё очень просто: WM_CREATE – единственное сообщение, которое сто процентов хоть раз за цикл жизни приложения будет отправлено процедуре WndProc(), проще говоря, мы пишем ОКОННОЕ приложение, значит в нем будет хотя бы одно окно, при своем формировании это окно отправит сообщение обработчику команд. Будут или нет другие сообщения мы не знаем, а вот, что WM_CREATE будет, знаем точно. А значит, если мы хотим сделать что-то с приложением сразу после создания главного окна, нам достаточно поместить соответствующий код под case WM_CREATE. То есть из WinMain() мы можем вызвать создание одного окна hwnd, а всё остальное откуда угодно. Достаточно вызвать это «откуда угодно» обработчиком команд после того, как hwnd при появлении отправит приложению сообщение WM_CREATE. И говоря «откуда угодно» я имею ввиду в прямом смысле «откуда угодно», хоть из отдельной библиотеки (главное не забыть подключить ее к проекту).

Для простого приложения, такого как наше, выносить код в отдельную библиотеку не обязательно (в нем вряд ли наберется даже тысяча строчек). Чтобы разгрузить WinMain(), перенесем всё, что не связано с созданием главного окна в функции. Функции можно создать любого типа. Можно сделать функцию типа int, которая будет возвращать значение какое-нибудь числовое значение при завершении своей работы, и мы сможем, с помощью этого значения поверять отработала эта функция или нет. Нам не нужно ничего отслеживать: мы и так увидим если что-то пойдет не так. Создадим функцию типа void(), которая в результате своей работы не возвращает никакого значения. Что ж, с типом определились пишем. Нам нужно создать четыре функции: для статиков, для эдитов, для кнопок, ну и для функции вывода локальной даты на экран. К последней мы отнесем и создание окошка, в который осуществляется вывод даты. Мудрить не будем. Назовем функцию для элементов static «StaticWindows()», для edit– «EditWindows()», для кнопок «ButtonWindows()», для индикации даты «DateStaticWindows()». Пусть все эти функции будут типа void. Пролистываем файл main.cpp в конец, отступаем от кода пару строк и создаем определение для функции StaticWindows():

Рисунок 3 – Пишем определение функции StaticWindows()
Рисунок 3 – Пишем определение функции StaticWindows()

В эту функцию из WinMain() сгружаем всё, что связано с элементами static, которые мы создавали:

Рисунок 4 – Переносим часть кода из WinMain() в StaticWindows()
Рисунок 4 – Переносим часть кода из WinMain() в StaticWindows()

Добавляем комментарии к каждой строчке кода:

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

Функция WinMain() после этого станет менее загруженной:

Рисунок 6 – Часть содержимого функции WinMain()
Рисунок 6 – Часть содержимого функции WinMain()

Как говорится, мы на правильном пути. Возвращаемся к создаваемой нами функции StaticWindows(). Кое-что мы в ней не доделали. Если мы вот в таком виде передадим функцию в case WM_CREATE, компилятор выдаст нам ошибку. Почему? Потому как в нашей функции присутствуют две неизвестные ему переменные: hwnd и hInstance (посмотрите на CreateWindow() в этой функции, данные параметры указаны именно в них). Помните, как в первой статье мы экспериментировали с hInstance? Здесь приблизительно то же самое. И, кажется я тогда намекнул, что можно вытащить значение hInstanceиз функции WinMain() и сделать это будет не сложнее, чем создать окно. Поможет нам в этом функция GetModuleHandle(). Найдем ее в справочнике:

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

Казалось бы, при чем тут hInstance? Читаем самый короткий абзац из раздела Параметры:

«Если этот параметр имеет значение NULL, GetModuleHandle возвращает дескриптор, используемый для создания вызывающего процесса (.exeфайла)…».

Проще говоря, с помощью GetModuleHandle() можно узнать дескриптор (идентификатор) модуля (программы проще говоря), который в настоящий момент работает. Достаточно только передать в функцию в качестве параметра имя этого модуля (имя, под которым сохранена программа), ну и что самое главное, если передаем в качестве параметра значение NULL, то функция вернет нам идентификатор того процесса, который ее вызвал. Если не понятно, поясню: у нас есть программа, которую мы пишем. У нее есть свои идентификаторы. Один из них hInstance. В какой-то момент мы вызываем функцию GetModuleHandle() и передаем в нее значение NULL. Функция вернет дескриптор процесса, который ее вызвал, а вызывает ее наша программа STOruk.exe, у которой прописан дескриптор hInstance. Главное теперь вызвать функцию GetModuleHandle() в правильном месте. Можно сделать это прямо в функции StaticWindows(). Отработает она успешно и ошибок не возникнет, НО: так же нам придется поступить и с функцией EditWindows(), и с ButtonWindows() и со всеми последующими. А это не есть хорошо: что подумает о нас программист, который откроет наш код? К тому же переменная hInstance не случайно лежит в WinMain() и не глобализована. То есть у нас сформулировались два конкретных требования: функция GetModuleHandle() не должна создавать глобальную переменную – это первое и функцию GetModuleHandle() мы должны вызвать, по возможности, только один раз.

Лучше всего расположить эту функцию в обработчике команд. Ведь по своей сути обработчик – функция. А как мы знаем, переменные созданные внутри функции не доступны для прочих компонентов программы (локальные переменные). Поэтому размещаем функцию GetModuleHandle() в обработчике команд ПЕРЕД оператором switch(). Почему «перед», думаю, пояснять не нужно: switch() – оператор множественного выбора, внутри него лишнее нам ни к чему. Назовем новую переменную, в которую извлечем идентификатор ID_WMain:

Рисунок 8 – Извлекаем дескриптор экземпляра STOruk.exe
Рисунок 8 – Извлекаем дескриптор экземпляра STOruk.exe

С hInstanceмы вроде бы разобрались. Но не стоит забывать про hwnd. Ведь его мы также должны передать в функцию StaticWindows(), чтобы она могла загрузить его в функции CreateWindow() в качестве параметра hWndParrent. Эту проблему решить просто: нужно просто сделать переменную HWND hwnd глобальной, то есть вынести ее из функции WinMain() в файл resources.h:

Рисунок 9 – Добавляем HWND hwnd в файл resources.h и не забываем удалить это объявление из WinMain()
Рисунок 9 – Добавляем HWND hwnd в файл resources.h и не забываем удалить это объявление из WinMain()

Теперь переменная hwndстала глобальной и к ней можно обратиться из любой точки программы, а не только внутри WinMain(). Функция StaticWindows() почти готова. Осталось только указать параметры, которые нужно в нее передать. Всего этих параметров два: hwnd и hInstance. Назовем параметр для hwnd «hWnd», а параметр для hInstance «hInst». Указываем:

Рисунок 10 – Добавляем параметры в функцию
Рисунок 10 – Добавляем параметры в функцию

Мы создал функцию, но обратиться к ней компилятор не сможет, потому что она не объявлена. Объявлять ее мы будем в resources.h. Переходим в этот файл, пролистываем код, немного отступаем и пишем:

Рисунок 11 – Содержимое файла resources.h
Рисунок 11 – Содержимое файла resources.h

Теперь эту функцию можно вызвать из любой точки программы: в любом case обработчика команд, внутри любой другой функции, в том числе и внутри WinMain(). Мы будем вызывать эту функцию из обработчика команд в ответ на сообщение WM_CREATE, которое отправит обработчику в момент своего создания окно hwnd. Чтобы реализовать задуманное создаем новый кейс внутри оператора switch():

Рисунок 12 – Создаем case WM_CREATE внутри оператора switch() в обработчике команд (строки 10-12)
Рисунок 12 – Создаем case WM_CREATE внутри оператора switch() в обработчике команд (строки 10-12)

Внутри этого caseмы помещаем вызов созданной ранее функции StaticWindows() и передаем ей в качестве параметров hwnd и ID_WMain:

Рисунок 13 – Вызываем функцию StaticWindows() и передаем ей в качестве параметров hwnd и ID_WMain
Рисунок 13 – Вызываем функцию StaticWindows() и передаем ей в качестве параметров hwnd и ID_WMain

Теперь мы можем запустить компиляцию и убедиться, что всё работает как надо (статики с надписями «Фамилия», «Имя» и «Отчество» должны отображаться также, как и до внесения в код правок):

Рисунок 14 – Наше приложение
Рисунок 14 – Наше приложение

Как видим, элементы static с надписями «Фамилия», «Имя», «Отчество» и «VIN-номер» успешно созданы. Перейдем к созданию функций EditWindows() и ButtonWindows(). Также не будем мудрить и как говорится, сваяем их по образу и подобию:

Рисунок 15 – Создаем функции EditWindows(), ButtonWindows() и DateStaticWindows(). В каждую передаем в качестве параметров hWnd и hInst
Рисунок 15 – Создаем функции EditWindows(), ButtonWindows() и DateStaticWindows(). В каждую передаем в качестве параметров hWnd и hInst

Объявляем созданные функции в файле resources.h:

Рисунок 16 – Объявляем функции EditWindows(), ButtonWindows() и DateStaticWindows()
Рисунок 16 – Объявляем функции EditWindows(), ButtonWindows() и DateStaticWindows()

Вызываем функции из case WM_CREATE:

Рисунок 17 - Вызываем созданные функции из case WM_CREATE
Рисунок 17 - Вызываем созданные функции из case WM_CREATE

Запускаем компиляцию:

Рисунок 18 – Наше приложение
Рисунок 18 – Наше приложение

Все элементы на своих местах, значит всё работает хорошо. Посмотрим теперь на функцию WinMain():

Рисунок 19 – Содержимое функции WinMain()
Рисунок 19 – Содержимое функции WinMain()

Теперь в основной функции нет ничего лишнего. Можно переходить к основной теме статьи. А вернее будет сказать «к основным темам».

Во-первых, я обещал, что сегодня мы будем учиться обрабатывать команды от элементов управления программы. Да, постепенно, по мере написания программы, мы будем добавлять в нее новые компоненты, а пока что обойдемся тем, что имеем: для обучения этого будет более чем достаточно. Итак, переходим в файл main.cpp. Начнем мы, пожалуй с эдитов, а конкретно с окошка «Surname». Казалось бы, что можно сделать с текстовым полем, кроме как считать с него текст?.. Да и что с ним может быть не так? Отвечаю, не так в нем вот это:

Рисунок 20 – Наше приложение
Рисунок 20 – Наше приложение

Смотрите: мы нажали на строку левой кнопкой мыши, курсор заморгал и-и-и… всё на этом. Для того, чтобы ввести фамилию клиента нам придется самостоятельно стереть «шаблонное» значение из этой строки. Гораздо удобнее было бы, если бы при нажатии на графу, программа сама делала это за нас. Отлично: мы знаем целых два условия, которые должны быть выполнены, чтобы сформировать команду – щелчок левой кнопкой мыши – это первое, и щелчок этот должен быть выполнен внутри окна «Surname» - это второе условие. Сделать это очень просто. Дело в том, что, когда окно edit получает фокусировку клавиатуры (то есть в нем появляется курсор), оно отправляет главному окну сообщение EN_SETFOCUS. Найдем это сообщение в справочнике:

Рисунок 21 – Смотрим информацию о сообщении EN_SETFOCUS в справочнике (Источник: https://learn.microsoft.com/en-us/windows/win32/controls/en-setfocus , Дата обращения: 06.03.2025)
Рисунок 21 – Смотрим информацию о сообщении EN_SETFOCUS в справочнике (Источник: https://learn.microsoft.com/en-us/windows/win32/controls/en-setfocus , Дата обращения: 06.03.2025)

Первое, на что мы обращаем внимание – это два ранее неизвестных нам слова LOWORD и HIWORDв параметрах wParamсообщения. В LOWORD(младшее слово) содержится идентификатор окошка, которое отправило сообщение EN_SETFOCUS, в HIWORD (старшее слово) содержится код сообщения. Однако нас больше должно интересовать значение, которое будет содержаться в lParam– дескриптор окна edit, то есть его имя HWND. С помощью него мы сможем отследить нажатие левой кнопки мыши в конкретном edit. Также стоит обратить внимание на самый первый абзац на странице в справочнике. Здесь говорится, что EN_SETFOCUS - не самостоятельная команда. Она приходит не напрямую в обработчик, а «внутри» сообщения WM_COMMAND. Что такое WM_COMMAND? Это такое сообщение, которое отправляют главному окну все органы управления приложения при взаимодействии с ними. Например, мы нажали кнопку, она тут же отправит это сообщение своему родительскому окну, а мы сможем указать, что должно сделать приложение после нажатия на эту кнопку. Казалось бы, ничего сложного. Но, сложность определенная есть. Заключается она в том, что уведомления от органов управления передаются в параметре wParam сообщения WM_COMMAND. То есть приблизительно так:

-22

Видите: внутри сообщения как бы вложено еще одно сообщение. А это значит, что для того, чтобы обработать сообщение «завернутое» в WM_COMMAND нам понадобится сначала обработать само уведомление WM_COMMAND, а затем создать switch() внутри его case:

Рисунок 22 – Добавляем WM_COMMAND в обработчик сообщений (строки 11 - 19)
Рисунок 22 – Добавляем WM_COMMAND в обработчик сообщений (строки 11 - 19)

Обратите внимание на switch(): прежде чем закрыть скобку я добавил туда return. Зачем это нужно мы с Вами уже говорили в прошлой статье. Также советую внимательно посмотреть на параметр, с которым будет работать switch(). Это HIWORD(wParam), то есть «вложенные» в WM_COMMAND сообщения. Не будем тянуть: добавляем туда новый кейс. Какой? Case EN_SETFOCUS:

Рисунок 23 – Добавляем в switch() кейса WM_COMMAND case EN_SETFOCUS
Рисунок 23 – Добавляем в switch() кейса WM_COMMAND case EN_SETFOCUS

Теперь возвращаемся немного назад и смотрим на рисунок 21. Чтобы понять от какого окна пришло сообщение, мы должны заглянуть в параметр lParam уведомления EN_SETFOCUS. Проще говоря, нам нужно сравнить параметр lParamсообщения EN_SETFOCUS с дескрипторами окошек, которые могут прислать это сообщение, то есть с HWND всех эдитов. Как это сделать правильно? Использовать switch() мы не можем по той простой причине, что в lParam лежит не простое число, а HWND. Выход прост: мы используем операторы ветвления, то есть if и else. Составим простое условие: если lParam совпадает с, допустим, Surname, то следует выполнить такое-то действие. Добавляем это условие:

Рисунок 24 – Добавляем условие в case EN_SETFOCUS
Рисунок 24 – Добавляем условие в case EN_SETFOCUS

Давайте поясню что написано в строках 11-28 простыми словами. От какого-то окошка главному окну программы приходит сообщение WM_COMMAND. Обработчик «разворацивает» это сообщение как конфету и смотрит что у него внутри. Конкретно он смотрит на старшее слово, которое лежит в wParam. Если это EN_SETFOCUS, то обработчик смотрит на lParamсообщения и понимает, какое окно прислало его ему.

Хотите проверим работает ли наш код? Сделаем так, чтобы при нажатии на окошко Surname выводилось диалоговое окно с оповещением о том, что в обработчик пришло сообщение EN_SETFOCUS. Реализовать подобное нам поможет функция MessageBox(). Давайте найдем ее в справочнике:

Рисунок 25 – Смотрим в справочнике сведения о функции MessageBox() (Источник: https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messagebox , Дата обращения: 06.03.2025)
Рисунок 25 – Смотрим в справочнике сведения о функции MessageBox() (Источник: https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messagebox , Дата обращения: 06.03.2025)

Первый параметр в этой функции – родительское окно. Здесь его указывать не обязательно (можно вписать вместо дескриптора окна «NULL»), так как мы просто выводим информацию на экран.

Второй параметр – текст сообщения. Его мы указываем в кавычках.

Третий параметр – заголовок. Также указываем его в кавычках, так как это строка.

Четвертый параметр – флаг. А вот тут всё намного интереснее, чем в предыдущих пунктах. Давайте пролистаем страницу справочника до списка возможных значений этого параметра:

Рисунок 26 – Возможные значения параметра uType  функции MessageBox()
Рисунок 26 – Возможные значения параметра uType функции MessageBox()

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

Рисунок 27 – Значения Возможные значения параметра uType  функции MessageBox() для установления в диалоговом окне иконки
Рисунок 27 – Значения Возможные значения параметра uType функции MessageBox() для установления в диалоговом окне иконки

Создадим простое окошко с кнопкой ОК, при нажатии на которую, это окно будет закрываться. Этому требованию соответствует значение MB_OK из первой таблицы. Какой бы значок выбрать для нашего сообщения? А какие они, собственно вообще бывают? Смотрите:

-29

Выберем из списка значение MB_ICONINFORMATION. Добавим функцию MessageBox() в скобки условия:

Рисунок 28 – При нажатии на окошко Surname выведется диалоговое окошко
Рисунок 28 – При нажатии на окошко Surname выведется диалоговое окошко

Компилируем, запускается приложение, жмем левой кнопкой мыши на окошко Surname:

Рисунок 29 – Наше приложение
Рисунок 29 – Наше приложение

Как видите, окошко действительно появилось, а это значит обработчик команд прочитал сообщение EN_SETFOCUS и определил, что оно было отправлено именно окошком Surname. Как говорится, поигрались и хватит. Удаляем строчку с функцией MessageBox() из нашего кода. Напомню: мы проделали это всё не для того, чтобы вывести сообщение, а для того, чтобы при нажатии на строку сделать ее пустой. Как этого добиться? Для этого мы можем использовать специальную функцию SetWindowText(), с помощью которой в указанное окошко можно передать текстовую строку. Найддем эту функцию в справочнике:

Рисунок 30 – Смотрим информацию по функции SetWindowText() в справочнике (Источник: https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowtexta, Дата обращения: 06.03.2025)
Рисунок 30 – Смотрим информацию по функции SetWindowText() в справочнике (Источник: https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowtexta, Дата обращения: 06.03.2025)

Как видите, ничего сложного в синтаксисе этой функции нет: первый параметр – это дескриптор окошка, в которое мы будем записывать текст, а второй параметр – сам текст, который мы будем записывать. Переходим в main.cppи на месте строки, которую недавно удалили, пишем: SetWindowText(Surname, «»). То есть окно –Surname, текст – пустая строка:

Рисунок 31 – Добавляем строчку кода в обработчик команд
Рисунок 31 – Добавляем строчку кода в обработчик команд

Компилируем, запускается приложение, жмем левой кнопкой мыши на окошко Surname и оно тут же пустеет:

Рисунок 32 – Наше приложение
Рисунок 32 – Наше приложение

Теперь давайте представим такую ситуацию: мы нажали на графу «Фамилия», шаблонная строка «Иванов» в ней стерлась, но вместо того, чтобы ввести фамилию клиента, мы передумали и решили сначала ввести имя. Когда мы перейдем в графу «Имя», графа «Фамилия» останется пустой. Не порядок! Нужно сделать так, чтобы если окошко Surnameутратит фокусировку клавиатуры, и при этом в нем нет введенного пользователем текста (пустая строка), в него записывалась шаблонная строка. Когда элемент управления теряет фокус клавиатуры, он отправляет главному окну сообщение EN_KILLFOCUS. Найдем это сообщение в справочнике:

Рисунок 33 – Смотрим в справочнике информацию о сообщении EN_KILLFOCUS (Источник: https://learn.microsoft.com/ru-ru/windows/win32/controls/en-killfocus , Дата обращения: 06.03.2025)
Рисунок 33 – Смотрим в справочнике информацию о сообщении EN_KILLFOCUS (Источник: https://learn.microsoft.com/ru-ru/windows/win32/controls/en-killfocus , Дата обращения: 06.03.2025)

То есть параметры те же, что и у EN_SETFOCUS. Добавляем в switch(), расположенный в WM_COMMAND новый case:

Рисунок 34 – Добавляем case EN_KILLFOCUS в switch()
Рисунок 34 – Добавляем case EN_KILLFOCUS в switch()

Также добавляем сюда условие для сравнения lParam с дескриптором Surname, как делали это в case EN_SETFOCUS:

Рисунок 35 – Добавляем условие в case EN_KILLFOCUS
Рисунок 35 – Добавляем условие в case EN_KILLFOCUS

В условии пропишем, что если графа Surnameпустая – вписать в нее шаблонную строку «Иванов».Но как мы поймем, что строка пустая? В этом нам поможет функция GetWindowText(). Найдем ее в справочнике:

Рисунок 36 – Смотрим в справочнике информацию о функции GetWindowText() (Источник: https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowtexta , Дата обращения: 06.03.2025)
Рисунок 36 – Смотрим в справочнике информацию о функции GetWindowText() (Источник: https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowtexta , Дата обращения: 06.03.2025)

Первый параметр тут, как видим, окно из которого мы будем читать текст. Второй параметр – массив (строка), в который мы будем его считывать. Третий, и последний, параметр – максимальный размер буфера. То есть количество символов, которые мы можем считать. Второй параметр говорит нам о том, что перед тем, как вызвать функцию GetWindowText(), нам нужно создать массив char, в который будем считывать строку из окошка. Что ж, создадим массив на 32 символа, думаю, даже на самую длинную фамилию этого хватит. Создаем мы его прямо в обработчике:

Рисунок 37 – Создаем пустой массив для символов типа char
Рисунок 37 – Создаем пустой массив для символов типа char

Теперь мы можем смело вызывать функцию GetWindowText():

Рисунок 38 – Вызываем функцию GetWindowText()
Рисунок 38 – Вызываем функцию GetWindowText()

Проверим, считываются ли данные из окошка Surnameв массив StrSurname. Для этого вызовем диалоговое окно сразу после того, как функция GetWindowText() закончит свою работу:

Рисунок 39 – Вызываем диалоговое окно, которое покажет, что лежит в массиве StrSurname
Рисунок 39 – Вызываем диалоговое окно, которое покажет, что лежит в массиве StrSurname

Компилируем, запускается приложение, кликаем левой кнопкой мыши на окошко Surname, оно становится пустым, вводим туда какой-нибудь текст, допустим «Петров» и кликаем на графу «Имя». Должно появиться диалоговое окно, в котором будет указан считанный из окошка Surnameтекст:

Рисунок 40 – Наше приложение
Рисунок 40 – Наше приложение

Отлично! Теперь мы можем прописать здесь условие, при котором в графу будет возвращаться шаблонная строка. Условие это будет простым: если строка пустая или на первом месте в слове пробел – заменить ее на шаблонную строку с помощью функции SetWindowText(). Проверить совпадают ли две строки можно с помощью функции lstrcmpi(). Найдем ее в справочнике:

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

То есть, если строки не отличаются друг от друга, функция в результате своей работы вернет ноль.

Рисунок 42 – Добавляем в программу условие
Рисунок 42 – Добавляем в программу условие

Обратите внимание: в случае совпадения строк, функция lstrcmpi() возвращает ноль, а оператор ifсработает только тогда, когда в условии появится единица. Поэтому мы инвертируем результат работы функции lstrcmpi(). Компилируем и проверяем как будет работать наша программа:

Рисунок 43 – Наше приложение
Рисунок 43 – Наше приложение

Работает. Но в приложении есть одна существенная недоработка: попробуем ввести в графе «Фамилия», допустим «Петров». Как думаете, что случится, когда мы перейдем в графу «Имя», и вдруг увидим, что опечатались в фамилии и опять нажмем на графу «Фамилия»?

Рисунок 44 – Наше приложение
Рисунок 44 – Наше приложение

Провал! Поле полностью затирается при повторном его нажатии. Не переживайте, исправить это очень просто: достаточно добавить условие в обработчик команд. Что-то вроде «Если фамилия Иванов, только тогда затирать графу». Хммм… А что если у клиента фамилия Иванов? Как быть тогда? Путей решения этой проблемы много. Рассмотрим один из них. Создадим глобальную булевую переменную. Назовем ее, скажем, IsIvanov. Будем отслеживать ввод значения с клавиатуры в графу, и если первый введенный символ «И», а второй «в», присвоим переменной IsIvanovзначение TRUE. После чего мы сможем прописать условие в EN_SETFOCUS, в котором укажем, что если IsIvanov==TRUE или если строка в графе «Фамилия» не «Иванов», строку не затирать. Итак, идем в файл resources.h и создаем там переменную типа bool:

Рисунок 45 – Создаем глобальную переменную IsIvanov и присваиваем ей значение FALSE
Рисунок 45 – Создаем глобальную переменную IsIvanov и присваиваем ей значение FALSE

Возвращаемся в main.cpp, пролистываем код до кейса EN_SETFOCUS и под условием if(lParam == Surname) создаем еще одно условие: если IsIvanov равно FALSEИ если строка равняется «Иванов», либо если строка равняется «Иванов» без других условий, тогда затирать графу. То есть первым делом мы должны считать текст из графы в буфер. Не будем мудрить: просто скопируем все, что относится к этой операции из кейса EN_KILLFOCUS:

Рисунок 46 – Копипастим участок кода из EN_KILLFOCUS в EN_SETFOCUS
Рисунок 46 – Копипастим участок кода из EN_KILLFOCUS в EN_SETFOCUS

Теперь пишем условие:

Рисунок 47 – Добавляем условие в программу
Рисунок 47 – Добавляем условие в программу

Поясняю смысл кода в двадцатой строке: по умолчанию, переменная IsIvanov имеет значение FALSE. Такое же значение возвращает функция lstrcmpi(), если строки, которые она сравнивает, совпадают. Мы инвертируем оба этих значения. То есть всё что находится в фигурных скобках под этим условием сработает только в том случае если переменная IsIvanov будет равна FALSE, а в графе будет фамилия-шаблон «Иванов». Смотрите: если мы вводим другую фамилию, не Иванов, переменная IsIvanov свое значение на TRUE не изменит, а значит условие в EN_SETFOCUS не будет выполняться и строчка в графе «Фамилия» затираться не будет. То же произойдет, если мы введем фамилию «Иванов» с клавиатуры: IsIvanovстанет TRUE, но так как в условии оно отрицается, под оператором if у нас получится значение FALSE и он так же не даст обработать принадлежащий себе код в фигурных скобках. Осталось только прописать условие для изменения значения переменной IsIvanov. Для этого нам придется создать еще один case, привязанный к сообщению, которое отправляет окошко edit главному окну в тот момент, когда в него вводят данные с клавиатуры. Такое сообщение существует: EN_CHANGE. Его edit отправляет главному окну всякий раз, когда пользователь изменяет текст в его рабочей области. Уведомление это также не является «самостоятельным» и попадает в обработчик «внутри» сообщения WM_COMMAND, аналогично EN_SETFOCUS и EN_KILLFOCUS. Создаем этой case:

Рисунок 48 – Добавляем в оператор switch() кейс EN_CHANGE
Рисунок 48 – Добавляем в оператор switch() кейс EN_CHANGE

Здесь мы должны также создать массив char-ов, в который при каждом изменении текста пользователем в графе «Фамилия», будут считываться символы из строки Surname. Если пользователь введет с клавиатуры «Иванов», программа должна присвоить переменной IsIvanovзначение TRUE. Также не забываем добавить проверку на дескриптор окошка, которое отправляет главному окну сообщение:

Рисунок 49 – Прописываем условие в блоке case EN_CHANGE
Рисунок 49 – Прописываем условие в блоке case EN_CHANGE

Проверяем. Теперь, если мы нажмем на графу «Фамилия» впервые, строчка в ней должна затереться. Если введем фамилию, перейдем в другое окошко и вернемся назад, содержимое затираться не должно. Также, если мы введем с клавиатуры фамилию «Иванов», при повторном переходе в графу «Фамилия», строка не должна стираться.

Рисунок 50 – Наше приложение
Рисунок 50 – Наше приложение

Обратите внимание на строку 49. Я не опечатался при написании программного кода. Если мы укажем в качестве второго параметра функции lstrcmpi() значение «Иванов», то программа изменит значение переменной IsIvanov на TRUE в тот момент, когда мы перейдем в другое окно, оставив в Surnameпустую строку (потому как переходе в другое окно с пустой строкой, в нее, если помните, записывается строка-шаблон «Иванов». При этом строка записывается в окошко целиком, а значит, если убрать из строки, находящейся в 49 строчке кода всего одну букву, ошибки не возникнет и при автоматическом вводе в строку слова «Иванов», переменная IsIvanovне изменит свое значение на TRUE).

Теперь сделаем всё описанное для остальных трех граф: «Имя», «Отчество», «VIN-номер». Сложного в этом нет вообще ничего: всё что нужно сделать – это создать две глобальные булевы переменные и скопировать условия в case EN_SETFOCUS, case EN_KILLFOCUS и case EN_CHANGE. Делаем для графы «Имя»:

Рисунок 51 – Создаем две глобальные булевы переменные (строки 20, 21)
Рисунок 51 – Создаем две глобальные булевы переменные (строки 20, 21)
Рисунок 52 – Копипастим код в блоке EN_SETFOCUS
Рисунок 52 – Копипастим код в блоке EN_SETFOCUS
Рисунок 53 – Копипастим код в блоке EN_KILLFOCUS
Рисунок 53 – Копипастим код в блоке EN_KILLFOCUS
Рисунок 54 – Копипастим код в блоке EN_CHANGE
Рисунок 54 – Копипастим код в блоке EN_CHANGE

То же самое делаем для графы «Отчество»:

Рисунок 55 – Копипастим код в блоке EN_SETFOCUS
Рисунок 55 – Копипастим код в блоке EN_SETFOCUS
Рисунок 56 – Копипастим код в блоке EN_KILLFOCUS
Рисунок 56 – Копипастим код в блоке EN_KILLFOCUS
Рисунок 57 – Копипастим код в блоке EN_CHANGE
Рисунок 57 – Копипастим код в блоке EN_CHANGE

Проверяем:

Рисунок 58 – Наше приложение
Рисунок 58 – Наше приложение

Как видите, всё работает как надо. Переходим к строке, в которую пользователь будет вводить VIN-номер автомобиля. Чтобы человек не ошибся при вводе, сделаем так, чтобы при нажатии на поле VINзвездочки в нем не исчезали. Более того, необходимо создать условие, при котором, если пользователь захочет стереть звездочку из этого поля, она будет возвращаться на место. Придумал это я не из вредности (хотя представьте, насколько смешно будет: человек трудится, Backspace жмет, а звездочки обратно лезут и лезут), а для того, чтобы звездочки исчезали в тот момент, когда на их место будет вводиться символ с клавиатуры, то есть для того, чтобы в поле VINвсегда было 17 символов. Если оператор введет меньше, чем 17 символов, в поле останутся звездочки, и система при сохранении подскажет пользователю, что он ошибся.

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

Рисунок 59 – Смотрим в справочнике информацию о сообщении EM_SETSEL (Источник: https://learn.microsoft.com/ru-ru/windows/win32/controls/em-setsel , Дата обращения: 06.03.2025)
Рисунок 59 – Смотрим в справочнике информацию о сообщении EM_SETSEL (Источник: https://learn.microsoft.com/ru-ru/windows/win32/controls/em-setsel , Дата обращения: 06.03.2025)

Выделять мы ничего не будем, поэтому в lParamукажем то же самое значение, что и в wParam, а именно – ноль. Ага… С тем, что отправлять будем разобрались, знаем кому отправлять, а вот каким образом это сделать понятия не имеем. Существует два способа это сделать: отправить сообщение окну, дождаться пока оно его получит и выполнит команду или создать сообщение и поставить его в очередь из других сообщений. Первый способ можно реализовать с помощью функции SendMessage(). Найдем ее в справочнике:

Рисунок 60 – Смотрим в справочнике информацию о функции SendMessage() (Источник: https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-sendmessage , Дата обращения: 06.03.2025)
Рисунок 60 – Смотрим в справочнике информацию о функции SendMessage() (Источник: https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-sendmessage , Дата обращения: 06.03.2025)

Первый параметр функции – окошко, которому мы отправляем сообщение, второй параметр – код сообщения (в нашем случае EM_SETSEL), третий и четвертый параметры – это lParamи wParam(в нашем случае это нули).

Если мы хотим поставить сообщение в очередь, то нужно использовать функцию PostMessage(). Синтаксис у нее точно такой же как у SendMessage(). А теперь немного порассуждаем: что происходит, когда мы жмем на строку VIN? Она захватывает фокус клавиатуры на себя. Мгновенно? Нет. Ведь нужно отправить сообщение окну, получить в ответ другое сообщение (которое, кстати, устанавливает курсор в конец строки) и т.д. Если мы вмешаемся в этот обмен сообщениями, то операция, которую мы хотим выполнить просто-напросто нивелируется последующими сообщениями от главного окна. Если не поняли, поясню: если мы отправим сообщение EM_SETSEL сразу же, как главное окно получит сообщение EN_SETFOCUS, оно не будет иметь никакой значимости, так как после его выполнения выполнятся другие сообщения и курсор переместится в конец графы. То есть в нашем случае использовать функцию SendMessage() бесполезно. Для успешной работы программы нужно использовать функцию PostMessage().

Теперь нужно разобраться в какой момент отправлять это сообщение. Лучше всего делать это сразу после того, как окошко «перехватит» клавиатуру, то есть после нажатия на него левой кнопкой мыши. После того, как все сообщения от главного окна будут обработаны окошком VIN, оно получит наше сообщение с уведомлением EM_SETSEL и успешно его обработает. Добавляем в обработчик соответствующее условие с оператором if:

Рисунок 61 – Добавляем условие в кейс EN_SETFOCUS
Рисунок 61 – Добавляем условие в кейс EN_SETFOCUS

Компилируем и смотрим, получится ли задуманное:

Рисунок 62 – Наше приложение
Рисунок 62 – Наше приложение

Все как мы и задумывали: при нажатии на окошко VIN, курсор перемещается в нем в начало строки. Теперь подумаем, как реализовать удаление звездочек при вводе с клавиатуры в графу новых символов, а также как сделать так, чтобы при удалении символов, звездочки занимали их место. Будем «отталкиваться» от количества символов в строке. Всего их в VINдолжно быть 17. Чтобы их сосчитать, мы должны считать строку из edit VIN в какой-нибудь буфер (это делать мы уже умеем). Затем, мы должны, сравнить количество символов в строке с числом 17 и, если символов в строке меньше, чем 17, то мы должны добавить звездочку в конец строки. Если символов больше, чем 17, мы должны удалить последнюю звездочку из строки. Давайте составим небольшой план действий:

1. Создать буфер, в который будет считываться строка из окошка VIN;

2. Считать в этот буфер строку;

3. Узнать количество символов в буфере и сравнить его с числом 17;

4. Если символов в строке меньше 17 – добавить в конец строки символ «*»;

Если символов в строке больше 17 – удалить из конца строки символ «*».

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

Рисунок 63 – Создаем новую функцию
Рисунок 63 – Создаем новую функцию

Назвал я функцию StarInEdit(), то есть «звездочки в edit». В качестве параметров нам нужно передать в функцию дескриптор окна, с которым мы будем работать и количество звездочек, которое нужно поддерживать постоянным в этом окне. Итак, создаем буфер на 24 символа (этого нам будет «за глаза»). Назовем его StrVIN. Теперь узнаем текущую позицию курсора в окне. Зачем? Чуть позже сами увидите. Для того, чтобы узнать на какой позиции находится курсор, нам нужно отправить окошку edit сообщение EM_GETSEL. Обычно это сообщение используется для того, чтобы узнать область выделения текста в строке (начальный – конечный символ в выделенном тексте), но если выделения в тексте нет, это сообщение вернет нам именно позицию курсора в окошке:

Рисунок 64 – Смотрим в справочнике информацию о сообщении EM_GETSEL
Рисунок 64 – Смотрим в справочнике информацию о сообщении EM_GETSEL

Результат нужно будет записать в интовую переменную. Назовем ее CursorPos. В параметрах wParam и lParamукажем NULL, а позицию курсора считаем через возвращаемое значение:

Рисунок 65 – Считываем позицию курсора через возвращаемое значение сообщения EM_GETSEL
Рисунок 65 – Считываем позицию курсора через возвращаемое значение сообщения EM_GETSEL

Обратите внимание, мы могли указать CursorPosв качестве параметра wParam или lParam. Такой записью я просто сэкономил строчку в коде. Но! Внимательно посмотрите на строчку 275: мы загружаем в переменную CursorPos HIWORD (старшее слово) из возвращаемого функцией значения (на рисунке 64 в разделе «Возвращаемое значение» есть все пояснения). Теперь считываем текст из edit VIN в массив StrVIN:

Рисунок 66 – Считываем текст из окошка VIN в буфер StrVIN
Рисунок 66 – Считываем текст из окошка VIN в буфер StrVIN

Далее, нам нужно будет сосчитать количество символов в строке StrVIN. В этом нам поможет функция lstrlen(). Найдем ее в справочнике:

Рисунок 67 – Смотрим в справочнике информацию о функции lstrlen()
Рисунок 67 – Смотрим в справочнике информацию о функции lstrlen()

Функция возвращает длину строки в символах в виде числа int. Значит нам придется создать еще одну интовую переменную. Назовем ее lenVIN. В нее мы запишем число, которое вернет функция lstrlen() по завершении своей работы. В функцию lstrlen() передадим наш массив, приведенный к типу LPCSTR (в скобках перед именем массива прописываем LPCSTR).

Рисунок 68 – Считаем количество символов в строке StrVIN
Рисунок 68 – Считаем количество символов в строке StrVIN

На самом деле, последнее наше действие – всего лишь перестраховка, потому как массив char– это и так строка. Теперь нужно создать условие с if, в котором прописать: «если значение lenVINбольше 17 – стереть последний символ». Опасаться, что потребуется стереть два символа не нужно, потому как фунция, которую мы создаем, будет вызываться при каждом добавлении или удалении символа в окошке edit, а значит символов там, до вызова нашей функции, не может быть больше 18 и меньше 16 априори. Однако, есть вероятность, что пользователь выделит все символы и сотрет их, как быть тогда? Ведь 1 символ или 0 символов – это гораздо меньше, чем 16. Учтем и это при написании кода: если символов будет 0, то программа заполнит поле семнадцатью звездочками, если символ будет 1, то программа добавит к нему справа 16 звездочек.

Начнем с того случая, когда у нас 16 символов в массиве. Дополнить уже существующий текст в editможно двумя способами. Первый – считать текст, «нарастить» его нужными символами в буфере, а потом использовать функцию SetWindowText() и поместить уже отредактированную строчку обратно в окошко. Второй – выставить требуемую позицию курсора в окошке editи отправить ему специальное сообщение, в котором указать символы, которые нужно добавить в строку. Знаете, чем в нашем случае предпочтителен второй вариант? Курсор уже установлен в позицию! Не понимаете о чем я говорю? Смотрите: пользователь кликнул левой кнопкой мыши по окошку типа edit, оно перехватило на себя клавиатуру. Курсор в нем позиционировался в начало строки. Справа от курсора 17 звездочек (одинаковых символов). При вводе с клавиатуры в окошко любого символа оно будет отправлять процедуре главного окна сообщение (какое именно рассмотрим чуть ниже). Если пользователь нажмет на клавиатуре символ (букву или цифру) символов в окошке станет 18. Например, одна буква и 17 звездочек. Нам нужно, чтобы программа стерла одну звездочку. То есть символом, который вводит пользователь, программа должна заменить стоящую справа от курсора звездочку. Если же пользователь нажмет «Backspace» или «Delete», произойдет удаление символа, то есть в окошке символов станет 16 – программа должна заменить удаленный пользователем символ на звездочку, чтобы их снова стало 17 (символов в строке, а не звездочек). Курсор при этом будет находиться справа от введенного пользователем символа. Поэтому я и говорю, что его позиция нам известна.

Записать текст в поле с текущей позиции курсора можно с помощью сообщения EM_REPLACESEL. Найдем его в справочнике:

Рисунок 69 – Смотрим в справочнике информацию о сообщении EM_REPLACESEL
Рисунок 69 – Смотрим в справочнике информацию о сообщении EM_REPLACESEL

Синтаксис простой: в wParam сообщения передаем TRUE, в lParamпередаем указатель на строку. Мы не будем передавать указатель, а укажем саму строку (ну как строку… один символ).

Пишем условие для случая, когда символов в графе VIN меньше 17, но больше 1. Здесь мы будем добавлять символ «*» справа от курсора:

Рисунок 70 – Добавляем условие для случая, когда пользователь стирает символ из строки
Рисунок 70 – Добавляем условие для случая, когда пользователь стирает символ из строки

Объявляем функцию StarInEdit() в файле resources.h. Возвращаемся в main.cpp и идем в обработчик команд. Вызываем функцию в EN_CHANGE:

Рисунок 71 – Вызываем функцию StarInEdit() в EN_CHANGE
Рисунок 71 – Вызываем функцию StarInEdit() в EN_CHANGE

Компилируем, проверяем, работает ли наша функция:

Рисунок 72 – Наше приложение
Рисунок 72 – Наше приложение

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

Теперь создадим условие для случая, когда символов в строке становится больше 17 (это происходит при вводе в окошко данных с клавиатуры):

Рисунок 73 – Добавляем условие для случая, когда пользователь вводит символ с клавиатуры
Рисунок 73 – Добавляем условие для случая, когда пользователь вводит символ с клавиатуры

Компилируем, проверяем:

Рисунок 74 – Наше приложение
Рисунок 74 – Наше приложение

Идеально! Всё работает так, как и должно. Обратите внимание: если мы хотим заменить один символ на другой или стереть его, то для отправки сообщения используем функцию SendMessage(), потому как эти операции необходимо выполнить без очереди, если же мы переносим курсор на какую-либо позицию, то используем PostMessage(), чтобы сообщение обработалось после всех возможных, иначе курсор «уедет» в конец или в начало строки.

С двумя главными операциями разобрались. Программа не позволит стереть пользователю одну или несколько звездочек, не заменив их символами. Однако, как быть если тот захочет стереть всё и сразу? Я говорю об этом:

Рисунок75 – Стираем весь текст из строки
Рисунок75 – Стираем весь текст из строки

Мы должны предотвратить подобное. Пусть если строка будет полностью затерта, программа вернет в нее все звездочки, а если пользователь заменит все старые символы на один, программа установит справа от него необходимое количество «*». Первую проблему решить просто:

Рисунок 76 – Создаем условие, которое вернет все звездочки в строку, если пользователь их стер
Рисунок 76 – Создаем условие, которое вернет все звездочки в строку, если пользователь их стер

Цикл forя использовал потому что, как уже говорил ранее, мы создаем универсальную функцию для любого окошка edit, а не только для VIN. У for в С++ следующий синтаксис: for(начальное значение; конечное значение; инкремент).

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

Рисунок 77 – Наше приложение
Рисунок 77 – Наше приложение

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

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

Поясняю как теперь будет работать данный блок кода. Раньше под оператором if было условие «если количество символов в строке меньше единицы», мы заменили его на «если количество символов в строке меньше или равно единице». Теперь это условие справедливо не только для полностью пустой строки, но и для того случая, когда в ней присутствует один символ. Далее идет строка с позиционированием курсора. Раньше мы выставляли его в ноль, но теперь нам нужно, чтобы курсор стоял справа от введенного символа, либо уходил в начало строки. В CursorPos считывается позиция курсора сразу после вызова функции StarInEdit(), а значит наш ноль или единица уже будут туда записаны. Далее следует цикл for. Конечной точкой его является условие «QuantStar- lenVIN». QuantStarмы задаем в параметрах функции, когда ее вызываем, в нашем случае это 17. Значение lenVINмы получаем когда считаем количество символов в строке после того, как пользователь нажал что-то на клавиатуре. То есть, если пользователь сотрет все символы в строке, lenVINстанет равна нулю, и тогда программа добавит в окошко 17 звездочек, ну или правильнее будет сказать QuantStarзвездочек. Если же пользователь выделит строку и нажмет букву или цифру, в строке появится один символ, который будет позиционирован по левому краю строки. В этом случае цикл введет в строку QuantStar – 1 звездочек. В нашем случае это 16.

Рисунок 79 – Наше приложение
Рисунок 79 – Наше приложение

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

Рисунок 80 – Вводим символ с клавиатуры в произвольном участке строки
Рисунок 80 – Вводим символ с клавиатуры в произвольном участке строки

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

Рисунок 81 – Модернизируем блок кода
Рисунок 81 – Модернизируем блок кода
Рисунок 82 – Проверяем работоспособность кода
Рисунок 82 – Проверяем работоспособность кода

Осталось последнее: оператор может выделить не всю строку, а только несколько символов, причем в любом месте. Наш код прекрасно нивелирует разницу в стертых звездочках, но символ при этом на положенное место не переносит:

Рисунок 83 – Наше приложение
Рисунок 83 – Наше приложение

Исправить это можно очень просто: основная проблема заключается в том, что данная ситуация не попадает ни под одно из описанных нами в функции StarInEdit() условий, ведь количество символов в строке, с технической точки зрения, не изменяется, то есть не становится ни больше, чем QuantStart, ни меньше. В самой проблеме решение и заключается: достаточно сделать короткое условие, которое переполнит строку (а точнее попросту скорректирует значение переменной lenVIN нужным нам образом) в случае появления в ней в неположенном месте символа:

Рисунок 84 –Модернизируем первое условие
Рисунок 84 –Модернизируем первое условие

Компилируем, проверяем:

Рисунок 85 – Наше приложение
Рисунок 85 – Наше приложение

С этим разобрались. В данной статье нам осталось сделать только одно: рассмотреть как записать в файл информацию из текстовых окошек.

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

-88

Итак, когда мы выполняем какую-либо операцию с окошком, оно отправляет сообщение с кодовым словом и своим дескриптором главному окну. Если говорить о кнопках, то под кодовым словом понимается уведомление BN_CLICKED, в wParam сообщения при этом, они отправляют идентификатор, зарезервированное число, приведенное к типу «HMENU», то есть идентификатор меню. Что это такое мы обсудим с Вами в следующей статье, пока что же, переходим в файл resources.h и создаем там константу. Назовем ее btnSave. Присвоим ей значение 11 (Почему бы и нет? Хоть 100, хоть 1000):

Рисунок 86 – Присваиваем btnSave значение 11
Рисунок 86 – Присваиваем btnSave значение 11

Возвращаемся в main.cpp и находим строчку кода, в которой создавали кнопку SaveBtn. Нам нужно привязать к ней идентификатор. Для этого указываем его в параметре hMenu функции CreateWindow():

Рисунок 87 – Привязываем к кнопке идентификатор меню
Рисунок 87 – Привязываем к кнопке идентификатор меню

Отлично. Теперь при нажатии кнопки в обработчик придет сообщение с идентификатором btnSave. Мы уже научились работать с сообщением WM_COMMAND, с помощью него мы обрабатывали команды EN_SETSEL, EN_GETSEL и EN_CHANGE. Отличие их от HMENUзаключается в том, что HMENUнужно считывать из wParamсообщения целиком (указанные выше уведомления передаются в HIWORD wParam). Это значит, что для обработки HMENU нам нужно создать отдельный switch() в WM_COMMAND:

Рисунок 88 – Создаем новый switch() в WM_COMMAND
Рисунок 88 – Создаем новый switch() в WM_COMMAND

Тут в качестве caseуказываем наш идентификатор:

Рисунок 89 – Добавляем в switch() первый case
Рисунок 89 – Добавляем в switch() первый case

Проверим, работает ли наша кнопка. Пусть при нажатии на нее выводится сообщение «Нажата кнопка SaveBtn»:

Рисунок 90 – Вызываем функцию MessageBox из case btnSave
Рисунок 90 – Вызываем функцию MessageBox из case btnSave

Проверяем:

Рисунок 91 – При нажатии на кнопку выводится сообщение «Нажата кнопка SaveBtn»
Рисунок 91 – При нажатии на кнопку выводится сообщение «Нажата кнопка SaveBtn»

Как Вы могли убедиться, всё работает. Убираем вызов функции MessageBox из case btnSave. На ее месте мы вызовем функцию, которая считает текст из edit Surname и запишет его в файл. Однако, прежде чем вызвать функцию, ее нужно создать. Пролистываем файл до конца, отступаем и пишем… Как бы назвать функцию?.. Пусть будет TextInFile.

Что должна сделать эта функция?

1. Считать из указанного окна текст (Значит, нам понадобится буфер, куда мы поместим считанные символы);

2. Создать файл, в который мы поместим текст;

3. Записать текст в файл.

Считывать данные из edit в массив мы с Вами уже умеем. Для этого используем функцию GetWindowText(). Под считываемую строку создадим массив char-ов. Назовем его Buf. Почему так, думаю пояснять не надо:

Рисунок 92 – Считываем текст из окошка hWnd в массив Buf
Рисунок 92 – Считываем текст из окошка hWnd в массив Buf

Вы не могли не заметить, что я добавил в функцию параметр HWND hWnd. Функции GetWindowText() нужно ведь знать откуда «вытаскивать» строку. Итак, мы считали текст из edit. Теперь нам нужно создать файл. Сделать это можно с помощью функции CreateFile():

Рисунок 93 – Смотрим в справочнике сведения о функции CreateFile() (Источник: https://learn.microsoft.com/ru-ru/windows/win32/api/fileapi/nf-fileapi-createfilea , Дата обращения: 10.03.2025)
Рисунок 93 – Смотрим в справочнике сведения о функции CreateFile() (Источник: https://learn.microsoft.com/ru-ru/windows/win32/api/fileapi/nf-fileapi-createfilea , Дата обращения: 10.03.2025)

«Пробежимся» по параметрам:

1. LPCSTR lpFileName– Имя файла, который нужно создать (или открыть, если он уже создан);

2. DWORD dwDesiredAccess– Действия, которые можно будет производить с файлом. Допустимых значений здесь несколько (обязательно поговорим о них позже), в данный момент мы будем использовать два из них: GENERIC_READ и GENERIC_WRITE, то есть чтение и запись;

3. DWORD dwShareMode – Разрешение операций с файлом (сюда пока что не лезем, укажем ноль);

4. LPSECURITY_ATTRIBUTES lpSecurityAttributes – Указатель на структуру SECURITY_ATTRIBUTES (поступаем с этим параметром также как с предыдущим, потому что если начну расписывать их все, толку будет мало, но объем статьи значительно вырастет, а она и так уже 42 страницы в Ворде занимает!);

5. DWORD dwCreationDisposition– Действие, которое нужно совершить с файлом: открыть, создать новый и т.д. Мы будем использовать значение CREATE_ALWAYS. То есть «всегда создавать» файл перед записью в него данных;

6. DWORD dwFlagsAndAttributes – Атрибуты и флаги. Тут мы указываем каким образом будет сохраняться наш файл и какие операции с ним может совершать система;

7. HANDLE hTemplateFile– Дескриптор файла шаблона.

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

Рисунок 94 – Сохраняем текст из окошка edit в файл
Рисунок 94 – Сохраняем текст из окошка edit в файл

С помощью этого кода мы создаем файл «file.txt», в который мы можем записывать информацию и читать ее оттуда. При этом, при каждом повторном сохранении данный файл будет перезаписываться.

Теперь запишем данные из буфера в созданный нами файл. Чтобы это сделать можно воспользоваться функцией WriteFile(). Найдем ее в справочнике:

Рисунок 95 – Смотрим в справочнике информацию о функции WriteFile()
Рисунок 95 – Смотрим в справочнике информацию о функции WriteFile()

Разбираемся, что у нас здесь в параметрах:

1. HANDLE hFile– хэндл файла, дескриптор. В нашем случае сюда мы запишем «DATA»;

2. LPCVOID lpBuffer– буфер, в котором хранится информация, которую нужно будет записать в файл;

3. DWORD nNumberOfBytesToWrite– количество байт, которые нужно записать в файл;

4. LPDWORD lpNumberOfBytesWritten – указатель на переменную типа DWORD, в которую будет записано количество записанных в файл байтов;

5. LPOVERLAPPED lpOverlapped– указатель на структуру OVERLAPPED. Пока что укажем тут ноль.

Как видите, для записи информации в файл, нам не хватает переменной типа DWORD (пункт 4) и конечно же того значения, которое мы в эту переменную будем записывать. Разберемся сначала со значением. Мы умеем определять количество символов в строке (функция lstrlen()), но нам нужно знать не количество символов, а количество байт. Разве? Так как стандартный char занимает в памяти 1 байт, то вполне можно просто сосчитать количество символов в строке – это и будет количество байт информации. Теперь о переменной типа DWORD: можно сделать ее локальной и создать, скажем в самом начале, наравне с массивом Buf:

Рисунок 96 – Функция для записи информации в файл
Рисунок 96 – Функция для записи информации в файл

Объявляем функцию в resources.h, идем в обработчик и в case btnSave вызываем ее, передавая в качестве параметра дескриптор окошка Surname.

Компилируем, проверяем:

Рисунок 99 – Наше приложение
Рисунок 99 – Наше приложение

Как Вы могли заметить, всё работает правильно. Что ж, на сегодня, пожалуй, хватит. В этой статье мы рассмотрели ну о-о-очень много материала (не во всех источниках есть то, о чем мы здесь говорили). Надеюсь, он будет Вам полезен. В следующей статье мы познакомимся с тем, как создать настоящее меню, как нанести рисунок на кнопку и как сделать так, чтобы курсор, при наведении на определенные элементы управления приобретал вид «кликающей длани». Также мы подробнее поговорим о записи информации в файл и рассмотрим пару способов ее кодирования. А пока что до встречи. Спасибо, что читаете. Удачи в учебе и труде!

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

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

Содержимое файла resources.h:

#define FonWindow 1

#define STOicon 2

#define btnSave 11

HWND hwnd; //Создаем главное окно

HWND Surname; //Окно edit для ввода фамилии

HWND StSurname; //static с поясняющей надписью к окошку Surname

HWND Name; //Окно edit для ввода имени

HWND StName; //static с поясняющей надписью к окошку Name

HWND Patronymic; //Окно edit для ввода отчества

HWND StPatronymic; //static с поясняющей надписью к окошку Patronymic

HWND VIN; //Окно edit для ввода VIN-номера

HWND StVIN; //static с поясняющей надписью к окошку VIN

HWND StDate; //Создаем static для отображения в программе текущей даты

HWND SaveBtn; //кнопка "Сохранить"

bool IsIvanov = FALSE;

bool IsIvan = FALSE;

bool IsIvanovich = FALSE;

void StaticWindows(HWND hWnd, HINSTANCE hInst); //Объявляем функцию StaticWindows()

void EditWindows(HWND hWnd, HINSTANCE hInst); //Объявляем функцию EditWindows()

void ButtonWindows(HWND hWnd, HINSTANCE hInst); //Объявляем функцию ButtonWindows()

void DateStaticWindows(HWND hWnd, HINSTANCE hInst); //Объявляем функцию DateStaticWindows()

void StarInEdit(HWND hWnd, int QuantStar); //Объявляем функцию StarInEdit()

void TextInFile(HWND hWnd); //Объявляем функцию TextInFile()

Содержимое файла resources.rc:

#include "resources.h"

FonWindow BITMAP "fon_auto.bmp"

STOicon ICON "STO.ico"

Содержимое файла main.cpp:

#include <windows.h>

#include "resources.h"

/* This is where all the input to the window goes to */

LRESULT CALLBACK WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam) {

HINSTANCE ID_WMain = GetModuleHandle(NULL);
switch(Message) {

case WM_COMMAND:{

switch(wParam)
{
case btnSave:{
TextInFile(Surname);
break;
}
return 0;
}

switch(HIWORD(wParam))
{

case EN_SETFOCUS:{

//Åñëè ñîîáùåíèå ïðèøëî îò ãðàôû "Ôàìèëèÿ":
if((HWND)lParam == Surname)
{
char StrSurname[32] = "";
GetWindowText(Surname, StrSurname, 32);
if(!IsIvanov && !lstrcmpi(StrSurname, "Èâàíîâ"))
{
SetWindowText(Surname, "");
}

}

//Åñëè ñîîáùåíèå ïðèøëî îò ãðàôû "Èìÿ":
if((HWND)lParam == Name)
{
char StrName[32] = "";
GetWindowText(Name, StrName, 32);
if(!IsIvan && !lstrcmpi(StrName, "Èâàí"))
{
SetWindowText(Name, "");
}

}

//Åñëè ñîîáùåíèå ïðèøëî îò ãðàôû "Îò÷åñòâî":
if((HWND)lParam == Patronymic)
{
char StrPatrname[32] = "";
GetWindowText(Patronymic, StrPatrname, 32);
if(!IsIvanovich && !lstrcmpi(StrPatrname, "Èâàíîâè÷"))
{
SetWindowText(Patronymic, "");
}

}

//Åñëè ñîîáùåíèå ïðèøëî îò ãðàôû "VIN-íîìåð":
if((HWND)lParam == VIN)
{

PostMessage(VIN, EM_SETSEL, 0, 0);

}
break;
}

case EN_KILLFOCUS:{
//Åñëè ñîîáùåíèå ïðèøëî îò ãðàôû "Ôàìèëèÿ":
if((HWND)lParam == Surname)
{
char StrSurname[32] = "";
GetWindowText(Surname, StrSurname, 32);
if (!lstrcmpi(StrSurname, "") || !lstrcmpi(StrSurname, " "))
{
SetWindowText(Surname, "Èâàíîâ");
}
}

//Åñëè ñîîáùåíèå ïðèøëî îò ãðàôû "Èìÿ":
if((HWND)lParam == Name)
{
char StrName[32] = "";
GetWindowText(Name, StrName, 32);
if (!lstrcmpi(StrName, "") || !lstrcmpi(StrName, " "))
{
SetWindowText(Name, "Èâàí");
}
}

//Åñëè ñîîáùåíèå ïðèøëî îò ãðàôû "Îò÷åñòâî":
if((HWND)lParam == Patronymic)
{
char StrPatrname[32] = "";
GetWindowText(Patronymic, StrPatrname, 32);
if (!lstrcmpi(StrPatrname, "") || !lstrcmpi(StrPatrname, " "))
{
SetWindowText(Patronymic, "Èâàíîâè÷");
}
}

break;
}

case EN_CHANGE:{

//Åñëè ñîîáùåíèå ïðèøëî îò ãðàôû "Ôàìèëèÿ":
if((HWND)lParam == Surname)
{
char StrSurname[32] = "";
GetWindowText(Surname, StrSurname, 32);
if(!lstrcmpi(StrSurname, "Èâàíî"))
{
IsIvanov = TRUE;
}
}

//Åñëè ñîîáùåíèå ïðèøëî îò ãðàôû "Èìÿ":
if((HWND)lParam == Name)
{
char StrName[32] = "";
GetWindowText(Name, StrName, 32);
if(!lstrcmpi(StrName, "Èâà"))
{
IsIvan = TRUE;
}
}

//Åñëè ñîîáùåíèå ïðèøëî îò ãðàôû "Îò÷åñòâî":
if((HWND)lParam == Patronymic)
{
char StrPatrname[32] = "";
GetWindowText(Patronymic, StrPatrname, 32);
if(!lstrcmpi(StrPatrname, "Èâàíîâè"))
{
IsIvanovich = TRUE;
}
}

//Åñëè ñîîáùåíèå ïðèøëî îò ãðàôû "VIN":
if((HWND)lParam == VIN)
{
StarInEdit(VIN, 17);
}

break;
}


return 0;
}

break;
}

case WM_CREATE:{

StaticWindows(hwnd, ID_WMain);
EditWindows(hwnd, ID_WMain);

ButtonWindows(hwnd, ID_WMain);

DateStaticWindows(hwnd, ID_WMain);

break;
}


case WM_CTLCOLORSTATIC:{
SetBkMode((HDC)wParam, TRANSPARENT);
return (LRESULT)GetStockObject(NULL_BRUSH);
break;
}



/* Upon destruction, tell the main thread to stop */
case WM_DESTROY: {
PostQuitMessage(0);
break;
}

/* All other messages (a lot of them) are processed using default procedures */
default:
return DefWindowProc(hwnd, Message, wParam, lParam);
}
return 0;

}

/* The 'main' function of Win32 GUI programs: this is where execution starts */

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
WNDCLASSEX wc; /* A properties struct of our window */
MSG msg; /* A temporary location for all messages */

/* zero out the struct and set the stuff we want to modify */
memset(&wc,0,sizeof(wc));
wc.cbSize = sizeof(WNDCLASSEX);
wc.lpfnWndProc = WndProc; /* This is where we will send messages to */
wc.hInstance = hInstance;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);

/* White, COLOR_WINDOW is just a #define for a system color, try Ctrl+Clicking it */
wc.hbrBackground = CreatePatternBrush(LoadBitmap(hInstance, MAKEINTRESOURCE(FonWindow)));
wc.lpszClassName = "WindowClass";
wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(STOicon)); /* Load a standard icon */
wc.hIconSm = LoadIcon(hInstance, MAKEINTRESOURCE(STOicon)); /* use the name "A" to use the project icon */

if(!RegisterClassEx(&wc)) {
MessageBox(NULL, "Window Registration Failed!","Error!",MB_ICONEXCLAMATION|MB_OK);
return 0;
}

hwnd = CreateWindowEx(WS_EX_CLIENTEDGE,"WindowClass","ÑÒÎðóê",WS_VISIBLE|WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, /* x */
CW_USEDEFAULT, /* y */
640, /* width */
480, /* height */
NULL,NULL,hInstance,NULL);

if(hwnd == NULL) {
MessageBox(NULL, "Window Creation Failed!","Error!",MB_ICONEXCLAMATION|MB_OK);
return 0;
}

/*
This is the heart of our program where all input is processed and
sent to WndProc. Note that GetMessage blocks code flow until it receives something, so
this loop will not produce unreasonably high CPU usage
*/
while(GetMessage(&msg, NULL, 0, 0) > 0) { /* If no error is received... */
TranslateMessage(&msg); /* Translate key codes to chars if present */
DispatchMessage(&msg); /* Send it to WndProc */
}
return msg.wParam;

}

void StaticWindows(HWND hWnd, HINSTANCE hInst)

{
//Ñîçäàåì íàäïèñü "Ôàìèëèÿ" ðÿäîì ñ ãðàôîé äëÿ ââîäà ôàìèëèè êëèåíòà:
StSurname = CreateWindowA("static", "Ôàìèëèÿ:", WS_VISIBLE | WS_CHILD | SS_RIGHT, 305, 150, 80, 20, hWnd, NULL, hInst, NULL);
//Ñîçäàåì íàäïèñü "Èìÿ" ðÿäîì ñ ãðàôîé äëÿ ââîäà èìåíè êëèåíòà:
StName = CreateWindowA("static", "Èìÿ:", WS_VISIBLE | WS_CHILD | SS_RIGHT, 305, 175, 80, 20, hWnd, NULL, hInst, NULL);
//Ñîçäàåì íàäïèñü "Îò÷åñòâî" ðÿäîì ñ ãðàôîé äëÿ ââîäà îò÷åñòâà êëèåíòà:
StPatronymic = CreateWindowA("static", "Îò÷åñòâî:", WS_VISIBLE | WS_CHILD | SS_RIGHT, 305, 200, 80, 20, hWnd, NULL, hInst, NULL);
//Ñîçäàåì íàäïèñü "VIN-íîìåð" ðÿäîì ñ ãðàôîé äëÿ ââîäà VIN-íîìåðà àâòîìîáèëÿ:
StVIN = CreateWindowA("static", "VIN-íîìåð:", WS_VISIBLE | WS_CHILD | SS_RIGHT, 305, 225, 80, 20, hWnd, NULL, hInst, NULL);

}

void EditWindows(HWND hWnd, HINSTANCE hInst)

{
//Îêíî äëÿ ââîäà ôàìèëèè êëèåíòà:
Surname = CreateWindowA("edit", "Èâàíîâ", WS_VISIBLE | WS_CHILD, 390, 150, 150, 20, hWnd, NULL, hInst, NULL);
//Îêíî äëÿ ââîäà èìåíè êëèåíòà:

Name = CreateWindowA("edit", "Èâàí", WS_VISIBLE | WS_CHILD, 390, 175, 150, 20, hWnd, NULL, hInst, NULL);

//Îêíî äëÿ ââîäà îò÷åñòâà:

Patronymic = CreateWindowA("edit", "Èâàíîâè÷", WS_VISIBLE | WS_CHILD, 390, 200, 150, 20, hWnd, NULL, hInst, NULL);

//Îêíî äëÿ ââîäà VIN-íîìåðà:

VIN = CreateWindowA("edit", "*****************", WS_VISIBLE | WS_CHILD, 390, 225, 150, 20, hWnd, NULL, hInst, NULL);

}

void ButtonWindows(HWND hWnd, HINSTANCE hInst)

{

//Êíîïêà "Ñîõðàíèòü"
SaveBtn = CreateWindowA("button", "Ñîõðàíèòü", WS_VISIBLE | WS_CHILD, 450, 350, 90, 30, hWnd, (HMENU)btnSave, hInst, NULL);

}

void DateStaticWindows(HWND hWnd, HINSTANCE hInst)

{

SYSTEMTIME sdate; //Ñîçäàåì ïåðåìåííóþ sdate òèïà SYSTEMTIME
GetLocalTime(&sdate); //Ñ÷èòûâàåì äàííûå î äàòå è âðåìåíè â sdate

char* DateSys = new char[32]; //Ñîçäàåì ìàññèâ ñèìâîëîâ

GetDateFormat(LOCALE_CUSTOM_DEFAULT, NULL, &sdate, "dd MMMM yyyy", DateSys, 32); //Ñ÷èòûâàåì äàòó èç ñòðóêòóðû sdate â ìàññèâ DateSys

//Âûâîäèì äàòó èç ìàññèâà DateSys â static StDate

StDate = CreateWindowA("static", DateSys, WS_VISIBLE | WS_CHILD | SS_CENTER, 20, 400, 200, 20, hWnd, NULL, hInst, NULL);

}

void StarInEdit(HWND hWnd, int QuantStar)

{

char StrVIN[24] = "";

int CursorPos = HIWORD(SendMessage(hWnd, EM_GETSEL, NULL, NULL));
GetWindowText(hWnd, StrVIN, 24);
int lenVIN = lstrlen((LPCSTR)StrVIN);

if((lenVIN < QuantStar) && (lenVIN > 1))
{
SendMessage(hWnd, EM_REPLACESEL, TRUE, (LPARAM)"*"); //Âñòàâëÿåì çâåçäî÷êó ñïðàâà îò êóðñîðà
PostMessage(hWnd, EM_SETSEL, CursorPos, CursorPos); //Ñäâèãàåì êóðñîð íà ïðåæíåå ìåñòî

if((StrVIN[CursorPos-2] == '*')) //Åñëè ñëåâà îò ââåäåííîãî ñèìâîëà ñòîèò çâåçäî÷êà
{
lenVIN = QuantStar + 1; //Ôèêòèâíî óâåëè÷èòü äëèíó ñòðîêè
}
}

if(lenVIN > QuantStar)
{
char Litera[2] = ""; //Ñîçäàåì íåáîëüøîé ìàññèâ, êóäà ñêîïèðóåì ââåäåííûé ñèìâîë
if(StrVIN[CursorPos-2] == '*') //Åñëè ñëåâà îò ââåäåííîãî ñèìâîëà ñòîèò çâåçäî÷êà
{
Litera[0] = StrVIN[CursorPos-1]; //Êîïèðóåì ñèìâîë ñòîÿùèé ñëåâà îò êóðñîðà
SendMessage(hWnd, EM_SETSEL, CursorPos-1, CursorPos); //Âûäåëÿåì ñèìâîë, ñòîÿùèé ñëåâà îò êóðñîðà
SendMessage(hWnd, EM_REPLACESEL, TRUE, (LPARAM)""); //Çàìåíÿåì âûäåëåííûé ñèìâîë íà ïóñòóþ ñòðîêó (ñòèðàåì åãî)

if(StrVIN[0] != '*') //Åñëè ïåðâûé ñèìâîë â ñòðîêå íå çâåçäî÷êà

{
int k=1; //Ñîçäàåì ëîêàëüíóþ èíòîâóþ ïåðåìåííóþ
while(StrVIN[k] != '*') //Ïîêà k-òûé ñèìâîë â ñòðîêå íå ñòàíåò çâåçäî÷êîé
{
k++; //Èíêðåìåíòèðîâàòü k
}
SendMessage(hWnd, EM_SETSEL, k, k); //Óñòàíàâëèâàåì êóðñîð ñïðàâà îò ââåäåííûõ ðàíåå ñèìâîëîâ
SendMessage(hWnd, EM_REPLACESEL, TRUE, (LPARAM)Litera); //Çàìåíÿåì âûäåëåííûé ñèìâîë íà ñîõðàíåííûé ñèìâîë
CursorPos = k+1; //Ñþäà ïåðåíåñåòñÿ êóðñîð ïîñëå òîãî, êàê ñîòðåòñÿ ïîñëåíèé ñèìâîë â ñòðîêå
}
if(StrVIN[0] == '*') //Åñëè ïåðâûé ñèìâîë â ñòðîêå çâåçäî÷êà
{
SendMessage(hWnd, EM_SETSEL, 0, 0); //Ïåðåíîñèì êóðñîð â íà÷àëî ñòðîêè
SendMessage(hWnd, EM_REPLACESEL, TRUE, (LPARAM)Litera); //Çàìåíÿåì âûäåëåííûé ñèìâîë íà ñîõðàíåííûé ñèìâîë
CursorPos = 1; //Ñþäà ïåðåíåñåòñÿ êóðñîð ïîñëå òîãî, êàê ñîòðåòñÿ ïîñëåíèé ñèìâîë â ñòðîêå
}
}
SendMessage(hWnd, EM_SETSEL, QuantStar, QuantStar+1); //Âûäåëÿåì ñèìâîë, ñòîÿùèé â êîíöå ñòðîêè
SendMessage(hWnd, EM_REPLACESEL, TRUE, (LPARAM)""); //Çàìåíÿåì âûäåëåííûé ñèìâîë íà ïóñòóþ ñòðîêó (ñòèðàåì åãî)
PostMessage(hWnd, EM_SETSEL, CursorPos, CursorPos); //Ñäâèãàåì êóðñîð íà ïðåæíåå ìåñòî

}


if(lenVIN <= 1) //Åñëè â ñòðîêå òîëüêî îäèí ñèìâîë èëè èõ âîâñå íåò
{
PostMessage(hWnd, EM_SETSEL, CursorPos, CursorPos); //Ñäâèãàåì êóðñîð â íà÷àëî ñòðîêè èëè ñïðàâà îò ââåäåííîãî ñèìâîëà
for(int i=0; i < (QuantStar - lenVIN); i++)
{
SendMessage(hWnd, EM_REPLACESEL, TRUE, (LPARAM)"*"); //Âñòàâëÿåì íåîáõîäèìîå êîëè÷åñòâî çâåçäî÷åê ñïðàâà îò êóðñîðà
}

PostMessage(hWnd, EM_SETSEL, CursorPos, CursorPos); //Ñäâèãàåì êóðñîð â íà÷àëî ñòðîêè
}

}

void TextInFile(HWND hWnd)

{
char Buf[32] = "";
DWORD dwData;
GetWindowText(hWnd, Buf, 32);
HANDLE DATA = CreateFile("file.txt", GENERIC_READ | GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
WriteFile(DATA, Buf, lstrlen(Buf), &dwData, 0);

}