Последние записи
- TChromium (CEF3), сохранение изображений
- Как в Delphi XE обнулить таймер?
- Изменить цвет шрифта TextBox на форме
- Ресайз PNG без потери прозрачности
- Вывод на печать графического файла
- Взаимодействие через командную строку
- Перенести программу из Delphi в Lazarus
- Определить текущую ОС
- Автоматическая смена языка (раскладки клавиатуры)
- Сравнение языков на массивах. Часть 2
Интенсив по Python: Работа с API и фреймворками 24-26 ИЮНЯ 2022. Знаете Python, но хотите расширить свои навыки?
Slurm подготовили для вас особенный продукт! Оставить заявку по ссылке - https://slurm.club/3MeqNEk
Online-курс Java с оплатой после трудоустройства. Каждый выпускник получает предложение о работе
И зарплату на 30% выше ожидаемой, подробнее на сайте академии, ссылка - ttps://clck.ru/fCrQw
27th
Фев
Анимированный осцилограф на WinAPI в С++
Posted by Chas under Журнал, Статьи
Вэтой небольшой статье я бы хотел продемонстрировать, как создается окно и как рисовать средствами GDI+. Возможно данный материал будет полезен всем тем, кто хочет разобраться с созданием графический приложений Windows, средствами WinAPI, тем более в преддверии нового учебного года, новых лабораторных, новых сессий. Анимироваться, в данной статье, будет синусоида, получиться своего рода осциллограф.
Олег Кутков
by Oleg Kutkov elenbert@gmail.com
Для создания этого приложения я использовал среду Microsoft Visual C++ 6.0. Вы можете использовать более поздние версии Visual Studio, а так же Dev C++. Запустите IDE и создайте новое Win32 приложение, но укажите опцию, запрещающую генерацию любого кода, нам нужен чистый проект.
Так как мы собираемся использовать WinAPI функции, а так же некоторые математические функции, в начале программы следует подключить два заголовочных файла:
#include <math.h>
Так же, в программе, нам понадобится значение числа Пи, объявим его здесь же:
#define WIN32_LEAN_AND_MEAN // для более быстрой компиляции
Каждое Windows приложение имеет так называемую оконную функцию обратного вызова. Это особая функция, которая не вызывается непосредственно в приложении, ее вызывает операционная система, отсюда и название функции. Вызов это функции происходит каждый раз, когда приложению приходит какое-либо сообщение: перерисовать окно, нажата кнопка, приложение закрыто. Данная функция будет реализована чуть ниже, но что бы ее можно было вызывать из любой точки программы, объявим ее в начале:
LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM);
Функции передаются параметры, указывающие какому окну адресуется это сообщение, а также, какое именно сообщение пришло. Объявим еще две вспомогательные переменные:
TCHAR szTitle[] = "Осциллограф";
TCHAR szWindowClass[] = "oscill";
Это две строки, первая – текст, который будет отображен в заголовке окна, вторая – имя класса окна (это имя выбирается самостоятельно программистом).
Готовим тело приложения…
Вот мы и подобрались к самой главной функции Windows приложения – WinMain. Эта функция выполняет роль, аналогичную роли функции main. WinMain принимает ряд аргументов:
- HINSTANCE hInstance – дескриптор приложения, присваеваемый операционной системой;
- HINSTANCE hPrevInstance – параметр, ныне не используемы, оставленный для совместимости с очень старыми приложениями;
- LPSTR lpCmdLine – строка, содержащая аргументы запуска приложения, аналог argv[];
- int nCmdShow – режим показа главного окна (свернутое, развернутое, по умолчанию).
Общее объявление функции выглядит как:
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { }
Параметр WINAPI перед функцией обозначает, что функция является особой WINAPI функцией и нужен операционной системе. Далее, в самой функции, нам следует объявить три переменные, дескритор окна, системное сообщение и структура окна:
HWND hWnd;
MSG msg;
WNDCLASSEX wcex;
После объявления переменных, следует заполнить поля структуры, ниже показано как, с комментариями:
wcex.cbSize = sizeof(WNDCLASSEX); //размер структуры
wcex.style = CS_HREDRAW | CS_VREDRAW; //задаем стиль окна, подробнее смотрите в MSDN
wcex.lpfnWndProc = (WNDPROC)WindowProcedure; //указываем оконную процедуру
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance; //указываем дескриптор приложениея
wcex.hIcon = LoadIcon(NULL, IDI_APPLICATION); //устанавливаем иконку приложения по умолчанию
wcex.hCursor = LoadCursor(NULL, IDC_ARROW); //устанавливаем курсор по умолчанию
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); //задаем цвет окна
wcex.lpszMenuName = 0; //меню окна - нет меню
wcex.lpszClassName = szWindowClass; //указываем класс окна
wcex.hIconSm = LoadIcon(NULL, IDI_APPLICATION); //загружаем иконку окна
Далее необходимо зарегистрировать класс окна, с обязательной проверкой результата:
if(!RegisterClassEx(&wcex))
{
MessageBox(hWnd, "Ошибка регистрации класса окна", "Ошибка", IDI_ERROR || MB_OK);
return 1;
}
Теперь пришло время создания окна. Для этого будем использовать функцию CreateWindow. Ниже показано, как создать обычное окно, с координатами по умолчанию. Тут так же следует проводить проверку:
hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
if(!hWnd)
{
MessageBox(hWnd, "Ошибка создания окна", "Ошибка", IDI_ERROR || MB_OK); //в случае чего - говорим об ошибке
return 1;
}
Теперь окно можно показать на экране:
ShowWindow(hWnd, nCmdShow);
Мы подобрались к самому концу функции, здесь нас ждет очень важный код – именно тут запускается цикл обработки сообщений операционной системы:
while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
Это цикл, с помощью функции GetMessage, выбирающий следующее сообщение из очереди сообщений и выполняющий его преобразование и обработку. Цикл заканчивается, как только приходит сообщение WM_QUIT и GetMessage возвращает false.
В самом конце следует написать return msg.wParam;
Функция WinMain завершена.
Теперь на очереди реализация вышеобъявленной функции WindowProcedure. В ней происходит обработка всех сообщений и выполнение соответствующих действий. Сразу следует сообщить, так как в нашем приложении будет орисоваться анимация в окне — в данной функции объявлены необходимые переменные и обработчики сообщений. Ниже представлен скорректированный код, от форумчанина DomiNick, всей функции с комментариями:
LRESULT CALLBACK WindowProcedure (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
RECT rect;
static int offset = 0;
switch (message)
{
case WM_CREATE:
SetTimer(hWnd, 1, 150, NULL); // "включаем" таймер
return 0;
case WM_TIMER:
GetClientRect(hWnd, &rect);
InvalidateRect(hWnd, &rect, true);
UpdateWindow(hWnd);
++offset;
return 0;
case WM_PAINT:
PAINTSTRUCT ps;
HDC hdc;
hdc = BeginPaint(hWnd, &ps);
DrawDiagram(hWnd, hdc, offset);
EndPaint(hWnd, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
Думаю, что все тут все наглядно и понятно. Как вы заметили, в сообщении WM_PAINT происходит вызов функции DrawDiagram(hWnd, hdc, offset) – это не стандартная функция и нам следует ее реализовать. Ей передаются, в качестве параметров, дескриптор окна, дескриптор устройства вывода, а так же новое значение смещения для синусоиды. Для того, что бы вызывать функцию в этом месте, мы должно объявить ее ранее, что и сделаем, добавьте, в самом верху, после LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM) объявление:
void DrawDiagram(HWND hwnd, HDC hdc, int offset);
И в конце концов самая большая и сложная функция программы – функция рисования синусоиды. Её заголовок уже приведен выше. На самом деле данная функция рисует не только синусоиду, он так же отвечает за отрисовку вертикальных и горизонтальных линий координатной сетки, а так же числовые отметки осей абсцис и ординат. Пусть это будет напряжение и время.
Начать функцию следует с объявление важных констант – координаты рисования сетки, максимальные, минимальные значения, а так же два массива, содержащие текст – числа, которые будут нарисованы возле осей. Так же тут вызываются четыре API функции, задающие необходимые параметры рисования, подробнее о них Вы можете прочесть в MSDN.
RECT rect;
GetClientRect(hwnd, &rect);
const int xVE = rect.right - rect.left;
const int yVE = rect.bottom - rect.top;
const int xWE = xVE;
const int yWE = yVE;
double nPixPerVolt = yVE / 1000.0;
double nPixPerMs = xVE / 60.0;
SetMapMode(hdc, MM_ISOTROPIC);
SetWindowExtEx(hdc, xWE, yWE, NULL);
SetViewportExtEx(hdc, xVE, -yVE, NULL);
SetViewportOrgEx(hdc, 10*nPixPerMs, yVE/2, NULL);
const int tMin = 0;
const int tMax = 40;
const int uMin = -400;
const int uMax = 400;
const int tGridStep = 5;
const int uGridStep = 100;
int x, y;
int u = uMin;
int xMin = tMin * nPixPerMs;
int xMax = tMax * nPixPerMs;
char* xMark[] = {"0", "5", "10", "15", "20", "25", "30", "35", "40"};
char* yMark[] = {"-40", "-30", "-20", "-10", "0", "10", "20", "30", "40"};
Пока оставьте все как есть, потом, изменяя числовые значения, Вы сможете наблюдать за отрисовкой графика и координатной сетки. Далее создадим наше «перо», которым будет осущестляться рисование линй, так же зададим ему цвет.
HPEN hPen0 = CreatePen(PS_SOLID, 1, RGB(0, 160, 0));
HPEN hOldPen = (HPEN)SelectObject(hdc, hPen0);
Теперь выполним отрисовку сетки – линии ординат и соответсвущющих числовых меток. Для этого сначала переместимся в определенную точку, с помощью функции MoveToEx(hdc, x, y, NULL), а затем нарисуем, «пером», линию в другую точку, с помощью LineTo(hdc, x, y). Рисование текста выполняется с помощью TextOut(hdc, x, y, string, strlen(string)):
for(int i = 0; i < 9; ++i) {
y = u * nPixPerVolt;
MoveToEx(hdc, xMin, y, NULL); //перемещаемся в заданную точку
LineTo(hdc, xMax, y); //рисуем туда линию
TextOut(hdc, xMin-40, y+8, yMark, strlen(yMark)); //выводим текст
u += uGridStep;
}
Теперь выполним небольшие вычисления:
int t = tMin;
int yMin = uMin * nPixPerVolt;
int yMax = uMax * nPixPerVolt;
И аналогично нарисуем ось абсцис:
for(int a = 0; a < 9; ++a) {
x = t * nPixPerMs;
MoveToEx(hdc, x, yMin, NULL); //перемещаемся в заданную точку
LineTo(hdc, x, yMax); //рисуем туда линию
TextOut(hdc, x, yMin-10, xMark[a], strlen(xMark[a])); //выводим текст
t += tGridStep;
}
Сетка нарисована, теперь нужно нарисовать сами оси, для этого выберем другое «перо»:
HPEN hPen1 = CreatePen(PS_SOLID, 3, RGB(0, 0, 0)); //создаем кисть
SelectObject(hdc, hPen1);
И с помощью уже знакомых нам функций нарисуем оси:
MoveToEx(hdc, 0, 0, NULL); LineTo(hdc, xMax, 0);
MoveToEx(hdc, 0, yMin, NULL); LineTo(hdc, 0, yMax);
Теперь самое интересное, отрисовка графика функции. Вновь берем “перо”:
HPEN hPen2 = CreatePen(PS_SOLID, 5, RGB(200, 0, 100));
SelectObject(hdc, hPen2);
Сначала код с комментариями, затем некоторые пояснения:
int tStep = 1; //задаем шаг графика
double radianPerx = 2 * Pi / 30; вычисляем угол радиан
const double uAmplit = 250; //задаем амплитуду
t = tMin;
MoveToEx(hdc, 0, ((uAmplit * sin(t * radianPerx - offset)) * nPixPerVolt), NULL); //вычисляем начальную точку
while(t <= tMax) { //до достижения максимального значения х
u = uAmplit * sin(t * radianPerx - offset); //вычисляем синус и точку, куда рисовать линию
LineTo(hdc, t * nPixPerMs, u * nPixPerVolt); //рисуем линию
t += tStep;
}
SelectObject(hdc, hOldPen);
Сначала выполняются необходимые вычисления аргументов функции синуса, затем вычисляется собственно синус и в полученную точку осуществляется переход, с помощью MoveToEx. Затем запускается цикл, который, поточечно, начиная с точки, куда мы только что перешли (если-бы этого не сделали, то у синусоиды-бы появилась некрасивая «тянучка» в начале) рисует линию графика. цикл прерывается, как только достигается максимальное значение, заданное выше, в константах.
Все, программа закончена, можете смело компилировать, не обращая внимания на предупреждения компилятора (преобразования из int в double и наооборот, не существенно, в данном случае), и запускать. Ниже представлен скриншот того, что у Вас должно в итоге получиться:
Рисунок. Внешний вид приложения (разумеется анимацию тут не видно)
Посткриптум
Все упомянутые в статье модули приведены непосредственно в архиве с журналом.
Ресурсы
Среда компиляции Dev C++ (4.9.9.2)
Статья из одиннадцатого выпуска журнала «ПРОграммист».
Обсудить на форуме — Анимированный осцилограф на WinAPI в С++
Похожие статьи
Купить рекламу на сайте за 1000 руб
пишите сюда - alarforum@yandex.ru
Да и по любым другим вопросам пишите на почту
пеллетные котлы
Пеллетный котел Emtas
Наши форумы по программированию:
- Форум Web программирование (веб)
- Delphi форумы
- Форумы C (Си)
- Форум .NET Frameworks (точка нет фреймворки)
- Форум Java (джава)
- Форум низкоуровневое программирование
- Форум VBA (вба)
- Форум OpenGL
- Форум DirectX
- Форум CAD проектирование
- Форум по операционным системам
- Форум Software (Софт)
- Форум Hardware (Компьютерное железо)