sábado, 8 de janeiro de 2011

Criando um Aplicativo Windows


Criando um Aplicativo Windows

Autor : Albrecht  
Este tutorial foi escrito para dar uma noção básica de programação de aplicativos Windows utilizando a Win32 API.
O Objetivo final é que você aprenda a escrever um aplicativo, contendo uma janela.
O código de fonte do aplicativo criado, será um esqueleto para seus aplicativos utilizando OpenGl e Directx.
 Para melhor aproveitamento deste texto, será nescessário que o leitor tenho conhecimentos em linguagem C
e que possua o compilador Visual C++ 6.  
Caso não tenha o Visual C++ 6, pegue o Dev-C++ em  http://www.bloodshed.net/
Qualquer erro encontrado, comentários, elogios, críticas , meu email é pih@terra.com.br
Espero que aproveite.
Felipe Albrecht


Primeiramente criaremos um "Hello, world" simples para windows.
Um "Hello, world" deste tipo para Dos seria algo assim:

#include <stdio.h> 

void main( ) 
{
   
 printf("Hello, world! "); }


Vamos ver o "Hello, world" para windows:
#include<windows.h> 
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, 
   
                                 int nShowCmd) 
{
   
 MessageBox(NULL, " Hello, world!", "Meu primeiro programa em Windows!", NULL); 
   
 return 0; 
} 
Vamos agora analisar o código: 

A primeira linha: 
#include<windows.h> 
É 
o header para as principais funções e defines do windows.
A segunda linha: 
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,   int nShowCmd)
Nos programas em C e C++ para dos, Linux, Unix e etc, a função inicial é a "main", 
no Windows, a função main é a WinMain. 

Neste caso ela esta retornando um int e o WINAPI é uma convenção utilizada quando se usa alguma função da Win32 Api. 
Vamos observar o protótipo dela:
int WINAPI WinMain( 
  HINSTANCE
 hInstance ,      // ponteiro para a instancia atual; 
  HINSTANCE hPrevInstance ,  // (Ultrapassado!) 
  LPSTR lpCmdLine ,          // ponteiro para a linha de comando; 
  int nCmdShow              // estado que deve ser mostrada a janela. 
);

Vocês irão me perguntar o que é "Instancia":
- Instancia é uma referência do seu programa para o windows.
Atravez dela o windows
  manda mensagens , referências de eventos e outras coisas ligadas ao sistema operacional.  

Por enquanto é isso que se precisa saber. Na próxima seção estudaremos com mais detalhes questões mais complexas.

Começando a criação

Nesta seção vamos começar a criar o nosso aplicativo. 
Confesso que é complicado explicar o funcionamento e a programação de um aplicativo Win32.
A primeira vez que li como criar um programa com uma janela para o windows, fiquei bem confuso.
Tentarei utilizar uma maneira simples de explicar o funcionamento de todo aplicativo,
irei cortar-lo em pedaços e explicar cada parte separadamente.  

Um programa windows básico, se divide em tantas partes:
* A inicialização ( WinMain );
* A criação da janela ( CreateWindowEx );
* O loop de mensagens ( PeekMessage);
* O processamento das mensagens ( WndProc ).

 Vamos agora analisar cada parte: 
Inicialização:
Protótipo da função de inicialização: 

int WINAPI WinMain(
   
 HINSTANCE hInstance ,      // ponteiro para a instancia atual;
    HINSTANCE hPrevInstance ,  // (Ultrapassado!) 
    LPSTR lpCmdLine ,          // ponteiro para a linha de comando; 
    int nCmdShow              // estado que deve ser mostrada a janela.
);
Explicando: 
Quando o programa é carregado, a primeira função do programa a ser chamada é a WinMain. 
A função WinMain também passa a Instancia do aplicativo pelo parâmetro hInstance. 
A WinMain também passa para o programa a linha de comando excluindo o nome do programa. 
Também é especificado na WinMain como a janela do aplicativo deve ser mostrada.

Criação da janela: 

Para se criar uma janela, é necessário primeiro se criar uma classe que conterá os dados da janela criada. A Win32 API provede um struct para definir os dados da janela, o nome desta struct é WNDCLASSEX.
Abaixo é apresentado o protótipo desta classe:

typedef struct _WNDCLASSEX {              // Classe da janela; 
    UINT    cbSize;                          // Tamanho da janela; 
    UINT    style;                             // Estilos da janela; 
    WNDPROC lpfnWndProc;       // Ponteiro para funções; 
    int     cbClsExtra;                       // nao sera utilzado; 
    int     cbWndExtra;                    // nao sera utilzado; 
    HANDLE  hInstance;               // Instancia do aplicativo; 
    HICON   hIcon;                      // Ícone da janela; 
    HCURSOR hCursor;               // Cursor padrão da janela; 
    HBRUSH  hbrBackground;      // Cor de fundo da janela;      
    LPCTSTR lpszMenuName;     // Menu da janela; 
    LPCTSTR lpszClassName;      // Nome da classe 
    HICON   hIconSm;                  // Ícone pequeno da janela. 
} WNDCLASSEX; 

Para criar a variável da classe da janela, é só definir:
WNDCLASSEX            wndClass;

Depois ir definindo os atributos: 

windowClass.cbSize = sizeof(WNDCLASSEX); Essa definição serve para dar o tamanho da classe. 

windowClass.style = CS_HREDRAW | CS_VREDRAW; Esta parte define os estilos da janela. CS_HREDRAW  e CS_VREDRAW diz  que é para redesenhar a are quando é mudado o comprimento e a altura da janela. 

windowClass.lpfnWndProc = WndProc; Ponteiro para onde vem as funções 

windowClass.cbClsExtra = 0; Especifica o número de bytes extras que deverão ser alocados seguindo a estrutura da janela. 

windowClass.cbWndExtra = 0; Especifica o número de bytes extras que deverão ser alocados seguindo a instancia da janela.
windowClass.hInstance = hInstance;Para a janela saber qual é a instancia utilizada. 

windowClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);                       Define qual será o ícone “grande” da janela. Neste caso será um ícone padrão do windows.

windowClass.hCursor = LoadCursor(NULL, IDC_ARROW); 
Define qual será o cursor padrão da janela.

windowClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);Define qual será a cor de fundo, neste caso será branco.

windowClass.lpszMenuName = NULL; Ponteiro para o menu, neste exemplo não será usado menu.

windowClass.lpszClassName = "MyClass";O nome da classe. 

windowClass.hIconSm = LoadIcon(NULL, IDI_WINLOGO); E por fim,  o ícone pequeno da janela.

Depois falta apenas registrar a classe:  
RegisterClassEx(&windowClass);
Pronto! A classe da sua janela já está definida e registrada, pronta para o uso.

Criando a janela. 
Com a classe da janela setada e registrada, podemos criar a janela. Como foi mostrado no início do capítulo, a criação de uma janela é feita utilizando  a função CreateWindowEx(). 

Protótipo da função de criação da janela: 

HWND CreateWindowEx( 
  DWORD
 dwExStyle ,      // o estilo da janela extendido. 
  LPCTSTR lpClassName  ,  // ponteiro para o nome da classe registrada. 
  LPCTSTR lpWindowName // ponteiro para o nome da janela. 
  DWORD dwStyle ,        // estilo da janela. 
  int x,                // posição horizontal da janela. 
  int y,                // posição vertical da janela. 
  int nWidth ,           // comprimento da janela. 
  int nHeight ,          // altura da janela 
  HWND hWndParent,      // ponteiro para ohandle to parent or owner window 
  HMENU hMenu ,          // ponteiro para o menu 
  HINSTANCE hInstance ,  // ponteiro para a Instancia do aplicativo. 
  LPVOID lpParam        // dados para a criação da janela. 
); 
Leia os comentarios de cada paramêntro, a maioria é alto explicativo. Irei explicar algum dos paremetros mais importantes: 

lpClassName : Este parametro diz o nome da classe da janela que voce pretende utilizar. Já como vamos utilizar a classe criada anteriormente, deverá ser setado como “MyClass”. 

lpWindowName : O nome da janela, e também o título a ser mostrado. Poderiamos colocar como “Meu Primeiro Aplicativo”. 

dwStyle : É uma flag que diz para o CreateWindowEx como a janela deve se comportar e aparecer. Alguns exemplos: 
WS_TILED                        Cria uma janela com título e borda.
WS_MAXIMIZEBOX       Cria uma janela que tem o botão de maximizar.
WS_MINIMIZEBOX        Cria uma janela que tem o botão de minimizar.
WS_CAPTION                 Cria uma janela que contem a barra de título
WS_SYSMENU                Cria uma janela que contém o menu na sua barra de título. (WS_CAPTION tem que estar setado)
WS_SIZEBOX                  Cria uma janela que contenha borda para redimensionar o seu tamanho.
WS_OVERLAPPEDWINDOW    Cria uma janela com as especificações de todos anteriormente citados.
A função CreateWindowEx() retorna o valor NULL se não foi criada uma janela com sucesso, se não a função retorna um ponteiro para a janela criada. 

Agora que sabemos os parametros, podemos usar a função: 
hwnd = CreateWindowEx(NULL,            // Sem estilo adicionais 
                          "MyClass",                     // Nome da classe da janela a ser usada 
                          "A REAL Windows Application!",            // Nome do aplicativo 
                          WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_SYSMENU,        // Estilos 
                          100, 100,            // x,y cordenadas 
                          400, 400,            // largura, altura 
                          NULL,               // Ponteiro para parentes (Não há) 
                          NULL,               // Ponteiro para o menu 
                          hInstance,          // Instancia do aplicativo 
                          NULL);             // Parametros extras (Não há) 

Este código criará uma janela de 400 x 400, que quando visível ficará na esquerda ao topo. 

A janela está pronta, na próxima seção iremos ver o Loop de Mensagens.

Ultimos Detalhes

Já iniciamos o programa e criamos uma janela. Agora falta fazer ele funcionar. Como isso? 
Com o loop de mensagens e o processamento destas mensagens. 


Os aplicativos se comunicam com o windows através de uma fila de mensagem. Por causa desta fila, todos programas Windows necessitam por um loop de mensagem (Message Loop) na função WinMain(). O loop checa e lê as mensagens pendentes da fila e manda uma mensagem de volta para o Windows. 

Se usa a função PeekMessage() para checar se há mensagens na fila. Aqui está o protótipo da função PeekMessage():

BOOL PeekMessage( 
   
 LPMSG lpMsg,         // informação sobre a mensagem 
    HWND hWnd,           // ponteiro para a janela 
    UINT wMsgFilterMin,  // primeira mensagem 
    UINT wMsgFilterMax,  // ultima mensagem 
    UINT wRemoveMsg      // opções para remover a mensagem 
)
Se uma mensagem está na fila de espera, o PeekMessage() pega ela, tira da fila e a coloca no parâmetro lpMsg.
Especificando NULL para o parâmetro hWnd, faz com que o PeekMessage() cheque a pilha do aplicativo corrente.
Os parâmetros wMsgFilterMin e wMsgFilterMax serão setados como NULL para o nosso propósito.
O último paramento, wRemoveMsg será determinado com o valor PM_REMOVE,
que dirá ao PeekMessage() para remover mensagens depois que elas forem processadas. 

Outra função utilizada é a TranslateMessage(). Ela traduz as mensagens básicas do teclado (WM_CHAR, WM_KEYDOWN, WM_KEYUP) em mensagens WM_COMMAND dependendo dos aceleradores (atalhos) de teclado configurados.
A ultima função do loop de mensagens é a DispatchMessage().Ela manda a mensagem para a janela (e callback de janela) adequada. 

Vamos observar o código de um loop de mensagem:

while (!done)    

{     PeekMessage(&msg, hwnd, NULL, NULL, PM_REMOVE); 
    
if (msg.message == WM_QUIT)               // recebesse a mensagem QUIT               { 
                              done = true;                     // se sim, ento  hora de sair. 
               } 
               else 
               {
   
                TranslateMessage(&msg);       // traduza a mensagem para a lista de eventos    
   
                DispatchMessage(&msg);          // mande a mensagem para a lista de 
   
            }
} 
return msg.wParam; 
Quando se entra no loop, PeekMessage() checa se há alguma mensagem na fila de espera.Se  encontrado uma mensagem, e se ela for uma mensagem WM_QUIT, então o aplicativo chama o código específico para processar a mensagem. Após isto, a mensagem é traduzida e despachada. O loop de mensagens provê uma maneira particularmente simples e efetiva de tratar uma larga quantidade de mensagens que o Windows manda para o aplicativo. 

Agora já temos as mensagens “pegas” pelo loop de mensagens, falta processa-las. O problema está que o windows manda centenas mensagens diferentes para o aplicativo, então como pegar as mensagens e como pegar apenas as que nos importa?
A solução encontrada é utilizar o WndProc(). O WndProc atravéz de um switch , pega as mensagens de acordo com o conteúdo delas. 

O protótipo da função WndProc é

LRESULT CALLBACK WndProc( 
  HWND
 hwnd,      // ponteiro para a janela
  UINT uMsg,      // mensagem
  WPARAM wParam,  // primeiro paramentro da mensagem
  LPARAM lParam   // segundo parametro da mensagem:
);

Vamos agora observar um source onde é utilizado o WndProc
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) 
{ 
PAINTSTRUCT                     paintStruct; 
switch(message) {                     case WM_CREATE:   // a janela é criada                            return 0;                            break; 

                    case WM_CLOSE:    // a janela é fechada
                                        PostQuitMessage(0);   // Então saia do programa 
   
                                     return 0; 
   
                                     break; 

   
                  case WM_PAINT:    // Janela necessita ser repintada
   
                                     BeginPaint(hwnd, &paintStruct); 
   
                                     EndPaint(hwnd, &paintStruct); 
   
                                     return 0; 
   
                                     break;
                    default: 
                                       break;                     }                    return (DefWindowProc(hwnd, message, wParam, lParam)); } 
Este exemplo realmente não faz muito, de fato a única coisa que faz, é quando chega um WM_CLOSE, ele faz o programa fechar.
Percebe-se 3 “cases” no exemplo: 

O WM_CREATE é chamado quando o programa é inicializado. Ali é o melhor lugar para se por os códigos iniciais do programa. 

O WM_CLOSE é chamado quando a janela é fechada. O mais normal neste caso é utilizar a função PostQuitMessage(0) para sair do programa. O parametro 0 (zero), diz que houve uma saída sem erros.

Por fim, o WM_PAINT, ele é chamado quando a janela nescessita ser repintada. Como o  objetivo final deste artigo é fazer uma janela na qual apareça escrito “Hello, world”, será nesta local será posta a parte que escreve na tela o “Hello, world”. 

Primeiramente deve-se criar um ponteiro para o conteúdo de um dispositivo. O nome é estranho, mas pense sendo apenas o ponteiro para alguma função. Ele é declarado assim: 
HDC       hDC; 
Depois deve-se declarar uma estrutura onde será controlada a pintura. No exemplo acima ela já está declarada: 
PAINTSTRUCT               paintStruct; 
Por fim , declarar o que será escrito na tela, no nosso caso “Hello, World”. 
Char               string[] = “Hello, world”;

Dentro do case WM_PAINT, deve-se por tudo em operação: 

hDC = BeginPaint(hwnd, &paintStruct); // Define o ponteiro de contexto de dispositico como sendo o retorno desta função
SetTextColor(hDC, COLORREF(0x00FF0000)); // Seta a cor do texto como azu
TextOut(hDC, 150, 150, string, sizeof(string)-1); // Mostra o texto no meio da tela 
EndPaint(hwnd, &paintStruct); //  Termina a pintura da tela
return 0;// Retorna sem erro 

Pronto!

Iniciamos o aplicativo, definimos como deverá ser a janela, iniciamos a janela, cuidamos do loop de mensagem, cuidamos do tratamento das mensagens e por por fim escrevemos na tela nosso “Hello, word”. 

A próxima seção será o código de fonte funcional do aplicativo e comentado os principais pontos.
Caso queira baixar o source clique aqui

Código de Fonte

#include <windows.h>            // Header das funções padrões do windows
// O parte do programa que trata  as funções do Windows.

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) 
{ 
            PAINTSTRUCT paintStruct; 
            HDC hDC;                                       // Contexto do dispositivo
            char string[] = "Hello, world!";            // Texto a ser mostrado 

            switch(message)
            { 
                       case WM_CREATE:                       // A janela é criada
                                    return 0; 
                                    break; 

                       case WM_CLOSE:             // A janela é fechada
                                    PostQuitMessage(0);            // Sai do programa
                                    return 0; 
                                    break; 

                       case WM_PAINT:              // A janela presisa ser repintada
                                    hDC = BeginPaint(hwnd, &paintStruct); 
                                    SetTextColor(hDC, COLORREF(0x00FF0000));            // seta a cor do texto como azul.
                                    TextOut(hDC, 150, 150, string, sizeof(string)-1);        // mostra o texto no meio da tela
                                    EndPaint(hwnd, &paintStruct); 
                                    return 0; 
                                    break; 

                          default: 
                                   break; 
            }
            return (DefWindowProc(hwnd, message, wParam, lParam)); 
} 

// Função inicial do programa
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) 
{ 
            WNDCLASSEX windowClass;            // Classe da janela
            HWND               hwnd;                        // “Apelido” para a janela
            MSG                  msg;                          // Mensagem 
            bool        done;                                   // Flag diz quando o programa está pronto

            // Define a strutura da classe do window
            windowClass.cbSize                            = sizeof(WNDCLASSEX); 
            windowClass.style                                = CS_HREDRAW | CS_VREDRAW; 
            windowClass.lpfnWndProc                 = WndProc; 
            windowClass.cbClsExtra                     = 0; 
            windowClass.cbWndExtra                  = 0; 
            windowClass.hInstance                        = hInstance; 
            windowClass.hIcon                              = LoadIcon(NULL, IDI_APPLICATION);            
            windowClass.hCursor              = LoadCursor(NULL, IDC_ARROW); 
            windowClass.hbrBackground   = (HBRUSH)GetStockObject(WHITE_BRUSH); 
            windowClass.lpszMenuName   = NULL;                                                
            windowClass.lpszClassName   = "MyClass"; 
            windowClass.hIconSm                         = LoadIcon(NULL, IDI_WINLOGO); 

            // Registra a classe da janela
            if (!RegisterClassEx(&windowClass)) 
                        return 0; 

            // Classe registrada, então vamos criar a janela
            hwnd = CreateWindowEx(NULL,      
                                                  "MyClass",            // Nome da classe                                     
                                                  "A REAL Windows Application!",            // nome do aplicativo 
                                                  WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_SYSMENU, 
                                                  100, 100,            // cordenadas x, y 
                                                  400, 400,            // Comprimento e altura
                                                  NULL, // Ponteiro para o parente                                                                          NULL, // Ponteiro para o menu
                                                  hInstance,            // Instancia do aplicativo
                                                  NULL);            // Sem parametros extras

            // Checa para ser se a janela foi criada.
            if (!hwnd) // caso não seja, saia do programa
                    return 0;   
             done = false;    // Inicializa a variaval do loop  
            // Principal loop de mensagem
            while (!done) 
            { 
               PeekMessage(&msg, hwnd, NULL, NULL, PM_REMOVE); 

                if (msg.message == WM_QUIT)            // Recebesse uma mensagem de saida?
                        { 
                                    done = true;                             // Se sim, é hora de sair do loop.
                        } 
                        else 
                       
                        { 
                           TranslateMessage(&msg);                    // Traduza a mensagem                              
                            DispatchMessage(&msg);                    // Mande a mensagem para a fila
                        } 
        }
      return msg.wParam; 
}

Nenhum comentário:

Postar um comentário