Как создать свое окно в windows

Disclaimer Казалось бы, что WinAPI уходит в прошлое. Давно уже существует огромное количество кросс-платформенных фреймфорков, Windows не только на десктопах, д...

Disclaimer

Казалось бы, что WinAPI уходит в прошлое. Давно уже существует огромное количество кросс-платформенных фреймфорков, Windows не только на десктопах, да и сами Microsoft в свой магазин не жалуют приложения, которые используют этого монстра. Помимо этого статей о том, как создать окошки на WinAPI, не только здесь, но и по всему интернету, исчисляется тысячами по уровню от дошколят и выше. Весь этот процесс разобран уже даже не по атомам, а по субатомным частицам. Что может быть проще и понятнее? А тут я еще…

Но не все так просто, как кажется.

Почему о WinAPI сейчас?

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

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

О чем это я? А вот об этом кусочке кода:

	
case WM_KEYDOWN:
	MessageBox(hwndDlg,"Die!","I'm dead!",MB_YESNO|MB_ICONINFORMATION);
	break;

Таким образом, авторы хотели добавить поддержку клавиатуры, но суровая реальность недр архитектуры диалоговых окон в Windows жестко пресекла такую самодеятельность. Те, кто пользовался эмулятором и отладчиком в нем, хоть раз видели это сообщение?
В чем же проблема?

Ответ такой: так делать нельзя!

И, возвращаясь, к изначальному вопросу о WinAPI: очень много популярных, и не очень, проектов продолжают его использовать и в настоящее время, т.к. лучше, чем на чистом API многие вещи не сделать (тут можно бесконечно приводить аналогии вроде сравнения высокоуровневых языков и ассемблера, но сейчас не об этом). Да и мало ли почему? Просто используют и все тут.

О проблеме

Диалоговые окна упрощают работу с GUI, одновременно лишая нас возможности сделать что-то самостоятельно. Например, сообщения WM_KEYDOWN/WM_KEYUP, приходящие в оконную процедуру, «съедаются» в недрах DefDlgProc, беря на себя такие вещи, как: Навигация по Tab, обработка клавиш Esc, Enter, и т.д. Кроме того, диалоги не нужно создавать вручную: проще, ведь, набросать кнопок, списков, в редакторе ресурсов, вызвать в WinMain CreateDialog/DialogBox и все готово.

Обойти такие мелкие неприятности просто. Есть, как минимум, два вполне легальных способа:

  1. Создать свой собственный класс через RegisterClassEx и в процедуре обработки класса схватывать WM_KEYDOWN, перенаправлять в процедуру обработки самого диалога. Да-да! Можно создавать диалоги со своим собственным классом, и встроенный в VS редактор даже позволяет задавать имя класса для диалога. Вот только кто об этом знает и этим пользуется?
    Минус очевиден: Нужно регистрировать еще один класс, иметь на 1 CALLBACK процедуру больше, суть которой будет всего-навсего в трансляции пары сообщений. Кроме того, мы не будем знать куда их транслировать, и придется городить костыли.
  2. Использовать встроенный механизм акселераторов. И нам даже не придется менять код диалоговой процедуры! Ну, разве что, добавить одну строчку в switch/case, но об этом ниже.

Tutorials?

Не побоюсь сказать, что все туториалы по созданию окон через WinAPI начинаются с такого незамысловатого кода, обозначая его, как «цикл обработки сообщений» (опущу детали по подготовке класса окна и прочую обвязку):


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

Здесь действительно все просто:

  1. GetMessage() выхватывает очередное сообщение из очереди, и ключевой момент: блокирует поток, если в очереди пусто.
  2. TranslateMessage() из WM_KEYDOWN/WM_KEYUP формирует сообщения WM_CHAR/WM_SYSCHAR (они нужны, если кто-то хочет сделать свой редактор текста).
  3. DispatchMessage() отправляет сообщение в оконную процедуру (если таковая существует).

Начнем с того, что этот код использовать опасно, и вот почему. Обратите внимание на сноску:

Because the return value can be nonzero, zero, or -1, avoid code like this:

while (GetMessage( lpMsg, hWnd, 0, 0)) ...

И ниже приводится пример правильного цикла.

Стоит сказать, что в шаблонах VS для Win32 приложений, написан именно такой неправильный цикл. И это очень печально. Ведь мало кто будет вникать в то, что сделали сами авторы, ведь это априори правильно. И неправильный код множится вместе с багами, которые очень сложно отловить.

После этого фрагмента кода, как правило, следует рассказ про акселераторы, и добавляется пара новых строчек (учитывая замечание в MSDN, предлагаю сразу писать правильный цикл):


    HACCEL hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR));
    BOOL bRet = 0;
    while ( bRet = GetMessage(&msg, nullptr, 0, 0) )
    {
           if ( -1 == bRet ) break;
           if ( !TranslateAccelerator(msg.hwnd, hAccel, &msg) )
           {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
            }
    }

Этот вариант я видел чаще всего. И он (та-дам) снова неправильный!

Сперва о том, что изменилось (потом о проблемах этого кода):

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

Собственно TranslateAccelerator этим и занимается: если видит WM_KEYDOWN и код клавиши, которые есть в этом списке, то (опять же ключевой момент) будет формировать сообщение WM_COMMAND (MAKEWPARAM(id, 1)) и отправлять в соответствующую для дескриптора окна, указанного в первом аргументе, процедуру обработки.

Из последней фразы, думаю, стало понятно, в чем проблема предыдущего кода.
Поясню: GetMessage выхватывает сообщения для ВСЕХ объектов типа «окно» (в число которых входят и дочерние: кнопки, списки и прочее), а TranslateAccelerator будет отправлять сформированную WM_COMMAND куда? Правильно: обратно в кнопку/список и т.д. Но мы обрабатываем WM_COMMAND в своей процедуре, а значит нам интересно ее получать в ней же.

Ясно, что TranslateAccelerator надо вызывать для нашего созданного окна:


    HWND hMainWnd = CreateWindow(...);
    HACCEL hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR));
    BOOL bRet = 0;
    while (bRet = GetMessage(&msg, nullptr, 0, 0))
    {
           if ( -1 == bRet ) break;
           if ( !TranslateAccelerator(hMainWnd, hAccel, &msg) )
           {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
            }
    }

И вроде все хорошо и замечательно теперь: мы разобрали все детально и все должно работать идеально.

И снова нет. :-) Это будет работать правильно, пока у нас ровно одно окно — наше. Как только появится немодальное новое окно (диалог), все клавиши, которые будут в нем нажаты оттранслируются в WM_COMMAND и отправляться куда? И опять же правильно: в наше главное окно.

На этом этапе предлагаю не городить костылей по решению этой тупиковой ситуации, а предлагаю рассмотреть вещи, которые уже реже (или почти не встречаются) в туториалах.

IsDialogMessage

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

На самом деле, делает она чуть больше, чем следует из названия. А именно:

  • Осуществляет навигацию по дочерним контролам кнопками Tab/Shift+Tab/вверх/вниз/вправо/влево. Плюс еще кое-что, но этого нам достаточно
  • По нажатии на ESC формирует WM_COMMAND( IDCANCEL )
  • По нажатии на Enter формирует WM_COMMAND( IDOK ) или нажатие на текущую кнопку по умолчанию
  • Переключает кнопки по умолчанию (рамочка у таких кнопок чуть ярче остальных)
  • Ну и еще разные штуки, которые облегчают пользователю работу с диалогом

Что она нам дает? Во-первых, нам не надо думать о навигации внутри окна. Нам и так все сделают. Кстати, навигацию по Tab можно сделать, добавив стиль WS_EX_CONTROLPARENT нашему основному окну, но это топорно и не так функционально.

Во-вторых, она нам облегчит жизнь по всем остальным пунктам, перечисленным в списке (и даже немного больше).

Вообще, она используется где-то в недрах Windows для обеспечения работы модальных диалоговых окон, а программистам дана, чтобы вызывать ее для немодальных диалогов. Однако мы ее можем использовать где угодно:

Although the IsDialogMessage function is intended for modeless dialog boxes, you can use it with any window that contains controls, enabling the windows to provide the same keyboard selection as is used in a dialog box.

Т.е. теперь, если мы оформим цикл так:


    HWND hMainWnd = CreateWindow(...);
    HACCEL hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR));
    BOOL bRet = 0;
    while (bRet = GetMessage(&msg, nullptr, 0, 0))
    {
           if ( -1 == bRet ) break;
           if ( !TranslateAccelerator(hMainWnd, hAccel, &msg) )
           {
                if ( !IsDialogMessage(hMainWnd, &msg) )
                {
		           TranslateMessage(&msg);
		           DispatchMessage(&msg);
                 }
            }
    }

То наше окошко будет иметь навигацию, как в родном диалоге Windows. Но теперь мы получили два недостатка:

  1. Этот код также будет хорошо работать только с одним (немодальным) окном;
  2. Получив все достоинства диалоговой навигации, мы лишаемся прелестей в виде сообщений WM_KEYDOWN/WM_KEYUP (только для самого окна, а не для дочерних контролов);

И вот на этом этапе вообще все туториалы заканчиваются и начинаются вопросы: How to handle keyboard events in a winapi standard dialog?
Это первая ссылка в гугле, но поверьте: тысячи их. Про предлагаемые решений (лучшее из которых — это создать свой класс диалогов, о чем я писал выше, до subclassing и RegisterHotKey. Где-то я даже видел «лучший» из способов: использовать Windows Hooks).

Пора поговорить о том, чего нет в туториалах и ответах.

Как правило (как правило! Если кому-то захочется большего, то можно регистрировать свой класс для диалогов и работать так. И, если же, кому-то это интересно, я могу дополнить этим статью) WM_KEYDOWN хотят тогда, когда хотят обработать нажатие на клавишу, которая выполнит функцию в независимости от выбранного контрола в окне — т.е. некая общая функция для всего данного конкретного диалога. А раз так, то почему бы не воспользоваться богатыми возможностями, которые нам сама WinAPI и предлагает: TranslateAccelerator.

Везде используют ровно одну таблицу акселераторов, и только для главного окна. Ну действительно: цикл GetMessage-loop один, значит и таблица одна. Куда еще их девать?

На самом деле, циклы GetMessage-loop могут быть вложенными. Давайте еще раз посмотрим описание PostQuitMessage:

The PostQuitMessage function posts a WM_QUIT message to the thread’s message queue and returns immediately; the function simply indicates to the system that the thread is requesting to quit at some time in the future.

И GetMessage:

If the function retrieves the WM_QUIT message, the return value is zero.

Таким образом, выход из GetMessage-loop осуществится, если мы вызовем PostQuitMessage в процедуре окна. Что это значит?

Мы можем для каждого немодального окна в нашей программе создавать свой собственный подобный цикл. В данном случае DialogBoxParam нам не подходит, т.к. оно крутит свой собственный цикл и повлиять мы на него не можем. Однако если создадим диалог через CreateDialogBoxParam или окно через CreateWindow, то можно закрутить еще один цикл. При этом в каждом таком окне и диалоге мы должны вызывать PostQuitMessage:


    HWND hMainWnd = CreateWindow(...);
    HACCEL hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR));
    BOOL bRet = 0;
    while (bRet = GetMessage(&msg, nullptr, 0, 0))
    {
           if ( -1 == bRet ) break;
           if ( !TranslateAccelerator(hMainWnd, hAccel, &msg) )
           {
                if ( !IsDialogMessage(hMainWnd, &msg) )
                {
		           TranslateMessage(&msg);
		           DispatchMessage(&msg);
                 }
            }
    }
// .... 
LRESULT CALLBACK WndProc(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam)
{
    switch( umsg )
    {
        case WM_MYMESSAGE:
        {
                    HWND hDlg = CreateDialog(hInstance, MAKEINTRESOURCE(IDD_MYDIALOG), hwnd, MyDialogBoxProc);
                    HACCEL hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR_FOR_MY_DIALOG));
                    BOOL bRet = 0, fSavedEnabledState = IsWindowEnabled(hwnd);
                    EnableWindow(hwnd, FALSE);  // disable parent window, as dialog window is modal
                    while (bRet = GetMessage(&msg, nullptr, 0, 0))
                    {
                           if ( -1 == bRet ) break;
                           if ( !TranslateAccelerator(hDlg, hAccel, &msg) )
                           {
                                if ( !IsDialogMessage(hDlg, &msg) )
                                {
                		           TranslateMessage(&msg);
                		           DispatchMessage(&msg);
                                 }
                            }
                    }
                    EnableWindow(hwnd, fSavedEnabledState);  // enable parent window. Dialog was closed
                    break;
        }
    }
}

INT_PTR CALLBACK MyDlgProc(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam)
{
      switch(umsg)
      {
            case WM_CLOSE:
             {
                    // EndDialog( hwnd, 0 ); -- DONT DO THAT! 
                    // EndDialog is valid ONLY for Modal Dialogs, created with DialogBox(Param)
                    DestroyWindow( hwnd );
                    break;
             }
             case WM_DESTROY:
             {
                    PostQuitMessage( 0 );
                    break;
              }
       // ....
      }
     
      return 0;
}

Обратите внимание: теперь для каждого нового окна в нашей программе мы можем добавить в обработку собственную таблицу акселераторов. WM_QUIT будет выхватывать GetMessage из цикла для диалога, а внешний цикл его даже не увидит. Почему так происходит?

Дело в том, что внешний цикл «встал» на вызове DispatchMessage, который вызвал нашу процедуру, которая крутит свой собственный внутренний цикл GetMessage с таким же DispatchMessage. Классический вложенный вызов (в данном случае DispatchMessage). Посему внешний цикл не получит WM_QUIT и не завершится на этом этапе. Все будет работать стройно.

Но и тут есть свои недостатки:
Каждый такой цикл будет обрабатывать сообщения только для «своего» окна. Про другие-то мы здесь не знаем. А значит, если где-то объявится еще один цикл, то все остальные окна не будут получать нужной обработки своих сообщений парой TranslateAccelerator/IsDialogMessage.

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

Делаем красиво

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

Во-первых, было бы логично, что только активное окно принимает сообщения. Т.е. для неактивного окна мы не будем транслировать акселераторы и передавать сообщения в IsDialogMessage.

Во-вторых, если для окна не задана таблица акселераторов, то транслировать нечего, будем просто отдавать сообщение в IsDialogMessage.

Создадим простой std::map, который будет мапить дескриптор окна в дескриптор таблицы акселераторов. Вот так:

std::map<HWND,HACCEL> l_mAccelTable;

И по мере создания окон будем в него добавлять новые окна с дескриптором на свою любимую таблицу (или нуль, если такая обработка не требуется).

Вот так:


BOOL AddAccelerators(HWND hWnd, HACCEL hAccel)
{
	if ( IsWindow( hWnd ) )
	{
		l_mAccelTable[ hWnd ] = hAccel; 
		return TRUE;
	}
	return FALSE;
}

BOOL AddAccelerators(HWND hWnd, LPCTSTR accel)
{
	return AddAccelerators( hWnd, LoadAccelerators( hInstance, accel ) );
}

BOOL AddAccelerators(HWND hWnd, int accel)
{
	return AddAccelerators( hWnd, MAKEINTRESOURCE( accel ) );
}

BOOL AddAccelerators(HWND hWnd)
{
	return AddAccelerators( hWnd, HACCEL( NULL ) );
}

Ну и после закрытия окна удалять. Вот так:


void DelAccel(HWND hWnd)
{
	std::map<HWND, HACCEL>::iterator me = l_mAccelTable.find( hWnd );

	if ( me != l_mAccelTable.end() )
	{
		if ( me->second )
		{
			DestroyAcceleratorTable( me->second );
		}
		l_mAccelTable.erase( me );
	}
}

Теперь, как создаем новый диалог/окно, вызываем AddAccelerators( hNewDialog, IDR_MY_ACCEL_TABLE ). Как закрываем: DelAccel( hNewDialog ).

Список окон с нужными дескрипторами у нас есть. Немного модифицируем наш основной цикл обработки сообщений:


    // ...
    HWND hMainWnd = CreateWindow(...);
    AddAccelerators(hMainWnd, IDR_ACCELERATOR);
    BOOL bRet = 0;
    while (bRet = GetMessage(&msg, nullptr, 0, 0))
    {
           if ( -1 == bRet ) break;
           if ( !HandleAccelArray( GetActiveWindow(), msg ) )
           {
	           TranslateMessage(&msg);
	           DispatchMessage(&msg);
            }
    }
    // ...

Значительно лучше! Что же там в HandleAccelArray и зачем там GetActiveWindow()?

Немного теории:

Есть две функции, возвращающих дескриптор активного окна GetForegroundWindow и GetActiveWindow. Отличие первой от второй вполне доходчиво описано в описании второй:

The return value is the handle to the active window attached to the calling thread’s message queue. Otherwise, the return value is NULL.

Если первая будет возвращать дескриптор любого окна в системе, то последняя только того, которое использует очередь сообщений нашего потока. Т.к. нас интересуют окна только нашего потока (а значит те, которые будут попадать в нашу очередь сообщений), то и возьмем последнюю.

Так вот HandleAccelArray, руководствуясь переданным ей дескриптором на активное окно, ищет это самое окно в нашей мапе, и если оно там есть, отдает это сообщение на трансляцию в TranslateAccelerator, а затем (если первый не увидел нужного) в IsDialogMessage. Если и последняя не обработала сообщение, то возвращаем FALSE, чтобы пройти по стандартной процедуре TranslateMessage/DispatchMessage.

Выглядит так:


BOOL HandleAccelWindow(std::map<HWND,HACCEL>::const_iterator mh, MSG & msg)
{
	const HWND & hWnd = mh->first;
	const HACCEL & hAccel = mh->second;

	if ( !TranslateAccelerator( hWnd, hAccel, &msg ) )
	{
		// message not for TranslateAccelerator. Try it with IsDialogMessage
		if ( !IsDialogMessage( hWnd, &msg ) )
		{
			// so, do default stuff
			return FALSE;
		}
	}

	// ok, message translated. Say to message-loop, to get next message
	return TRUE;
}

BOOL HandleAccelArray( HWND hActive, MSG & msg )
{
	if ( !hActive )
		return FALSE;  // no active window. Nothing to do

	std::map<HWND, HACCEL>::const_iterator mh = l_mAccelTable.find( hActive );
	if ( mh != l_mAccelTable.end() )
	{
		// Got it! Try to translate this message for the active window
		return HandleAccelWindow( mh, msg );
	}

	return FALSE;
}

Теперь каждое дочернее окно вправе добавить себе любимую таблицу акселераторов и спокойно ловить и обрабатывать WM_COMMAND с нужным кодом.

А что там еще об одной строчке в коде обработчика WM_COMMAND?

Описание в TranslateAccelerator гласит:

To differentiate the message that this function sends from messages sent by menus or controls, the high-order word of the wParam parameter of the WM_COMMAND or WM_SYSCOMMAND message contains the value 1.

Обычно код обработки WM_COMMAND выглядит так:


switch( HIWORD( wParam ) )
{
       case BN_CLICKED:     // command from buttons/menus
       {
              switch( LOWORD( wParam ) )
              {
                    case IDC_BUTTON1: DoButton1Stuff(); break;
                    case IDC_BUTTON2: DoButton2Stuff(); break; 
// ...
              }
              break;
       }
}

Теперь можно написать так:


switch( HIWORD( wParam ) )
{
       case 1: // accelerator
       case BN_CLICKED:     // command from buttons/menus
       {
              switch( LOWORD( wParam ) )
              {
                    case IDC_BUTTON1: DoButton1Stuff(); break;
                    case IDC_BUTTON2: DoButton2Stuff(); break; 
// ...
              }
              break;
       }
}

И теперь, возвращаясь к тому же fceux, добавив всего одну строчку в код обработки команд от кнопок, мы получим желаемое: управлять дебагером с клавиатуры. Достаточно добавить небольшую обертку вокруг главного цикла сообщений и новую таблицу акселераторов с нужными соответствиями VK_KEY => IDC_DEBUGGER_BUTTON.

P.S.: Мало кто знает, но можно создавать свою собственную таблицу акселераторов, а теперь и применять ее прямо налету.

P.P.S.: Т.к. DialogBox/DialogBoxParam крутит собственный цикл, то от при вызове диалога через них акселераторы работать не будут и наш цикл (или циклы) будет «простаивать».

P.P.P.S.: После вызова HandleAccelWindow мап l_mAccelTable может измениться, т.к. TranslateAccelerator или IsDialogMessage вызывают DispatchMessage, а там может встретиться AddAccelerators или DelAccel в наших обработчиках! Поэтому лучше его после этой функции не трогать.

Пощупать код можно здесь. За основу был взят код, генерируемый из стандартного шаблона MS VS 2017.


Загрузить PDF


Загрузить PDF

Вы никогда не задумывались, как сделаны такие программы, как Paint или калькулятор? Ну, узнайте тогда, как создать простое приложение, используя это пошаговое руководство.

Шаги

  1. Изображение с названием 46622 1

    1

    Приобретите компилятор. Компилятор преобразует необработанный исходный код (который вы скоро напишете) в исполняемое приложение. Для целей этого урока приобретите DEV-CPP IDE. Вы можете скачать его здесь here.

  2. Изображение с названием 46622 2

    2

    Установив DEV-CPP, откройте его. Вам будет представлено окно с текстовой областью, где вы будете писать свой исходный код.

  3. Изображение с названием 46622 3

    3

    Подготовьтесь к написанию программы для отображения текста в текстовом поле. Перед тем как начать писать исходный текст, имейте в виду, что приложения Win32 не ведут себя таким же образом, как другие языки, такие как JAVA.

  4. Изображение с названием 46622 4

    4

    В главном окне DEV- CPP перейдите в меню File -> New -> Project. Вам будет предложено другое окно. Выберите небольшую картинку с названием «Windows Application» и установите язык как «C», а не «C++.» В текстовом поле, где написано «Name», введите «SimpleProgram». Далее DEV-CPP спросит вас, куда вы хотите сохранить его. Сохраните файл в любом каталоге, но только убедитесь, что сохранили его. Как только закончите с этим, вам будет представлен шаблон на экране источника. Нажмите Ctrl + A, а затем Backspace. Причина, почему мы делаем так, это то, что мы можем начинать заново.

  5. Изображение с названием 46622 5

    5

    В начале вашего исходного текста напишите «#include <windows.h>» (без кавычек). Это включает в себя библиотеку windows, так что вы можете создавать приложение. Прямо под этим напишите: #include «resource.h» И затем введите: const char g_szClassName[] = «myWindowClass»;

  6. Изображение с названием 46622 6

    6

    Напишите один способ для обработки всех сообщений и напишите другой метод, где мы будем обрабатывать сообщения из ресурсов. Не волнуйтесь, если вас это смущает. Все станет ясно в дальнейшем. Теперь сохраните ваш источник как SimpleProg.c. На данный момент мы оставив все как есть.

  7. Изображение с названием 46622 7

    7

    Создайте скрипт ресурса Resource Script. Resource Script является частью исходного кода, который определяет все элементы управления (например,TextBox, Buttons, и т.д.). Вы включите Resource Script в вашу программу и вуаля! У вас будет программа. Написать Resource Script хоть и просто, но это может занять много времени, если у вас нет Visual Editor. Это потому, что вам нужно будет подсчитать приблизительно точные X и Y координаты элементов управления и т.д. В главном окне DEV-CPP перейдите в меню File -> New -> Resource File. DEV-CPP спросит вас: «Add resource file to current Project?». Нажмите YES. В верхней части вашего скрипта ресурса введите #include «resource.h», and also type #include <afxres.h> Это касается всех элементов управления.

  8. Изображение с названием 46622 8

    8

    Создайте свой первый элемент управления: простое меню.
    Введите:

    IDR_THEMENU MENU
    BEGIN
    POPUP "&File"
    BEGIN
    MENUITEM "E&xit", ID_FILE_EXIT
    END
    END
    

    Часть «IDR_THEMENU» определяет ваше меню как THEMENU. Вы можете назвать его, как хотите. Часть BEGIN говорит сама за себя. POPUP «&File» создает категорию нового меню под названием File. Символ & позволяет пользователю вашего приложения нажимать Ctrl + F на клавиатуре и быстро получить доступ к меню. The MENUITEM «E&xit», ID_FILE_EXIT добавляет пункт меню в категорию File. Вы должны, однако, определить пункт меню с помощью ID_FILE_EXIT.

  9. Изображение с названием 46622 9

    9

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

    IDD_SIMPLECONTROL DIALOG 50, 50, 150, 142
    STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
    MENU IDR_THEMENU
    CAPTION "Simple Prog"
    FONT 8, "MS Sans Serif"
    BEGIN
    DEFPUSHBUTTON "Hello!", ID_HELLO, 10, 10, 40, 15
    END
    

    Часть IDD_SIMPLECONTROL определяет ваш диалог. Четыре цифры после слова «DIALOG» определяют х-позицию, у-позицию, ширину и высоту диалогового окна. Не волнуйтесь слишком о части Style на данный момент. Часть MENU IDR_THEMENU помещает наше старое меню в программу. Часть CAPTION говорит сама за себя, как и шрифт. Часть DEFPUSHBUTTON создает нашу кнопку с названием «Hello!» и определим ее, написав ID_HELLO и задав ей координаты х–позиции, у-позиции, ширину и высоту.

  10. Изображение с названием 46622 10

    10

    Вот и все! Мы закончили с нашим скриптом ресурса. Только еще одно: мы должны присвоить значения всем величинам, которых мы определили в нашем скрипте ресурсов (например, IDR_THEMENU, и т.д.). Сохраните файл ресурса как SimpleProg.rc

  11. Изображение с названием 46622 11

    11

    Выберите File -> New -> Source File. Add the source file to the current project? -> Yes. Вам будет предложен пустой экран. Для присвоения значений нашим определенным элементам управления мы даем им числа. Не имеет большого значения, каким числам вы присваиваете элементы управления, но они должны быть организованными. Например, не определяйте элемент управления, присвоив ему случайное число (как 062 491 или пр.). Поэтому введите:

    #define IDR_THEMENU 100
    #define ID_FILE_EXIT 200
    #define IDD_SIMPLECONTROL 300
    #define ID_HELLO 400
    
  12. Изображение с названием 46622 12

    12

    Сохранить этот файл как resource.h . Помните, как мы писали «#include «resource.h»»? Ну, вот почему мы это сделали. Нам нужно было присвоить значения.

  13. Изображение с названием 46622 13

    13

    Вернитесь к ресурсу, нашему SimpleProg.c или тому, как его вы назвали. Введите:

    int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow){return DialogBox(hInstance, MAKEINTRESOURCE(IDD_SIMPLECONTROL), NULL, SimpleProc);}
    
  14. Изображение с названием 46622 14

    14

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

  15. Изображение с названием 46622 15

    15

    Введите: BOOL CALLBACK SimpleProc(HWND hWndDlg, UINT Message, WPARAM wParam, LPARAM lParam){switch(Message){case WM_INITDIALOG:return TRUE;case WM_COMMAND:switch ( LOWORD (wParam) ) {case ID_HELLO:MessageBox(NULL,»Hey», «Hallo!», MB_OK)break; case ID_FILE_EXIT:EndDialog(hWndDlg, 0);break;}break;case WM_CLOSE:EndDialog(hWndDlg, 0); break; default: return FALSE;}return TRUE;}

  16. Изображение с названием 46622 16

    16

    Эта часть обрабатывает диалоговые сообщения. Например, в случае ID_HELLO (наша кнопка), мы создаем окно сообщения с содержанием «Hello!». Кроме того, в случае, когда переходим в File и Exit, мы закрываем окно в случае ID_FILE_EXIT.

  17. Изображение с названием 46622 17

    17

    Убедитесь, что ваш SimpleProc предшествует части int WINAPI WINMAIN ! Это важно, если вы хотите, чтобы ваша программа работала.

  18. Изображение с названием 46622 18

    18

    Нажмите F9, чтобы скомпилировать и запустить программу!

    Реклама

Советы

  • Если вы расстроены (на любом этапе работы) — отдохните и вернитесь обратно.
  • Это учебник для начинающих, так много частей не объясняются. Даже если это учебник для начинающих, рекомендуется, чтобы у вас был некоторый опыт в области программирования (например, знание switch statments, if-else, и т.д.)
  • Если вы запутались, есть много учебников, доступных в Интернете.

Реклама

Предупреждения

  • Изучение Win32 не является легкой задачей. Вам нужно обратить внимание на это. Это, безусловно, не для слабонервных.
  • Нумерация строк в этом учебнике несколько искажает исходный код.

Реклама

Об этой статье

Эту страницу просматривали 18 703 раза.

Была ли эта статья полезной?

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

Как создать окно Windows

Вам понадобится

  • — компилятор;
  • — Windows Platform SDK.

Инструкция

Зарегистрируйте класс окна, которое должно быть создано, если это необходимо. Произведите вызов API-функций RegisterClass, RegisterClassEx или используйте соответствующий функционал применяемого фреймворка.

Функции RegisterClass и RegisterClassEx принимают в качестве единственного параметра указатели на структуры типа WNDCLASS и WNDCLASSEX соответственно. Возвращаемое значение типа ATOM может использоваться вместо имени класса при создании окна. Если вызов функции завершится неудачей, будет возвращено значение 0.

Создайте экземпляр структуры типа WNDCLASS или WNDCLASSEX. Заполните все необходимые поля. В частности, корректные значения должны быть помещены в:

— cbSize — размер структуры в байтах;
— style — набор стилей класса окна;
— lpfnWndProc — указатель на оконную процедуру;
— hInstance дескриптор модуля, в котором производится регистрация класса окна;
— lpszClassName — символическое имя класса.

В остальные поля могут быть записаны значения NULL. Произведите вызов функции для регистрации класса окна. Проверьте возвращенный результат.

Выберите существующий класс окна, если это необходимо. Вам должно быть известно символическое имя класса (то, которое передается через указатель lpszClassName при его регистрации) или соответствующее значение типа ATOM. Класс может быть локальным на уровне приложения, глобальным на уровне приложения (регистрация выполнена с флагом CS_GLOBALCLASS) или системным. К последнему типу относятся классы окон с именами: Button, ComboBox, Edit, ListBox, MDIClient, ScrollBar, Static. Такие классы, как RichEdit20W или SysListView32, регистрируются при загрузке соответствующих библиотек.

Создайте окно Windows. Воспользуйтесь API-функциями CreateWindow, CreateWindowEx или соответствующими методами-обертками объектов классов используемого фреймворка или библиотеки. Прототип функции CreateWindowEx выглядит следующим образом:

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
);

Функция CreateWindow отличается от CreateWindowEx лишь отсутствием параметра dwExStyle.

Осуществите вызов CreateWindow или CreateWindowEx. Передайте в качестве параметра lpClassName имя или значение типа ATOM класса окна, определенное на первом или втором шаге. Параметрами x, y, nWidth, nHeight могут являться координаты и размеры создаваемого окна. Дескриптор окна-родителя (если такое имеется) передается через hWndParent.

Сохраните и проанализируйте значение, возвращенное функциями CreateWindow или CreateWindowEx. При успехе они вернут дескриптор нового окна, при неудаче — NULL.

Видео по теме

Войти на сайт

или

Забыли пароль?
Еще не зарегистрированы?

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

Создание окна

Приложение создает
свои окна (в том числе главное окно),
используя функцию CreateWindow()илиCreateWindowEx()и предоставляя Windows информацию, которая
требуется для определения атрибутов
окна.CreateWindowEx()имеет параметрdwExStyle,
который отсутствует у функцииCreateWindow();
в остальном эти функции идентичны.
ФактическиCreateWindow()просто вызываетCreateWindowEx()с параметромdwExStyle,
равным нулю.

Win32 API поддерживает
дополнительные функции, в том числе
DialogBox(),CreateDialog(),
иMessageBox(),
необходимые для создания окон специального
назначения таких, как диалоговые окна
и окна сообщений.

Атрибуты окна

При создании окна
приложение должно предоставить Windows
следующую информацию:

Класс окна (Window
class)
Имя окна (Window name)
Стиль окна
(Window style)
Родитель или владелец окна
(Parent or owner window)
Размер (Size)
Расположение
(Location)
Позиция (Position)
Идентификатор
дочернего окна или дескриптор меню
(Child-window identifier or menu
handle)
Дескриптор копии
приложения (Instance
handle)
Дополнительные
данные (Creation data)

Класс окна

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

Имя окна

Окно может иметь
имя, которое представляет собой текстовую
строку, идентифицирующую окно для
пользователя. Главное окно, диалоговое
окно или окно сообщений обычно отображают
свое имя в заголовке окна, если он
присутствует. Для контрола способ
отображения имени окна зависит от
класса, к которому относится данный
контрол. Например, кнопка и поле
редактирования (edit control) отображают свое
имя в пределах прямоугольника, занимаемого
контролом. Список (list box) и раскрывающийся
список (combo box) не отображают свое имя.

С помощью функции
SetWindowText()приложение может изменить имя окна
после его создания. Используя функцииGetWindowTextLength()иGetWindowText(),
можно получить текущий текст названия
окна.

Стиль окна

Каждое окно имеет
один или более стилей окна. Стиль окна
— это именованная константа, которая
определяет особенности вида и поведения
окна, которые не определены классом
окна. Например, класс SCROLLBAR создает
полосу прокрутки, но стили SBS_HORZ, и
SBS_VERT определяют, создается ли горизонтальная
или вертикальная полоса прокрутки.
Несколько стилей окна относятся ко всем
окнам, но большинство относятся к окнам
определенных классов. За интерпретацию
стилей окна отвечает Windows и, в некоторой
степени, функция окна данного класса.

Родитель или владелец окна

Окно может иметь
родительское окно. Окно, которое имеет
родителя, называется дочерним окном.Родительское окнопредоставляет
систему координат, используемую для
позиционирования дочернего окна. Наличие
родительского окна воздействует на вид
и поведение окна; например, дочернее
окно отсекается так, чтобы никакая его
часть не могла появиться за пределами
родительского окна. Окно, которое не
имеет родителя, или чей родитель — окно
рабочего стола (desktop window), называетсяокном верхнего уровня(top-level
window). Приложение использует функциюEnumWindows(),
чтобы получить дескриптор каждого
своего окна верхнего уровня.EnumWindows()передает поочередно дескриптор каждого
окна верхнего уровня определенной
приложением функции обратного вызоваEnumWindowsProc().

Окно может владеть
другим окном или принадлежать другому
окну. Окно, принадлежащее другому окну,
всегда появляется поверх окна-владельца
(но может выходить за его пределы),
становится невидимым, когда окно-владелец
сворачивается, и уничтожается, когда
уничтожается окно-владелец.

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

Вам понадобится

  • — компилятор;
  • — Windows Platform SDK.

Инструкция

  • Зарегистрируйте класс окна, которое должно быть создано, если это необходимо. Произведите вызов API-функций RegisterClass, RegisterClassEx или используйте соответствующий функционал применяемого фреймворка.Функции RegisterClass и RegisterClassEx принимают в качестве единственного параметра указатели на структуры типа WNDCLASS и WNDCLASSEX соответственно. Возвращаемое значение типа ATOM может использоваться вместо имени класса при создании окна. Если вызов функции завершится неудачей, будет возвращено значение 0.Создайте экземпляр структуры типа WNDCLASS или WNDCLASSEX. Заполните все необходимые поля. В частности, корректные значения должны быть помещены в:- cbSize — размер структуры в байтах;- style — набор стилей класса окна;- lpfnWndProc — указатель на оконную процедуру;- hInstance дескриптор модуля, в котором производится регистрация класса окна;- lpszClassName — символическое имя класса.В остальные поля могут быть записаны значения NULL. Произведите вызов функции для регистрации класса окна. Проверьте возвращенный результат.
  • Выберите существующий класс окна, если это необходимо. Вам должно быть известно символическое имя класса (то, которое передается через указатель lpszClassName при его регистрации) или соответствующее значение типа ATOM. Класс может быть локальным на уровне приложения, глобальным на уровне приложения (регистрация выполнена с флагом CS_GLOBALCLASS) или системным. К последнему типу относятся классы окон с именами: Button, ComboBox, Edit, ListBox, MDIClient, ScrollBar, Static. Такие классы, как RichEdit20W или SysListView32, регистрируются при загрузке соответствующих библиотек.
  • Создайте окно Windows. Воспользуйтесь API-функциями CreateWindow, CreateWindowEx или соответствующими методами-обертками объектов классов используемого фреймворка или библиотеки. Прототип функции CreateWindowEx выглядит следующим образом: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);Функция CreateWindow отличается от CreateWindowEx лишь отсутствием параметра dwExStyle.Осуществите вызов CreateWindow или CreateWindowEx. Передайте в качестве параметра lpClassName имя или значение типа ATOM класса окна, определенное на первом или втором шаге. Параметрами x, y, nWidth, nHeight могут являться координаты и размеры создаваемого окна. Дескриптор окна-родителя (если такое имеется) передается через hWndParent.Сохраните и проанализируйте значение, возвращенное функциями CreateWindow или CreateWindowEx. При успехе они вернут дескриптор нового окна, при неудаче — NULL.
  • Оцените статью!

     

    Оконные приложения строятся по принципам событийно-управляемого программирования (event-driven programming) — стиля программирования, при котором поведение компонента системы определяется набором возможных внешних событий и ответных реакций компонента на них. Такими компонентами в Windows являются окна.

    С каждым окном в Windows связана определенная функция обработки событий – оконная функция. События для окон называются сообщениями. Сообщение относится к тому или иному типу, идентифицируемому определенным кодом (32-битным целым числом), и сопровождается парой 32-битных параметров (WPARAM и LPARAM), интерпретация которых зависит от типа сообщения.

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

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

    Классическое оконное приложение, как правило, состоит по крайней мере из двух функций:

    • стартовая функция, создающая главное окно WinMain();
    • функция обработки сообщений окна (оконная функция).

    Стартовая функция WinMain

    В консольной программе на С точкой входа является функция main(). С этого места программа начинает выполняться.
    Точкой входа программы для Windows является функция WinMain().

    int WINAPI WinMain(
          HINSTANCE hInstance,
          HINSTANCE hPrevInstance,
          PSTR szCmdLine,
          int iCmdShow) {…}

    Эта функция использует последовательность вызовов API и при завершении возвращает операционной системе целое число.
    Аргументы функции:

    • hInstance – дескриптор процесса (instance handle) – число, идентифицирующее программу, когда она работает под Windows. Если одновременно работают несколько копий одной программы, каждая копия имеет свое значение hInstance.
    • hPrevInstance — предыдущий дескриптор процесса (previous instance) — в настоящее время устарел, всегда равен NULL.
    • szCmdLine — указатель на оканчивающуюся нулем строку, в которой содержатся параметры, переданные в программу из командной строки. Можно запустить программу с параметром командной строки, вставив этот параметр после имени программы в командной строке.
    • iCmdShow — целое константное значение, показывающее, каким должно быть выведено на экран окно в начальный момент. Задается при запуске программы другой программой.  В большинстве случаев число равно 1 (SW_SHOWNRMAL).
    Имя Значение Описание
    SW_HIDE 0 Скрывает окно и делает активным другое окно
    SW_SHOWNORMAL 1 Отображает и делает активным окно в его первоначальном размере и положении.
    SW_SHOWMINIMIZED 2 Активизирует окно и отображает его в свернутом виде
    SW_SHOWMAXIMIZED 3 Активизирует окно и отображает его в полноэкранном виде
    SW_SHOWNOACTIVATE 4 Отображает окно аналогично SW_SHOWNORMAL, но не активизирует его
    SW_SHOW 5 Отображает и делает активным окно с текущим размером и положением.
    SW_MINIMIZE 6 Сворачивает текущее окно и делает активным следующее окно в порядке очереди.
    SW_SHOWMINNOACTIVE 7 Сворачивает окно аналогично SW_SHOWMINIMIZED, но не активизирует его.
    SW_SHOWNA 8 Отображает окно в текущей позиции аналогично SW_SHOW, но не активизирует его.
    SW_RESTORE 9 Отображает и активизирует окно. Если окно было свернуто или развернуто во весь экран, оно отображается в своем первоначальном положении и размере.
    SW_SHOWDEFAULT 10 Отображает окно способом, заданным по умолчанию.
    SW_FORCEMINIMIZE 11 Применяется для минимизации окон, связанных с различными потоками.

     
    В структуре стартовой функции Windows можно выделить следующие операции, образующие «скелет» программы:

    • регистрация класса окна;
    • создание главного окна;
    • отображение и перерисовка главного окна;
    • цикл обработки очереди сообщений.

    Регистрация класса окна

    Регистрация класса окна осуществляется функцией

    ATOM WINAPI RegisterClass(_In_ const WNDCLASS *lpWndClass);

    Прототип функции находится в файле библиотеки user32.dll.
    Единственным аргументом функции является указатель на структуру

    typedef struct _WNDCLASS {
      UINT      style;
      WNDPROC   lpfnWndProc;
      int       cbClsExtra;
      int       cbWndExtra;
      HINSTANCE hInstance;
      HICON     hIcon;
      HCURSOR   hCursor;
      HBRUSH    hbrBackground;
      LPCTSTR   lpszMenuName;
      LPCTSTR   lpszClassName; } WNDCLASS;

    Члены структуры
    style — устанавливает стиль(и) класса. Этот член структуры может быть любой комбинацией стилей класса.

    Имя Значение Описание
    CS_VREDRAW 0x01 Вертикальная перерисовка: осуществлять перерисовку окна при перемещении или изменении высоты окна.
    CS_HREDRAW 0x02 Горизонтальная перерисовка: осуществлять перерисовку окна при перемещении или изменении ширины окна.
    CS_KEYCVTWINDOW 0x04 В окне будет выполняться преобразование виртуальных клавиш.
    CS_DBLCLKS 0x08 Окну будут посылаться сообщения о двойном щелчке кнопки мыши.
    CS_OWNDC 0x20 Каждому экземпляру окна присваивается собственный контекст изображения.
    CS_CLASSDC 0x40 Классу окна присваивается собственный контекст изображения,который можно разделить между копиями.
    CS_PARENTDC 0x80 Классу окна передается контекст изображения родительского окна.
    CS_NOKEYCVT 0x100 Отключается преобразование виртуальных клавиш.
    CS_NOCLOSE 0x200 Незакрываемое окно: в системном меню блокируется выбор пункта закрытия окна.
    CS_SAVEBITS 0x800 Часть изображения на экране, закрытая окном, сохраняется.
    CS_BYTEALIGNCLIENT 0x1000 Выравнивание клиентской области окна: использование границы по байту по оси x.
    CS_BYTEALIGNWINDOW 0x2000 Выравнивание окна: bспользование границы по байту по оси x.
    CS_PUBLICCLASS CS_GLOBALCLASS 0x4000 Определяется глобальный класс окон.

    lpfnWndProc — указатель на оконную процедуру.

    cbClsExtra — устанавливает число дополнительных байт, которые размещаются вслед за структурой класса окна. Система инициализирует эти байты нулями, в большинстве случаев равен 0.

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

    hInstance — дескриптор экземпляра, который содержит оконную процедуру для класса.

    hIcon — дескриптор значка класса, дескриптор ресурса значка. Если этот член структуры — NULL, система предоставляет заданный по умолчанию значок.

    hCursor — дескриптор курсора класса, дескриптор ресурса курсора. Если этот член структуры — NULL, приложение устанавливает форму курсора всякий раз, когда мышь перемещается в окно прикладной программы.

    hbrBackground — дескриптор кисти фона класса, дескриптор физической кисти, которая используется, чтобы красить цветом фона, или код цвета, преобразованный к типу HBRUSH.

    lpszMenuName — указатель на символьную строку с символом конца строки (‘’), которая устанавливает имя ресурса меню класса. Можно использовать целое число, чтобы идентифицировать меню с помощью макроса MAKEINTRESOURCE(int). Если этот член структуры — NULL, окна, принадлежащие этому классу,  не имеют заданного по умолчанию меню.

    lpszClassName — указатель на символьную строку с именем класса, оканчивающуюся ‘’.

    Создание окна

    Создание окна осуществляется функцией

    HWND WINAPI CreateWindow(
    _In_opt_  LPCTSTR lpClassName,
    _In_opt_  LPCTSTR lpWindowName,
    _In_      DWORD dwStyle,
    _In_      int x,
    _In_      int y,
    _In_      int nWidth,
    _In_      int nHeight,
    _In_opt_  HWND hWndParent,
    _In_opt_  HMENU hMenu,
    _In_opt_  HINSTANCE hInstance,
    _In_opt_  LPVOID lpParam );

    Прототип функции находится в файле библиотеки user32.dll.
    Возвращаемое значение – дескриптор создаваемого окна. В случае невозможности создать окно возвращается  NULL.

    Аргументы функции:
    lpClassName – указывает на строку с ‘’ в конце, которая определяет имя класса окна. Имя класса может быть зарегистрированным функцией RegisterClass или любым из предопределенных имен класса элементов управления.

    lpWindowName — указывает на строку с ‘’ в конце, которая определяет имя окна.

    dwStyle — определяет стиль создаваемого окна.

    Имя Значение Описание
    WS_BORDER 0x00800000 Окно имеет тонкую границу в виде линии.
    WS_CAPTION 0x00C00000 Окно имеет строку заголовка.
    WS_CHILD 0x40000000 Окно является дочерним.
    WS_DISABLED 0x08000000 Окно является изначально неактивным.
    WS_GROUP 0x00020000 Окно группирует другие управляющие элементы.
    WS_HSCROLL 0x00100000 Окно содержит горизонтальную полосу прокрутки.
    WS_MAXIMIZE 0x01000000 Исходный размер окна – во весь экран.
    WS_MINIMIZE 0x20000000 Исходно окно свернуто.
    WS_OVERLAPPED 0x00000000 Окно может быть перекрыто другими окнами.
    WS_POPUP 0x80000000 Всплывающее окно.
    WS_SYSMENU 0x00080000 Окно имеет системное меню в строке заголовка.
    WS_VISIBLE 0x10000000 Окно изначально видимое.
    WS_VSCROLL 0x00200000 Окно имеет вертикальную полосу прокрутки.

    x — определяет координату левой стороны окна относительно левой стороны экрана. Измеряется в единицах измерения устройства, чаще всего в точках (pt). Для дочернего окна определяет координату левой стороны относительно начальной координаты родительского окна. Если установлен как CW_USEDEFAULT, Windows выбирает заданную по умолчанию позицию окна.

    у – определяет координату верхней стороны окна относительно верхней стороны экрана. Измеряется в единицах измерения устройства, чаще всего в точках (pt). Для дочернего окна определяет координату верхней стороны относительно начальной координаты родительского окна.

    nWidth – определяет ширину окна в единицах измерения устройства. Если параметр соответствует CW_USEDEFAULT, Windows выбирает заданную по умолчанию ширину и высоту для окна.

    nHeight – определяет высоту окна в единицах измерения устройства.

    hWndParent – дескриптор родительского окна.

    hMenu – идентифицирует меню, которое будет использоваться окном. Этот параметр может быть NULL, если меню класса будет использовано.

    hInstance — идентифицирует экземпляр модуля, который будет связан с окном.

    lpParam — указывает на значение, переданное окну при создании.

    Отображение и перерисовка окна

    Отображение окна осуществляется функцией

    BOOL WINAPI ShowWindow(
       _In_  HWND hWnd,
       _In_  int nCmdShow);

    Прототип функции находится в файле библиотеки user32.dll.
    Возвращаемое значение: 1 – успешное отображение окна, 0 – ошибка.

    Аргументы функции:
    hWnd – дескриптор отображаемого окна.

    nCmdShow – константа, определяющая, как будет отображаться окно согласно таблице.

     
    Перерисовка окна осуществляется функцией

    BOOL UpdateWindow(_In_  HWND hWnd);

    Прототип функции находится в файле библиотеки user32.dll.
    Возвращаемое значение: 1 – успешная перерисовка окна, 0 – ошибка.
    Аргумент функции hWnd – дескриптор окна.

    Цикл обработки сообщений

    После вызова функции UpdateWindow, окно окончательно выведено на экран. Теперь программа должна подготовить себя для получения информации от пользователя через клавиатуру и мышь. Windows поддерживает «очередь сообщений» (message queue) для каждой программы, работающей в данный момент в системе Windows. Когда происходит ввод информации, Windows преобразует ее в «сообщение», которое помещается в очередь сообщений программы. Программа извлекает сообщения из очереди сообщений, выполняя блок команд, известный как «цикл обработки сообщений» (message loop):

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

    Для получения сообщения из очереди используется функция:

    BOOL WINAPI GetMessage(
    _Out_     LPMSG lpMsg,
    _In_opt_  HWND hWnd,
    _In_      UINT wMsgFilterMin,
    _In_      UINT wMsgFilterMax);

    Прототип функции находится в файле библиотеки user32.dll.
    В случае получения из очереди сообщения, отличного от WM_QUIT, возвращает ненулевое значение.

    Аргументы функции:
    lpMsg — указатель на структуру сообщения.

    typedef struct MSG {
      HWND hwnd; // дескриптор окна, очередь сообщений которого просматривается
      UINT message; // идентификатор сообщения
      WPARAM wParam; // дополнительная информация о сообщении,
      LPARAM lParam; // зависит от идентификатора сообщения
      DWORD time; // время помещения сообщения в очередь
      POINT pt; // структура, содержащая координаты курсора в момент помещения сообщения в очередь
    MSG;

    Структура POINT имеет вид

    typedef struct POINT
    {
      LONG x; // координата x
      LONG y; // координата y
    POINT;

    hWnd — дескриптор окна, очередь для которого просматривается.

    wMsgFilterMin — нижняя граница фильтра идентификаторов сообщений.

    wMsgFilterMax — верхняя граница фильтра идентификаторов сообщений.

    Функция

    BOOL WINAPI TranslateMessage(_In_ const MSG *lpMsg);

    передает аргумент — структуру msg обратно в Windows для преобразования какого-либо сообщения с клавиатуры. Возвращает ненулевое значение в случае успешной расшифровки сообщения, 0 – ошибка.

    Функция

    LRESULT WINAPI DispatchMessage(_In_ const MSG *lpmsg );

    передает аргумент — структуру msg обратно в Windows. Windows отправляет сообщение для его обработки соответствующей оконной процедуре — таким образом, Windows вызывает соответствующую оконную функцию, указанную при регистрации класса окна.

    После того, как оконная функция обработает сообщение, оно возвращается в Windows, которая все еще обслуживает вызов функции DispatchMessage. Когда Windows возвращает управление в стартовую функцию WinMain() к следующему за вызовом DispatchMessage коду, цикл обработки сообщений в очередной раз возобновляет работу, вызывая GetMessage.
    Возвращает значение, определяемое оконной функцией, которое чаще всего игнорируется.
    Прототипы функций находятся в файле библиотеки user32.dll.

    Пример стартовой функции, создающей и выводящей окно размером 500х300 точек:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29

    #include <windows.h>
    LONG WINAPI WndProc(HWNDUINTWPARAM,LPARAM);
    int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                                 LPSTR lpCmdLine, int nCmdShow)
    {
      HWND hwnd; // дескриптор окна
      MSG msg; // структура сообщения
      WNDCLASS w; // структура класса окна
      // Регистрация класса окна
      memset(&w,0,sizeof(WNDCLASS));
      w.style = CS_HREDRAW | CS_VREDRAW;
      w.lpfnWndProc = WndProc; // имя оконной функции
      w.hInstance = hInstance;
      w.hbrBackground = (HBRUSH)(WHITE_BRUSH);
      w.lpszClassName = «My Class»;
      RegisterClass(&w);
      // Создание окна
      hwnd = CreateWindow(«My Class»,»Окно пользователя»,
      WS_OVERLAPPEDWINDOW, 500, 300, 500, 380, NULLNULL, hInstance, NULL);
      ShowWindow(hwnd,nCmdShow); // отображение
      UpdateWindow(hwnd);          // перерисовка
      // Цикл обработки сообщений
      while(GetMessage(&msg,NULL,0,0))
      {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
      }
      return msg.wParam;
    }

    Оконная функция — обработка сообщений окна

    Оконная функция предназначена для обработки сообщений окна. Функция обработки сообщений окна организована по принципу ветвления, состоящего из последовательной проверки типа сообщения. При совпадении типа сообщения, переданного в структуре Message с соответствующей веткой, осуществляется его обработка. Минимальный вид оконной функции представлен ниже.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    LONG WINAPI WndProc(HWND hwnd, UINT Message, WPARAM wparam, LPARAM lparam)
    {
      switch (Message)
      {
        case WM_DESTROY:
          PostQuitMessage(0);
          break;
        default:
          return DefWindowProc(hwnd, Message, wparam, lparam);
      }
      return 0;
    }

    4 аргумента оконной функции идентичны первым четырем полям структуры сообщения MSG.

    В примере обрабатывается только один тип сообщения WM_DESTROY, которое передается оконной функции при закрытии окна.

    Вызов функции DefWindowProc() обрабатывает по умолчанию все сообщения, которые не обрабатывает оконная процедура.
    Функция PostQuitMessage() сообщает Windows, что данный поток запрашивает завершение. Аргументом является целочисленное значение, которое функция вернет операционной системе.

    Результат выполнения программы, выводящей окно:
    Окно

    Назад: Создание Windows-приложений

    Понравилась статья? Поделить с друзьями:
  • Как создать сборку windows 10 с нужными программами
  • Как создать самоподписанный сертификат ssl windows 10
  • Как создать сайт на компьютере windows 7
  • Как создать сайт iis на windows
  • Как создать сайт html на windows 10