Создание окна
Из этой статьи вы узнаете, как создать и отобразить окно.
Классы Окон
Класс window определяет набор поведений, которые могут быть общими для нескольких окон. Например, в группе кнопок каждая кнопка имеет аналогичное поведение, когда пользователь нажимает кнопку. Конечно, кнопки не полностью идентичны. Каждая кнопка отображает собственную текстовую строку и собственные экранные координаты. Данные, уникальные для каждого окна, называются данными экземпляра.
Каждое окно должно быть связано с классом window, даже если программа когда-либо создает только один экземпляр этого класса. Оконный класс не является классом в смысле C++. Скорее, это структура данных, используемая внутренне операционной системой. Классы окон регистрируются в системе во время выполнения. Чтобы зарегистрировать новый класс окна, заполните структуру WNDCLASS :
// Register the window class. const wchar_t CLASS_NAME[] = L"Sample Window Class"; WNDCLASS wc = < >; wc.lpfnWndProc = WindowProc; wc.hInstance = hInstance; wc.lpszClassName = CLASS_NAME;
Необходимо задать следующие элементы структуры:
- lpfnWndProc — это указатель на определяемую приложением функцию, называемую процедурой окна или оконным процессом. Процедура окна определяет большую часть поведения окна. На данный момент это значение представляет собой прямое объявление функции. Дополнительные сведения см. в статье Написание процедуры Window.
- hInstance — это дескриптор экземпляра приложения. Получите это значение из параметра hInstance объекта wWinMain .
- lpszClassName — это строка, идентифицирующая класс окна.
Имена классов являются локальными для текущего процесса, поэтому имя должно быть уникальным только в рамках процесса. Однако стандартные элементы управления Windows также имеют классы. Если вы используете любой из этих элементов управления, необходимо выбрать имена классов, которые не конфликтуют с именами классов элементов управления. Например, класс window для элемента управления button называется Button.
Структура WNDCLASS содержит другие члены, которые здесь не показаны. Вы можете задать для них нулевое значение, как показано в этом примере, или заполнить их. Дополнительные сведения см. в разделе WNDCLASS.
Затем передайте адрес структуры WNDCLASS в функцию RegisterClass . Эта функция регистрирует класс window в операционной системе.
RegisterClass(&wc);
Создание окна
Чтобы создать новый экземпляр окна, вызовите функцию CreateWindowEx :
HWND hwnd = CreateWindowEx( 0, // Optional window styles. CLASS_NAME, // Window class L"Learn to Program Windows", // Window text WS_OVERLAPPEDWINDOW, // Window style // Size and position CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, // Parent window NULL, // Menu hInstance, // Instance handle NULL // Additional application data ); if (hwnd == NULL)
Подробные описания параметров см. в разделе CreateWindowEx. Вот краткая сводка:
- Первый параметр позволяет указать некоторые необязательные варианты поведения для окна, например прозрачные окна. Задайте для этого параметра нулевое значение для поведения по умолчанию.
- CLASS_NAME — это имя класса окна. Это имя определяет тип создаваемого окна.
- Текст окна используется различными способами различными типами окон. Если в окне есть строка заголовка, текст отображается в строке заголовка.
- Стиль окна — это набор флагов, определяющих внешний вид окна. Константная WS_OVERLAPPEDWINDOW на самом деле представляет собой несколько флагов в сочетании с побитовой OR . Вместе эти флаги предоставляют окна заголовок окна, границу, системное меню и кнопки Свернуть и Развернуть . Этот набор флагов является наиболее распространенным стилем для окна приложения верхнего уровня.
- Для положения и размера константная CW_USEDEFAULT означает использование значений по умолчанию.
- Следующий параметр задает родительское окно или окно владельца для нового окна. Задайте родительский параметр if для создания дочернего окна. Для окна верхнего уровня задайте для этого значения значение NULL .
- Для окна приложения следующий параметр определяет меню для окна. В этом примере меню не используется, поэтому значение равно NULL .
- hInstance — это дескриптор экземпляра, описанный выше. См . раздел WinMain: точка входа приложения.
- Последний параметр является указателем на произвольные данные типа void* . Это значение можно использовать для передачи структуры данных в процедуру окна. Один из возможных способов использования этого параметра см. в разделе Управление состоянием приложения.
CreateWindowEx возвращает дескриптор новому окну или ноль в случае сбоя функции. Чтобы отобразить окно, то есть сделать окно видимым, передайте дескриптор окна в функцию ShowWindow :
ShowWindow(hwnd, nCmdShow);
Параметр hwnd — это дескриптор окна, возвращаемый командой CreateWindowEx. Параметр nCmdShow можно использовать для свернуть или развернуть окно. Операционная система передает это значение программе через функцию wWinMain .
Ниже приведен полный код для создания окна. Помните, что WindowProc по-прежнему представляет собой только прямое объявление функции.
// Register the window class. const wchar_t CLASS_NAME[] = L"Sample Window Class"; WNDCLASS wc = < >; wc.lpfnWndProc = WindowProc; wc.hInstance = hInstance; wc.lpszClassName = CLASS_NAME; RegisterClass(&wc); // Create the window. HWND hwnd = CreateWindowEx( 0, // Optional window styles. CLASS_NAME, // Window class L"Learn to Program Windows", // Window text WS_OVERLAPPEDWINDOW, // Window style // Size and position CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, // Parent window NULL, // Menu hInstance, // Instance handle NULL // Additional application data ); if (hwnd == NULL) < return 0; >ShowWindow(hwnd, nCmdShow);
Поздравляем, вы создали окно!
Сейчас окно не содержит никакого содержимого и не взаимодействует с пользователем. В реальном графическом приложении окно будет реагировать на события пользователя и операционной системы. В следующем разделе описывается, как сообщения окна обеспечивают такого рода интерактивность.
См. также раздел
Перейдите к оконным сообщениям , чтобы продолжить этот модуль.
Как создать окно на чистом Си используя только стандартную библиотеку по стандарту?
Всем добрый день. Как можно создать свое окно на голом Си? Возможность такая должна быль, поскольку все сторонние библиотеки с графикой написаны именно на Си. Про windows.h и другие инструменты не писать, хочу своими руками попробовать сделать это.
Отслеживать
задан 25 июн 2020 в 12:25
430 3 3 серебряных знака 10 10 бронзовых знаков
Никак. Нет такого понятия в стандартной библиотеке С++.
25 июн 2020 в 12:28
Все сторонние библиотеки, да и стандартная библиотека тоже, задействуют системеные API, в т.ч. из windows.h Никаких средств работы с GUI в стандартной библиотеке нет.
25 июн 2020 в 12:28
4 ответа 4
Сортировка: Сброс на вариант по умолчанию
Никак не создать, в стандартной библиотеке нет таких функций и в ближайшее время не будет. Уже было предложение по добавлению 2D-графики в C++ . Историю предложения и рассуждения, почему ничего вышло, можно почитать тут.
Отслеживать
ответ дан 25 июн 2020 в 12:47
181 2 2 серебряных знака 23 23 бронзовых знака
@avp нет, это не стандартная библиотека
25 июн 2020 в 12:58
@avp так POSIX это не ISO C стандарт
25 июн 2020 в 13:03
@avp спрашивают же на голом Си, а не «с использованием любого стандарта»
25 июн 2020 в 13:04
@avp «Стандартная библиотека С/С++» означает набор функций/классов/. из хедеров, описанных в стандартах С/С++. (Хотя это и называется «библиотека», это не обязательно ровно один целый .a / .so / .dll файл.) Если в libc входит что-то еще, не описанное стандартом С, то это «что-то» — уже не часть стандартной библиотеки.
25 июн 2020 в 13:18
@avp Не понимаю, где я такое сказал? Я просто сказал, что сокеты из libc не являются частью «стандартной библиотеки», потому что не описаны в стандартах C/C++. Я не говорил, что поэтому их не стоит использовать.
25 июн 2020 в 13:53
Ну, если Вы используете консольный ввод-вывод в окно ДОС, то можете создать в нём своё собственное окошко с помощью символов псевдографики. Правда, я так поступал ещё в прошлом тысячелетии примерно так:
void Displays::Interface(float snr) < int i; clrscr(); window(10,8,71,19); /* координаты углов окна в виде кол,стр,кол,стр */ textbackground(BLUE); /* цвет фона */ textcolor(WHITE); /* цвет символов */ clrscr(); cprintf("г=============== Моделирование системы связи ===============¬\r\n"); cprintf("¦ ПАРАМЕТРЫ КАНАЛА ¦\r\n"); cprintf("¦ Объём входного алфавита кода: ¦\r\n"); cprintf("¦ Отношение сигнал-шум [дБ]: ¦\r\n"); cprintf("¦ Вид Модуляции: ¦\r\n"); cprintf("¦ Код: ¦\r\n"); cprintf("¦ ¦\r\n"); cprintf("¦ РЕЗУЛЬТАТ НАБЛЮДЕНИЯ ¦\r\n"); cprintf("¦ Передано символов: ¦\r\n"); cprintf("¦ Поступило ошибок на вход декодера: ¦\r\n"); cprintf("¦ Обнаружено ошибок на выходе декодера: ¦\r\n"); cprintf("L===========================================================-"); gotoxy(46,3); cout
Этому фрагменту кода более 30 лет и на писан он для окна ДОС ещё в Borland C++ Builder 3.1
Отслеживать
ответ дан 25 июн 2020 в 13:52
347 3 3 серебряных знака 13 13 бронзовых знаков
window(10,8,71,19); это точно стандартная функция?
25 июн 2020 в 14:00
Тут вообще ни одной стандартной функции. (¬‿¬) Окошечки рисовать можно, но на голом cout / printf .
25 июн 2020 в 14:03
Тут все функции из conio.h , который не только не был стандартным, но и который сейчас почти искоренили и в современных компиляторах этих функций нет. А ещё псевдографика тогда была лучше - есть же нормальные символы линий, в не вот эти палочки.
1 июл 2020 в 9:19
В стандартной библиотеки Си нет возможности создавать окна , так как это функции ОС. Но используя библиотеку GTK можно создавать окна на чистом Си.
Отслеживать
ответ дан 26 июн 2020 в 3:40
3,030 1 1 золотой знак 11 11 серебряных знаков 20 20 бронзовых знаков
Например, используя socket API (вполне стандартно находится в libc) организовать взаимодействие с X-window сервером прямо на X-протоколе.
По сути таким образом вы будете делать свою раелизацию библиотеки X11.
Если отказаться от пуризма (в смысле, что есть стандартная библиотека), то на практике есть интересный способ (что-то забылся, когда писал ответ, хотя я его как-то реально использовал) -- это запустить стороннее приложение, например, wish и организовать с ним "диалог" по паре пайпов.
Пошаговое руководство. Создание традиционного классического приложения Windows (C++)
В этом пошаговом руководстве показано, как создать традиционное классическое приложение Windows в Visual Studio. Приложение, которое вы создаете, использует API Windows для отображения "Hello, Windows desktop!" в окне. Код, который вы разрабатываете в этом пошаговом руководстве, можно использовать в качестве шаблона для создания классических приложений Windows.
API Windows (также известный как API Win32, КЛАССИЧЕСКИЙ API Windows и Классический API Windows) — это платформа на основе языка C для создания приложений Windows. Он использовался для создания приложений Windows на протяжении десятилетий. Более сложные и простые платформы программ были созданы на основе API Windows. Например, MFC, ATL, платформы .NET. Даже самый современный код среда выполнения Windows для приложений UWP и Store, написанных в C++/WinRT, использует API Windows в нижней части. Дополнительные сведения об API Windows см. в разделе "Индекс API Windows".
В разделе "Сборка кода " в конце этого документа показан полный код. В этом пошаговом руководстве рассматриваются различные фрагменты кода, которые входят в приложение Windows, но вы не будете кодировать по мере использования, так как некоторые сведения опущены в фрагментах кода, чтобы сосредоточиться на наиболее важных частях. Вы можете скопировать полный код и вставить его в проект в конце.
Необходимые компоненты

- Компьютер под управлением Microsoft Windows 7 или более поздних версий. Мы рекомендуем Windows 11 или более поздней версии для оптимальной разработки.
- копия Visual Studio. Сведения о скачивании и установке Visual Studio см. в этой статье. Когда вы запускаете установщик, убедитесь, что установлена рабочая нагрузка Разработка классических приложений на C++. Не беспокойтесь, если вы не установили эту рабочую нагрузку при установке Visual Studio. Вы можете снова запустить установщик и установить ее сейчас.
- Базовое понимание использования интегрированной среды разработки Visual Studio. Если вы уже использовали классические приложения для Windows, вы, вероятно, справитесь. Общие сведения см. в обзоре возможностей интегрированной среды разработки Visual Studio.
- Основные навыки владения языком C++. Не волнуйтесь, мы не будем делать ничего сложного.
Создание классического проекта Windows
Выполните следующие действия, чтобы создать первый классический проект Windows. Запишите в начале этого пошагового руководства полный код доступен в разделе "Сборка кода" в конце пошагового руководства. Следуйте инструкциям по созданию проекта, но удерживайте вставку следующих разделов кода до конца, когда будет представлен полный код приложения. Некоторые сведения опущены в фрагментах кода, чтобы сосредоточиться на наиболее важных частях. Вы можете скопировать полный код и вставить его в проект в конце.
Чтобы упростить объяснение. Чтобы ознакомиться с документацией по предпочтительной версии Visual Studio, используйте селектор Версия. Он расположен в верхней части оглавление на этой странице.
Создание классического проекта Windows в Visual Studio
- В главном меню выберите Файл >Создать >Проект, чтобы открыть диалоговое окно Создание проекта.
- В верхней части диалогового окна задайте для языка C ++, установите для платформы значение "Платформа" в Windows и задайте для типа Project значениеDesktop.
- Из отфильтрованного списка типов проектов выберите мастер рабочего стола Windows и нажмите кнопку "Далее". На следующей странице введите имя проекта, например DesktopApp.
- Нажмите кнопку Создать, чтобы создать проект.
- Откроется диалоговое окно "Проект рабочего стола Windows". В раскрывающемся списке "Тип приложения" выберите классическое приложение (.exe). Так как мы делаем приложение Windows, выбор консольного приложения приводит к созданию проекта, который не будет строиться с учетом кода, который мы будем использовать. Затем в разделе "Дополнительные параметры" выберите "Пустой проект". Нажмите кнопку ОК, чтобы создать проект.
- В Обозреватель решений щелкните правой кнопкой мыши проект DesktopApp, выберите "Добавить" и выберите "Создать элемент".
Анимация показывает правой кнопкой мыши имя проекта в Обозреватель решений, выбрав "Добавить" в появившемся меню и выбрав "Создать элемент".

Теперь проект создается, и исходный файл открывается в редакторе.
Создание классического проекта Windows в Visual Studio 2017

- В меню Файл выберите команду Создать, а затем пункт Проект.
- В диалоговом окне "Создать проект" в левой области разверните узел "Установленный>Visual C++", а затем выберите "Рабочий стол Windows". В средней области выберите мастер рабочего стола Windows. В поле "Имя" введите имя проекта, например DesktopApp. Выберите OK.
- В диалоговом окне "Классический проект Windows" в разделе "Тип приложения" выберите приложение Windows (.exe). В поле Дополнительные параметрывыберите Пустой проект. Убедитесь, что предварительно скомпилированные заголовки не выбраны. Нажмите кнопку ОК, чтобы создать проект.
- В Обозреватель решений щелкните правой кнопкой мыши проект DesktopApp, выберите "Добавить" и выберите "Создать элемент".
Анимация показывает правой кнопкой мыши имя проекта в Обозреватель решений, выбрав "Добавить" в появившемся меню, а затем выберите новый элемент.
введите имя файла, например HelloWindowsDesktop.cpp. Нажмите кнопку Добавить. Visual C plus plus is selected on the left and the C plus plus File option is highlighted." />
Теперь проект создается, и исходный файл открывается в редакторе.
Создание классического проекта Windows в Visual Studio 2015
- В меню Файл выберите команду Создать, а затем пункт Проект.
- В диалоговом окне "Создать проект" в левой области разверните узел "Установленные>шаблоны >Visual C++", а затем выберите Win32. В средней области выберите шаблон Проект Win32. В поле "Имя" введите имя проекта, например DesktopApp. Выберите OK.

- На странице "Обзор" мастера приложений Win32 нажмите кнопку "Далее".

- На странице "Приложение Параметры" в разделе "Тип приложения" выберите приложение Windows. В разделе "Дополнительные параметры" не проверка заголовок предварительной компиляции, а затем выберите "Пустой проект". Чтобы создать проект, нажмите кнопку Готово.
- В Обозреватель решений щелкните правой кнопкой мыши проект DesktopApp, выберите "Добавить" и выберите "Создать элемент".
Анимация показывает правой кнопкой мыши имя проекта в Обозреватель решений, выбрав "Добавить" в появившемся меню и выбрав "Создать элемент".
введите имя файла, например HelloWindowsDesktop.cpp. Нажмите кнопку Добавить. Visual C plus plus selected and the C plus plus File option highlighted." />
Теперь проект создается, и исходный файл открывается в редакторе.
Код
Далее вы узнаете, как создать код для классического приложения Windows в Visual Studio.
Где код запускается в классическом приложении Windows
- Так же, как каждое приложение C и приложение C++ должны иметь main функцию в качестве отправной точки, каждое классическое приложение Windows должно иметь WinMain функцию. WinMain имеет следующий синтаксис:
int WINAPI WinMain( _In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow );
Сведения о параметрах и возвращаемых значениях этой функции см. в статье "Точка входа WinMain".
Примечание. Что такое все эти дополнительные слова, например WINAPI , или , или CALLBACK , или HINSTANCE _In_ ? Традиционный API Windows использует макросы typedefs и препроцессоров для абстрагирования некоторых сведений о типах и коде для конкретной платформы, таких как соглашения о вызовах, __declspec объявлениях и pragmas компилятора. В Visual Studio вы можете использовать функцию быстрого сведений IntelliSense, чтобы узнать, что определяют эти типдефы и макросы. Наведите указатель мыши на слово, интересующее вас, или выберите его и нажмите клавиши CTRL K, CTRL++ I для небольшого всплывающего окна, содержащего определение. Дополнительные сведения см. в разделе Using IntelliSense. Параметры и типы возвращаемых значений часто используют заметки SAL для перехвата ошибок программирования. Дополнительные сведения см. в статье "Использование заметок SAL для уменьшения дефектов кода C/C++".
#include #include
LRESULT CALLBACK WndProc( _In_ HWND hWnd, _In_ UINT message, _In_ WPARAM wParam, _In_ LPARAM lParam );
Добавление функций в функцию WinMain
- WinMain В функции необходимо записать некоторые основные сведения о главном окне. Это можно сделать, заполнив структуру типа WNDCLASSEX . Структура содержит сведения о окне, таком как значок приложения, цвет фона окна, имя, отображаемое в строке заголовка, помимо прочего. Важно отметить, что он содержит указатель функции на процедуру окна, которая обрабатывает сообщения, которые Windows отправляет в приложение. В следующем примере показана типичная WNDCLASSEX структура:
WNDCLASSEX wcex; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = LoadIcon(wcex.hInstance, IDI_APPLICATION); wcex.hCursor = LoadCursor(NULL, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wcex.lpszMenuName = NULL; wcex.lpszClassName = szWindowClass; wcex.hIconSm = LoadIcon(wcex.hInstance, IDI_APPLICATION);
if (!RegisterClassEx(&wcex))
static TCHAR szWindowClass[] = _T("DesktopApp"); static TCHAR szTitle[] = _T("Windows Desktop Guided Tour Application"); // The parameters to CreateWindowEx explained: // WS_EX_OVERLAPPEDWINDOW : An optional extended window style. // szWindowClass: the name of the application // szTitle: the text that appears in the title bar // WS_OVERLAPPEDWINDOW: the type of window to create // CW_USEDEFAULT, CW_USEDEFAULT: initial position (x, y) // 500, 100: initial size (width, length) // NULL: the parent of this window // NULL: this application does not have a menu bar // hInstance: the first parameter from WinMain // NULL: not used in this application HWND hWnd = CreateWindowEx( WS_EX_OVERLAPPEDWINDOW, szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 500, 100, NULL, NULL, hInstance, NULL ); if (!hWnd)
// The parameters to ShowWindow explained: // hWnd: the value returned from CreateWindow // nCmdShow: the fourth parameter from WinMain ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd);
MSG msg; while (GetMessage(&msg, NULL, 0, 0)) < TranslateMessage(&msg); DispatchMessage(&msg); >return (int) msg.wParam;
Дополнительные сведения о структурах и функциях в цикле сообщений см. в разделе MSG , GetMessage TranslateMessage и DispatchMessage . Базовая WinMain функция, которая создает главное окно приложения и прослушивает сообщения, которые Windows отправляет приложение, будут выглядеть следующим образом:
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) < WNDCLASSEX wcex; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = LoadIcon(wcex.hInstance, IDI_APPLICATION); wcex.hCursor = LoadCursor(NULL, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wcex.lpszMenuName = NULL; wcex.lpszClassName = szWindowClass; wcex.hIconSm = LoadIcon(wcex.hInstance, IDI_APPLICATION); if (!RegisterClassEx(&wcex)) < MessageBox(NULL, _T("Call to RegisterClassEx failed!"), _T("Windows Desktop Guided Tour"), NULL); return 1; >// Store instance handle in our global variable hInst = hInstance; // The parameters to CreateWindowEx explained: // WS_EX_OVERLAPPEDWINDOW : An optional extended window style. // szWindowClass: the name of the application // szTitle: the text that appears in the title bar // WS_OVERLAPPEDWINDOW: the type of window to create // CW_USEDEFAULT, CW_USEDEFAULT: initial position (x, y) // 500, 100: initial size (width, length) // NULL: the parent of this window // NULL: this application dows not have a menu bar // hInstance: the first parameter from WinMain // NULL: not used in this application HWND hWnd = CreateWindowEx( WS_EX_OVERLAPPEDWINDOW, szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 500, 100, NULL, NULL, hInstance, NULL ); if (!hWnd) < MessageBox(NULL, _T("Call to CreateWindow failed!"), _T("Windows Desktop Guided Tour"), NULL); return 1; >// The parameters to ShowWindow explained: // hWnd: the value returned from CreateWindow // nCmdShow: the fourth parameter from WinMain ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); // Main message loop: MSG msg; while (GetMessage(&msg, NULL, 0, 0)) < TranslateMessage(&msg); DispatchMessage(&msg); >return (int) msg.wParam; >
Обработка сообщений в WndProc функции
- Для обработки сообщений, получаемых приложением, вы реализуете инструкцию switch в WndProc функции. Важное сообщение для обработки . WM_PAINT Приложение получает WM_PAINT сообщение, когда необходимо обновить часть отображаемого окна. Событие может возникать, когда пользователь перемещает окно перед окном и снова перемещает его. Оно получает это сообщение при первом отображении окна, что дает возможность отображать пользовательский интерфейс приложения. Приложение узнает об этих событиях, когда Windows отправляет их. При первом отображении окна все его необходимо обновить. Чтобы обработать сообщение, сначала вызовите, а затем обработайте WM_PAINT BeginPaint всю логику для размещения текста, кнопок и других элементов управления в окне. Затем вызовите EndPaint . Для этого приложения код между BeginPaint() и отображается Hello, Windows desktop! в окне, созданном в WinMain() EndPaint() . В следующем коде TextOut функция отображает текст в указанном расположении в окне.
PAINTSTRUCT ps; HDC hdc; TCHAR greeting[] = _T("Hello, Windows desktop!"); switch (message) < case WM_PAINT: hdc = BeginPaint(hWnd, &ps); // Here your application is laid out. // For this introduction, we just print out "Hello, Windows desktop!" // in the top left corner. TextOut(hdc, 5, 5, greeting, _tcslen(greeting)); // End application-specific layout section. EndPaint(hWnd, &ps); break; >
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) < PAINTSTRUCT ps; HDC hdc; TCHAR greeting[] = _T("Hello, Windows desktop!"); switch (message) < case WM_PAINT: hdc = BeginPaint(hWnd, &ps); // Here your application is laid out. // For this introduction, we just print out "Hello, Windows desktop!" // in the top left corner. TextOut(hdc, 5, 5, greeting, _tcslen(greeting)); // End application specific layout section. EndPaint(hWnd, &ps); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); break; >return 0; >
Сборка кода
Как обещало, полный код рабочего приложения следует.
Сборка примера
- Удалите весь код в HelloWindowsDesktop.cpp в редакторе. Скопируйте этот пример кода и вставьте его в HelloWindowsDesktop.cpp:
// HelloWindowsDesktop.cpp // compile with: /D_UNICODE /DUNICODE /DWIN32 /D_WINDOWS /c #include #include #include #include // Global variables // The main window class name. static TCHAR szWindowClass[] = _T("DesktopApp"); // The string that appears in the application's title bar. static TCHAR szTitle[] = _T("Windows Desktop Guided Tour Application"); // Stored instance handle for use in Win32 API calls such as FindResource HINSTANCE hInst; // Forward declarations of functions included in this code module: LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); int WINAPI WinMain( _In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow ) < WNDCLASSEX wcex; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = LoadIcon(wcex.hInstance, IDI_APPLICATION); wcex.hCursor = LoadCursor(NULL, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wcex.lpszMenuName = NULL; wcex.lpszClassName = szWindowClass; wcex.hIconSm = LoadIcon(wcex.hInstance, IDI_APPLICATION); if (!RegisterClassEx(&wcex)) < MessageBox(NULL, _T("Call to RegisterClassEx failed!"), _T("Windows Desktop Guided Tour"), NULL); return 1; >// Store instance handle in our global variable hInst = hInstance; // The parameters to CreateWindowEx explained: // WS_EX_OVERLAPPEDWINDOW : An optional extended window style. // szWindowClass: the name of the application // szTitle: the text that appears in the title bar // WS_OVERLAPPEDWINDOW: the type of window to create // CW_USEDEFAULT, CW_USEDEFAULT: initial position (x, y) // 500, 100: initial size (width, length) // NULL: the parent of this window // NULL: this application does not have a menu bar // hInstance: the first parameter from WinMain // NULL: not used in this application HWND hWnd = CreateWindowEx( WS_EX_OVERLAPPEDWINDOW, szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 500, 100, NULL, NULL, hInstance, NULL ); if (!hWnd) < MessageBox(NULL, _T("Call to CreateWindow failed!"), _T("Windows Desktop Guided Tour"), NULL); return 1; >// The parameters to ShowWindow explained: // hWnd: the value returned from CreateWindow // nCmdShow: the fourth parameter from WinMain ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); // Main message loop: MSG msg; while (GetMessage(&msg, NULL, 0, 0)) < TranslateMessage(&msg); DispatchMessage(&msg); >return (int) msg.wParam; > // FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM) // // PURPOSE: Processes messages for the main window. // // WM_PAINT - Paint the main window // WM_DESTROY - post a quit message and return LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) < PAINTSTRUCT ps; HDC hdc; TCHAR greeting[] = _T("Hello, Windows desktop!"); switch (message) < case WM_PAINT: hdc = BeginPaint(hWnd, &ps); // Here your application is laid out. // For this introduction, we just print out "Hello, Windows desktop!" // in the top left corner. TextOut(hdc, 5, 5, greeting, _tcslen(greeting)); // End application-specific layout section. EndPaint(hWnd, &ps); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); break; >return 0; >
Анимация показывает нажатие кнопки "Сохранить все", а затем выберите "Сборка > Сборка решения" в главном меню.

Поздравляем! Вы создали традиционное классическое приложение Windows.
Окна на чистом WinAPI. Или просто о сложном
Казалось бы, что WinAPI уходит в прошлое. Давно уже существует огромное количество кросс-платформенных фреймфорков, Windows не только на десктопах, да и сами Microsoft в свой магазин не жалуют приложения, которые используют этого монстра. Помимо этого статей о том, как создать окошки на WinAPI, не только здесь, но и по всему интернету, исчисляется тысячами по уровню от дошколят и выше. Весь этот процесс разобран уже даже не по атомам, а по субатомным частицам. Что может быть проще и понятнее? А тут я еще…
Но не все так просто, как кажется.
Почему о WinAPI сейчас?
В один прекрасный момент, изучая потроха одной из игр в весьма неплохом эмуляторе NES, я подумал: Вроде неплохой такой эмуль, а в отладчике нет такой простой вещи, как навигация по кнопкам клавиатуры, которая есть в любом нормальном отладчике.
Здесь я не зря дал ссылку на репозиторий, т.к. видно, что ребята столкнулись с проблемой, о которой речь пойдет ниже, но так и не решили ее.
О чем это я? А вот об этом кусочке кода:
case WM_KEYDOWN: MessageBox(hwndDlg,"Die!","I'm dead!",MB_YESNO|MB_ICONINFORMATION); break;
Таким образом, авторы хотели добавить поддержку клавиатуры, но суровая реальность недр архитектуры диалоговых окон в Windows жестко пресекла такую самодеятельность. Те, кто пользовался эмулятором и отладчиком в нем, хоть раз видели это сообщение?
В чем же проблема?
Ответ такой: так делать нельзя!
И, возвращаясь, к изначальному вопросу о WinAPI: очень много популярных, и не очень, проектов продолжают его использовать и в настоящее время, т.к. лучше, чем на чистом API многие вещи не сделать (тут можно бесконечно приводить аналогии вроде сравнения высокоуровневых языков и ассемблера, но сейчас не об этом). Да и мало ли почему? Просто используют и все тут.
О проблеме
Диалоговые окна упрощают работу с GUI, одновременно лишая нас возможности сделать что-то самостоятельно. Например, сообщения WM_KEYDOWN/WM_KEYUP, приходящие в оконную процедуру, «съедаются» в недрах DefDlgProc, беря на себя такие вещи, как: Навигация по Tab, обработка клавиш Esc, Enter, и т.д. Кроме того, диалоги не нужно создавать вручную: проще, ведь, набросать кнопок, списков, в редакторе ресурсов, вызвать в WinMain CreateDialog/DialogBox и все готово.
Обойти такие мелкие неприятности просто. Есть, как минимум, два вполне легальных способа:
- Создать свой собственный класс через RegisterClassEx и в процедуре обработки класса схватывать WM_KEYDOWN, перенаправлять в процедуру обработки самого диалога. Да-да! Можно создавать диалоги со своим собственным классом, и встроенный в VS редактор даже позволяет задавать имя класса для диалога. Вот только кто об этом знает и этим пользуется?
Минус очевиден: Нужно регистрировать еще один класс, иметь на 1 CALLBACK процедуру больше, суть которой будет всего-навсего в трансляции пары сообщений. Кроме того, мы не будем знать куда их транслировать, и придется городить костыли. - Использовать встроенный механизм акселераторов. И нам даже не придется менять код диалоговой процедуры! Ну, разве что, добавить одну строчку в switch/case, но об этом ниже.
Tutorials?
Не побоюсь сказать, что все туториалы по созданию окон через WinAPI начинаются с такого незамысловатого кода, обозначая его, как «цикл обработки сообщений» (опущу детали по подготовке класса окна и прочую обвязку):
while (GetMessage(&msg, nullptr, 0, 0))
Здесь действительно все просто:
- GetMessage() выхватывает очередное сообщение из очереди, и ключевой момент: блокирует поток, если в очереди пусто.
- TranslateMessage() из WM_KEYDOWN/WM_KEYUP формирует сообщения WM_CHAR/WM_SYSCHAR (они нужны, если кто-то хочет сделать свой редактор текста).
- DispatchMessage() отправляет сообщение в оконную процедуру (если таковая существует).
Because the return value can be nonzero, zero, or -1, avoid code like this:
while (GetMessage( lpMsg, hWnd, 0, 0)) .
И ниже приводится пример правильного цикла.
Стоит сказать, что в шаблонах VS для Win32 приложений, написан именно такой неправильный цикл. И это очень печально. Ведь мало кто будет вникать в то, что сделали сами авторы, ведь это априори правильно. И неправильный код множится вместе с багами, которые очень сложно отловить.
После этого фрагмента кода, как правило, следует рассказ про акселераторы, и добавляется пара новых строчек (учитывая замечание в MSDN, предлагаю сразу писать правильный цикл):
HACCEL hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR)); BOOL bRet = 0; while ( bRet = GetMessage(&msg, nullptr, 0, 0) ) < if ( -1 == bRet ) break; if ( !TranslateAccelerator(msg.hwnd, hAccel, &msg) ) < TranslateMessage(&msg); DispatchMessage(&msg); >>
Этот вариант я видел чаще всего. И он (та-дам) снова неправильный!
Сперва о том, что изменилось (потом о проблемах этого кода):
В первой строчке из ресурсов загружается таблица клавиш, при нажатии на которые, будет формироваться сообщение WM_COMMAND с соответствующим id команды.
Собственно TranslateAccelerator этим и занимается: если видит WM_KEYDOWN и код клавиши, которые есть в этом списке, то (опять же ключевой момент) будет формировать сообщение WM_COMMAND (MAKEWPARAM(id, 1)) и отправлять в соответствующую для дескриптора окна, указанного в первом аргументе, процедуру обработки.
Из последней фразы, думаю, стало понятно, в чем проблема предыдущего кода.
Поясню: GetMessage выхватывает сообщения для ВСЕХ объектов типа «окно» (в число которых входят и дочерние: кнопки, списки и прочее), а TranslateAccelerator будет отправлять сформированную WM_COMMAND куда? Правильно: обратно в кнопку/список и т.д. Но мы обрабатываем WM_COMMAND в своей процедуре, а значит нам интересно ее получать в ней же.
Ясно, что TranslateAccelerator надо вызывать для нашего созданного окна:
HWND hMainWnd = CreateWindow(. ); HACCEL hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR)); BOOL bRet = 0; while (bRet = GetMessage(&msg, nullptr, 0, 0)) < if ( -1 == bRet ) break; if ( !TranslateAccelerator(hMainWnd, hAccel, &msg) ) < TranslateMessage(&msg); DispatchMessage(&msg); >>
И вроде все хорошо и замечательно теперь: мы разобрали все детально и все должно работать идеально.
И снова нет. 🙂 Это будет работать правильно, пока у нас ровно одно окно — наше. Как только появится немодальное новое окно (диалог), все клавиши, которые будут в нем нажаты оттранслируются в WM_COMMAND и отправляться куда? И опять же правильно: в наше главное окно.
На этом этапе предлагаю не городить костылей по решению этой тупиковой ситуации, а предлагаю рассмотреть вещи, которые уже реже (или почти не встречаются) в туториалах.
IsDialogMessage
По названию этой функции можно подумать, что она зачем-то определяет: относится данное сообщение диалогу или нет. Но, во-первых, зачем нам это знать? А во-вторых, что с этой информацией нам делать дальше?
На самом деле, делает она чуть больше, чем следует из названия. А именно:
- Осуществляет навигацию по дочерним контролам кнопками Tab/Shift+Tab/вверх/вниз/вправо/влево. Плюс еще кое-что, но этого нам достаточно
- По нажатии на ESC формирует WM_COMMAND( IDCANCEL )
- По нажатии на Enter формирует WM_COMMAND( IDOK ) или нажатие на текущую кнопку по умолчанию
- Переключает кнопки по умолчанию (рамочка у таких кнопок чуть ярче остальных)
- Ну и еще разные штуки, которые облегчают пользователю работу с диалогом
Во-вторых, она нам облегчит жизнь по всем остальным пунктам, перечисленным в списке (и даже немного больше).
Вообще, она используется где-то в недрах Windows для обеспечения работы модальных диалоговых окон, а программистам дана, чтобы вызывать ее для немодальных диалогов. Однако мы ее можем использовать где угодно:
Although the IsDialogMessage function is intended for modeless dialog boxes, you can use it with any window that contains controls, enabling the windows to provide the same keyboard selection as is used in a dialog box.
Т.е. теперь, если мы оформим цикл так:
HWND hMainWnd = CreateWindow(. ); HACCEL hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR)); BOOL bRet = 0; while (bRet = GetMessage(&msg, nullptr, 0, 0)) < if ( -1 == bRet ) break; if ( !TranslateAccelerator(hMainWnd, hAccel, &msg) ) < if ( !IsDialogMessage(hMainWnd, &msg) ) < TranslateMessage(&msg); DispatchMessage(&msg); >> >
То наше окошко будет иметь навигацию, как в родном диалоге Windows. Но теперь мы получили два недостатка:
- Этот код также будет хорошо работать только с одним (немодальным) окном;
- Получив все достоинства диалоговой навигации, мы лишаемся прелестей в виде сообщений WM_KEYDOWN/WM_KEYUP (только для самого окна, а не для дочерних контролов);
Пора поговорить о том, чего нет в туториалах и ответах.
Как правило (как правило! Если кому-то захочется большего, то можно регистрировать свой класс для диалогов и работать так. И, если же, кому-то это интересно, я могу дополнить этим статью) WM_KEYDOWN хотят тогда, когда хотят обработать нажатие на клавишу, которая выполнит функцию в независимости от выбранного контрола в окне — т.е. некая общая функция для всего данного конкретного диалога. А раз так, то почему бы не воспользоваться богатыми возможностями, которые нам сама WinAPI и предлагает: TranslateAccelerator.
Везде используют ровно одну таблицу акселераторов, и только для главного окна. Ну действительно: цикл GetMessage-loop один, значит и таблица одна. Куда еще их девать?
На самом деле, циклы GetMessage-loop могут быть вложенными. Давайте еще раз посмотрим описание PostQuitMessage:
The PostQuitMessage function posts a WM_QUIT message to the thread's message queue and returns immediately; the function simply indicates to the system that the thread is requesting to quit at some time in the future.
И GetMessage:
If the function retrieves the WM_QUIT message, the return value is zero.
Таким образом, выход из GetMessage-loop осуществится, если мы вызовем PostQuitMessage в процедуре окна. Что это значит?
Мы можем для каждого немодального окна в нашей программе создавать свой собственный подобный цикл. В данном случае DialogBoxParam нам не подходит, т.к. оно крутит свой собственный цикл и повлиять мы на него не можем. Однако если создадим диалог через CreateDialogBoxParam или окно через CreateWindow, то можно закрутить еще один цикл. При этом в каждом таком окне и диалоге мы должны вызывать PostQuitMessage:
HWND hMainWnd = CreateWindow(. ); HACCEL hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR)); BOOL bRet = 0; while (bRet = GetMessage(&msg, nullptr, 0, 0)) < if ( -1 == bRet ) break; if ( !TranslateAccelerator(hMainWnd, hAccel, &msg) ) < if ( !IsDialogMessage(hMainWnd, &msg) ) < TranslateMessage(&msg); DispatchMessage(&msg); >> > // . LRESULT CALLBACK WndProc(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam) < switch( umsg ) < case WM_MYMESSAGE: < HWND hDlg = CreateDialog(hInstance, MAKEINTRESOURCE(IDD_MYDIALOG), hwnd, MyDialogBoxProc); HACCEL hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR_FOR_MY_DIALOG)); BOOL bRet = 0, fSavedEnabledState = IsWindowEnabled(hwnd); EnableWindow(hwnd, FALSE); // disable parent window, as dialog window is modal while (bRet = GetMessage(&msg, nullptr, 0, 0)) < if ( -1 == bRet ) break; if ( !TranslateAccelerator(hDlg, hAccel, &msg) ) < if ( !IsDialogMessage(hDlg, &msg) ) < TranslateMessage(&msg); DispatchMessage(&msg); >> > EnableWindow(hwnd, fSavedEnabledState); // enable parent window. Dialog was closed break; > > > INT_PTR CALLBACK MyDlgProc(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam) < switch(umsg) < case WM_CLOSE: < // EndDialog( hwnd, 0 ); -- DONT DO THAT! // EndDialog is valid ONLY for Modal Dialogs, created with DialogBox(Param) DestroyWindow( hwnd ); break; >case WM_DESTROY: < PostQuitMessage( 0 ); break; >// . > return 0; >
Обратите внимание: теперь для каждого нового окна в нашей программе мы можем добавить в обработку собственную таблицу акселераторов. WM_QUIT будет выхватывать GetMessage из цикла для диалога, а внешний цикл его даже не увидит. Почему так происходит?
Дело в том, что внешний цикл «встал» на вызове DispatchMessage, который вызвал нашу процедуру, которая крутит свой собственный внутренний цикл GetMessage с таким же DispatchMessage. Классический вложенный вызов (в данном случае DispatchMessage). Посему внешний цикл не получит WM_QUIT и не завершится на этом этапе. Все будет работать стройно.
Но и тут есть свои недостатки:
Каждый такой цикл будет обрабатывать сообщения только для «своего» окна. Про другие-то мы здесь не знаем. А значит, если где-то объявится еще один цикл, то все остальные окна не будут получать нужной обработки своих сообщений парой TranslateAccelerator/IsDialogMessage.
Что ж, пора учесть все эти замечание и написать наконец правильную обработку всех сообщений от всех окон нашей программы. Хочу заметить, что ниже рассматривается случай для одного потока. Т.к. каждый поток имеет свою очередь сообщений, то для каждого потока придется создавать свои структуры. Делается это весьма тривиальными изменениями в коде.
Делаем красиво
Т.к. правильная постановка задачи является половиной решения, то сперва надо эту самую задачу правильно же и поставить.
Во-первых, было бы логично, что только активное окно принимает сообщения. Т.е. для неактивного окна мы не будем транслировать акселераторы и передавать сообщения в IsDialogMessage.
Во-вторых, если для окна не задана таблица акселераторов, то транслировать нечего, будем просто отдавать сообщение в IsDialogMessage.
Создадим простой std::map, который будет мапить дескриптор окна в дескриптор таблицы акселераторов. Вот так:
std::map l_mAccelTable;
И по мере создания окон будем в него добавлять новые окна с дескриптором на свою любимую таблицу (или нуль, если такая обработка не требуется).
BOOL AddAccelerators(HWND hWnd, HACCEL hAccel) < if ( IsWindow( hWnd ) ) < l_mAccelTable[ hWnd ] = hAccel; return TRUE; >return FALSE; > BOOL AddAccelerators(HWND hWnd, LPCTSTR accel) < return AddAccelerators( hWnd, LoadAccelerators( hInstance, accel ) ); >BOOL AddAccelerators(HWND hWnd, int accel) < return AddAccelerators( hWnd, MAKEINTRESOURCE( accel ) ); >BOOL AddAccelerators(HWND hWnd)
Ну и после закрытия окна удалять. Вот так:
void DelAccel(HWND hWnd) < std::map::iterator me = l_mAccelTable.find( hWnd ); if ( me != l_mAccelTable.end() ) < if ( me->second ) < DestroyAcceleratorTable( me->second ); > l_mAccelTable.erase( me ); > >
Теперь, как создаем новый диалог/окно, вызываем AddAccelerators( hNewDialog, IDR_MY_ACCEL_TABLE ). Как закрываем: DelAccel( hNewDialog ).
Список окон с нужными дескрипторами у нас есть. Немного модифицируем наш основной цикл обработки сообщений:
// . HWND hMainWnd = CreateWindow(. ); AddAccelerators(hMainWnd, IDR_ACCELERATOR); BOOL bRet = 0; while (bRet = GetMessage(&msg, nullptr, 0, 0)) < if ( -1 == bRet ) break; if ( !HandleAccelArray( GetActiveWindow(), msg ) ) < TranslateMessage(&msg); DispatchMessage(&msg); >> // .
Значительно лучше! Что же там в HandleAccelArray и зачем там GetActiveWindow()?
Есть две функции, возвращающих дескриптор активного окна GetForegroundWindow и GetActiveWindow. Отличие первой от второй вполне доходчиво описано в описании второй:
The return value is the handle to the active window attached to the calling thread's message queue. Otherwise, the return value is NULL.
Если первая будет возвращать дескриптор любого окна в системе, то последняя только того, которое использует очередь сообщений нашего потока. Т.к. нас интересуют окна только нашего потока (а значит те, которые будут попадать в нашу очередь сообщений), то и возьмем последнюю.
Так вот HandleAccelArray, руководствуясь переданным ей дескриптором на активное окно, ищет это самое окно в нашей мапе, и если оно там есть, отдает это сообщение на трансляцию в TranslateAccelerator, а затем (если первый не увидел нужного) в IsDialogMessage. Если и последняя не обработала сообщение, то возвращаем FALSE, чтобы пройти по стандартной процедуре TranslateMessage/DispatchMessage.
BOOL HandleAccelWindow(std::map::const_iterator mh, MSG & msg) < const HWND & hWnd = mh->first; const HACCEL & hAccel = mh->second; if ( !TranslateAccelerator( hWnd, hAccel, &msg ) ) < // message not for TranslateAccelerator. Try it with IsDialogMessage if ( !IsDialogMessage( hWnd, &msg ) ) < // so, do default stuff return FALSE; >> // ok, message translated. Say to message-loop, to get next message return TRUE; > BOOL HandleAccelArray( HWND hActive, MSG & msg ) < if ( !hActive ) return FALSE; // no active window. Nothing to do std::map::const_iterator mh = l_mAccelTable.find( hActive ); if ( mh != l_mAccelTable.end() ) < // Got it! Try to translate this message for the active window return HandleAccelWindow( mh, msg ); >return FALSE; >
Теперь каждое дочернее окно вправе добавить себе любимую таблицу акселераторов и спокойно ловить и обрабатывать WM_COMMAND с нужным кодом.
А что там еще об одной строчке в коде обработчика WM_COMMAND?
Описание в TranslateAccelerator гласит:
To differentiate the message that this function sends from messages sent by menus or controls, the high-order word of the wParam parameter of the WM_COMMAND or WM_SYSCOMMAND message contains the value 1.
Обычно код обработки WM_COMMAND выглядит так:
switch( HIWORD( wParam ) ) < case BN_CLICKED: // command from buttons/menus < switch( LOWORD( wParam ) ) < case IDC_BUTTON1: DoButton1Stuff(); break; case IDC_BUTTON2: DoButton2Stuff(); break; // . >break; > >
Теперь можно написать так:
switch( HIWORD( wParam ) ) < case 1: // accelerator case BN_CLICKED: // command from buttons/menus < switch( LOWORD( wParam ) ) < case IDC_BUTTON1: DoButton1Stuff(); break; case IDC_BUTTON2: DoButton2Stuff(); break; // . >break; > >
И теперь, возвращаясь к тому же fceux, добавив всего одну строчку в код обработки команд от кнопок, мы получим желаемое: управлять дебагером с клавиатуры. Достаточно добавить небольшую обертку вокруг главного цикла сообщений и новую таблицу акселераторов с нужными соответствиями VK_KEY => IDC_DEBUGGER_BUTTON.
P.S.: Мало кто знает, но можно создавать свою собственную таблицу акселераторов, а теперь и применять ее прямо налету.
P.P.S.: Т.к. DialogBox/DialogBoxParam крутит собственный цикл, то от при вызове диалога через них акселераторы работать не будут и наш цикл (или циклы) будет «простаивать».
P.P.P.S.: После вызова HandleAccelWindow мап l_mAccelTable может измениться, т.к. TranslateAccelerator или IsDialogMessage вызывают DispatchMessage, а там может встретиться AddAccelerators или DelAccel в наших обработчиках! Поэтому лучше его после этой функции не трогать.
Пощупать код можно здесь. За основу был взят код, генерируемый из стандартного шаблона MS VS 2017.
Как создать окно на чистом Си используя только стандартную библиотеку по стандарту?
Всем добрый день. Как можно создать свое окно на голом Си? Возможность такая должна быль, поскольку все сторонние библиотеки с графикой написаны именно на Си. Про windows.h и другие инструменты не писать, хочу своими руками попробовать сделать это.
Отслеживать
задан 25 июн 2020 в 12:25
430 3 3 серебряных знака 10 10 бронзовых знаков
Никак. Нет такого понятия в стандартной библиотеке С++.
25 июн 2020 в 12:28
Все сторонние библиотеки, да и стандартная библиотека тоже, задействуют системеные API, в т.ч. из windows.h Никаких средств работы с GUI в стандартной библиотеке нет.
25 июн 2020 в 12:28
4 ответа 4
Сортировка: Сброс на вариант по умолчанию
Никак не создать, в стандартной библиотеке нет таких функций и в ближайшее время не будет. Уже было предложение по добавлению 2D-графики в C++ . Историю предложения и рассуждения, почему ничего вышло, можно почитать тут.
Отслеживать
ответ дан 25 июн 2020 в 12:47
181 2 2 серебряных знака 23 23 бронзовых знака
@avp нет, это не стандартная библиотека
25 июн 2020 в 12:58
@avp так POSIX это не ISO C стандарт
25 июн 2020 в 13:03
@avp спрашивают же на голом Си, а не "с использованием любого стандарта"
25 июн 2020 в 13:04
@avp "Стандартная библиотека С/С++" означает набор функций/классов/. из хедеров, описанных в стандартах С/С++. (Хотя это и называется "библиотека", это не обязательно ровно один целый .a / .so / .dll файл.) Если в libc входит что-то еще, не описанное стандартом С, то это "что-то" - уже не часть стандартной библиотеки.
25 июн 2020 в 13:18
@avp Не понимаю, где я такое сказал? Я просто сказал, что сокеты из libc не являются частью "стандартной библиотеки", потому что не описаны в стандартах C/C++. Я не говорил, что поэтому их не стоит использовать.
25 июн 2020 в 13:53
Ну, если Вы используете консольный ввод-вывод в окно ДОС, то можете создать в нём своё собственное окошко с помощью символов псевдографики. Правда, я так поступал ещё в прошлом тысячелетии примерно так:
void Displays::Interface(float snr) < int i; clrscr(); window(10,8,71,19); /* координаты углов окна в виде кол,стр,кол,стр */ textbackground(BLUE); /* цвет фона */ textcolor(WHITE); /* цвет символов */ clrscr(); cprintf("г=============== Моделирование системы связи ===============¬\r\n"); cprintf("¦ ПАРАМЕТРЫ КАНАЛА ¦\r\n"); cprintf("¦ Объём входного алфавита кода: ¦\r\n"); cprintf("¦ Отношение сигнал-шум [дБ]: ¦\r\n"); cprintf("¦ Вид Модуляции: ¦\r\n"); cprintf("¦ Код: ¦\r\n"); cprintf("¦ ¦\r\n"); cprintf("¦ РЕЗУЛЬТАТ НАБЛЮДЕНИЯ ¦\r\n"); cprintf("¦ Передано символов: ¦\r\n"); cprintf("¦ Поступило ошибок на вход декодера: ¦\r\n"); cprintf("¦ Обнаружено ошибок на выходе декодера: ¦\r\n"); cprintf("L===========================================================-"); gotoxy(46,3); cout
Этому фрагменту кода более 30 лет и на писан он для окна ДОС ещё в Borland C++ Builder 3.1
Отслеживать
ответ дан 25 июн 2020 в 13:52
347 3 3 серебряных знака 13 13 бронзовых знаков
window(10,8,71,19); это точно стандартная функция?
25 июн 2020 в 14:00
Тут вообще ни одной стандартной функции. (¬‿¬) Окошечки рисовать можно, но на голом cout / printf .
25 июн 2020 в 14:03
Тут все функции из conio.h , который не только не был стандартным, но и который сейчас почти искоренили и в современных компиляторах этих функций нет. А ещё псевдографика тогда была лучше - есть же нормальные символы линий, в не вот эти палочки.
1 июл 2020 в 9:19
В стандартной библиотеки Си нет возможности создавать окна , так как это функции ОС. Но используя библиотеку GTK можно создавать окна на чистом Си.
Отслеживать
ответ дан 26 июн 2020 в 3:40
3,030 1 1 золотой знак 11 11 серебряных знаков 20 20 бронзовых знаков
Например, используя socket API (вполне стандартно находится в libc) организовать взаимодействие с X-window сервером прямо на X-протоколе.
По сути таким образом вы будете делать свою раелизацию библиотеки X11.
Если отказаться от пуризма (в смысле, что есть стандартная библиотека), то на практике есть интересный способ (что-то забылся, когда писал ответ, хотя я его как-то реально использовал) -- это запустить стороннее приложение, например, wish и организовать с ним "диалог" по паре пайпов.
Окна на чистом WinAPI. Или просто о сложном
Казалось бы, что WinAPI уходит в прошлое. Давно уже существует огромное количество кросс-платформенных фреймфорков, Windows не только на десктопах, да и сами Microsoft в свой магазин не жалуют приложения, которые используют этого монстра. Помимо этого статей о том, как создать окошки на WinAPI, не только здесь, но и по всему интернету, исчисляется тысячами по уровню от дошколят и выше. Весь этот процесс разобран уже даже не по атомам, а по субатомным частицам. Что может быть проще и понятнее? А тут я еще…
Но не все так просто, как кажется.
Почему о WinAPI сейчас?
В один прекрасный момент, изучая потроха одной из игр в весьма неплохом эмуляторе NES, я подумал: Вроде неплохой такой эмуль, а в отладчике нет такой простой вещи, как навигация по кнопкам клавиатуры, которая есть в любом нормальном отладчике.
Здесь я не зря дал ссылку на репозиторий, т.к. видно, что ребята столкнулись с проблемой, о которой речь пойдет ниже, но так и не решили ее.
О чем это я? А вот об этом кусочке кода:
case WM_KEYDOWN: MessageBox(hwndDlg,"Die!","I'm dead!",MB_YESNO|MB_ICONINFORMATION); break;
Таким образом, авторы хотели добавить поддержку клавиатуры, но суровая реальность недр архитектуры диалоговых окон в Windows жестко пресекла такую самодеятельность. Те, кто пользовался эмулятором и отладчиком в нем, хоть раз видели это сообщение?
В чем же проблема?
Ответ такой: так делать нельзя!
И, возвращаясь, к изначальному вопросу о WinAPI: очень много популярных, и не очень, проектов продолжают его использовать и в настоящее время, т.к. лучше, чем на чистом API многие вещи не сделать (тут можно бесконечно приводить аналогии вроде сравнения высокоуровневых языков и ассемблера, но сейчас не об этом). Да и мало ли почему? Просто используют и все тут.
О проблеме
Диалоговые окна упрощают работу с GUI, одновременно лишая нас возможности сделать что-то самостоятельно. Например, сообщения WM_KEYDOWN/WM_KEYUP, приходящие в оконную процедуру, «съедаются» в недрах DefDlgProc, беря на себя такие вещи, как: Навигация по Tab, обработка клавиш Esc, Enter, и т.д. Кроме того, диалоги не нужно создавать вручную: проще, ведь, набросать кнопок, списков, в редакторе ресурсов, вызвать в WinMain CreateDialog/DialogBox и все готово.
Обойти такие мелкие неприятности просто. Есть, как минимум, два вполне легальных способа:
- Создать свой собственный класс через RegisterClassEx и в процедуре обработки класса схватывать WM_KEYDOWN, перенаправлять в процедуру обработки самого диалога. Да-да! Можно создавать диалоги со своим собственным классом, и встроенный в VS редактор даже позволяет задавать имя класса для диалога. Вот только кто об этом знает и этим пользуется?
Минус очевиден: Нужно регистрировать еще один класс, иметь на 1 CALLBACK процедуру больше, суть которой будет всего-навсего в трансляции пары сообщений. Кроме того, мы не будем знать куда их транслировать, и придется городить костыли. - Использовать встроенный механизм акселераторов. И нам даже не придется менять код диалоговой процедуры! Ну, разве что, добавить одну строчку в switch/case, но об этом ниже.
Tutorials?
Не побоюсь сказать, что все туториалы по созданию окон через WinAPI начинаются с такого незамысловатого кода, обозначая его, как «цикл обработки сообщений» (опущу детали по подготовке класса окна и прочую обвязку):
while (GetMessage(&msg, nullptr, 0, 0))
Здесь действительно все просто:
- GetMessage() выхватывает очередное сообщение из очереди, и ключевой момент: блокирует поток, если в очереди пусто.
- TranslateMessage() из WM_KEYDOWN/WM_KEYUP формирует сообщения WM_CHAR/WM_SYSCHAR (они нужны, если кто-то хочет сделать свой редактор текста).
- DispatchMessage() отправляет сообщение в оконную процедуру (если таковая существует).
Because the return value can be nonzero, zero, or -1, avoid code like this:
while (GetMessage( lpMsg, hWnd, 0, 0)) .
И ниже приводится пример правильного цикла.
Стоит сказать, что в шаблонах VS для Win32 приложений, написан именно такой неправильный цикл. И это очень печально. Ведь мало кто будет вникать в то, что сделали сами авторы, ведь это априори правильно. И неправильный код множится вместе с багами, которые очень сложно отловить.
После этого фрагмента кода, как правило, следует рассказ про акселераторы, и добавляется пара новых строчек (учитывая замечание в MSDN, предлагаю сразу писать правильный цикл):
HACCEL hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR)); BOOL bRet = 0; while ( bRet = GetMessage(&msg, nullptr, 0, 0) ) < if ( -1 == bRet ) break; if ( !TranslateAccelerator(msg.hwnd, hAccel, &msg) ) < TranslateMessage(&msg); DispatchMessage(&msg); >>
Этот вариант я видел чаще всего. И он (та-дам) снова неправильный!
Сперва о том, что изменилось (потом о проблемах этого кода):
В первой строчке из ресурсов загружается таблица клавиш, при нажатии на которые, будет формироваться сообщение WM_COMMAND с соответствующим id команды.
Собственно TranslateAccelerator этим и занимается: если видит WM_KEYDOWN и код клавиши, которые есть в этом списке, то (опять же ключевой момент) будет формировать сообщение WM_COMMAND (MAKEWPARAM(id, 1)) и отправлять в соответствующую для дескриптора окна, указанного в первом аргументе, процедуру обработки.
Из последней фразы, думаю, стало понятно, в чем проблема предыдущего кода.
Поясню: GetMessage выхватывает сообщения для ВСЕХ объектов типа «окно» (в число которых входят и дочерние: кнопки, списки и прочее), а TranslateAccelerator будет отправлять сформированную WM_COMMAND куда? Правильно: обратно в кнопку/список и т.д. Но мы обрабатываем WM_COMMAND в своей процедуре, а значит нам интересно ее получать в ней же.
Ясно, что TranslateAccelerator надо вызывать для нашего созданного окна:
HWND hMainWnd = CreateWindow(. ); HACCEL hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR)); BOOL bRet = 0; while (bRet = GetMessage(&msg, nullptr, 0, 0)) < if ( -1 == bRet ) break; if ( !TranslateAccelerator(hMainWnd, hAccel, &msg) ) < TranslateMessage(&msg); DispatchMessage(&msg); >>
И вроде все хорошо и замечательно теперь: мы разобрали все детально и все должно работать идеально.
И снова нет. 🙂 Это будет работать правильно, пока у нас ровно одно окно — наше. Как только появится немодальное новое окно (диалог), все клавиши, которые будут в нем нажаты оттранслируются в WM_COMMAND и отправляться куда? И опять же правильно: в наше главное окно.
На этом этапе предлагаю не городить костылей по решению этой тупиковой ситуации, а предлагаю рассмотреть вещи, которые уже реже (или почти не встречаются) в туториалах.
IsDialogMessage
По названию этой функции можно подумать, что она зачем-то определяет: относится данное сообщение диалогу или нет. Но, во-первых, зачем нам это знать? А во-вторых, что с этой информацией нам делать дальше?
На самом деле, делает она чуть больше, чем следует из названия. А именно:
- Осуществляет навигацию по дочерним контролам кнопками Tab/Shift+Tab/вверх/вниз/вправо/влево. Плюс еще кое-что, но этого нам достаточно
- По нажатии на ESC формирует WM_COMMAND( IDCANCEL )
- По нажатии на Enter формирует WM_COMMAND( IDOK ) или нажатие на текущую кнопку по умолчанию
- Переключает кнопки по умолчанию (рамочка у таких кнопок чуть ярче остальных)
- Ну и еще разные штуки, которые облегчают пользователю работу с диалогом
Во-вторых, она нам облегчит жизнь по всем остальным пунктам, перечисленным в списке (и даже немного больше).
Вообще, она используется где-то в недрах Windows для обеспечения работы модальных диалоговых окон, а программистам дана, чтобы вызывать ее для немодальных диалогов. Однако мы ее можем использовать где угодно:
Although the IsDialogMessage function is intended for modeless dialog boxes, you can use it with any window that contains controls, enabling the windows to provide the same keyboard selection as is used in a dialog box.
Т.е. теперь, если мы оформим цикл так:
HWND hMainWnd = CreateWindow(. ); HACCEL hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR)); BOOL bRet = 0; while (bRet = GetMessage(&msg, nullptr, 0, 0)) < if ( -1 == bRet ) break; if ( !TranslateAccelerator(hMainWnd, hAccel, &msg) ) < if ( !IsDialogMessage(hMainWnd, &msg) ) < TranslateMessage(&msg); DispatchMessage(&msg); >> >
То наше окошко будет иметь навигацию, как в родном диалоге Windows. Но теперь мы получили два недостатка:
- Этот код также будет хорошо работать только с одним (немодальным) окном;
- Получив все достоинства диалоговой навигации, мы лишаемся прелестей в виде сообщений WM_KEYDOWN/WM_KEYUP (только для самого окна, а не для дочерних контролов);
Пора поговорить о том, чего нет в туториалах и ответах.
Как правило (как правило! Если кому-то захочется большего, то можно регистрировать свой класс для диалогов и работать так. И, если же, кому-то это интересно, я могу дополнить этим статью) WM_KEYDOWN хотят тогда, когда хотят обработать нажатие на клавишу, которая выполнит функцию в независимости от выбранного контрола в окне — т.е. некая общая функция для всего данного конкретного диалога. А раз так, то почему бы не воспользоваться богатыми возможностями, которые нам сама WinAPI и предлагает: TranslateAccelerator.
Везде используют ровно одну таблицу акселераторов, и только для главного окна. Ну действительно: цикл GetMessage-loop один, значит и таблица одна. Куда еще их девать?
На самом деле, циклы GetMessage-loop могут быть вложенными. Давайте еще раз посмотрим описание PostQuitMessage:
The PostQuitMessage function posts a WM_QUIT message to the thread's message queue and returns immediately; the function simply indicates to the system that the thread is requesting to quit at some time in the future.
И GetMessage:
If the function retrieves the WM_QUIT message, the return value is zero.
Таким образом, выход из GetMessage-loop осуществится, если мы вызовем PostQuitMessage в процедуре окна. Что это значит?
Мы можем для каждого немодального окна в нашей программе создавать свой собственный подобный цикл. В данном случае DialogBoxParam нам не подходит, т.к. оно крутит свой собственный цикл и повлиять мы на него не можем. Однако если создадим диалог через CreateDialogBoxParam или окно через CreateWindow, то можно закрутить еще один цикл. При этом в каждом таком окне и диалоге мы должны вызывать PostQuitMessage:
HWND hMainWnd = CreateWindow(. ); HACCEL hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR)); BOOL bRet = 0; while (bRet = GetMessage(&msg, nullptr, 0, 0)) < if ( -1 == bRet ) break; if ( !TranslateAccelerator(hMainWnd, hAccel, &msg) ) < if ( !IsDialogMessage(hMainWnd, &msg) ) < TranslateMessage(&msg); DispatchMessage(&msg); >> > // . LRESULT CALLBACK WndProc(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam) < switch( umsg ) < case WM_MYMESSAGE: < HWND hDlg = CreateDialog(hInstance, MAKEINTRESOURCE(IDD_MYDIALOG), hwnd, MyDialogBoxProc); HACCEL hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR_FOR_MY_DIALOG)); BOOL bRet = 0, fSavedEnabledState = IsWindowEnabled(hwnd); EnableWindow(hwnd, FALSE); // disable parent window, as dialog window is modal while (bRet = GetMessage(&msg, nullptr, 0, 0)) < if ( -1 == bRet ) break; if ( !TranslateAccelerator(hDlg, hAccel, &msg) ) < if ( !IsDialogMessage(hDlg, &msg) ) < TranslateMessage(&msg); DispatchMessage(&msg); >> > EnableWindow(hwnd, fSavedEnabledState); // enable parent window. Dialog was closed break; > > > INT_PTR CALLBACK MyDlgProc(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam) < switch(umsg) < case WM_CLOSE: < // EndDialog( hwnd, 0 ); -- DONT DO THAT! // EndDialog is valid ONLY for Modal Dialogs, created with DialogBox(Param) DestroyWindow( hwnd ); break; >case WM_DESTROY: < PostQuitMessage( 0 ); break; >// . > return 0; >
Обратите внимание: теперь для каждого нового окна в нашей программе мы можем добавить в обработку собственную таблицу акселераторов. WM_QUIT будет выхватывать GetMessage из цикла для диалога, а внешний цикл его даже не увидит. Почему так происходит?
Дело в том, что внешний цикл «встал» на вызове DispatchMessage, который вызвал нашу процедуру, которая крутит свой собственный внутренний цикл GetMessage с таким же DispatchMessage. Классический вложенный вызов (в данном случае DispatchMessage). Посему внешний цикл не получит WM_QUIT и не завершится на этом этапе. Все будет работать стройно.
Но и тут есть свои недостатки:
Каждый такой цикл будет обрабатывать сообщения только для «своего» окна. Про другие-то мы здесь не знаем. А значит, если где-то объявится еще один цикл, то все остальные окна не будут получать нужной обработки своих сообщений парой TranslateAccelerator/IsDialogMessage.
Что ж, пора учесть все эти замечание и написать наконец правильную обработку всех сообщений от всех окон нашей программы. Хочу заметить, что ниже рассматривается случай для одного потока. Т.к. каждый поток имеет свою очередь сообщений, то для каждого потока придется создавать свои структуры. Делается это весьма тривиальными изменениями в коде.
Делаем красиво
Т.к. правильная постановка задачи является половиной решения, то сперва надо эту самую задачу правильно же и поставить.
Во-первых, было бы логично, что только активное окно принимает сообщения. Т.е. для неактивного окна мы не будем транслировать акселераторы и передавать сообщения в IsDialogMessage.
Во-вторых, если для окна не задана таблица акселераторов, то транслировать нечего, будем просто отдавать сообщение в IsDialogMessage.
Создадим простой std::map, который будет мапить дескриптор окна в дескриптор таблицы акселераторов. Вот так:
std::map l_mAccelTable;
И по мере создания окон будем в него добавлять новые окна с дескриптором на свою любимую таблицу (или нуль, если такая обработка не требуется).
BOOL AddAccelerators(HWND hWnd, HACCEL hAccel) < if ( IsWindow( hWnd ) ) < l_mAccelTable[ hWnd ] = hAccel; return TRUE; >return FALSE; > BOOL AddAccelerators(HWND hWnd, LPCTSTR accel) < return AddAccelerators( hWnd, LoadAccelerators( hInstance, accel ) ); >BOOL AddAccelerators(HWND hWnd, int accel) < return AddAccelerators( hWnd, MAKEINTRESOURCE( accel ) ); >BOOL AddAccelerators(HWND hWnd)
Ну и после закрытия окна удалять. Вот так:
void DelAccel(HWND hWnd) < std::map::iterator me = l_mAccelTable.find( hWnd ); if ( me != l_mAccelTable.end() ) < if ( me->second ) < DestroyAcceleratorTable( me->second ); > l_mAccelTable.erase( me ); > >
Теперь, как создаем новый диалог/окно, вызываем AddAccelerators( hNewDialog, IDR_MY_ACCEL_TABLE ). Как закрываем: DelAccel( hNewDialog ).
Список окон с нужными дескрипторами у нас есть. Немного модифицируем наш основной цикл обработки сообщений:
// . HWND hMainWnd = CreateWindow(. ); AddAccelerators(hMainWnd, IDR_ACCELERATOR); BOOL bRet = 0; while (bRet = GetMessage(&msg, nullptr, 0, 0)) < if ( -1 == bRet ) break; if ( !HandleAccelArray( GetActiveWindow(), msg ) ) < TranslateMessage(&msg); DispatchMessage(&msg); >> // .
Значительно лучше! Что же там в HandleAccelArray и зачем там GetActiveWindow()?
Есть две функции, возвращающих дескриптор активного окна GetForegroundWindow и GetActiveWindow. Отличие первой от второй вполне доходчиво описано в описании второй:
The return value is the handle to the active window attached to the calling thread's message queue. Otherwise, the return value is NULL.
Если первая будет возвращать дескриптор любого окна в системе, то последняя только того, которое использует очередь сообщений нашего потока. Т.к. нас интересуют окна только нашего потока (а значит те, которые будут попадать в нашу очередь сообщений), то и возьмем последнюю.
Так вот HandleAccelArray, руководствуясь переданным ей дескриптором на активное окно, ищет это самое окно в нашей мапе, и если оно там есть, отдает это сообщение на трансляцию в TranslateAccelerator, а затем (если первый не увидел нужного) в IsDialogMessage. Если и последняя не обработала сообщение, то возвращаем FALSE, чтобы пройти по стандартной процедуре TranslateMessage/DispatchMessage.
BOOL HandleAccelWindow(std::map::const_iterator mh, MSG & msg) < const HWND & hWnd = mh->first; const HACCEL & hAccel = mh->second; if ( !TranslateAccelerator( hWnd, hAccel, &msg ) ) < // message not for TranslateAccelerator. Try it with IsDialogMessage if ( !IsDialogMessage( hWnd, &msg ) ) < // so, do default stuff return FALSE; >> // ok, message translated. Say to message-loop, to get next message return TRUE; > BOOL HandleAccelArray( HWND hActive, MSG & msg ) < if ( !hActive ) return FALSE; // no active window. Nothing to do std::map::const_iterator mh = l_mAccelTable.find( hActive ); if ( mh != l_mAccelTable.end() ) < // Got it! Try to translate this message for the active window return HandleAccelWindow( mh, msg ); >return FALSE; >
Теперь каждое дочернее окно вправе добавить себе любимую таблицу акселераторов и спокойно ловить и обрабатывать WM_COMMAND с нужным кодом.
А что там еще об одной строчке в коде обработчика WM_COMMAND?
Описание в TranslateAccelerator гласит:
To differentiate the message that this function sends from messages sent by menus or controls, the high-order word of the wParam parameter of the WM_COMMAND or WM_SYSCOMMAND message contains the value 1.
Обычно код обработки WM_COMMAND выглядит так:
switch( HIWORD( wParam ) ) < case BN_CLICKED: // command from buttons/menus < switch( LOWORD( wParam ) ) < case IDC_BUTTON1: DoButton1Stuff(); break; case IDC_BUTTON2: DoButton2Stuff(); break; // . >break; > >
Теперь можно написать так:
switch( HIWORD( wParam ) ) < case 1: // accelerator case BN_CLICKED: // command from buttons/menus < switch( LOWORD( wParam ) ) < case IDC_BUTTON1: DoButton1Stuff(); break; case IDC_BUTTON2: DoButton2Stuff(); break; // . >break; > >
И теперь, возвращаясь к тому же fceux, добавив всего одну строчку в код обработки команд от кнопок, мы получим желаемое: управлять дебагером с клавиатуры. Достаточно добавить небольшую обертку вокруг главного цикла сообщений и новую таблицу акселераторов с нужными соответствиями VK_KEY => IDC_DEBUGGER_BUTTON.
P.S.: Мало кто знает, но можно создавать свою собственную таблицу акселераторов, а теперь и применять ее прямо налету.
P.P.S.: Т.к. DialogBox/DialogBoxParam крутит собственный цикл, то от при вызове диалога через них акселераторы работать не будут и наш цикл (или циклы) будет «простаивать».
P.P.P.S.: После вызова HandleAccelWindow мап l_mAccelTable может измениться, т.к. TranslateAccelerator или IsDialogMessage вызывают DispatchMessage, а там может встретиться AddAccelerators или DelAccel в наших обработчиках! Поэтому лучше его после этой функции не трогать.
Пощупать код можно здесь. За основу был взят код, генерируемый из стандартного шаблона MS VS 2017.
Структура оконного приложения
Оконные приложения строятся по принципам событийно-управляемого программирования (event-driven programming) - стиля программирования, при котором поведение компонента системы определяется набором возможных внешних событий и ответных реакций компонента на них. Такими компонентами в Windows являются окна.
С каждым окном в Windows связана определенная функция обработки событий – оконная функция . События для окон называются сообщениями . Сообщение относится к тому или иному типу, идентифицируемому определенным кодом (32-битным целым числом), и сопровождается парой 32-битных параметров ( WPARAM и LPARAM ), интерпретация которых зависит от типа сообщения.
Задача любого оконного приложения - создать главное окно и сообщить Windows функцию обработки событий для этого окна. Все самое интересное для приложения будет происходить именно в функции обработки событий главного окна.
В Windows программа пассивна. После запуска она ждет, когда ей уделит внимание операционная система. Операционная система делает это посылкой сообщений. Сообщения могут быть разного типа, они функционируют в системе достаточно хаотично, и приложение не знает, какого типа сообщение придет следующим. Логика построения Windows-приложения должна обеспечивать корректную и предсказуемую работу при поступлении сообщений любого типа.
Классическое оконное приложение, как правило, состоит по крайней мере из двух функций:
- стартовая функция, создающая главное окно WinMain();
- функция обработки сообщений окна (оконная функция).
Стартовая функция WinMain
В консольной программе на С точкой входа является функция main() . С этого места программа начинает выполняться.
Точкой входа программы для Windows является функция WinMain() .
int WINAPI WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
PSTR szCmdLine,
int iCmdShow)
Эта функция использует последовательность вызовов API и при завершении возвращает операционной системе целое число.
Аргументы функции:
- hInstance – дескриптор процесса (instance handle) – число, идентифицирующее программу, когда она работает под Windows. Если одновременно работают несколько копий одной программы, каждая копия имеет свое значение hInstance .
- hPrevInstance — предыдущий дескриптор процесса (previous instance) — в настоящее время устарел, всегда равен NULL.
- szCmdLine — указатель на оканчивающуюся нулем строку, в которой содержатся параметры, переданные в программу из командной строки. Можно запустить программу с параметром командной строки, вставив этот параметр после имени программы в командной строке.
- iCmdShow — целое константное значение, показывающее, каким должно быть выведено на экран окно в начальный момент. Задается при запуске программы другой программой. В большинстве случаев число равно 1 ( SW_SHOWNRMAL ).
| Имя | Значение | Описание |
| SW_HIDE | 0 | Скрывает окно и делает активным другое окно |
| SW_SHOWNORMAL | 1 | Отображает и делает активным окно в его первоначальном размере и положении. |
| SW_SHOWMINIMIZED | 2 | Активизирует окно и отображает его в свернутом виде |
| SW_SHOWMAXIMIZED | 3 | Активизирует окно и отображает его в полноэкранном виде |
| SW_SHOWNOACTIVATE | 4 | Отображает окно аналогично SW_SHOWNORMAL , но не активизирует его |
| SW_SHOW | 5 | Отображает и делает активным окно с текущим размером и положением. |
| SW_MINIMIZE | 6 | Сворачивает текущее окно и делает активным следующее окно в порядке очереди. |
| SW_SHOWMINNOACTIVE | 7 | Сворачивает окно аналогично SW_SHOWMINIMIZED , но не активизирует его. |
| SW_SHOWNA | 8 | Отображает окно в текущей позиции аналогично SW_SHOW , но не активизирует его. |
| SW_RESTORE | 9 | Отображает и активизирует окно. Если окно было свернуто или развернуто во весь экран, оно отображается в своем первоначальном положении и размере. |
| SW_SHOWDEFAULT | 10 | Отображает окно способом, заданным по умолчанию. |
| SW_FORCEMINIMIZE | 11 | Применяется для минимизации окон, связанных с различными потоками. |
В структуре стартовой функции Windows можно выделить следующие операции, образующие «скелет» программы:
- регистрация класса окна;
- создание главного окна;
- отображение и перерисовка главного окна;
- цикл обработки очереди сообщений.
Регистрация класса окна
Регистрация класса окна осуществляется функцией
ATOM WINAPI RegisterClass (_In_ const WNDCLASS *lpWndClass);
Прототип функции находится в файле библиотеки user32.dll.
Единственным аргументом функции является указатель на структуру
typedef struct _ WNDCLASS <
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCTSTR lpszMenuName;
LPCTSTR lpszClassName; > WNDCLASS ;
Члены структуры
style - устанавливает стиль(и) класса. Этот член структуры может быть любой комбинацией стилей класса.
| Имя | Значение | Описание |
| CS_VREDRAW | 0x01 | Вертикальная перерисовка: осуществлять перерисовку окна при перемещении или изменении высоты окна. |
| CS_HREDRAW | 0x02 | Горизонтальная перерисовка: осуществлять перерисовку окна при перемещении или изменении ширины окна. |
| CS_KEYCVTWINDOW | 0x04 | В окне будет выполняться преобразование виртуальных клавиш. |
| CS_DBLCLKS | 0x08 | Окну будут посылаться сообщения о двойном щелчке кнопки мыши. |
| CS_OWNDC | 0x20 | Каждому экземпляру окна присваивается собственный контекст изображения. |
| CS_CLASSDC | 0x40 | Классу окна присваивается собственный контекст изображения,который можно разделить между копиями. |
| CS_PARENTDC | 0x80 | Классу окна передается контекст изображения родительского окна. |
| CS_NOKEYCVT | 0x100 | Отключается преобразование виртуальных клавиш. |
| CS_NOCLOSE | 0x200 | Незакрываемое окно: в системном меню блокируется выбор пункта закрытия окна. |
| CS_SAVEBITS | 0x800 | Часть изображения на экране, закрытая окном, сохраняется. |
| CS_BYTEALIGNCLIENT | 0x1000 | Выравнивание клиентской области окна: использование границы по байту по оси x. |
| CS_BYTEALIGNWINDOW | 0x2000 | Выравнивание окна: bспользование границы по байту по оси x. |
| CS_PUBLICCLASS CS_GLOBALCLASS | 0x4000 | Определяется глобальный класс окон. |
lpfnWndProc - указатель на оконную процедуру.
cbClsExtra - устанавливает число дополнительных байт, которые размещаются вслед за структурой класса окна. Система инициализирует эти байты нулями, в большинстве случаев равен 0.
cbWndExtra - устанавливает число дополнительных байтов, которые размещаются вслед за экземпляром окна. Система инициализирует байты нулями.
hInstance - дескриптор экземпляра, который содержит оконную процедуру для класса.
hIcon - дескриптор значка класса, дескриптор ресурса значка. Если этот член структуры - NULL, система предоставляет заданный по умолчанию значок.
hCursor - дескриптор курсора класса, дескриптор ресурса курсора. Если этот член структуры - NULL, приложение устанавливает форму курсора всякий раз, когда мышь перемещается в окно прикладной программы.
hbrBackground - дескриптор кисти фона класса, дескриптор физической кисти, которая используется, чтобы красить цветом фона, или код цвета, преобразованный к типу HBRUSH .
lpszMenuName - указатель на символьную строку с символом конца строки ( '\0' ), которая устанавливает имя ресурса меню класса. Можно использовать целое число, чтобы идентифицировать меню с помощью макроса MAKEINTRESOURCE( int ) . Если этот член структуры - NULL , окна, принадлежащие этому классу, не имеют заданного по умолчанию меню.
lpszClassName - указатель на символьную строку с именем класса, оканчивающуюся '\0' .
Создание окна
Создание окна осуществляется функцией
HWND WINAPI CreateWindow (
_In_opt_ LPCTSTR lpClassName,
_In_opt_ LPCTSTR lpWindowName,
_In_ D WORD dwStyle,
_In_ int x,
_In_ int y,
_In_ int nWidth,
_In_ int nHeight,
_In_opt_ HWND hWndParent,
_In_opt_ HMENU hMenu,
_In_opt_ HINSTANCE hInstance,
_In_opt_ LPVOID lpParam );
Прототип функции находится в файле библиотеки user32.dll.
Возвращаемое значение – дескриптор создаваемого окна. В случае невозможности создать окно возвращается NULL.
Аргументы функции :
lpClassName – указывает на строку с '\0' в конце, которая определяет имя класса окна. Имя класса может быть зарегистрированным функцией RegisterClass или любым из предопределенных имен класса элементов управления.
lpWindowName - указывает на строку с '\0' в конце, которая определяет имя окна.
dwStyle - определяет стиль создаваемого окна.
| Имя | Значение | Описание |
| WS_BORDER | 0x00800000 | Окно имеет тонкую границу в виде линии. |
| WS_CAPTION | 0x00C00000 | Окно имеет строку заголовка. |
| WS_CHILD | 0x40000000 | Окно является дочерним. |
| WS_DISABLED | 0x08000000 | Окно является изначально неактивным. |
| WS_GROUP | 0x00020000 | Окно группирует другие управляющие элементы. |
| WS_HSCROLL | 0x00100000 | Окно содержит горизонтальную полосу прокрутки. |
| WS_MAXIMIZE | 0x01000000 | Исходный размер окна – во весь экран. |
| WS_MINIMIZE | 0x20000000 | Исходно окно свернуто. |
| WS_OVERLAPPED | 0x00000000 | Окно может быть перекрыто другими окнами. |
| WS_POPUP | 0x80000000 | Всплывающее окно. |
| WS_SYSMENU | 0x00080000 | Окно имеет системное меню в строке заголовка. |
| WS_VISIBLE | 0x10000000 | Окно изначально видимое. |
| WS_VSCROLL | 0x00200000 | Окно имеет вертикальную полосу прокрутки. |
x - определяет координату левой стороны окна относительно левой стороны экрана. Измеряется в единицах измерения устройства, чаще всего в точках (pt). Для дочернего окна определяет координату левой стороны относительно начальной координаты родительского окна. Если установлен как CW_USEDEFAULT , Windows выбирает заданную по умолчанию позицию окна.
у – определяет координату верхней стороны окна относительно верхней стороны экрана. Измеряется в единицах измерения устройства, чаще всего в точках (pt). Для дочернего окна определяет координату верхней стороны относительно начальной координаты родительского окна.
nWidth – определяет ширину окна в единицах измерения устройства. Если параметр соответствует CW_USEDEFAULT , Windows выбирает заданную по умолчанию ширину и высоту для окна.
nHeight – определяет высоту окна в единицах измерения устройства.
hWndParent – дескриптор родительского окна.
hMenu – идентифицирует меню, которое будет использоваться окном. Этот параметр может быть NULL , если меню класса будет использовано.
hInstance - идентифицирует экземпляр модуля, который будет связан с окном.
lpParam - указывает на значение, переданное окну при создании.
Отображение и перерисовка окна
Отображение окна осуществляется функцией
BOOL WINAPI ShowWindow(
_In_ HWND hWnd,
_In_ int nCmdShow);
Прототип функции находится в файле библиотеки user32.dll.
Возвращаемое значение: 1 – успешное отображение окна, 0 – ошибка.
Аргументы функции :
hWnd – дескриптор отображаемого окна.
nCmdShow – константа, определяющая, как будет отображаться окно согласно таблице.
Перерисовка окна осуществляется функцией
BOOL UpdateWindow(_In_ HWND hWnd);
Прототип функции находится в файле библиотеки user32.dll.
Возвращаемое значение: 1 – успешная перерисовка окна, 0 – ошибка.
Аргумент функции hWnd – дескриптор окна.
Цикл обработки сообщений
После вызова функции UpdateWindow , окно окончательно выведено на экран. Теперь программа должна подготовить себя для получения информации от пользователя через клавиатуру и мышь. Windows поддерживает "очередь сообщений" (message queue) для каждой программы, работающей в данный момент в системе Windows. Когда происходит ввод информации, Windows преобразует ее в "сообщение", которое помещается в очередь сообщений программы. Программа извлекает сообщения из очереди сообщений, выполняя блок команд, известный как "цикл обработки сообщений" (message loop):
while ( GetMessage (&msg, NULL ,0,0))
TranslateMessage(&msg);
DispatchMessage (&msg);
>
Для получения сообщения из очереди используется функция:
BOOL WINAPI GetMessage (
_Out_ LP MSG lpMsg,
_In_opt_ HWND hWnd,
_In_ UINT wMsgFilterMin,
_In_ UINT wMsgFilterMax);
Прототип функции находится в файле библиотеки user32.dll.
В случае получения из очереди сообщения, отличного от WM_QUIT , возвращает ненулевое значение.
Аргументы функции :
lpMsg - указатель на структуру сообщения.
typedef struct MSG <
HWND hwnd; // дескриптор окна, очередь сообщений которого просматривается
UINT message; // идентификатор сообщения
WPARAM wParam; // дополнительная информация о сообщении,
LPARAM lParam; // зависит от идентификатора сообщения
D WORD time; // время помещения сообщения в очередь
POINT pt; // структура, содержащая координаты курсора в момент помещения сообщения в очередь
> MSG ;
Структура POINT имеет вид
typedef struct POINT
LONG x; // координата x
LONG y; // координата y
> POINT ;
hWnd - дескриптор окна, очередь для которого просматривается.
wMsgFilterMin - нижняя граница фильтра идентификаторов сообщений.
wMsgFilterMax - верхняя граница фильтра идентификаторов сообщений.
BOOL WINAPI TranslateMessage(_In_ const MSG *lpMsg);
передает аргумент - структуру msg обратно в Windows для преобразования какого-либо сообщения с клавиатуры. Возвращает ненулевое значение в случае успешной расшифровки сообщения, 0 – ошибка.
LRESULT WINAPI DispatchMessage (_In_ const MSG *lpmsg );
передает аргумент - структуру msg обратно в Windows. Windows отправляет сообщение для его обработки соответствующей оконной процедуре — таким образом, Windows вызывает соответствующую оконную функцию, указанную при регистрации класса окна.
После того, как оконная функция обработает сообщение, оно возвращается в Windows, которая все еще обслуживает вызов функции DispatchMessage . Когда Windows возвращает управление в стартовую функцию WinMain() к следующему за вызовом DispatchMessage коду, цикл обработки сообщений в очередной раз возобновляет работу, вызывая GetMessage .
Возвращает значение, определяемое оконной функцией, которое чаще всего игнорируется.
Прототипы функций находятся в файле библиотеки user32.dll.
Пример стартовой функции, создающей и выводящей окно размером 500х300 точек:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include
LONG WINAPI WndProc( HWND , UINT , WPARAM , LPARAM );
int WINAPI WinMain ( HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
HWND hwnd; // дескриптор окна
MSG msg; // структура сообщения
WNDCLASS w; // структура класса окна
// Регистрация класса окна
memset(&w,0, sizeof ( WNDCLASS ));
w.style = CS_HREDRAW | CS_VREDRAW ;
w.lpfnWndProc = WndProc; // имя оконной функции
w.hInstance = hInstance;
w.hbrBackground = ( HBRUSH )(WHITE_BRUSH);
w.lpszClassName = "My Class" ;
RegisterClass (&w);
// Создание окна
hwnd = CreateWindow ( "My Class", "Окно пользователя" ,
WS_OVERLAPPEDWINDOW , 500, 300, 500, 380, NULL , NULL , hInstance, NULL );
ShowWindow(hwnd,nCmdShow); // отображение
UpdateWindow(hwnd); // перерисовка
// Цикл обработки сообщений
while ( GetMessage (&msg, NULL ,0,0))
TranslateMessage(&msg);
DispatchMessage (&msg);
>
return msg.wParam;
>
Примечание : Для корректной сборки приложения используется многобайтовая кодировка.
Оконная функция - обработка сообщений окна
Оконная функция предназначена для обработки сообщений окна. Функция обработки сообщений окна организована по принципу ветвления, состоящего из последовательной проверки типа сообщения. При совпадении типа сообщения, переданного в структуре Message с соответствующей веткой, осуществляется его обработка. Минимальный вид оконной функции представлен ниже.
1
2
3
4
5
6
7
8
9
10
11
12
LONG WINAPI WndProc( HWND hwnd, UINT Message, WPARAM wparam, LPARAM lparam)
switch (Message)
case WM_DESTROY :
PostQuitMessage(0);
break ;
default:
return DefWindowProc (hwnd, Message, wparam, lparam);
>
return 0;
>
4 аргумента оконной функции идентичны первым четырем полям структуры сообщения MSG .
В примере обрабатывается только один тип сообщения WM_DESTROY , которое передается оконной функции при закрытии окна.
Вызов функции DefWindowProc() обрабатывает по умолчанию все сообщения, которые не обрабатывает оконная процедура.
Функция PostQuitMessage() сообщает Windows, что данный поток запрашивает завершение. Аргументом является целочисленное значение, которое функция вернет операционной системе.

Результат выполнения программы, выводящей окно:
Как создать окно (приложение)
wikiHow работает по принципу вики, а это значит, что многие наши статьи написаны несколькими авторами. При создании этой статьи над ее редактированием и улучшением работали, в том числе анонимно, 28 человек(а).
Количество просмотров этой статьи: 19 764.
В этой статье:
Вы никогда не задумывались, как сделаны такие программы, как Paint или калькулятор? Ну, узнайте тогда, как создать простое приложение, используя это пошаговое руководство.
![]()
Приобретите компилятор. Компилятор преобразует необработанный исходный код (который вы скоро напишете) в исполняемое приложение. Для целей этого урока приобретите DEV-CPP IDE. Вы можете скачать его здесь here.
![]()
Установив DEV-CPP, откройте его. Вам будет представлено окно с текстовой областью, где вы будете писать свой исходный код.
![]()
Подготовьтесь к написанию программы для отображения текста в текстовом поле. Перед тем как начать писать исходный текст, имейте в виду, что приложения Win32 не ведут себя таким же образом, как другие языки, такие как JAVA.
![]()
В главном окне DEV- CPP перейдите в меню File -> New -> Project. Вам будет предложено другое окно. Выберите небольшую картинку с названием "Windows Application" и установите язык как "C", а не "C++." В текстовом поле, где написано "Name", введите "SimpleProgram". Далее DEV-CPP спросит вас, куда вы хотите сохранить его. Сохраните файл в любом каталоге, но только убедитесь, что сохранили его. Как только закончите с этим, вам будет представлен шаблон на экране источника. Нажмите Ctrl + A, а затем Backspace. Причина, почему мы делаем так, это то, что мы можем начинать заново.
![]()
В начале вашего исходного текста напишите "#include " (без кавычек). Это включает в себя библиотеку windows, так что вы можете создавать приложение. Прямо под этим напишите: #include "resource.h" И затем введите: const char g_szClassName[] = "myWindowClass";
![]()
Напишите один способ для обработки всех сообщений и напишите другой метод, где мы будем обрабатывать сообщения из ресурсов. Не волнуйтесь, если вас это смущает. Все станет ясно в дальнейшем. Теперь сохраните ваш источник как SimpleProg.c. На данный момент мы оставив все как есть.
![]()
Создайте скрипт ресурса Resource Script. Resource Script является частью исходного кода, который определяет все элементы управления (например,TextBox, Buttons, и т.д.). Вы включите Resource Script в вашу программу и вуаля! У вас будет программа. Написать Resource Script хоть и просто, но это может занять много времени, если у вас нет Visual Editor. Это потому, что вам нужно будет подсчитать приблизительно точные X и Y координаты элементов управления и т.д. В главном окне DEV-CPP перейдите в меню File -> New -> Resource File. DEV-CPP спросит вас: "Add resource file to current Project?". Нажмите YES. В верхней части вашего скрипта ресурса введите #include "resource.h", and also type #include Это касается всех элементов управления.
![]()
Создайте свой первый элемент управления: простое меню. Введите:
IDR_THEMENU MENU BEGIN POPUP "&File" BEGIN MENUITEM "E&xit", ID_FILE_EXIT END END
Часть "IDR_THEMENU" определяет ваше меню как THEMENU. Вы можете назвать его, как хотите. Часть BEGIN говорит сама за себя. POPUP "&File" создает категорию нового меню под названием File. Символ & позволяет пользователю вашего приложения нажимать Ctrl + F на клавиатуре и быстро получить доступ к меню. The MENUITEM "E&xit", ID_FILE_EXIT добавляет пункт меню в категорию File. Вы должны, однако, определить пункт меню с помощью ID_FILE_EXIT.
![]()
Теперь что касается кнопки. Ваша кнопка будет внутри диалогового окна, поэтому мы должны создать его в первую очередь. Сделайте это, набрав:
IDD_SIMPLECONTROL DIALOG 50, 50, 150, 142 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU MENU IDR_THEMENU CAPTION "Simple Prog" FONT 8, "MS Sans Serif" BEGIN DEFPUSHBUTTON "Hello!", ID_HELLO, 10, 10, 40, 15 END
Часть IDD_SIMPLECONTROL определяет ваш диалог. Четыре цифры после слова "DIALOG" определяют х-позицию, у-позицию, ширину и высоту диалогового окна. Не волнуйтесь слишком о части Style на данный момент. Часть MENU IDR_THEMENU помещает наше старое меню в программу. Часть CAPTION говорит сама за себя, как и шрифт. Часть DEFPUSHBUTTON создает нашу кнопку с названием "Hello!" и определим ее, написав ID_HELLO и задав ей координаты х–позиции, у-позиции, ширину и высоту.
![]()
Вот и все! Мы закончили с нашим скриптом ресурса. Только еще одно: мы должны присвоить значения всем величинам, которых мы определили в нашем скрипте ресурсов (например, IDR_THEMENU, и т.д.). Сохраните файл ресурса как SimpleProg.rc
![]()
Выберите File -> New -> Source File. Add the source file to the current project? -> Yes. Вам будет предложен пустой экран. Для присвоения значений нашим определенным элементам управления мы даем им числа. Не имеет большого значения, каким числам вы присваиваете элементы управления, но они должны быть организованными. Например, не определяйте элемент управления, присвоив ему случайное число (как 062 491 или пр.). Поэтому введите:
#define IDR_THEMENU 100 #define ID_FILE_EXIT 200 #define IDD_SIMPLECONTROL 300 #define ID_HELLO 400
![]()
Сохранить этот файл как resource.h . Помните, как мы писали "#include "resource.h""? Ну, вот почему мы это сделали. Нам нужно было присвоить значения.
![]()
Вернитесь к ресурсу, нашему SimpleProg.c или тому, как его вы назвали. Введите:
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)return DialogBox(hInstance, MAKEINTRESOURCE(IDD_SIMPLECONTROL), NULL, SimpleProc);>
![]()
Не беспокойтесь слишком о всех технических нюансах здесь. Просто знайте, что это части возвращают диалоговое окно к нашей процедуре обработки сообщений, называемой SimpleProc.
![]()
![]()
Эта часть обрабатывает диалоговые сообщения. Например, в случае ID_HELLO (наша кнопка), мы создаем окно сообщения с содержанием "Hello!". Кроме того, в случае, когда переходим в File и Exit, мы закрываем окно в случае ID_FILE_EXIT.
![]()
Убедитесь, что ваш SimpleProc предшествует части int WINAPI WINMAIN ! Это важно, если вы хотите, чтобы ваша программа работала.