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