Показаны сообщения с ярлыком Win32 API. Показать все сообщения
Показаны сообщения с ярлыком Win32 API. Показать все сообщения

пятница, 4 ноября 2011 г.

Обобщенная Windows программа

(перевел, как смог http://www.relisoft.com/win32/generic.html)
Эта программа использует основной набор классов инкапсулирующих Windows API.
Controller - мост между оконной процедурой и объектно-ориентированным миром.
View - инкапсулирует вывод; Windows программы.
Canvas - инкапсулирует различные контексты устройств и то, что вы можете делать с ним.
Model - руки и мозг вашей программы, которые не имеют дело с Windows совсем.
Давайте начнем с WinMain, там мы создаем оконный класс и окно верхнего уровня нашего приложения. Я скрою действия внутри двух классов: WinClass и WinMaker. WinClass может сказать нам, о том, что уже запущен экземпляр; нашей программы. Когда нечто подобное случается, в нашем примере, мы можем легко активировать подготовленный к запуску экземпляр приложения и выйти. Вы можете делать это, тогда когда захотите, только один экземпляр вашей программы может быть запущен за раз.
Раз окно верхнего уровня прекрасно создано, то мы входим в цикл (петлю) обработки сообщений. Извещение о том, что в этот раз мы обрабатываем клавиатурные вызовы, получаем посредством вызова TranslateMessage. Поскольку наша программа имеет пункты меню, то должно быть доступно использование Alt+[клавиша] комбинаций.
Другая интересная черта этой программы - это то, что мы не будем использовать строки в названии наших ресурсов - мы используем числовые идентификаторы. Более того, когда происходит API вызов для получения строки, наподобие имени оконного класса или заголовка, мы храним строки в строковых ресурсах и доступ к ним получаем через идентификаторы (ids). Ваша среда разработки под Windows очень вероятно имеет редактор ресурсов, что позволяет создавать иконки, меню, и строковые ресурсы и определять им подходящие числовые идентификаторы. Символические имена для этих идентификаторов хранятся в заголовочном файле, созданном таким редактором, в нашем случае - это resource.h.
Постоянная ID_MAIN, например, относится к главным иконкам приложения (большая и маленькая в некотором ресурсе), главному меню, и строке с именем оконного класса. ID_CAPTION относится к строке заголовка окна. Такая организация расширяет возможности повторного использования кода, не говоря уж об удобстве локализации.


int WINAPI WinMain
(HINSTANCE hInst, HINSTANCE hPrevInst,
char * cmdParam, int cmdShow)
{
_set_new_handler (& NewHandler);

// Использование исключений поможет отлаживать вашу программу
// и защитить от неожиданных инцидентов.
try
{
// Создание класса окна верхнего уровня
TopWinClass topWinClass (ID_MAIN, hInst, MainWndProc);
// Действительно ли запущен экземпляр этой программы?
HWND hwndOther = topWinClass.GetRunningWindow ();
if (hwndOther != 0)
{
::SetForegroundWindow (hwndOther);
if (::IsIconic (hwndOther))
::ShowWindow (hwndOther, SW_RESTORE);
return 0;
}
topWinClass.Register ();

// Создаем окно верхнего уровня
ResString caption (hInst, ID_CAPTION);
TopWinMaker topWin (topWinClass, caption);
topWin.Create ();
topWin.Show (cmdShow);
// Главная петля сообщений
MSG msg;
int status;
while ((status = ::GetMessage (&msg, 0, 0, 0)) != 0)
{
if (status == -1)
return -1;
::TranslateMessage (&msg);
::DispatchMessage (&msg);
}

return msg.wParam;
}
catch ( WinException e )
{
char buf [50];
wsprintf (buf, "%s, Error %d", e.GetMessage (), e.GetError ());
::MessageBox (0, buf, "Exception", MB_ICONEXCLAMATION | MB_OK);
}
catch (...)
{
::MessageBox (0, "Unknown", "Exception", 
MB_ICONEXCLAMATION | MB_OK);
}

return 0;
}


Давайте взглянем на класс WinClass. Он инкапсулирует вызов Windows-определенной структуры WNDCLASSEX и обеспечивает приемлемые значения по умолчанию для всех его полей. Этот класс является производным от простого класса WinSimpleClass, который вы можете использовать для встраивания некоторых Windows классов (подобных кнопкам, спискам и т.д.).
Я определил, примерные методы, которые могут быть использованы для переопределения значений по умолчанию. Например, SetBgSysColor изменяет цвет фона определенный по умолчанию в клиентской области окна в один из предопределенных системой цвет. Метод SetResIcons загружает соответствующие иконки из ресурсов и закрепляет их за оконным классом. Эти иконки будут потом появляться в верхнем левом углу главного окна и на панели задач Windows.
TopWinClass производный от WinClass позволяет использовать его методы. Он также устанавливает меню вверху оконного класса.

class WinSimpleClass
{
public:
 WinSimpleClass (char const * name, HINSTANCE hInst)
: _name (name), _hInstance (hInst)
{}
WinSimpleClass (int resId, HINSTANCE hInst);
char const * GetName () const
{
return _name.c_str ();
}
HINSTANCE GetInstance () const
{
return _hInstance;
}
HWND GetRunningWindow ();
protected:
 HINSTANCE _hInstance;
std::string _name;
};
WinSimpleClass::WinSimpleClass (int resId, HINSTANCE hInst)
: _hInstance (hInst)
{
ResString resStr (hInst, resId);
_name = resStr;
}
HWND WinSimpleClass::GetRunningWindow ()
{
HWND hwnd = ::FindWindow (GetName (), 0);
if (::IsWindow (hwnd))
{
HWND hwndPopup = ::GetLastActivePopup (hwnd);
if (::IsWindow (hwndPopup))
hwnd = hwndPopup;
}
else 
hwnd = 0;
return hwnd;
}
class WinClass: public WinSimpleClass
{
public:
WinClass (char const * className, HINSTANCE hInst, WNDPROC wndProc);
WinClass (int resId, HINSTANCE hInst, WNDPROC wndProc);
void SetBgSysColor (int sysColor)
{
_class.hbrBackground = reinterpret_cast (sysColor + 1);
}
void SetResIcons (int resId);
void Register ();
protected:
void SetDefaults ();
WNDCLASSEX _class;
};
WinClass::WinClass (char const * className, 
HINSTANCE hInst, 
WNDPROC wndProc)
: WinSimpleClass (className, hInst)
{
_class.lpfnWndProc = wndProc;
SetDefaults ();
}
WinClass::WinClass (int resId, HINSTANCE hInst, WNDPROC wndProc)
: WinSimpleClass (resId, hInst)
{
_class.lpfnWndProc = wndProc;
SetDefaults ();
}
void WinClass::SetDefaults ()
{
// Provide reasonable default values
_class.cbSize = sizeof (WNDCLASSEX);
_class.style = 0;
_class.lpszClassName = GetName ();
_class.hInstance = GetInstance ();
_class.hIcon = 0;
_class.hIconSm = 0;
_class.lpszMenuName = 0;
_class.cbClsExtra = 0;
_class.cbWndExtra = 0;
_class.hbrBackground = reinterpret_cast (COLOR_WINDOW + 1);
_class.hCursor = ::LoadCursor (0, IDC_ARROW);
}
void WinClass::SetResIcons (int resId)
{
_class.hIcon = reinterpret_cast (
::LoadImage (
_class.hInstance, 
MAKEINTRESOURCE (resId), 
IMAGE_ICON, 
::GetSystemMetrics (SM_CXICON),
::GetSystemMetrics (SM_CYICON),
0));
// Small icon can be loaded from the same resource
_class.hIconSm = reinterpret_cast (
::LoadImage (
_class.hInstance, 
MAKEINTRESOURCE (resId), 
IMAGE_ICON, 
::GetSystemMetrics (SM_CXSMICON),
::GetSystemMetrics (SM_CYSMICON),
0));
}
void WinClass::Register ()
{
if (::RegisterClassEx (&_class) == 0)
throw WinException ("Internal error: RegisterClassEx failed.");
}
class TopWinClass: public WinClass
{
public:
TopWinClass (int resId, HINSTANCE hInst, WNDPROC wndProc);
};
TopWinClass::TopWinClass (int resId, 
HINSTANCE hInst, WNDPROC wndProc)
: WinClass (resId, hInst, wndProc)
{
SetResIcons (resId);
_class.lpszMenuName = MAKEINTRESOURCE (resId);
}


Од;нажды зарегистрировав в системе Windows класс, вы можете создать много окон этого класса, если только захотите. Они, конечно, будут совместно использовать некоторую оконную процедуру, которая была зарегистрированная вместе с классом. Мы увидим позже, как можно различать экземпляры окна внутри процедуры.
Класс WinMaker работает в большинстве случаев как WinClass. Его конструктор обеспечивает разумные значения по умолчанию, которые могут быть переопределены посредством вызова специальных методов. Как только все определено, вы вызываете метод Create, который создает окно и метод Show для его отображения. Внимание, в тот момент, когда вы вызываете Create, ваша оконная процедура вызывается с сообщением WM_CREATE .
Окно верхнего уровня, созданное с использованием класса TopWinMaker, который обеспечивает соответствующий стиль и надпись.

class WinMaker
{
public:
WinMaker (WinClass & winClass);
operator HWND ()
{
return _hwnd;
}
void AddCaption (char const * caption)
{
_windowName = caption;
}
void AddSysMenu ()
{
_style |= WS_SYSMENU;
}
void AddVScrollBar ()
{
_style |= WS_VSCROLL;
}
void AddHScrollBar ()
{
_style |= WS_HSCROLL;
}
void Create ();
void Show (int nCmdShow = SW_SHOWNORMAL);
protected:
WinClass & _class;
HWND _hwnd;
DWORD _exStyle; // extended window style
char const * _windowName; // pointer to window name
DWORD _style; // window style
int _x; // horizontal position of window
int _y; // vertical position of window
int _width; // window width
int _height; // window height
HWND _hWndParent; // handle to parent or owner window
HMENU _hMenu; // handle to menu, or child-window ID
void * _data; // pointer to window-creation data
};
WinMaker::WinMaker (WinClass & winClass)
: _hwnd (0),
_class (winClass),
_exStyle (0), // extended window style
_windowName (0), // pointer to window name
_style (WS_OVERLAPPED), // window style
_x (CW_USEDEFAULT), // horizontal position of window
_y (0), // vertical position of window
_width (CW_USEDEFAULT), // window width
_height (0), // window height
_hWndParent (0), // handle to parent or owner window
_hMenu (0), // handle to menu, or child-window identifier
_data (0) // pointer to window-creation data
{
}
void WinMaker::Create ()
{
_hwnd = ::CreateWindowEx (
_exStyle,
_class.GetName (),
_windowName,
_style,
_x,
_y,
_width,
_height,
_hWndParent,
_hMenu,
_class.GetInstance (),
_data);
if (_hwnd == 0)
throw WinException ("Internal error: Window Creation Failed.");
}
void WinMaker::Show (int nCmdShow)
{
::ShowWindow (_hwnd, nCmdShow);
::UpdateWindow (_hwnd);
}
// Makes top overlapped window with caption
class TopWinMaker: public WinMaker
{
public:
TopWinMaker (WinClass & winClass, char const * caption);
};
TopWinMaker::TopWinMaker ((WinClass & winClass, char const * caption)
: WinMaker (winClass)
{
_style = WS_OVERLAPPEDWINDOW | WS_VISIBLE;
_windowName = caption;
}


Перед тем как мы пойдем дальше, здесь будут некоторые легкие классы для общей выгоды. WinException - это примерно, то, что мы хотим, он выбросит исключение в любом случае при неудаче с Windows API. Он позаботится о возврате кода ошибки Windows. Для конвертации кода ошибки в строку используется FormatMessage API.
Класс ResString является простой l0;нкапсуляцией хранения строковых ресурсов вашего приложения.

// Класс исключения: сохраняет сообщение и код ошибки
class WinException
{
public:
WinException (char* msg)
: _err (::GetLastError()), _msg(msg)
{}
DWORD GetError() const
{
return _err;
}
char const * GetMessage () const
{
return _msg;
}
private:
DWORD _err;
char * _msg;
};

// Выход за пам 103;ть дескриптора: выбрасывает исключение
int NewHandler (size_t size)
{
throw WinException ( "Out of memory" );
return 0;
}

class ResString
{
enum { MAX_RESSTRING = 255 };
public:
ResString (HINSTANCE hInst, int resId);
operator char const * ()
{
return _buf;
}
private:
char _buf [MAX_RESSTRING + 1];
};

ResString::ResString (HINSTANCE hInst, int resId)
{
if (!::LoadString (hInst, resId, _buf, MAX_RESSTRING + 1))
throw WinException ("Load String failed");
}


Contriller (контроллер) - это нервная система исключительно оконного экземпляра. Он создается вместе с окном, сохраняется с ним и разрушается с ним. Вы можете определить любую информацию характерную для экземпляра окна в его контроллере. В общем, контроллер связан с представлением (view), которое отрисовывает на поверхности окна и он имеет доступ к модели (model), которая является мозгом вашего приложения (это называют MVC, или Model-View-Controller паттерн, изобретенный Smalltalk программистами).
Как это часто бывает, ваше приложение имеет только одно окно верхнего уровня, вы можете непосредственно включить модель (model) в его контроллер (controller). Это упрощает управление ресурсами, но там возникают накладные расходы на соединение контроллера с моделью. В крупных проектах избегают таких соединений — используют (умный) указатель на модель внутри контроллера, что предпочтительно.
Большинство методов контроллера зависят от дескриптора окна, которым они оперируют. Это дескриптор передается с каждым сообщением Windows, но его проще хранить один раз внутри объекта контроллера и использовать его всякий раз когда необходимо. Помните, они находятся в отношении один-к-одному, соответственно между окном экземпляра (и следовательно дескрипторами окна) и объектами контроллера.

class Controller
{
public:
Controller(HWND hwnd, CREATESTRUCT * pCreate);
~Controller ();
void Size (int x, int y);
void Paint ();
void Command (int cmd);

private:

HWND _hwnd;

Model _model;
View _view;
};

Оконная процедура - это главный коммутатор приложения Windows. Вы не вызываете ее из; вашей программы - Windows делает это! Кажды;й раз что-то интересное происходит, Windows отправляет вашей программе сообщения. Это сообщение передается в вашу оконную процедуру. Вы можете обработать его, или вы можете передать его оконной процедуре по умолчанию.
Оконная процедура вызывается с дескриптором окна, которому переданное сообщение направлено. Этот дескриптор однозначно идентифицирует внутреннюю структуру данных Windows, которая соответствует переданному экземпляру окна. К стати, GWL_USERDATA часть этой структуры гарантированно представлена во всех окнах, включая окна сообщений (MessageBox), диалоги и кнопки.

template
inline T WinGetLong (HWND hwnd, int which = GWL_USERDATA)
{
return reinterpret_cast (::GetWindowLong (hwnd, which));
}

template
inline void WinSetLong (HWND hwnd, T value, int which = GWL_USERDATA)
{
::SetWindowLong (hwnd, which, reinterpret_cast (value));
}

Каждый раз Windows вызывает нашу оконную процедуру, мы должны вначале определить объект-контроллер. Помните, что может быть несколько окон разделяющих некоторую оконную процедуру, и мы желаем иметь отдельный контроллер для каждого окна. Как можем мы узнать, какой контроллер использовать, когда мы делаем вызов? Мы находим его посредством дескриптора окна. В этом дескрипторе мы храним указатель на контроллер окна, используем Win[Set/Get]Long функций.
При первом вызове, оконная процедура вызывается с WM_CREATE сообщением. В этот время мы создаем объект контроллер и инициализируем его дескриптором окна и специальной структурой данных называемой CREATESTRUCT, которую мы передаём в распор;яжение Windows. Как только мы имеем контроллер, мы сохраняем указатель на него в соответствующую внутреннюю структуру данных Windows под меткой текущего hwnd (дескриптор окна). В следующий раз оконная процедура вызывается с сообщением отличным от WM_CREATE, мы просто находим указатель на наш контроллер, используя hwnd.
Спокойно отдохнем. Оконная процедура интерпретирует параметры сообщения и вызывает соответствующие методы контроллера.

LRESULT CALLBACK WndProc
(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
Controller * pCtrl = WinGetLong (hwnd);

switch (message)
{
case WM_CREATE:
// Ловим исключение в случае нового исключения!
try
{
pCtrl = new Controller (hwnd, 
reinterpret_cast (lParam));
WinSetLong (hwnd, pCtrl);
}
catch (WinException e)
{
::MessageBox (hwnd, e.GetMessage(), "Initialization",
MB_ICONEXCLAMATION | MB_OK);
return -1;
}
catch (...)
{
::MessageBox (hwnd, "Unknown Error", "Initialization",
MB_ICONEXCLAMATION | MB_OK);
return -1;
}
return 0;
case WM_SIZE:
pCtrl->Size (LOWORD(lParam), HIWORD(lParam));
return 0;
case WM_PAINT:
pCtrl->Paint ();
return 0;
case WM_COMMAND:
pCtrl->Command (LOWORD (wParam));
return 0;
case WM_DESTROY:
WinSetLong (hwnd, 0);
delete pCtrl;
return 0;
}
return ::DefWindowProc (hwnd, message, wParam, lParam);
}

Здесь примеры простого выполнения нескольких методов контроллера. Конструктор запоминает дескриптор окна для дальнейшего использования, деструктор отправляет сообщение о выходе. Метод Size передает его аргумент в View, и т. д. Мы говорим об отрисовке окна немного позже. Теперь, заметим, что контроллер подготавливает область рисования для работы View.

Controller::Controller (HWND hwnd, CREATESTRUCT * pCreate)
:_hwnd (hwnd), 
_model ("Generic")
{
}

Controller::~Controller ()
{
::PostQuitMessage(0);
}

void Controller::Size (int cx, int cy)
{
_view.SetSize (cx, cy);
}

void Controller::Paint ()
{
// подготавливаем холст и передаём View
PaintCanvas canvas (_hwnd);
_view.Paint (canvas, _model);
// Заметим: деструктор PaintCanvas вызывается автоматически!
}

Когда пользователь выбирает один из пунктов меню, оконная процедура вызывается с сообщением WM_COMMAND. Соответствующий метод контроллера отправляет команду, зависящую от идентификатора (id). Когда вы создаете меню используя ваш редактор ресурсов, вы выбираете эти командные идентификаторы для каждого пункта меню. Они хранятся в соответствующем заголовочном файле (resource.h в нашем случае), он должен быть включен в исходник контроллера.
Наше меню содержит только три пункта с идентификаторами IDM_EXIT, IDM_HELP, и IDM_ABOUT. Диалог, который отображается в ответ на IDM_ABOUT, также создан с использованием редактора ресурсов, и ему дан идентификатор IDD_ABOUT. Его диалоговая процедура - AboutDlgProc.
Наконец, для нормального отображения диалога мы нуждаемся в дескрипторе экземпляра приложения. Обычно, доступ к нему получаn2;т через внутреннюю структуру данных Windows, использованием соответствующего hwnd.

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

void Controller::Command (int cmd)
{
switch (cmd)
{
case IDM_EXIT:
::SendMessage (_hwnd, WM_CLOSE, 0, 0L);
break;
case IDM_HELP:
::MessageBox (_hwnd, "Go figure!",
"Generic", MB_ICONINFORMATION | MB_OK);
break;
case IDM_ABOUT:
{
// Дескриптор экземпляра приложения доступен черехз HWND
HINSTANCE hInst = WinGetLong (_hwnd, 
GWL_HINSTANCE);
::DialogBox (hInst, 
MAKEINTRESOURCE (IDD_ABOUT), 
_hwnd, 
AboutDlgProc);
}
break;
}
}

Объект представления обычно хранит параметры клиентской области. Они обновляются всякий раз, когда процессы контроллера сообщают WM_SIZE. Первое WM_SIZE сообщение посылается вовремя создания и перед WM_PAINT, так мы можем быть уверены, что к моменту вызова Paint, параметры клиентской области известны.
Графический вывод в окно сделан, посредством вызова соответствующих методов объекта Canvas. В нашем случае, мы отрисовываем текст получаемый из модели и рисуем вертикальную линию на 10 пикселей левее от края клиентской области.

class View
{
public:

void SetSize (int cxNew, int cyNew)
{
_cx = cxNew;
_cy = cyNew;
}

void Paint (Canvas & canvas, Model & model);

protected:

int _cx;
int _cy;
};

void View::Paint (Canvas & canvas, Model & model)
{
canvas.Text (12, 1, model.GetText(), model.GetLen());
canvas.Line (10, 0, 10, _cy);
}


Объект холста инкапсулирует, то что называется контекстом устройства (Device Context) в языке Windows. Наш Canvas - это очень прост, он только знает как печатать текст и рисовать линии, но ваш Canvas может иметь на много больше методов, которые делают творческие вещи. 

class Canvas
{
public:
operator HDC ()
{
return _hdc;
}

void Line ( int x1, int y1, int x2, int y2 )
{
::MoveToEx (_hdc, x1, y1, 0);
::LineTo (_hdc, x2, y2);
}

void Text (int x, int y, char const * buf, int cBuf)
{
::TextOut ( _hdc, x, y, buf, cBuf );
}

void Char (int x, int y, char c)
{
::TextOut (_hdc, x, y, & c, 1);
}

protected:

// Защищенный конструктор: Вы не можете строить
// объект типа Canvas, но вам доступно наследование его.
 Canvas (HDC hdc): _hdc (hdc) {}

HDC _hdc;
};

Холст (canvas), что вы создаете, в ответ на сообщение WM_PAINT может быть специально изменен. Этот контекст устройства захватывается вызовом BeginPaint и освобождается вызовом EndPaint. PAINTSTRUCT содержит дополнительную информацию о каждой части клиентской области, которая отрисована и др. Здесь мы игнорируем некоторые детали, но если это важно для вас вы могли бы узнать больше об этом.

// Concrete example of canvas.
// Создаем этот объект после сообщения WM_PAINT

class PaintCanvas: public Canvas
{
public:
// Конструктор получает DC
PaintCanvas (HWND hwnd)
: Canvas (::BeginPaint (hwnd, & _paint)),
_hwnd (hwnd)
{}

// Деструктор освобождает DC
~PaintCanvas ()
{
::EndPaint(_hwnd, & _paint);
}

protected:

PAINTSTRUCT _paint;
HWND _hwnd;
};


Хотели бы вы программировать без контролов?

пятница, 23 сентября 2011 г.

Простейшая Windows программа

Простейшая Windows программа

(как смог, так и перевел http://relisoft.com/win32/winnie.html)

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

Вызовы Windows API выделены синим, а определенные в Windows типы данных показаны зеленым. Я буду обычно ставить двойное двоеточие в API вызовах. В C++, это просто способ вызова глобальных функций, на случай избегания двусмысленностей.

Для проверки работоспособности кода надо создать проект типа Win32 Application.

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

Объявление оконной процедуры (WindowProcedure). Windows вызывает ее с дескриптором окна (handle), сообщением (message) и двумя переменными (данные сообщения) связанными с сообщением, параметры типа WPARAM и LPARAM.

В WinClass мы определяемся с некоторыми вещами, такими как дескриптор экземпляра приложения HINSTANCE, курсор мыши (загружается стандартная стрелка), кисть закраски фона окна (выбран цвет кисти по умолчанию), и имя нашего класса.

Как только все поля WINDCLASS заполнены, мы регистрируем класс в системе Windows.

#include <windows.h>
 
 
LRESULT CALLBACK WindowProcedure
    (HWND hwnd, unsigned int message, WPARAM wParam, LPARAM lParam);
 
class WinClass
{
public:
    WinClass (WNDPROC winProc, char const * className, HINSTANCE hInst);
    void Register ()
    {
        ::RegisterClass (&_class);
    }
private:
    WNDCLASS _class;
};
 
WinClass::WinClass
    (WNDPROC winProc, char const * className, HINSTANCE hInst)
{
    _class.style = 0;
    _class.lpfnWndProc = winProc; // обязательная оконная процедура
    _class.cbClsExtra = 0;
    _class.cbWndExtra = 0;
    _class.hInstance = hInst;         // владелец класса
    _class.hIcon = 0;
    _class.hCursor = ::LoadCursor (0, IDC_ARROW); // опция
    _class.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1); // опция
    _class.lpszMenuName = 0;
    _class.lpszClassName = className; // обязательно имя класса окна
}

Как только класс окна зарегистрирован мы можем создать окно. Это делается вызовом CreateWindow, принимающим следующие аргументы:

 имя класса окна, которое мы зарегистрировали; заголовок окна; стиль окна;  позиция; размер; экземпляр приложения. Эта часть программы может также быть заключена в C++ класс, WinMaker.

class WinMaker
{
public:
    WinMaker (): _hwnd (0) {}
    WinMaker (char const * caption, 
              char const * className,
              HINSTANCE hInstance);
    void Show (int cmdShow)
    {
        ::ShowWindow (_hwnd, cmdShow);
        ::UpdateWindow (_hwnd);
    }
protected:
    HWND _hwnd;
};
 
WinMaker::WinMaker (char const * caption, 
                    char const * className,
                    HINSTANCE hInstance)
{
    _hwnd = ::CreateWindow (
        className,            // имя зарегистрированного класса окна
        caption,              // заголовок окна
        WS_OVERLAPPEDWINDOW,  // стиль окна
        CW_USEDEFAULT,        // x позиция
        CW_USEDEFAULT,        // y позиция
        CW_USEDEFAULT,        // ширена
        CW_USEDEFAULT,        // высота
        0,                    // дескриптор родительского окна
        0,                    // дескриптор меню
        hInstance,            // дескриптор экземпляра приложения
        0);                   // данные окна
}

Windows программа управляется событиями. Это означает, что вы, как программист, должны построить оборону. Поскольку, пользователь бомбардирует Windows различными действиями ввода, а Windows будет бомбардировать вашу программу сообщениями соответствующими этим действиям. Всё, что вы можете – это отвечать на эти сообщения. На рисунке схематично показано как это происходит.

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

Все эти сообщения выстраиваются в очередь сообщений. Windows создает очередь сообщений для каждого запущенного приложения (фактически для каждого потока). Ваша обязанность отлавливать сообщения одно за другим в петле (цикле) сообщений. Ваша программа содержит вызов GetMessage извлекающий сообщение из очереди. Потом вы вызываете DispatchMessage передовая его обратно в Windows. Может ли Windows выполнятся дальше без задержки и доставки всех этих своих сообщений? В принципе это возможно, но петля сообщений дает вашей программе шанс посмотреть их и может быть совершать какие-либо дополнительные действия перед их отправкой. Или нет …

Каждое сообщение адресовано особому окну. Когда вы говорите Windows доставлять какое-либо сообщение, то это предполагает некоторый уникальный класс окна, поиск соответствующей оконной процедуры, и вызов ее. Каждый сигнал сообщение, посланное вашему окну, будет направлено вашей оконной процедуре. Которая будет отвечать на сообщение. Стоит ли отвечать на каждый возможный тип сообщения Windows? Сообщений может быть сотни. Так или иначе, мы передаем управление Windows, по умолчанию используя DefWindowProc.





 

Давайте взглянем на WinMain. Исполняемая в Windows программа не стартует с main, она стартует с WinMain. В нашей WinMain, мы создаем WinClass и регистрируем его. Потом мы создаем окно (класс которого только что зарегистрировали) и показываем его. Фактически, WinMain вызывается с соответствующей директивой показа, пользователь может захотеть стартовать приложение минимизированным или максимизированным. Далее, мы определяем петлю сообщений и продолжаем посылку сообщений до тех пор, пока GetMessage не вернет 0. В этом месте wParam сообщения будет содержать возвращаемый код программы в целом.

 

int WINAPI WinMain (HINSTANCE hInst, HINSTANCE hPrevInst,
            char * cmdParam, int cmdShow)
{
    char className [] = "Winnie";
 
    WinClass winClass (WindowProcedure, className, hInst);
    winClass.Register ();
 
    WinMaker win ("Hello Windows!", className, hInst);
    win.Show (cmdShow);
 
    MSG  msg;
    int status;
 
    while ((status = ::GetMessage (& msg, 0, 0, 0)) != 0)
    {
        if (status == -1)
            return -1;
        ::DispatchMessage (& msg);
    }
 
    return msg.wParam;
}

 

Другая важная часть каждой Windows программы – это оконная процедура. Помните, Windows будет вызывать ее для всех типов сообщений. Все сообщения могут быть проигнорированы вашим приложением вызовом DefWindowProc, которым мы как бы говорим, чтоб Windows обработала сообщение сама. Сообщение WM_DESTROY посылаемое Windows, когда пользователь решает закрыть окно (немедленное закрытие посредством кнопки в меню заголовка). Стандартный ответ WM_DESTROY – это уведомление о выходе и возвращает ноль.

 

// Оконная процедура вызываемая Windows
 
LRESULT CALLBACK WindowProcedure
    (HWND hwnd, unsigned int message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
        case WM_DESTROY:
            ::PostQuitMessage (0);
            return 0;
 
    }
    return ::DefWindowProc (hwnd, message, wParam, lParam );
}