Графический интерфейс Windows

Бизюк Андрей

ВГТУ

2025-02-24

Парадигмы консольного и графического пользовательского интерфейса

Введение

Пользовательский интерфейс (UI) — это важная часть взаимодействия человека с компьютером. Он определяет, как пользователь воспринимает и использует программное обеспечение. В истории развития компьютеров сформировались две основные парадигмы интерфейсов: консольный интерфейс (CUI, Command Line Interface) и графический интерфейс (GUI, Graphical User Interface). Каждая из этих парадигм имеет свои особенности, преимущества и недостатки.

1. Консольный пользовательский интерфейс (CUI)

1.1. Определение и история

Консольный интерфейс — это текстовый способ взаимодействия пользователя с компьютером. Пользователь вводит команды с клавиатуры, а система выводит результаты в текстовом виде. Этот тип интерфейса был основным в ранних компьютерах (например, в системах с операционными системами Unix, MS-DOS).

1.2. Особенности

  • Текстовый ввод/вывод: Все команды и результаты отображаются в виде текста.
  • Минимализм: Не требует графических ресурсов, что делает его легковесным.
  • Гибкость: Позволяет выполнять сложные операции с помощью команд и скриптов.
  • Автоматизация: Легко автоматизируется с помощью сценариев (скриптов).

1.3. Преимущества

  • Производительность: Подходит для опытных пользователей, которые могут быстро выполнять задачи.
  • Ресурсоэффективность: Не требует мощного железа для работы.
  • Универсальность: Подходит для удаленного управления через SSH или Telnet.

1.4. Недостатки

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

1.5. Примеры использования

  • Администрирование серверов.
  • Разработка программного обеспечения (например, использование Git через командную строку).
  • Автоматизация задач (например, написание bash-скриптов).

2. Графический пользовательский интерфейс (GUI)

2.1. Определение и история

Графический интерфейс — это визуальный способ взаимодействия пользователя с компьютером. Он использует графические элементы, такие как окна, кнопки, иконки и меню. GUI стал популярным с появлением операционных систем, таких как Windows, macOS и графических оболочек для Linux (например, GNOME, KDE).

2.2. Особенности

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

2.3. Преимущества

  • Удобство: Подходит для начинающих пользователей.
  • Наглядность: Визуальное представление данных упрощает восприятие.
  • Мультизадачность: Возможность работы с несколькими окнами одновременно.

2.4. Недостатки

  • Ресурсоемкость: Требует больше вычислительных ресурсов.
  • Ограниченная гибкость: Некоторые сложные операции сложнее выполнять через GUI.
  • Зависимость от оборудования: Требует наличия графического оборудования (монитор, видеокарта).

2.5. Примеры использования

  • Операционные системы (Windows, macOS, Linux с графической оболочкой).
  • Офисные приложения (Microsoft Office, LibreOffice).
  • Веб-браузеры (Google Chrome, Mozilla Firefox).

3. Сравнение CUI и GUI

Характеристика CUI (Консольный интерфейс) GUI (Графический интерфейс)
Удобство для новичков Низкое Высокое
Ресурсоемкость Низкая Высокая
Гибкость Высокая Ограниченная
Автоматизация Легко автоматизируется Сложнее автоматизировать
Визуализация Текстовая Графическая

4. Современные тенденции

4.1. Гибридные интерфейсы

Современные системы часто сочетают в себе элементы CUI и GUI. Например, в операционных системах, таких как Windows и macOS, можно использовать командную строку (PowerShell, Terminal) вместе с графическим интерфейсом.

4.2. Веб-интерфейсы

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

4.3. Голосовые интерфейсы

С развитием искусственного интеллекта и технологий распознавания речи появляются новые парадигмы взаимодействия, такие как голосовые помощники (Siri, Alexa).

Заключение

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

Класс  окна  в операционных  системах,  предопределенные  классы, получение  и изменение  данных  окна  и класса.

1. Введение

В операционных системах (ОС), таких как Windows, окна являются основными элементами графического интерфейса пользователя (GUI). Каждое окно принадлежит определенному классу, который определяет его поведение, внешний вид и функциональность. Класс окна — это шаблон, на основе которого создаются экземпляры окон.

2. Понятие класса окна

Класс окна — это структура данных, которая содержит информацию о: - Процедуре окна (Window Procedure) — функция, обрабатывающая сообщения, отправляемые окну. - Стилях окна (Window Styles) — параметры, определяющие внешний вид и поведение окна (например, наличие рамки, заголовка, кнопок управления). - Иконке и курсоре — ресурсы, используемые окном. - Фоне окна — цвет или кисть, используемая для заливки фона. - Меню — меню по умолчанию, связанное с окном.

Класс окна регистрируется в системе перед созданием окна.

3. Предопределенные классы окон

Операционные системы предоставляют набор предопределенных классов, которые можно использовать для создания стандартных окон. Примеры предопределенных классов в Windows: - BUTTON — класс для создания кнопок. - EDIT — класс для создания текстовых полей. - LISTBOX — класс для создания списков. - COMBOBOX — класс для создания комбинированных списков. - STATIC — класс для создания статических текстовых элементов.

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

4. Регистрация пользовательского класса окна

Если предопределенные классы не подходят, можно зарегистрировать собственный класс окна. Для этого используется функция RegisterClass или RegisterClassEx (в Windows). Пример:

WNDCLASS wc = {};
wc.lpfnWndProc = WindowProc; // Указатель на процедуру окна
wc.hInstance = hInstance;    // Дескриптор экземпляра приложения
wc.lpszClassName = L"MyWindowClass"; // Имя класса

// Регистрация класса
RegisterClass(&wc);

После регистрации класса можно создавать окна на его основе.

5. Получение данных окна и класса

Для получения информации об окне и его классе используются следующие функции: - GetWindowInfo — возвращает информацию об окне. - GetClassInfo — возвращает информацию о классе окна. - GetClassName — возвращает имя класса, к которому принадлежит окно.

Пример:

TCHAR className[256];
GetClassName(hwnd, className, 256); // Получение имени класса окна

6. Изменение данных окна и класса

Некоторые параметры окна и класса можно изменить динамически: - SetWindowLong — изменяет атрибуты окна (например, стили). - SetClassLong — изменяет атрибуты класса (например, иконку или курсор).

Пример:

SetWindowLong(hwnd, GWL_STYLE, WS_OVERLAPPEDWINDOW); // Изменение стиля окна
SetClassLong(hwnd, GCL_HICON, (LONG)newIcon); // Изменение иконки класса

7. Пример использования

Рассмотрим пример создания окна с пользовательским классом:

#include <windows.h>

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    switch (uMsg) {
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
        default:
            return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    WNDCLASS wc = {};
    wc.lpfnWndProc = WindowProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = L"MyWindowClass";

    RegisterClass(&wc);

    HWND hwnd = CreateWindowEx(0, L"MyWindowClass", L"Мое окно", WS_OVERLAPPEDWINDOW,
                               CW_USEDEFAULT, CW_USEDEFAULT, 500, 300, NULL, NULL, hInstance, NULL);

    ShowWindow(hwnd, nCmdShow);

    MSG msg = {};
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return 0;
}

8. Заключение

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

Событийное  управление  приложениями

1. Событийное управление приложениями

Событийное управление (Event-Driven Programming) — это парадигма, при которой выполнение программы определяется событиями (например, клик мыши, нажатие клавиши, закрытие окна).

Основные понятия:
- Событие (Event):
- Это действие или изменение состояния, которое обрабатывается программой (например, нажатие кнопки).
- Обработчик событий (Event Handler):
- Это функция, которая вызывается при возникновении определенного события.

Цикл обработки событий:
1. Программа ожидает события (например, ввод пользователя).
2. Когда событие происходит, система отправляет его в очередь событий.
3. Программа извлекает событие из очереди и вызывает соответствующий обработчик.
4. Обработчик выполняет необходимые действия.

Примеры событий в графическом интерфейсе:
- Click — клик мыши.
- KeyPress — нажатие клавиши.
- Resize — изменение размера окна.
- Close — закрытие окна.

Преимущества событийного управления:
- Удобство для создания интерактивных приложений.
- Эффективное использование ресурсов (программа не занимает процессорное время в ожидании).
- Поддержка многозадачности.

2. Пример работы с окнами и событиями (на примере Windows API)

#include <windows.h>

// Обработчик событий (Window Procedure)
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    switch (uMsg) {
        case WM_DESTROY:
            PostQuitMessage(0); // Завершение программы при закрытии окна
            return 0;
        case WM_PAINT:
            // Отрисовка содержимого окна
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hwnd, &ps);
            TextOut(hdc, 50, 50, L"Hello, World!", 13);
            EndPaint(hwnd, &ps);
            return 0;
    }
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    // Регистрация класса окна
    WNDCLASS wc = {};
    wc.lpfnWndProc = WindowProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = L"MainWindowClass";
    RegisterClass(&wc);

    // Создание окна
    HWND hwnd = CreateWindowEx(0, L"MainWindowClass", L"Hello, World!", WS_OVERLAPPEDWINDOW,
                               CW_USEDEFAULT, CW_USEDEFAULT, 800, 600, NULL, NULL, hInstance, NULL);

    ShowWindow(hwnd, nCmdShow);

    // Цикл обработки событий
    MSG msg = {};
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return 0;
}

3. Заключение

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

Использование GDI32

Простой пример с рисованием фигур

Пример простого приложения на Win32, которое демонстрирует возможности библиотеки gdi32, может включать в себя создание окна и рисование различных фигур и текста с использованием функций GDI (Graphics Device Interface). Вот пример такого приложения:

#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) {
    static TCHAR szAppName[] = TEXT("GDIExample");
    HWND hwnd;
    MSG msg;
    WNDCLASS wndclass;

    wndclass.style = CS_HREDRAW | CS_VREDRAW;
    wndclass.lpfnWndProc = WndProc;
    wndclass.cbClsExtra = 0;
    wndclass.cbWndExtra = 0;
    wndclass.hInstance = hInstance;
    wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
    wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wndclass.lpszMenuName = NULL;
    wndclass.lpszClassName = szAppName;
    if (!RegisterClass(&wndclass)) {
        MessageBox(NULL, TEXT("This program requires Windows NT!"), szAppName, MB_ICONERROR);
        return 0;
    }

    hwnd = CreateWindow(szAppName, TEXT("GDI Example"), WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, NULL, hInstance, NULL);

    ShowWindow(hwnd, iCmdShow);
    UpdateWindow(hwnd);

    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
    HDC hdc;
    PAINTSTRUCT ps;
    RECT rect;

    switch (message) {
    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);

        // Рисуем прямоугольник
        Rectangle(hdc, 50, 50, 200, 200);

        // Рисуем эллипс
        Ellipse(hdc, 250, 50, 400, 200);

        // Рисуем линию
        MoveToEx(hdc, 50, 250, NULL);
        LineTo(hdc, 400, 250);

        // Выводим текст
        SetTextAlign(hdc, TA_CENTER);
        TextOut(hdc, 225, 300, TEXT("Hello, GDI!"), 11);

        EndPaint(hwnd, &ps);
        return 0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, message, wParam, lParam);
}

Описание кода:

  1. WinMain: Это точка входа приложения. Здесь регистрируется класс окна, создается окно и запускается цикл обработки сообщений.
  2. WndProc: Это процедура окна, которая обрабатывает сообщения, отправляемые окну. В данном примере она обрабатывает сообщение WM_PAINT, которое отправляется, когда окно нужно перерисовать.
  3. WM_PAINT: В этом блоке кода используется контекст устройства (HDC) для рисования различных фигур и текста:
    • Rectangle рисует прямоугольник.
    • Ellipse рисует эллипс.
    • MoveToEx и LineTo рисуют линию.
    • TextOut выводит текст на экран.

Компиляция:

Для компиляции этого кода вам понадобится компилятор, поддерживающий WinAPI, например, MinGW или Visual Studio. В Visual Studio можно создать новый проект Win32 Application и вставить этот код в файл main.cpp.

Запуск:

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

Использование различных режимов наложения

Добавим в пример использование функций для работы с битмапами и различными режимами наложения (ROP2). Мы загрузим битмап из файла, отобразим его на экране и продемонстрируем различные режимы наложения с помощью функции SetROP2.

#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) {
    static TCHAR szAppName[] = TEXT("GDIExample");
    HWND hwnd;
    MSG msg;
    WNDCLASS wndclass;

    wndclass.style = CS_HREDRAW | CS_VREDRAW;
    wndclass.lpfnWndProc = WndProc;
    wndclass.cbClsExtra = 0;
    wndclass.cbWndExtra = 0;
    wndclass.hInstance = hInstance;
    wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
    wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wndclass.lpszMenuName = NULL;
    wndclass.lpszClassName = szAppName;

    if (!RegisterClass(&wndclass)) {
        MessageBox(NULL, TEXT("This program requires Windows NT!"), szAppName, MB_ICONERROR);
        return 0;
    }
    hwnd = CreateWindow(szAppName, TEXT("GDI Example with Bitmaps and ROP2"), WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, NULL, hInstance, NULL);

    ShowWindow(hwnd, iCmdShow);
    UpdateWindow(hwnd);

    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
    HDC hdc;
    PAINTSTRUCT ps;
    HBITMAP hBitmap;
    BITMAP bitmap;
    HDC hdcMem;
    HGDIOBJ oldBitmap;

    switch (message) {
    case WM_CREATE:
        // Загружаем битмап из файла
        hBitmap = (HBITMAP)LoadImage(NULL, TEXT("example.bmp"), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
        if (hBitmap == NULL) {
            MessageBox(hwnd, TEXT("Не удалось загрузить битмап!"), TEXT("Ошибка"), MB_ICONERROR);
        }
        // Сохраняем битмап в глобальной переменной
        SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)hBitmap);
        return 0;
    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);

        // Получаем битмап из глобальной переменной
        hBitmap = (HBITMAP)GetWindowLongPtr(hwnd, GWLP_USERDATA);
        if (hBitmap != NULL) {
            // Создаем контекст устройства в памяти
            hdcMem = CreateCompatibleDC(hdc);
            oldBitmap = SelectObject(hdcMem, hBitmap);

            // Получаем информацию о битмапе
            GetObject(hBitmap, sizeof(bitmap), &bitmap);

            // Отображаем битмап на экране
            BitBlt(hdc, 50, 50, bitmap.bmWidth, bitmap.bmHeight, hdcMem, 0, 0, SRCCOPY);

            // Демонстрация режимов наложения (ROP2)
            SetROP2(hdc, R2_NOT); // Инвертирование цвета
            Rectangle(hdc, 100, 100, 300, 200);

            SetROP2(hdc, R2_XORPEN); // Исключающее ИЛИ
            Ellipse(hdc, 150, 150, 350, 250);

            SetROP2(hdc, R2_MERGEPEN); // Объединение с цветом пера
            MoveToEx(hdc, 50, 300, NULL);
            LineTo(hdc, 400, 300);

            // Восстанавливаем контекст устройства
            SelectObject(hdcMem, oldBitmap);
            DeleteDC(hdcMem);
        }
        EndPaint(hwnd, &ps);
        return 0;

    case WM_DESTROY:
        // Удаляем битмап
        hBitmap = (HBITMAP)GetWindowLongPtr(hwnd, GWLP_USERDATA);
        if (hBitmap != NULL) {
            DeleteObject(hBitmap);
        }
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, message, wParam, lParam);
}

Описание:

  1. Загрузка битмапа:
    • В обработчике WM_CREATE загружается битмап из файла example.bmp с помощью функции LoadImage.
    • Битмап сохраняется в глобальной переменной, связанной с окном, с использованием SetWindowLongPtr.
  2. Отображение битмапа:
    • В обработчике WM_PAINT создается контекст устройства в памяти (CreateCompatibleDC), и битмап отображается на экране с помощью BitBlt.
  1. Режимы наложения (ROP2):
    • Используется функция SetROP2 для изменения режима наложения:
      • R2_NOT: Инвертирование цвета.
      • R2_XORPEN: Исключающее ИЛИ.
      • R2_MERGEPEN: Объединение с цветом пера.
    • Эти режимы применяются к прямоугольнику, эллипсу и линии.
  2. Удаление битмапа:
    • В обработчике WM_DESTROY битмап удаляется с помощью DeleteObject.

Как использовать:

  1. Создайте файл example.bmp в той же директории, где находится исполняемый файл.
  2. Скомпилируйте и запустите программу.
  3. Вы увидите:
    • Загруженный битмап.
    • Прямоугольник, эллипс и линию, нарисованные с разными режимами наложения.

Примечания:

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

Использование анимации и двойной буферизации

Для создания анимации движения объекта в окне с использованием двойной буферизации (double buffering) в Win32 API, мы будем использовать следующие техники: 1. Двойная буферизация: Рисование происходит в памяти (в буфере), а затем буфер копируется на экран. Это предотвращает мерцание. 2. Таймер: Для обновления позиции объекта и перерисовки окна через определенные интервалы времени.

В этом примере мы создадим анимацию движения круга по экрану.

Пример кода:

#include <windows.h>
#include <math.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) {
    static TCHAR szAppName[] = TEXT("AnimationExample");
    HWND hwnd;
    MSG msg;
    WNDCLASS wndclass;

    wndclass.style = CS_HREDRAW | CS_VREDRAW;
    wndclass.lpfnWndProc = WndProc;
    wndclass.cbClsExtra = 0;
    wndclass.cbWndExtra = 0;
    wndclass.hInstance = hInstance;
    wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
    wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wndclass.lpszMenuName = NULL;
    wndclass.lpszClassName = szAppName;

    if (!RegisterClass(&wndclass)) {
        MessageBox(NULL, TEXT("This program requires Windows NT!"), szAppName, MB_ICONERROR);
        return 0;
    }
    hwnd = CreateWindow(szAppName, TEXT("Animation with Double Buffering"), WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 800, 600,
        NULL, NULL, hInstance, NULL);

    ShowWindow(hwnd, iCmdShow);
    UpdateWindow(hwnd);

    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
    static int x = 100, y = 100; // Начальные координаты круга
    static int dx = 5, dy = 5;   // Скорость движения по осям X и Y
    static int radius = 50;      // Радиус круга

    switch (message) {
    case WM_CREATE:
        // Устанавливаем таймер для обновления анимации каждые 16 мс (~60 FPS)
        SetTimer(hwnd, 1, 16, NULL);
        return 0;

    case WM_TIMER:
        // Обновляем позицию круга
        x += dx;
        y += dy;

        // Проверяем столкновение с границами окна
        RECT rect;
        GetClientRect(hwnd, &rect);

        if (x - radius < 0 || x + radius > rect.right) {
            dx = -dx; // Меняем направление по оси X
        }
        if (y - radius < 0 || y + radius > rect.bottom) {
            dy = -dy; // Меняем направление по оси Y
        }

        // Перерисовываем окно
        InvalidateRect(hwnd, NULL, TRUE);
        return 0;
    case WM_PAINT: {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hwnd, &ps);

        // Создаем контекст устройства в памяти для двойной буферизации
        HDC hdcMem = CreateCompatibleDC(hdc);
        HBITMAP hBitmap = CreateCompatibleBitmap(hdc, rect.right, rect.bottom);
        SelectObject(hdcMem, hBitmap);

        // Очищаем фон
        HBRUSH hBrush = (HBRUSH)GetStockObject(WHITE_BRUSH);
        FillRect(hdcMem, &rect, hBrush);

        // Рисуем круг
        HBRUSH hCircleBrush = CreateSolidBrush(RGB(255, 0, 0)); // Красный круг
        SelectObject(hdcMem, hCircleBrush);
        Ellipse(hdcMem, x - radius, y - radius, x + radius, y + radius);
        DeleteObject(hCircleBrush);

        // Копируем буфер на экран
        BitBlt(hdc, 0, 0, rect.right, rect.bottom, hdcMem, 0, 0, SRCCOPY);

        // Освобождаем ресурсы
        DeleteObject(hBitmap);
        DeleteDC(hdcMem);

        EndPaint(hwnd, &ps);
        return 0;
    }
    case WM_DESTROY:
        // Удаляем таймер
        KillTimer(hwnd, 1);
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, message, wParam, lParam);
}

Описание кода:

  1. Двойная буферизация:
    • В обработчике WM_PAINT создается контекст устройства в памяти (hdcMem), в котором рисуется весь кадр.
    • После завершения рисования буфер копируется на экран с помощью BitBlt.
  2. Таймер:
    • В обработчике WM_CREATE устанавливается таймер, который срабатывает каждые 16 мс (примерно 60 кадров в секунду).
    • В обработчике WM_TIMER обновляется позиция круга и вызывается перерисовка окна с помощью InvalidateRect.
  1. Анимация:
    • Круг движется по экрану, меняя направление при столкновении с границами окна.
    • Позиция круга обновляется в обработчике WM_TIMER.
  2. Столкновение с границами:
    • Если круг достигает границы окна, его скорость по соответствующей оси инвертируется.

Как это работает:

  • Таймер обновляет позицию круга и вызывает перерисовку окна.
  • В WM_PAINT используется двойная буферизация для предотвращения мерцания.
  • Круг рисуется в новом положении, и буфер копируется на экран.

Результат:

  • Красный круг движется по экрану, отскакивая от границ окна.
  • Анимация плавная, без мерцания, благодаря двойной буферизации.

Примечания:

  • Вы можете изменить скорость движения, радиус круга или цвет, чтобы настроить анимацию.
  • Для более сложной анимации можно добавить несколько объектов или изменить их траектории.

Наложение с использованием маски

Для демонстрации наложения изображений с использованием маски в Win32 API, мы создадим пример, где два изображения накладываются друг на друга с использованием маски. Маска позволяет определить, какие части изображения будут прозрачными, а какие — непрозрачными. Это часто используется для создания спрайтов с прозрачным фоном.

Пример кода:

#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) {
    static TCHAR szAppName[] = TEXT("MaskedImageExample");
    HWND hwnd;
    MSG msg;
    WNDCLASS wndclass;

    wndclass.style = CS_HREDRAW | CS_VREDRAW;
    wndclass.lpfnWndProc = WndProc;
    wndclass.cbClsExtra = 0;
    wndclass.cbWndExtra = 0;
    wndclass.hInstance = hInstance;
    wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
    wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wndclass.lpszMenuName = NULL;
    wndclass.lpszClassName = szAppName;

    if (!RegisterClass(&wndclass)) {
        MessageBox(NULL, TEXT("This program requires Windows NT!"), szAppName, MB_ICONERROR);
        return 0;
    }
    hwnd = CreateWindow(szAppName, TEXT("Masked Image Example"), WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 800, 600,
        NULL, NULL, hInstance, NULL);

    ShowWindow(hwnd, iCmdShow);
    UpdateWindow(hwnd);

    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
    static HBITMAP hBitmapImage = NULL;    // Основное изображение
    static HBITMAP hBitmapMask = NULL;     // Маска
    static HBITMAP hBitmapBackground = NULL; // Фоновое изображение

    switch (message) {
    case WM_CREATE:
        // Загружаем изображения
        hBitmapImage = (HBITMAP)LoadImage(NULL, TEXT("image.bmp"), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
        hBitmapMask = (HBITMAP)LoadImage(NULL, TEXT("mask.bmp"), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
        hBitmapBackground = (HBITMAP)LoadImage(NULL, TEXT("background.bmp"), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);

        if (!hBitmapImage || !hBitmapMask || !hBitmapBackground) {
            MessageBox(hwnd, TEXT("Не удалось загрузить изображения!"), TEXT("Ошибка"), MB_ICONERROR);
        }
        return 0;
    case WM_PAINT: {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hwnd, &ps);

        // Получаем размеры клиентской области
        RECT rect;
        GetClientRect(hwnd, &rect);

        // Создаем контекст устройства в памяти для двойной буферизации
        HDC hdcMem = CreateCompatibleDC(hdc);
        HBITMAP hBitmapBuffer = CreateCompatibleBitmap(hdc, rect.right, rect.bottom);
        SelectObject(hdcMem, hBitmapBuffer);

        // Рисуем фоновое изображение
        HDC hdcBackground = CreateCompatibleDC(hdc);
        SelectObject(hdcBackground, hBitmapBackground);
        BitBlt(hdcMem, 0, 0, rect.right, rect.bottom, hdcBackground, 0, 0, SRCCOPY);
        DeleteDC(hdcBackground);

        // Рисуем изображение с маской
        if (hBitmapImage && hBitmapMask) {
            HDC hdcImage = CreateCompatibleDC(hdc);
            HDC hdcMask = CreateCompatibleDC(hdc);

            // Выбираем изображение и маску в контексты устройств
            SelectObject(hdcImage, hBitmapImage);
            SelectObject(hdcMask, hBitmapMask);
            // Шаг 1: Наложение маски на фоновое изображение (AND-операция)
            BitBlt(hdcMem, 100, 100, rect.right, rect.bottom, hdcMask, 0, 0, SRCAND);

            // Шаг 2: Наложение изображения на результат (OR-операция)
            BitBlt(hdcMem, 100, 100, rect.right, rect.bottom, hdcImage, 0, 0, SRCPAINT);

            // Освобождаем контексты устройств
            DeleteDC(hdcImage);
            DeleteDC(hdcMask);
        }

        // Копируем буфер на экран
        BitBlt(hdc, 0, 0, rect.right, rect.bottom, hdcMem, 0, 0, SRCCOPY);

        // Освобождаем ресурсы
        DeleteObject(hBitmapBuffer);
        DeleteDC(hdcMem);

        EndPaint(hwnd, &ps);
        return 0;
    }
    case WM_DESTROY:
        // Удаляем загруженные изображения
        if (hBitmapImage) DeleteObject(hBitmapImage);
        if (hBitmapMask) DeleteObject(hBitmapMask);
        if (hBitmapBackground) DeleteObject(hBitmapBackground);
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, message, wParam, lParam);
}

Описание кода:

  1. Загрузка изображений:
    • В обработчике WM_CREATE загружаются три изображения:
      • Основное изображение (image.bmp).
      • Маска (mask.bmp).
      • Фоновое изображение (background.bmp).
    • Маска должна быть черно-белой: черный цвет (0x000000) обозначает прозрачные области, белый цвет (0xFFFFFF) — непрозрачные.
  1. Наложение изображений:
    • В обработчике WM_PAINT используется двойная буферизация для предотвращения мерцания.
    • Фоновое изображение рисуется на буфере.
    • Основное изображение накладывается на фон с использованием маски:
      • Сначала маска применяется к фону с помощью операции SRCAND (AND-операция). Это делает прозрачные области черными.
      • Затем основное изображение накладывается с помощью операции SRCPAINT (OR-операция). Это заполняет прозрачные области цветами из изображения.
  2. Освобождение ресурсов:
    • В обработчике WM_DESTROY удаляются загруженные изображения.

Как это работает:

  • Маска определяет, какие части изображения будут прозрачными.
  • Основное изображение накладывается на фон только в тех областях, где маска белая.
  • Черные области маски делают соответствующие части фона прозрачными.

Результат:

  • На фоновом изображении отображается основное изображение с прозрачными областями, заданными маской.

Примечания:

  1. Файлы изображений:
    • Создайте три файла: image.bmp, mask.bmp и background.bmp.
    • Маска (mask.bmp) должна быть черно-белой. Черный цвет (0x000000) — прозрачные области, белый цвет (0xFFFFFF) — непрозрачные.
  2. Пример изображений:
    • image.bmp: Основное изображение (например, спрайт).
    • mask.bmp: Черно-белая маска, соответствующая основному изображению.
    • background.bmp: Фоновое изображение.
  1. Компиляция:
    • Скомпилируйте код с помощью компилятора, поддерживающего WinAPI (например, MinGW или Visual Studio).
  2. Запуск:
    • Убедитесь, что файлы image.bmp, mask.bmp и background.bmp находятся в той же директории, что и исполняемый файл.

Ресурсы  приложения,  их  создание  и использование

Ресурсы в Windows-приложениях — это данные, которые хранятся внутри исполняемых файлов (EXE, DLL) или в отдельных файлах и используются программой во время выполнения. Ресурсы позволяют включать в приложение такие элементы, как иконки, строки, диалоговые окна, меню, курсоры, изображения и другие данные, не загромождая основной код программы.

1. Типы ресурсов в Windows

Ресурсы в Windows делятся на несколько типов, каждый из которых имеет своё назначение:

1.1. Стандартные типы ресурсов

  • Иконки (ICON): используются для отображения значков приложения.
  • Курсоры (CURSOR): определяют внешний вид курсора мыши.
  • Диалоговые окна (DIALOG): шаблоны для создания окон с элементами управления.
  • Меню (MENU): определяют структуру меню приложения.
  • Строки (STRINGTABLE): текстовые данные, которые могут быть локализованы.
  • Битовые изображения (BITMAP): растровые изображения.
  • Версия (VERSIONINFO): информация о версии приложения.

1.2. Пользовательские типы ресурсов

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

2. Создание ресурсов

Ресурсы в Windows создаются с помощью файлов ресурсов (.rc), которые компилируются в бинарный формат и включаются в исполняемый файл.

2.1. Файл ресурсов (.rc)

Файл ресурсов — это текстовый файл, который описывает все ресурсы, используемые в приложении. Пример простого .rc файла:

// Иконка приложения
IDI_ICON1 ICON "icon.ico"

// Курсор
IDC_CURSOR1 CURSOR "cursor.cur"

// Строковая таблица
STRINGTABLE
BEGIN
    101 "Привет, мир!"
    102 "Это пример строки."
END

// Диалоговое окно
IDD_DIALOG1 DIALOGEX 0, 0, 200, 100
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION
CAPTION "Пример диалога"
BEGIN
    DEFPUSHBUTTON "OK", IDOK, 10, 70, 50, 14
    PUSHBUTTON "Отмена", IDCANCEL, 70, 70, 50, 14
END

2.2. Компиляция ресурсов

Файл ресурсов компилируется с помощью компилятора ресурсов (rc.exe), который создает бинарный файл (.res). Этот файл затем включается в исполняемый файл на этапе линковки.

Пример команды для компиляции:

rc.exe app.rc

3. Использование ресурсов

Ресурсы загружаются и используются в коде программы с помощью API Windows.

3.1. Загрузка иконки

Пример загрузки иконки:

HICON hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1));

3.2. Загрузка строки

Пример загрузки строки из строковой таблицы:

char buffer[100];
LoadString(hInstance, 101, buffer, sizeof(buffer));
MessageBox(NULL, buffer, "Ресурс", MB_OK);

3.3. Создание диалогового окна

Пример создания диалогового окна:

DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, DialogProc);

3.4. Загрузка битового изображения

Пример загрузки изображения:

HBITMAP hBitmap = LoadBitmap(hInstance, MAKEINTRESOURCE(IDB_BITMAP1));

4. Управление ресурсами

Ресурсы в Windows могут быть как встроенными (внутри EXE или DLL), так и внешними (отдельные файлы). Встроенные ресурсы защищены от изменений, но их сложнее обновлять. Внешние ресурсы проще обновлять, но они менее безопасны.

4.1. Встроенные ресурсы

  • Хранятся внутри исполняемого файла.

  • Загружаются с помощью функций API, таких как LoadIcon, LoadString, LoadBitmap.

  • Пример:

    HICON hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1));

4.2. Внешние ресурсы

  • Хранятся в отдельных файлах.

  • Загружаются с помощью функций, таких как LoadImage, LoadLibrary (для DLL).

  • Пример:

    HBITMAP hBitmap = (HBITMAP)LoadImage(NULL, "image.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);

5. Локализация ресурсов

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

5.1. Создание локализованных ресурсов

  • Создайте отдельный .rc файл для каждого языка.

  • Используйте директиву LANGUAGE для указания языка и кодовой страницы.

  • Пример:

    LANGUAGE LANG_RUSSIAN, SUBLANG_DEFAULT
    STRINGTABLE
    BEGIN
        101 "Привет, мир!"
    END

5.2. Загрузка локализованных ресурсов

  • Используйте функции API, такие как LoadString, для загрузки строк на нужном языке.

  • Пример:

    LoadString(hInstance, 101, buffer, sizeof(buffer));

6. Пример программы с использованием ресурсов

Рассмотрим пример простого Windows-приложения, которое использует ресурсы:

6.1. Файл ресурсов (app.rc)

IDI_ICON1 ICON "app.ico"
IDD_DIALOG1 DIALOGEX 0, 0, 200, 100
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION
CAPTION "Пример диалога"
BEGIN
    DEFPUSHBUTTON "OK", IDOK, 10, 70, 50, 14
    PUSHBUTTON "Отмена", IDCANCEL, 70, 70, 50, 14
END

6.2. Код программы (main.c)

#include <windows.h>

INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    switch (uMsg) {
        case WM_COMMAND:
            if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) {
                EndDialog(hwndDlg, LOWORD(wParam));
                return TRUE;
            }
            break;
    }
    return FALSE;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, DialogProc);
    return 0;
}

Организация  интерфейса  на основе меню

Организация интерфейса на основе меню

Меню — это один из основных элементов пользовательского интерфейса в Windows-приложениях. Оно предоставляет пользователю доступ к функциям программы через иерархическую структуру команд. Меню может быть главным (располагаться в верхней части окна), контекстным (появляться при щелчке правой кнопкой мыши) или всплывающим (popup).

1. Типы меню

1.1. Главное меню

Главное меню располагается в верхней части окна под заголовком. Оно содержит основные команды приложения, такие как “Файл”, “Правка”, “Вид” и т.д.

1.2. Контекстное меню

Контекстное меню появляется при щелчке правой кнопкой мыши на определённом элементе интерфейса. Оно содержит команды, актуальные для текущего контекста.

1.3. Всплывающее меню (Popup Menu)

Всплывающее меню — это меню, которое появляется в определённом месте экрана, например, при нажатии на кнопку.

2. Создание меню

Меню в Windows-приложениях создается с помощью файла ресурсов (.rc) или программно в коде.

2.1. Создание меню в файле ресурсов

Пример создания главного меню в файле ресурсов:

// Файл ресурсов (app.rc)
IDR_MAINMENU MENU
BEGIN
    POPUP "&Файл"
    BEGIN
        MENUITEM "&Открыть", ID_FILE_OPEN
        MENUITEM "&Сохранить", ID_FILE_SAVE
        MENUITEM SEPARATOR
        MENUITEM "В&ыход", ID_FILE_EXIT
    END
    POPUP "&Правка"
    BEGIN
        MENUITEM "&Вырезать", ID_EDIT_CUT
        MENUITEM "&Копировать", ID_EDIT_COPY
        MENUITEM "В&ставить", ID_EDIT_PASTE
    END
    POPUP "&Справка"
    BEGIN
        MENUITEM "&О программе", ID_HELP_ABOUT
    END
END

2.2. Создание меню программно

Пример создания меню в коде:

HMENU hMenu = CreateMenu();
HMENU hFileMenu = CreatePopupMenu();
HMENU hEditMenu = CreatePopupMenu();

AppendMenu(hFileMenu, MF_STRING, ID_FILE_OPEN, "&Открыть");
AppendMenu(hFileMenu, MF_STRING, ID_FILE_SAVE, "&Сохранить");
AppendMenu(hFileMenu, MF_SEPARATOR, 0, NULL);
AppendMenu(hFileMenu, MF_STRING, ID_FILE_EXIT, "В&ыход");

AppendMenu(hEditMenu, MF_STRING, ID_EDIT_CUT, "&Вырезать");
AppendMenu(hEditMenu, MF_STRING, ID_EDIT_COPY, "&Копировать");
AppendMenu(hEditMenu, MF_STRING, ID_EDIT_PASTE, "В&ставить");

AppendMenu(hMenu, MF_POPUP, (UINT_PTR)hFileMenu, "&Файл");
AppendMenu(hMenu, MF_POPUP, (UINT_PTR)hEditMenu, "&Правка");

SetMenu(hwnd, hMenu);

3. Обработка команд меню

Команды меню обрабатываются в оконной процедуре (WndProc) с помощью сообщения WM_COMMAND.

Пример обработки команд:

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case ID_FILE_OPEN:
                    MessageBox(hwnd, "Команда: Открыть", "Меню", MB_OK);
                    break;
                case ID_FILE_SAVE:
                    MessageBox(hwnd, "Команда: Сохранить", "Меню", MB_OK);
                    break;
                case ID_FILE_EXIT:
                    PostQuitMessage(0);
                    break;
                case ID_EDIT_CUT:
                    MessageBox(hwnd, "Команда: Вырезать", "Меню", MB_OK);
                    break;
                case ID_EDIT_COPY:
                    MessageBox(hwnd, "Команда: Копировать", "Меню", MB_OK);
                    break;
                case ID_EDIT_PASTE:
                    MessageBox(hwnd, "Команда: Вставить", "Меню", MB_OK);
                    break;
                case ID_HELP_ABOUT:
                    MessageBox(hwnd, "О программе", "Меню", MB_OK);
                    break;
            }
            break;
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

4. Контекстное меню

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

Пример создания и отображения контекстного меню:

case WM_RBUTTONUP: {
    HMENU hMenu = CreatePopupMenu();
    AppendMenu(hMenu, MF_STRING, ID_CONTEXT_COPY, "Копировать");
    AppendMenu(hMenu, MF_STRING, ID_CONTEXT_PASTE, "Вставить");

    POINT pt;
    GetCursorPos(&pt);
    TrackPopupMenu(hMenu, TPM_RIGHTBUTTON, pt.x, pt.y, 0, hwnd, NULL);
    DestroyMenu(hMenu);
    break;
}

5. Управление состоянием меню

Состояние пунктов меню (активен/неактивен, отмечен/не отмечен) можно изменять динамически.

5.1. Активация/деактивация пунктов меню

Пример:

EnableMenuItem(hMenu, ID_FILE_SAVE, MF_GRAYED); // Деактивировать пункт
EnableMenuItem(hMenu, ID_FILE_SAVE, MF_ENABLED); // Активировать пункт

5.2. Отметка пунктов меню

Пример:

CheckMenuItem(hMenu, ID_VIEW_TOOLBAR, MF_CHECKED); // Отметить пункт
CheckMenuItem(hMenu, ID_VIEW_TOOLBAR, MF_UNCHECKED); // Снять отметку

6. Пример программы с меню

Рассмотрим пример простого Windows-приложения с главным меню.

6.1. Файл ресурсов (app.rc)

IDR_MAINMENU MENU
BEGIN
    POPUP "&Файл"
    BEGIN
        MENUITEM "&Открыть", ID_FILE_OPEN
        MENUITEM "&Сохранить", ID_FILE_SAVE
        MENUITEM SEPARATOR
        MENUITEM "В&ыход", ID_FILE_EXIT
    END
    POPUP "&Правка"
    BEGIN
        MENUITEM "&Вырезать", ID_EDIT_CUT
        MENUITEM "&Копировать", ID_EDIT_COPY
        MENUITEM "В&ставить", ID_EDIT_PASTE
    END
END

6.2. Код программы (main.c)

#include <windows.h>

#define ID_FILE_OPEN 101
#define ID_FILE_SAVE 102
#define ID_FILE_EXIT 103
#define ID_EDIT_CUT 201
#define ID_EDIT_COPY 202
#define ID_EDIT_PASTE 203

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case ID_FILE_OPEN:
                    MessageBox(hwnd, "Открыть файл", "Меню", MB_OK);
                    break;
                case ID_FILE_SAVE:
                    MessageBox(hwnd, "Сохранить файл", "Меню", MB_OK);
                    break;
                case ID_FILE_EXIT:
                    PostQuitMessage(0);
                    break;
                case ID_EDIT_CUT:
                    MessageBox(hwnd, "Вырезать", "Меню", MB_OK);
                    break;
                case ID_EDIT_COPY:
                    MessageBox(hwnd, "Копировать", "Меню", MB_OK);
                    break;
                case ID_EDIT_PASTE:
                    MessageBox(hwnd, "Вставить", "Меню", MB_OK);
                    break;
            }
            break;
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    WNDCLASS wc = {0};
    wc.lpfnWndProc = WndProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = "MyWindowClass";
    RegisterClass(&wc);

    HWND hwnd = CreateWindow("MyWindowClass", "Окно с меню", WS_OVERLAPPEDWINDOW,
                             CW_USEDEFAULT, CW_USEDEFAULT, 400, 300, NULL, NULL, hInstance, NULL);

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
}

Использование диалоговых окон в Windows-приложениях

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

  • Модальные окна блокируют взаимодействие с родительским окном до тех пор, пока пользователь не закроет диалог.
  • Немодальные окна позволяют пользователю взаимодействовать как с диалогом, так и с родительским окном.

1. Создание диалогового окна

Диалоговые окна создаются с помощью файла ресурсов (.rc) или программно в коде.

1.1. Создание диалога в файле ресурсов

Пример создания диалогового окна в файле ресурсов:

// Файл ресурсов (app.rc)
IDD_DIALOG1 DIALOGEX 0, 0, 200, 100
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Пример диалога"
FONT 8, "MS Sans Serif"
BEGIN
    DEFPUSHBUTTON "OK", IDOK, 10, 70, 50, 14
    PUSHBUTTON "Отмена", IDCANCEL, 70, 70, 50, 14
    LTEXT "Введите текст:", IDC_STATIC, 10, 10, 80, 10
    EDITTEXT IDC_EDIT1, 10, 25, 180, 14
END

1.2. Создание диалога программно

Пример создания диалога в коде:

HWND hwndDlg = CreateDialog(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), hwndParent, DialogProc);
ShowWindow(hwndDlg, SW_SHOW);

2. Обработка диалогового окна

Обработка диалогового окна выполняется в специальной процедуре диалога (DialogProc). Эта процедура обрабатывает сообщения, такие как нажатие кнопок или ввод текста.

Пример процедуры диалога:

INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
        case WM_INITDIALOG:
            // Инициализация диалога
            return TRUE;
        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case IDOK: {
                    char text[100];
                    GetDlgItemText(hwndDlg, IDC_EDIT1, text, sizeof(text));
                    MessageBox(hwndDlg, text, "Введенный текст", MB_OK);
                    EndDialog(hwndDlg, IDOK);
                    return TRUE;
                }
                case IDCANCEL:
                    EndDialog(hwndDlg, IDCANCEL);
                    return TRUE;
            }
            break;
        case WM_CLOSE:
            EndDialog(hwndDlg, IDCANCEL);
            return TRUE;
    }
    return FALSE;
}

3. Отображение модального диалога

Модальные диалоги блокируют взаимодействие с родительским окном до закрытия. Для отображения модального диалога используется функция DialogBox.

Пример:

DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), hwndParent, DialogProc);

4. Отображение немодального диалога

Немодальные диалоги позволяют пользователю взаимодействовать с родительским окном. Для отображения немодального диалога используется функция CreateDialog.

Пример:

HWND hwndDlg = CreateDialog(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), hwndParent, DialogProc);
ShowWindow(hwndDlg, SW_SHOW);

5. Работа с элементами управления

Диалоговые окна содержат элементы управления, такие как кнопки, текстовые поля, списки и т.д. Для работы с этими элементами используются функции API, такие как GetDlgItemText, SetDlgItemText, SendDlgItemMessage.

5.1. Получение текста из текстового поля

Пример:

char text[100];
GetDlgItemText(hwndDlg, IDC_EDIT1, text, sizeof(text));

5.2. Установка текста в текстовое поле

Пример:

SetDlgItemText(hwndDlg, IDC_EDIT1, "Привет, мир!");

5.3. Работа с кнопками

Пример обработки нажатия кнопки:

case WM_COMMAND:
    if (LOWORD(wParam) == IDC_BUTTON1) {
        MessageBox(hwndDlg, "Кнопка нажата!", "Уведомление", MB_OK);
    }
    break;

6. Пример программы с диалоговым окном

Рассмотрим пример программы, которая отображает модальное диалоговое окно с текстовым полем и кнопками.

6.1. Файл ресурсов (app.rc)

IDD_DIALOG1 DIALOGEX 0, 0, 200, 100
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Пример диалога"
FONT 8, "MS Sans Serif"
BEGIN
    DEFPUSHBUTTON "OK", IDOK, 10, 70, 50, 14
    PUSHBUTTON "Отмена", IDCANCEL, 70, 70, 50, 14
    LTEXT "Введите текст:", IDC_STATIC, 10, 10, 80, 10
    EDITTEXT IDC_EDIT1, 10, 25, 180, 14
END

6.2. Код программы (main.c)

#include <windows.h>

#define IDC_EDIT1 101

INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
        case WM_INITDIALOG:
            return TRUE;
        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case IDOK: {
                    char text[100];
                    GetDlgItemText(hwndDlg, IDC_EDIT1, text, sizeof(text));
                    MessageBox(hwndDlg, text, "Введенный текст", MB_OK);
                    EndDialog(hwndDlg, IDOK);
                    return TRUE;
                }
                case IDCANCEL:
                    EndDialog(hwndDlg, IDCANCEL);
                    return TRUE;
            }
            break;
        case WM_CLOSE:
            EndDialog(hwndDlg, IDCANCEL);
            return TRUE;
    }
    return FALSE;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, DialogProc);
    return 0;
}

Прикладной интерфейс для обработки пользовательского ввода

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

1. Обработка ввода с клавиатуры

Клавиатурный ввод обрабатывается с помощью сообщений WM_KEYDOWN, WM_KEYUP, WM_CHAR и других.

1.1. Сообщения клавиатуры

  • WM_KEYDOWN: генерируется при нажатии клавиши.

  • WM_KEYUP: генерируется при отпускании клавиши.

  • WM_CHAR: генерируется при вводе символа (с учетом раскладки клавиатуры).

Пример обработки клавиатурного ввода:

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
        case WM_KEYDOWN:
            MessageBox(hwnd, "Клавиша нажата", "Клавиатура", MB_OK);
            break;
        case WM_CHAR:
            if (wParam == 'A') {
                MessageBox(hwnd, "Нажата клавиша 'A'", "Клавиатура", MB_OK);
            }
            break;
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

1.2. Параметры сообщений клавиатуры

  • WPARAM: виртуальный код клавиши (Virtual Key Code). Например, VK_SPACE для пробела, VK_RETURN для Enter.
  • LPARAM: содержит дополнительную информацию, такую как:
    • Повторение нажатия (биты 0–15).
    • Скан-код клавиши (биты 16–23).
    • Флаги расширенных клавиш (бит 24).
    • Состояние клавиш-модификаторов (Alt, Ctrl, Shift) (биты 29–31).

Пример обработки WM_KEYDOWN:

case WM_KEYDOWN:
    if (wParam == VK_SPACE) {
        MessageBox(hwnd, "Пробел нажат", "Клавиатура", MB_OK);
    }
    break;

1.3. Функции для работы с клавиатурой

  • GetAsyncKeyState: проверяет состояние клавиши (нажата/не нажата).
  • GetKeyboardState: получает состояние всех клавиш клавиатуры.

Пример использования GetAsyncKeyState:

if (GetAsyncKeyState(VK_SPACE) {
    MessageBox(hwnd, "Пробел нажат", "Клавиатура", MB_OK);
}

2. Обработка ввода с мыши

Мышиный ввод обрабатывается с помощью сообщений, таких как WM_MOUSEMOVE, WM_LBUTTONDOWN, WM_RBUTTONDOWN и других.

2.1. Сообщения мыши

  • WM_MOUSEMOVE: генерируется при перемещении мыши.
  • WM_LBUTTONDOWN: генерируется при нажатии левой кнопки мыши.
  • WM_RBUTTONDOWN: генерируется при нажатии правой кнопки мыши.
  • WM_MOUSEWHEEL: генерируется при прокрутке колеса мыши.

Пример обработки мышиного ввода:

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
        case WM_LBUTTONDOWN:
            MessageBox(hwnd, "Левая кнопка мыши нажата", "Мышь", MB_OK);
            break;
        case WM_RBUTTONDOWN:
            MessageBox(hwnd, "Правая кнопка мыши нажата", "Мышь", MB_OK);
            break;
        case WM_MOUSEMOVE: {
            int x = LOWORD(lParam);
            int y = HIWORD(lParam);
            char buffer[50];
            sprintf(buffer, "Мышь: X=%d, Y=%d", x, y);
            SetWindowText(hwnd, buffer);
            break;
        }
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

2.2. Параметры сообщений мыши

  • WPARAM: состояние клавиш-модификаторов (Ctrl, Shift, Alt) и кнопок мыши.
    • MK_LBUTTON: нажата левая кнопка мыши.
    • MK_RBUTTON: нажата правая кнопка мыши.
    • MK_MBUTTON: нажата средняя кнопка мыши.
    • MK_CONTROL: нажата клавиша Ctrl.
    • MK_SHIFT: нажата клавиша Shift.
  • LPARAM: координаты курсора (X и Y) относительно верхнего левого угла клиентской области окна.
    • LOWORD(lParam): координата X.

    • HIWORD(lParam): координата Y.

Пример обработки WM_MOUSEMOVE:

case WM_MOUSEMOVE: {
    int x = LOWORD(lParam);
    int y = HIWORD(lParam);
    char buffer[50];
    sprintf(buffer, "Мышь: X=%d, Y=%d", x, y);
    SetWindowText(hwnd, buffer);
    break;
}

2.3. Сообщение WM_MOUSEWHEEL

  • WPARAM:
    • HIWORD(wParam): значение прокрутки (положительное — вверх, отрицательное — вниз).
    • LOWORD(wParam): состояние клавиш-модификаторов.
  • LPARAM: координаты курсора (X и Y) в экранных координатах.

Пример обработки WM_MOUSEWHEEL:

case WM_MOUSEWHEEL: {
    int delta = GET_WHEEL_DELTA_WPARAM(wParam); // Значение прокрутки
    if (delta > 0) {
        MessageBox(hwnd, "Прокрутка вверх", "Мышь", MB_OK);
    } else {
        MessageBox(hwnd, "Прокрутка вниз", "Мышь", MB_OK);
    }
    break;
}

2.4. Функции для работы с мышью

  • GetCursorPos: получает текущие координаты курсора.
  • SetCursorPos: устанавливает позицию курсора.

Пример использования GetCursorPos:

POINT pt;
GetCursorPos(&pt);
char buffer[50];
sprintf(buffer, "Курсор: X=%d, Y=%d", pt.x, pt.y);
MessageBox(hwnd, buffer, "Мышь", MB_OK);

3. Обработка сенсорного ввода

Сенсорный ввод обрабатывается с помощью сообщений, таких как WM_TOUCH, и функций, таких как GetTouchInputInfo.

3.1. Сообщения сенсорного ввода

  • WM_TOUCH: генерируется при касании экрана.

Параметры сообщения WM_TOUCH: - WPARAM: количество точек касания. - LPARAM: дескриптор структуры HTOUCHINPUT, содержащей информацию о касаниях.

Пример обработки сенсорного ввода:

case WM_TOUCH: {
    UINT inputCount = LOWORD(wParam);
    HTOUCHINPUT hTouchInput = (HTOUCHINPUT)lParam;
    TOUCHINPUT* pTouchInputs = new TOUCHINPUT[inputCount];
    if (GetTouchInputInfo(hTouchInput, inputCount, pTouchInputs, sizeof(TOUCHINPUT))) {
        for (UINT i = 0; i < inputCount; i++) {
            int x = pTouchInputs[i].x / 100;
            int y = pTouchInputs[i].y / 100;
            char buffer[50];
            sprintf(buffer, "Касание: X=%d, Y=%d", x, y);
            MessageBox(hwnd, buffer, "Сенсорный ввод", MB_OK);
        }
    }
    delete[] pTouchInputs;
    CloseTouchInputHandle(hTouchInput);
    break;
}

4. Обработка голосового ввода

Голосовой ввод может быть обработан с помощью API распознавания речи, такого как Windows Speech Recognition или Microsoft Speech API (SAPI).

4.1. Использование SAPI

Пример инициализации распознавания речи:

#include <sapi.h>

ISpRecognizer* pRecognizer = NULL;
CoInitialize(NULL);
CoCreateInstance(CLSID_SpSharedRecognizer, NULL, CLSCTX_ALL, IID_ISpRecognizer, (void**)&pRecognizer);

ISpRecoContext* pRecoContext = NULL;
pRecognizer->CreateRecoContext(&pRecoContext);

pRecoContext->SetNotifyWindowMessage(hwnd, WM_USER, 0, 0);
pRecoContext->SetInterest(SPFEI(SPEI_RECOGNITION), SPFEI(SPEI_RECOGNITION));

ISpRecoGrammar* pGrammar = NULL;
pRecoContext->CreateGrammar(0, &pGrammar);
pGrammar->LoadDictation(NULL, SPLO_STATIC);
pGrammar->SetDictationState(SPRS_ACTIVE);

5. Пример программы для обработки пользовательского ввода

Рассмотрим пример программы, которая обрабатывает клавиатурный и мышиный ввод.

5.1. Код программы (main.c)

#include <windows.h>
#include <stdio.h>

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
        case WM_KEYDOWN:
            MessageBox(hwnd, "Клавиша нажата", "Клавиатура", MB_OK);
            break;
        case WM_CHAR:
            if (wParam == 'A') {
                MessageBox(hwnd, "Нажата клавиша 'A'", "Клавиатура", MB_OK);
            }
            break;
        case WM_LBUTTONDOWN:
            MessageBox(hwnd, "Левая кнопка мыши нажата", "Мышь", MB_OK);
            break;
        case WM_RBUTTONDOWN:
            MessageBox(hwnd, "Правая кнопка мыши нажата", "Мышь", MB_OK);
            break;
        case WM_MOUSEMOVE: {
            int x = LOWORD(lParam);
            int y = HIWORD(lParam);
            char buffer[50];
            sprintf(buffer, "Мышь: X=%d, Y=%d", x, y);
            SetWindowText(hwnd, buffer);
            break;
        }
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    WNDCLASS wc = {0};
    wc.lpfnWndProc = WndProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = "MyWindowClass";
    RegisterClass(&wc);

    HWND hwnd = CreateWindow("MyWindowClass", "Обработка ввода", WS_OVERLAPPEDWINDOW,
                             CW_USEDEFAULT, CW_USEDEFAULT, 400, 300, NULL, NULL, hInstance, NULL);

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
}