Curso completo de DirectX 9 com C\C++
Gameprog - Escola de programação de jogos digitais
Contato: gameprog.br@gmail.com
Fase 01-2

index << >>


01.2 A janela principal

1.1 Visão geral
Esta primeira aplicação ilustra como criar uma janela e isso é uma tarefa de muitas partes com um código difícil de dominar e de compreender por parte do iniciante em programação Windows. Para a correta compreensão do código que envolve toda a criação e gestão da janela é necessário enxergar as aplicações do ponto de vista dos desafios que o sistema Windows enfrenta para dar a vida artificial de cada aplicação. O Windows é um sistema multitarefa que geralmente pode rodar várias janelas ao mesmo tempo. Cada janela representa uma aplicação. Nesse contexto um dos primeiros desafios do Windows é gerenciar uma lista de janelas com cada janela tendo uma identificação própria que aponte para todos os recursos utilizados por aquela janela: memória, vídeo, áudio, teclado, mouse, etc. Lembrando ainda que estes recursos precisam ser compartilhados sem conflitos entre cada aplicação. A identificação de cada janela é chamado de handle representado pelo tipo de dado HWND. Cada recurso utilizado tem um handle que lhe identifica, assim temos handle de bitmap, handle de cursor, etc. Uma mesma aplicação pode ser aberta várias vezes, e assim cada cópia da aplicação é chamado de instância e identificada pelo tipo HINSTANCE. Cada janela tem sua própria aparência e isso necessita de uma estrutura que guarde a configuração de aparência da janela. Todas as janelas sofrem eventos externos do usuário, por exemplo, o mouse clicado sobre as janelas. O Windows precisa de uma forma de comunicar esse evento para a janela e a janela precisa bombear esse evento para um local que vai tratar adequadamente esse evento. Disso decorre que o Windows sustenta uma fila de eventos para cada janela,e cada janela deve ter um centro para receber e bombear pra frente as mensagens e uma central de processamento dessas mensagens endereçadas para a janela ou várias janelas da aplicação. Uma mensagem nada mais é do que um evento codificado e comunicado em forma de número para dentro de uma aplicação. Geralmente um computador tem uma só placa de vídeo que faz o trabalho de escrever ou desenhar no monitor. Duas janelas que estão desenhando em seu próprio espaço na verdade estão alternando o uso do monitor que consegue atender apenas uma janela por vez. Antes de desenhar a janela precisa ter uma forma de pedir licença para usar a placa de vídeo e depois disso precisa liberar a placa de vídeo para outra aplicação. Ainda, cada janela precisa ter uma área de memória para guardar o seu próprio conteúdo visual. O Windows resolve essas questões dando a cada janela um "monitor virtual" chamado dispositivo contextual geralmente abreviado como DC com sua área própria de memória para guardar o conteúdo visual da janela. Essas justificativas colocadas visam esclarecer parte da complexidade que vai ser encontrada no código C++ de geração e gestão da janela. Outra parte da complexidade encontrada do código vem pelo fato do Windows manter uma compatibilidade com suas versões antigas e ter de lidar com a diversidade de idiomas com um diferente conjunto de caracteres. Os nossos programas serão compilados sem o uso de caracteres UNICODE que atende todos os idiomas do mundo.Se o conjunto UNICODE estiver habilitado o programa não compila. O uso de caracteres UNICODE deve ser desabilitado na opção General na janela de propriedades do projeto conforme a imagem:
1.2 Estrutura principal da aplicação
Arquivo: entrada.cpp
WinMain() criação da classe da janela registro da classe da janela criação da janela de acordo com a classe definida mostra a janela estabelecimento do laço de mensagens finaliza a aplicação processaJanela() tratamento das mensagens
Essa aplicação inicial tem apenas duas funções. WinMain() é a função de entrada obrigatória de qualquer aplicação Windows. Essa função é responsável por criar a janela e de estabelecer o laço de mensagens que obtém as mensagens dos eventos que ocorrem sobre a janela e as despacha para a função processaJanela() que tem que fazer o trabalho de dar uma resposta adequada para as mensagens ou eventos que são de interesse da aplicação.O laço de mensagens é o 'coração' da aplicação e a função processaJanela() é o 'cérebro'. 2.1 Aspectos globais
#include <windows.h> // Declaração da função que atende as mensagens da janela LRESULT CALLBACK processaJanela (HWND hJanela, UINT mensagem, WPARAM wParam, LPARAM lParam); // Dimensões da janela int g_xtela = 320; int g_ytela = 240;
#include <windows.h> Toda aplicação que vai utilizar os recursos oferecidos pelo sistema Windows como interface gráfica e outros aspectos deve incluir este arquivo em destaque. Dentro deste arquivo outros arquivos são incluidos dando acesso a uma extensa gama de funcionalidades do sistema Windows. // Declaração da função que atende as mensagens da janela LRESULT CALLBACK processaJanela ( HWND hJanela, UINT mensagem, WPARAM wParam, LPARAM lParam); Essa é a assinatura obrigatória da função que trata as mensagens recebidas para a janela ou janelas de uma aplicação. Toda aplicação deve ter pelo menos uma função dessas porém é normal e mais didático produzir uma função dessas para cada janela da aplicação. HWND hJanela Esse argumento de entrada identifica a janela para a qual a mensagem está endereçada. Havendo uma só janela, esse valor refere-se a janela principal da aplicação. UINT mensagem Este argumento identifica a mensagem em si. As mensagens são valores numéricos que representam eventos que ocorrem sobre a janela. Por exemplo, WM_QUIT é uma mensagem que sinaliza o encerramento de uma aplicação e seu valor é zero (0). WPARAM wParam LPARAM lParam Estes dois argumentos trazem informações extras relevantes sobre a mensagem. Por exemplo, em mensagens atreladas ao mouse, os dados de posicionamento do mouse são trazidos por estes argumentos. // Dimensões da janela int g_xtela = 320; int g_ytela = 240; A intenção destes valores é representar globalmente o tamanho da área útil que a aplicação tem para desenhar. Estas variáveis são globais porque serão utilizadas em muitos outros pontos da aplicação conforme a necessidade do contexto. 2.2 Criando a janela e o laço de mensagens
int WINAPI WinMain (HINSTANCE app_instancia, HINSTANCE app_anterior, LPSTR sComando, int nExibir) { // alça da janela HWND hJanela = NULL; // Estrutura de recepção das mensagens MSG mensagem; // Estrutura de descrição da janela WNDCLASSEX wcls; // Nome da classe da janela char sclasseJanela[ ] = "cls_directx"; // Estrutura que descreve a janela wcls.hInstance = app_instancia; wcls.lpszClassName = sclasseJanela; wcls.lpfnWndProc = processaJanela; wcls.style = CS_HREDRAW | CS_VREDRAW; wcls.cbSize = sizeof (WNDCLASSEX); // O cursor e os ícones da aplicação são default wcls.hIcon = LoadIcon (NULL, IDI_APPLICATION); wcls.hIconSm = LoadIcon (NULL, IDI_APPLICATION); wcls.hCursor = LoadCursor (NULL, IDC_ARROW); // Aplicação sem menu wcls.lpszMenuName = NULL; // Nada de espaço extra atrelado a classe da janela (wcls) wcls.cbClsExtra = 0; // Nada de espaço extra atrelado a janela wcls.cbWndExtra = 0; // Cor default da janela wcls.hbrBackground = (HBRUSH) COLOR_BACKGROUND; // Registra a janela e retorna se esta operação falhar int status = RegisterClassEx (&wcls); if(status == 0) { MessageBox (NULL, "Registro falhou!", "prj_Janela", MB_OK); return 0; } // endif // Depois da classe criada cria-se a janela DWORD estiloExtra = 0; const char janelaTitulo[] = "prj_Janela"; DWORD controleEstilo = WS_OVERLAPPED | WS_SYSMENU; int xpos = 160; int ypos = 120; HWND hjanelaPai = HWND_DESKTOP; HMENU sem_menu = NULL; LPVOID dadoExtra = NULL; hJanela = CreateWindowEx(estiloExtra, sclasseJanela, janelaTitulo, controleEstilo, xpos, xpos, g_xtela, g_ytela, hjanelaPai, sem_menu, app_instancia, dadoExtra ); // Mostra a janela ShowWindow (hJanela, nExibir); UpdateWindow (hJanela); // Rode o bombeamento de mensagens até GetMessage() retornar 0 while (GetMessage (&mensagem, NULL, 0, 0) ) { // Traduz mensagem de tecla virtual em mensagem de caracteres TranslateMessage(&mensagem); // Despacha a mensagem para a função processaJanela */ DispatchMessage(&mensagem); } // endwhile // O valor de retorno é zero(0) passado por PostQuitMessage() UnregisterClass(sclasseJanela, app_instancia ); return mensagem.wParam; } // WinMain().fim
int WINAPI WinMain (HINSTANCE app_instancia, HINSTANCE app_anterior, LPSTR sComando, int nExibir) Essa é a assinatura obrigatória da função WinMain() que é o ponto de entrada de uma aplicação para sistema Windows. Os argumentos estão explicados abaixo. HINSTANCE app_instancia Esse argumento identifica a instância da aplicação perante outras cópias da mesma que estejam rodando simultâneamente. Esse argumento é muito solicitado em outras funções, portanto ele é um bom candidato para uma variável global. HINSTANCE app_anterior Há muito tempo atrás esse argumento identificava uma cópia anterior do programa. Porém, esse argumento não é mais sustentado pelo Windows e existe apenas para prover compatibilidade com programas antigos. LPSTR sComando Esse argumento são os argumentos da linha de comando que porventura o usuário pode utilizar para chamar seu programa. int nExibir Esse argumento representa o modo inicial de exibição da janela, se ela vai iniciar minimizada, maximizada ou normal. Esse argumento vai parar mais adiante no código na função ShowWindow() que mostra a janela. // alça da janela HWND hJanela = NULL; hJanela é então a 'alça' da janela pela qual o sistema ou a própria aplicação vai 'pegar', 'apontar' e 'manipular' a janela. // Estrutura de recepção das mensagens MSG mensagem; Essa estrutura vai receber as mensagens endereçadas para esta aplicação. Basicamente os elementos internos desta estrutura identificam a janela destino, a mensagem e informações extras da mensagem que vão ter significados particulares conforme cada mensagem. // Estrutura de descrição da janela WNDCLASSEX wcls; Esta estrutura define a configuração da janela. Detalhando melhor, esta estrutura define a configuração de uma classe com aspectos que podem ser aplicados a uma ou mais janelas que recebem a configuração que é definida pelos elementos desta estrutura. Segue abaixo o detalhamento de cada componente. // Nome da classe da janela char sclasseJanela[ ] = "cls_directx"; Esta variável define o nome da classe. Posteriormente esse nome é usado para registrar janelas dessa classe. // Estrutura que descreve a janela wcls.hInstance = app_instancia; Este elemento identifica a cópia ou instância da aplicação que está rodando. wcls.lpszClassName = sclasseJanela; Este elemento recebe o nome da janela. wcls.lpfnWndProc = processaJanela; Este elemento é muito importante pois aponta para a função que trata as mensagens recebidas pela aplicação, processaJanela() no caso particular desta aplicação. wcls.style = CS_HREDRAW | CS_VREDRAW; Esse argumento define como deve ocorrer o processo de atualização da da janela; define como ocorre o alocamento do espaço do dispositivo gráfico particular da janela e outros aspectos. A configuração passada, CS_HREDRAW | CS_VREDRAW, indica que o redesenhamento da janela deve ocorrer sempre que a janela mudar de posição ou tamanho. wcls.cbSize = sizeof (WNDCLASSEX); Este argumento é sempre usado dessa forma. Indica o total de bytes que deve ser reservado para conter esta estrutura. // O cursor e os ícones da aplicação são default wcls.hIcon = LoadIcon (NULL, IDI_APPLICATION); wcls.hIconSm = LoadIcon (NULL, IDI_APPLICATION); wcls.hCursor = LoadCursor (NULL, IDC_ARROW); Esses argumentos definem os ícones da aplicação que são usados na barra de títulos da aplicação e o ícone que é exibido quando a aplicação está minimizada na barra de tarefas. hCursor é o cursor da aplicação que é exibido quando o mouse está dentro da janela. Os ícones e o cursor selecionado são os padrões da coleção interna do sistema Windows. // Aplicação sem menu wcls.lpszMenuName = NULL; Esse argumento recebe o handle do menu da janela. NULL quer dizer que a janela não vai ter menu. // Nada de espaço extra atrelado a classe da janela (wcls) wcls.cbClsExtra = 0; // Nada de espaço extra atrelado a janela wcls.cbWndExtra = 0; Estes argumentos são usados para alocar espaço extra para dados de usuário que podem ser atrelados à janela. // Cor default da janela wcls.hbrBackground = (HBRUSH) COLOR_BACKGROUND; Este argumento define o aspecto visual do interior da janela. Nesse momento a configuração desse aspecto não é importante pois este espaço será manipulado pelo Directx em seus processos de renderização. Porém é importante saber que esse espaço de desenho é chamado de área cliente. // Registra a janela e retorna se esta operação falhar int status = RegisterClassEx (&wcls); Essa função registra a janela. Se houver falha o valor de retorno é zero (0) que é usado no bloco abaixo para encerrar graciosamente a aplicação: if(status == 0) { MessageBox (NULL, "Registro falhou!", "prj_Janela", MB_OK); return 0; } // endif Agora no bloco de código abaixo apresentamos os argumentos que vão na função CreateWindowEx() responsável por criar a janela e que estão mais relacionados com o aspecto visual da janela. // Depois da classe criada cria-se a janela DWORD estiloExtra = 0; Este argumento permite usar outros estilos que controlam a aparência e funcionalidades da janela. const char janelaTitulo[] = "prj_Janela"; Esse argumento é a string de título da janela. DWORD controleEstilo = WS_OVERLAPPED | WS_SYSMENU; Esse argumento define se a janela vai ter botão minimizar, maximizar, barra de menu entre outros aspectos visuais. int xpos = 160; int ypos = 120; Estas variáveis indicam o posicionamento da janela na tela. Lembramos que g_xtela,g_ytela definidos globalmente indicam largura e altura da janela. HWND hjanelaPai = HWND_DESKTOP; Esse argumento indica quem é o pai da janela. É comum uma aplicação com várias janelas apresentar uma hierarquia das janelas uma em relação às outras. Nessa aplicação, apontamos para a janela desktop do Windows. Esse parâmetro poderia ser também configurado como NULL. HMENU sem_menu = NULL; Esta janela não vai ter menu por isso esse argumento foi configurado como NULL. LPVOID dadoExtra = NULL; Esse argumento representa informação extra que pode ser atrelada à janela. hJanela = CreateWindowEx(estiloExtra, sclasseJanela, janelaTitulo, controleEstilo, xpos, xpos, g_xtela, g_ytela, hjanelaPai, sem_menu, app_instancia, dadoExtra ); Essa função cria de fato a janela. Se houver falha hJanela recebe NULL que deve ser verificado para confirmar a criação da janela ou não. // Mostra a janela ShowWindow (hJanela, nExibir); Essa função exibe a janela apontada por hJanela; nExibir veio de fora mas geralmente seu valor é SW_NORMAL (1). A janela pode iniciar minimizada ou maximizada se for chamada de fora com essa configuração. UpdateWindow (hJanela); Essa função atualiza a janela. Neste primeiro programa não ocorre um efeito prático mas é bom saber que existe essa função para ser utilizada depois da janela sofrer uma reconfiguração.
// Rode o bombeamento de mensagens até GetMessage() retornar 0 while (GetMessage (&mensagem, NULL, 0, 0) ) { // Traduz mensagem de tecla virtual em mensagem de caracteres TranslateMessage(&mensagem); // Despacha a mensagem para a função processaJanela */ DispatchMessage(&mensagem); } // endwhile
Este bloco de código representa o coração de uma aplicação Windows. Aqui, GetMessage() retira uma mensagem da fila de mensagens da aplicação que é despachada pela função DispatchMessage() para a função que processa as mensagens da janela (processaJanela() em nosso caso). Antes da mensagem ser despachada para o cérebro da aplicação, a mensagem recebe um pequeno tratamento dado pela função TranslateMessage() para facilitar o processamento do teclado. Quando GetMessage() retira uma mensagem WM_QUIT da fila ela retorna zero (0) quebrando assim o laço feito por while e finaliza-se assim a aplicação. // O valor de retorno é zero(0) passado por PostQuitMessage() UnregisterClass(sclasseJanela, app_instancia ); return mensagem.wParam; Este pequeno bloco finaliza a aplicação Windows. A classe da janela é apagada do sistema (UnregisterClass()) e return retorna o último valor que geralmente é zero(0) que adveio da função PostQuitMessage(). 2.2 O cérebro processador de mensagens
// Esta função é chamada por DispatchMessage() LRESULT CALLBACK processaJanela ( HWND hJanela, UINT mensagem, WPARAM wParam, LPARAM lParam ) { switch (mensagem) { case WM_DESTROY: // Coloca uma mensagem WM_QUIT na fila de mensagem PostQuitMessage (0); break; case WM_KEYDOWN: if (wParam == VK_ESCAPE) PostQuitMessage( 0); return 0; break; // Processamento default de mensagens não tratada pela aplicação default: return DefWindowProc (hJanela, mensagem, wParam, lParam); } // endswitch return 0; } // processaJanela().fim
// Esta função é chamada por DispatchMessage() LRESULT CALLBACK processaJanela ( HWND hJanela, UINT mensagem, WPARAM wParam, LPARAM lParam ) Esta é a assinatura obrigatória de uma função criada para processar mensagens para a janela ou janelas de uma aplicação. A assinatura recebe a identificação da janela (HWND hJanela), a mensagem (UINT mensagem) e informações extras sobre a mensagem ( LPARAM lParam e WPARAM wParam ); estes últimos parâmetros tem variados significados conforme as particularidades de cada mensagem.
switch (mensagem) { case WM_DESTROY: // Coloca uma mensagem WM_QUIT na fila de mensagem PostQuitMessage (0); break; case WM_KEYDOWN: if ( wParam == VK_ESCAPE) PostQuitMessage( 0 ); return 0; break; // Processamento default de mensagens não tratadas pela aplicação default: return DefWindowProc (hJanela, mensagem, wParam, lParam); } // endswitch
Este é o modelo de como processar as mensagens recebidas pela aplicação com cada case processando uma mensagem particular. A mensagem WM_DESTROY é gerada no evento de fechamento da janela. Ao receber esse evento a janela coloca uma mensagem WM_QUIT (0) na sua fila de mensagens com PostQuitMessage() para quebrar o laço de mensagens e assim finalizar a aplicação. A mensagem WM_KEYDOWN ocorre no pressionamento das teclas e wParam indica a tecla pressionada. VK_ESCAPE está definido no arquivo WinUser.h e equivale a tecla ESCAPE. Dê uma olhada neste arquivo de cabeçalho para verificar o código VK_ das outras teclas. Todas as mensagens que a sua aplicação não processar devem ser passadas para a função DefWindowProc() fazer este trabalho: // Processamento default de mensagens não tratadas pela aplicação default: return DefWindowProc (hJanela, mensagem, wParam, lParam); return 0; Depois do processamento das mensagens, a função processaJanela() retorna 0. 3. Código fonte do projeto de exemplo:prj_Janela
// Projeto prj_Janela arquivo: entrada.cpp // Este programa ilustra como criar uma janela // Produzido por www.gameprog.com.br #include <windows.h> // Declaração da função que atende as mensagens da janela LRESULT CALLBACK processaJanela (HWND hJanela, UINT mensagem, WPARAM wParam, LPARAM lParam); // Dimensões da janela int g_xtela = 320; int g_ytela = 240; int WINAPI WinMain (HINSTANCE app_instancia, HINSTANCE app_anterior, LPSTR sComando,int nExibir) { // alça da janela HWND hJanela = NULL; // Estrutura de recepção das mensagens MSG mensagem; // Estrutura de descrição da janela WNDCLASSEX wcls; // Nome da classe da janela char sclasseJanela[ ] = "cls_directx"; // Estrutura que descreve a janela wcls.hInstance = app_instancia; wcls.lpszClassName = sclasseJanela; wcls.lpfnWndProc = processaJanela; wcls.style = CS_HREDRAW | CS_VREDRAW; wcls.cbSize = sizeof (WNDCLASSEX); // O cursor e os ícones da aplicação são default wcls.hIcon = LoadIcon (NULL, IDI_APPLICATION); wcls.hIconSm = LoadIcon (NULL, IDI_APPLICATION); wcls.hCursor = LoadCursor (NULL, IDC_ARROW); // Aplicação sem menu wcls.lpszMenuName = NULL; // Nada de espaço extra atrelado a classe da janela (wcls) wcls.cbClsExtra = 0; // Nada de espaço extra atrelado a janela wcls.cbWndExtra = 0; // Cor default da janela wcls.hbrBackground = (HBRUSH) COLOR_BACKGROUND; // Registra a janela e retorna se esta operação falhar int status = RegisterClassEx (&wcls); if(status == 0) { MessageBox (NULL, "Registro falhou!", "prj_Janela", MB_OK); return 0; } // endif // Depois da classe criada cria-se a janela DWORD estiloExtra = 0; const char janelaTitulo[] = "prj_Janela"; DWORD controleEstilo = WS_OVERLAPPED | WS_SYSMENU; int xpos = 160; int ypos = 120; HWND hjanelaPai = HWND_DESKTOP; HMENU sem_menu = NULL; LPVOID dadoExtra = NULL; hJanela = CreateWindowEx(estiloExtra, sclasseJanela, janelaTitulo, controleEstilo, xpos, xpos, g_xtela, g_ytela, hjanelaPai, sem_menu, app_instancia, dadoExtra ); // Mostra a janela ShowWindow (hJanela, nExibir); UpdateWindow (hJanela); // Rode o bombeamento de mensagens até GetMessage() retornar 0 while (GetMessage (&mensagem, NULL, 0, 0) ) { // Traduz mensagem de tecla virtual em mensagem de caracteres TranslateMessage(&mensagem); // Despacha a mensagem para a função processaJanela */ DispatchMessage(&mensagem); } // endwhile // O valor de retorno é zero(0) passado por PostQuitMessage() UnregisterClass(sclasseJanela, app_instancia ); return mensagem.wParam; } // WinMain().fim // Esta função é chamada por DispatchMessage() LRESULT CALLBACK processaJanela ( HWND hJanela, UINT mensagem, WPARAM wParam, LPARAM lParam ) { switch (mensagem) { case WM_DESTROY: // Coloca uma mensagem WM_QUIT na fila de mensagem PostQuitMessage (0); break; case WM_KEYDOWN: if (wParam == VK_ESCAPE) PostQuitMessage( 0 ); return 0; break; // Processamento default de mensagens não tratada pela aplicação default: return DefWindowProc (hJanela, mensagem, wParam, lParam); } // endswitch return 0; } // processaJanela().fim

index << >>

Produzido por Gameprog: Jair Pereira - Agosto/2014 © gameprog.br@gmail.com http://www.gameprog.com.br