Search

segunda-feira, 14 de abril de 2014

A aplicação padrão Win32

Criando a instancia de uma janela(windows).


Quando criamos um projeto do windows no Visual Studio, ele nos fornece o código abaixo para a criação de uma janela padrão:
// Comment
#include "stdafx.h"
#include "FirstWindow.h"

#define MAX_LOADSTRING 100

// Global Variables:
HINSTANCE hInst;        // current instance
TCHAR szTitle[MAX_LOADSTRING];     // The title bar text
TCHAR szWindowClass[MAX_LOADSTRING];   // the main window class name

// Forward declarations of functions included in this code module:
ATOM    MyRegisterClass(HINSTANCE hInstance);
BOOL    InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);

Podemos notar que ele cria três variáveis(hInst,szTitle e szWindowClass ).
hInst -> Variável global para armazenar o handle da instância da nossa aplicação.
szTitle -> Array de caracteres wchar_t(UNICODE) que dará nome a nossa aplicação.
szWindowClass -> Arra de wchar_t que dará nome a nossa primeira janela.

E quatro funções(MyRegisterClass,InitInstance,WndProc e About).

MyRegisterClass-> É usada para instanciar a minha classe WNDCLASSEX, usada para descrever algumas propriedades da minha janela.

InitInstance-> Aqui é feita a chamada para a função CreateWindow, onde minha janela é criada.

WndProc-> Este ponto é muito importante, pois, é onde processamos todas as mensagens passadas pelo sistema operacional,mouse, teclado etc ... para o handle da nossa janela.

About-> É o callback de uma janela secundária.

Vamos falar um pouco sobre essa abordagem da MS para a criação deste projeto genérico de win32 e porque ele não é o mais apropriado para uma game engine.

int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
 UNREFERENCED_PARAMETER(hPrevInstance);
 UNREFERENCED_PARAMETER(lpCmdLine);//Previne algumas Warnings Messages do compilador.
//Isso acontece quando algum parâmetro não é usado no corpo da função.
//Apenas uma boa prática de programação, pois assim podemos compilar nosso código sem warnings por parte do VS.


  // TODO: Place code here.
 MSG msg;
 HACCEL hAccelTable;

 // Initialize global strings
 LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
 LoadString(hInstance, IDC_FIRSTWINDOW, szWindowClass, MAX_LOADSTRING);
 MyRegisterClass(hInstance);
//Dá nome a nossa aplicação e a nossa primeira janla


 // Perform application initialization:
 if (!InitInstance (hInstance, nCmdShow))
 {
  return FALSE;
 }

 hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_FIRSTWINDOW));

 // Main message loop:
 while (GetMessage(&msg, NULL, 0, 0))
 {
  if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
  {
   TranslateMessage(&msg);
   DispatchMessage(&msg);
  }
 }

 return (int) msg.wParam;
}
Acima podemos verificar nosso método main( wWinMain ) com alguns comentários, mas minha principal observação é na função GetMessage.

Uma alternativa ao GetMessage é a função PeekMessage, muitas aplicações Win32 usam GetMessage, o problema é que ao usar GetMessage ficamos bloqueados dentro laço esperando por uma mensagem.

while (GetMessage(...) > 0)
{
 TranslateMessage();
 DispatchMessage();
}

Enquanto a função PeekMessage não, essa função consulta a queue de mensagens, ela retorna a primeira mensagem da fila, se não houver nenhuma mensagem ela retorna NULL.

while (WM_QUIT != uMsg.message)
{
     while (PeekMessage (&uMsg, NULL, 0, 0, PM_REMOVE) > 0) //Or use an if statement
     {
          TranslateMessage (&uMsg);
          DispatchMessage (&uMsg);
     }
}

O processamento de mensagens é uma parte crítica da programação Win32, através dela nos comunicamos com o Sistema Operacional, mouse, teclado etc... quando uma aplicação não responde as mensagens ao sistema operacional, o sistema informa ao usuário que a aplicação não está respondendo. O usuário por sua vez acredita que a aplicação travou, ou que algum erro ocorreu. Fazemos isso através do PeekMessage, TranslateMessage e DispatchMessage;

ATOM MyRegisterClass(HINSTANCE hInstance)
{
 WNDCLASSEX wcex;

 wcex.cbSize = sizeof(WNDCLASSEX);

 wcex.style   = CS_HREDRAW | CS_VREDRAW;
 wcex.lpfnWndProc = WndProc;
 wcex.cbClsExtra  = 0;
 wcex.cbWndExtra  = 0;
 wcex.hInstance  = hInstance;
 wcex.hIcon   = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_FIRSTWINDOW));
 wcex.hCursor  = LoadCursor(NULL, IDC_ARROW);
 wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
 wcex.lpszMenuName = MAKEINTRESOURCE(IDC_FIRSTWINDOW);
 wcex.lpszClassName = szWindowClass;
 wcex.hIconSm  = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

 return RegisterClassEx(&wcex);
}

WNDCLASSEX é a classe que descreve algumas propriedades de uma janela, está ligada a instância da nossa aplicação. Na propriedade lpfnWndProc é onde definimos qual será nossa função callback descrita mais abaixo.

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   HWND hWnd;

   hInst = hInstance; // Store instance handle in our global variable

   hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);

   if (!hWnd)
   {
      return FALSE;
   }

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   return TRUE;
}

    Aqui criamos a nossa janela, uma das coisas que eu adicionaria a este sample da microsoft seria mudar a variável local hwnd para global, assim eu declararia ela no início do programa juntamente com as outras três variáveis comentadas no início do artigo.

// Global Variables:
g_hwnd = NULL;  // global windows handle

HINSTANCE hInst; // current instance TCHAR szTitle[MAX_LOADSTRING]; // The title bar text TCHAR szWindowClass[MAX_LOADSTRING]; // the main window class name

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
 int wmId, wmEvent;
 PAINTSTRUCT ps;
 HDC hdc;

 switch (message)
 {
 case WM_COMMAND:
  wmId    = LOWORD(wParam);
  wmEvent = HIWORD(wParam);
  // Parse the menu selections:
  switch (wmId)
  {
  case IDM_ABOUT:
   DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
   break;
  case IDM_EXIT:
   DestroyWindow(hWnd);
   break;
  default:
   return DefWindowProc(hWnd, message, wParam, lParam);
  }
  break;
 case WM_PAINT:
  hdc = BeginPaint(hWnd, &ps);
  // TODO: Add any drawing code here...
  EndPaint(hWnd, &ps);
  break;
 case WM_DESTROY:
  PostQuitMessage(0);
  break;
 default:
  return DefWindowProc(hWnd, message, wParam, lParam);
 }
 return 0;
}

A função WinProc( windows procedure ) trata e processa as mensagens recebidas pela nossa janela,
Podemos observar que ela possui os mesmos parametros de uma mensagem, O primeiro é o handle da janela retornado na função CreateWindow, o segundo parametro identifica a mensagem, os ultimos dois são so parametros da mensagem.

Vamos imaginar que o usuário mexa o mouse, nossa janela vai receber uma mensagem do sistema operacional identificada como WM_MOUSEMOVE, para sabermos onde o mouse se encontra no eixo x e y devemos então obter os parametros da mensagem:
xPos = GET_X_LPARAM(lParam); 
yPos = GET_Y_LPARAM(lParam); 



Uma defProc bem básica abaixo demonstra por si só uma esqueleto bem básico:
switch (iMsg)
{
case WM_CREATE :
return 0 ;
case WM_PAINT :
return 0 ;
case WM_DESTROY :
return 0 ;
}
return DefWindowProc (hwnd, iMsg, wParam, lParam) ;

Podemos criar nossas próprias mensagens e processa-las em nossas janelas abaixo um exemplo:
#define MY_MSG_NOVO_DOCUMENTO (WM_USER+1)
mas falaremos disso em uma postagem mais adiante.

INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
 UNREFERENCED_PARAMETER(lParam);
 switch (message)
 {
 case WM_INITDIALOG:
  return (INT_PTR)TRUE;

 case WM_COMMAND:
  if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
  {
   EndDialog(hDlg, LOWORD(wParam));
   return (INT_PTR)TRUE;
  }
  break;
 }
 return (INT_PTR)FALSE;
}
Quando compilamos e rodamos o código padrão do Visual Studio para uma aplicação Windows, podemos observar no defproc da nossa janela que ela trata um clique no menu, para chamar uma janela about:
  case IDM_ABOUT:
   DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
   break;
Neste momento criamos uma janela secundária, ela foi confeccionada anteriormente e ficou armazenada nos recursos do programa. Note que passamos o hWnd Owner, ou seja a janela pai, e o método About, que se trata do defproc para essa janela. que é tratado separadamente.

Nenhum comentário:

Postar um comentário