PDA

Просмотр полной версии : [Руководство] Пишем простое окно на С++


Ivan_32
30.12.2008, 21:30
Предполагается что читатель знаком с языком С++ хотя бы на начальном уровне.

Самым сложным при изучении WinAPI наверно является создание элементов управления и самих форм(окон). Все остальное предельно просто и понятно. А тут..тут обычно для новичка много нелогичного и непонятного, но мы попробуем внести логику;)

Что такое WinAPI в целом? Это набор функций для общения с устройствами операционной системы, это кнопочки если угодно. Вы пишите тот же ShellExecute и по вашей просьбе ОС сделает то что вы ее попросили, в данном случае запустит например что ни будь, это ведь зависит от параметров функции.
--
А вот на этом остановимся. Только что было слышно слово параметры, не правда ли? Точно так же как вы запускаете микроволновку с параметрами , включаете плиту тоже с параметрами, аргументами.
--
Точно так же нужно делать и с WinAPI функциями, на самом деле в каждой такой функции спрятано несколько других. Но не будем увлекатся теорией а перейдем к сути.
--
Создайте в вашей студии проект консольного приложения. Создали ? Хорошо. Теперь подключите h-файлы windows.h (директива #include <windows.h>). Напишите в функции main такую строку.

MessageBox(0,"Hello world","WinAPI is simple",0);

Запускайте и радуйтесь первому использование функции.
--
Идем дальше. Теперь давайте ознакомимся с каркасом приложения для Windows. Каркасом для него является следующий код:

#include <windows.h>
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
return 0;
}

Вот это уже полноценное Windows приложение. Разберемся что к чему.
Функция WinMain является заменой привычному main. С нее начинается любая программа. Ее аргументы:
hInstance - хендл на exe-файл из которого запущена программа(или Thread)
hPrevInstance - хендл на что то мистическое, всегда равно нулю.
lpCmdLine и nCmdShow отвечают за командную строку запуска проще говоря параметры с которыми запущена программа если она запущена из CMD.exe или же ярлыком с параметрами.
--
С каркасом разобрались.
Любая программа на WinAPI состоит из нескольких частей.
1. Это графическая часть, нам нужно показать пользователю интерфейс.
2. Это очередь сообщений, мы должны создать цикл обработчик сообщений и функцию для их обработки.
Приступим:

За графическую часть у на отвечает функция CreateWindowEx, она же предоставляет каркас для работы с сообщениями. К слову весь Windows построен на системе сообщений.
Вот она наша функция:

HWND CreateWindowEx(
DWORD dwExStyle,
LPCTSTR lpClassName,
LPCTSTR lpWindowName,
DWORD dwStyle,
int x,
int y,
int nWidth,
int nHeight,
HWND hWndParent,
HMENU hMenu,
HINSTANCE hInstance,
LPVOID lpParam
);

1. особый дополнительный стиль создаваемого окна, нам пока не нужно.
2. Имя класса, либо нашего либо стандартного класса окна. Используем WC_DIALOG.
3. Заголовок окна.
4. Стиль окна. Тут нам понадобятся два параметра которые между собой обьединяються бинарным OR тоесть символом "|" Эти параметры по своей сути являются битами. Мы используем WS_VISIBLE | WS_SYSMENU.
Первый определяет что наше окно сразу будет видимым при создании, а второй добавляет к окну кнопки close/max/min на рамке, без этого стиля их там не будет.
5. Кордината X на экране. Отсчет кординат идет с верхнего левого угла монитора к нижнему правому.
6. Кордината Y.
7. Ширина окна.
8. Длина окна.
9. Отчество окна, этот параметр применяется для создания кнопочек итд. итп.
10. Уникальный ID нашего окна, что бы различать его в общей очереди сообщений, нам пока не нужно потом ставим ноль.
11. Хендл на модуль нашей программы, можно поставить ноль.
12. Параметр с которым создается окно. Мистический аргумент, я не знаю для чего его можно использовать.

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

HWND hMain=CreateWindowEx(0,WC_DIALOG,"Hello world",WS_VISIBLE | WS_SYSMENU, 500,500,200,200,0,0,0,0);

И запускайте. Вроде бы на экране на миг появилось окно , неправда ли ?
--
Это объясняется тем что у окна нет очереди сообщений, которую мы сейчас добавим. Для начала нам нужен прототип очереди сообщений, иными словами структура. Такой структурой является MSG.
Обрабатывают сообщения с помощью Функций TranslateMessage и DispatchMessage у обоих один аргумент - адрес экземпляра структуры MSG. Но , для начала как нам положить туда эти сообщения? А очень просто. С помощью функции GetMessage:

BOOL GetMessage(
LPMSG lpMsg,
HWND hWnd,
UINT wMsgFilterMin,
UINT wMsgFilterMax
);
1. Это адрес экземпляра структуры MSG.
2. Хендл на окно для которого будут обрабатываться сообщения, в нашем случае ноль, так как обрабатывать будем сообщения для всей программы.
3 и 4 - это специальные параметры для фильтрации сообщений. Все сообщения это int -число , вот его пороги и задают эти параметры, то есть например от 20 до 40, нам не нужно, так как обрабатывать будем все.
--
Функция проверяет наличие сообщений и в случае наличия возвращает ноль. Нам нужно создать цикл который будет работать пока функция возвращает единицу.

Итак давайте сразу напишем код всей измененной программы:
[CODE]
#include <windows.h>
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
MSG msg;
HWND hMain=CreateWindowEx(0,WC_DIALOG,"Hello world",WS_VISIBLE | WS_SYSMENU,CW_USEDEFAULT,CW_USEDEFAULT,200,200,0,0 ,0,0);
while(GetMessage(&msg,0,0,0)!=0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}

Предупреждаю. Как только вы запустите вы уже не сможете закрыть ее традиционным способом - только через Диспетчер Задач.
--
Ну думаю все понятно, но я еще раз повторю. Вот видите появился цикл.
Он работает пока GetMessage не возвратит ноль. А пока она возвращает единицу, сообщения переводятся и обрабатываются.
--
Но у нас нет обработчика событий. Функция обработчик событий привязывается к окну а не к msg, вопреки ожиданиям. Тоесть сообщения перенаправляются на функции обработчики, вернее функции обработчики вызываются с сообщением в аргументах.
--
Нам нужно написать функцию обработчик, как же она выглядит:

LRESULT WINAPI mainProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{
//Тут вот будем обрабатывать сообщения.
switch(message)
{
case WM_CLOSE:
{
PostQuitMessage(0);
};break;
}
}

Много непонятного ? Ничего не страшно. Это каркас функции. На самом деле вам нужна функция которая отвечает ряду требований а именно:
1. Должна иметь вышеуказанные аргументы
2. И должна возвращать вышеуказанный тип - LRESULT.
Остальное ваш код, что хотите то и пишите в нем, и название функции тоже значения не имеет.
В параметр message попадает сообщение - unsigned int==UINT. Как и WS_VISIBLE WC_DIALOG - это константы заданные директивой #define, по сути это числа. Нам нужно определить число WM_CLOSE и написать действие для него. Вот мы его и написали :
PostQuitMessage(0); - заставляет программу закрыться корректным способом, вернув 0 - Sucess.
--
Добавте этот код к вашей программе. Все равно не работает не правда ли?) Это объясняется тем что мы не задали нашему окну обработчик - нашу функцию.
--
Сделать это можно функцией SetWindowLong, это супер пупер функция для работы с параметрами окна, в зависимости от аргументов она делает совершенно разные вещи.

LONG SetWindowLong(
HWND hWnd,
int nIndex,
LONG dwNewLong
);

1. Хендл на окно.
2. Индекс значения, в структуре окна.
3. Заменяемое значение.
--
Нам нужно поменть для нашего окна значение DWL_DLGPROC заменить его на адрес нашей процедуры обработчика:

SetWindowLong(hMain,DWL_DLGPROC,(long)mainProc);

Думаю тут все понятно, если не понятно то сейчас это не критично, главное запомнить как писать каркас приложения.

Ну что ж приведем код уже работающей программы:


#include <windows.h>
LRESULT WINAPI mainProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{
switch(message)
{
case WM_CLOSE:
{
PostQuitMessage(0);
};break;
}
return 0;
}
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
MSG msg;
HWND hMain=CreateWindowEx(0,WC_DIALOG,"Hello world",WS_VISIBLE | WS_SYSMENU,CW_USEDEFAULT,CW_USEDEFAULT,200,200,0,0 ,0,0);
SetWindowLong(hMain,DWL_DLGPROC,(long)mainProc);
while(GetMessage(&msg,0,0,0)!=0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}


Продолжение следует:)

ZliLO
31.12.2008, 00:40
а причем здесь асм и с++?

Ivan_32
31.12.2008, 00:57
Раздел С++ сделан для обсуждения решений построенных на особенностях языка, то есть по сути там можно обсуждать работу с указателями нюансы работы с классами - ООП итд , то есть все то что является конструктивно зависимым от языка. А этот гайд больше рассказывает о платформе Win32. С++ взят как более простой язык. Я подумал что на асме будет тяжеловато понять азы.:)
Но раздел для WinAPI наверно все же стоит отдельный сделать, но это в будущем , сейчас без должного контента и тем более посетителей смысла нет...

Ivan_64
11.12.2009, 06:53
#define UNICODE
#include <windows.h>

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

INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, PSTR, INT iCmdShow)
{
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 = L"TestWND";

RegisterClass(&wndClass);

hWnd = CreateWindow(
L"TestWND",
L"Test Window",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
320,
240,
0,0,hInstance,0);

ShowWindow(hWnd, iCmdShow);
UpdateWindow(hWnd);

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

return 0;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message,
WPARAM wParam, LPARAM lParam)
{

switch(message)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}

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