sexta-feira, 2 de janeiro de 2015

C++ Win32 API - Primeira Janela - Parte 1

 Hoje vou começar a publicar tutoriais explicando como criar programas em C++ com janelas, usando o Win32 API. Eu recomendo que quem esta começando a usar Win32 API não apenas copie o código, mas que também leia o tutorial com atenção para entender o codigo.

 Vantagens e Desvantagens:
 O uso do Win32 API tem vantagens e desvantagens, a primeira desvantagem é em relação a dificuldade, a maioria das pessoas que começam a programar usam linguagens como Visual Basic, C# e outras, e as IDEs usadas para criar programas nessas linguagens facilitam na criação de formularios de forma que todos os formularios são criados de forma grafica (apenas puxando controles de uma lista e colocando-os sobre um formulario) e ao usar Win32 API tudo é feito através de códigos (e códigos muito grande) e quando alguém esta acostumado com a criação de janelas completamente de forma grafica e começa a escrever codigo em Win32 API acaba desistindo. Por isso quero fazer estes tutoriais com o maximo de detalhes e da forma mais fácil possivel.
 Mas o Win32 API não tem apenas desvantagens, uma das vantagens é o fato de que você não tem que baixar nenhuma biblioteca isso porque a maioria dos compiladores já traz os arquivos necessários para essas tarefas, porem algumas coisas podem variar de compilador para compilador, por isso vou mostrar as diferenças de código ao usar o compilador Visual C++ (Usado no Visual Studio), e do G++ do MinGW (Usado no Dev-C++ e no Code::Blocks), outra vantagem é em relação a velocidade já que o programa não vai ser linkado a outras bibliotecas.

 O que é necessário?
 Como dito acima, não é necessário baixar nenhuma biblioteca, tudo que você precisa é de uma IDE como Visual Studio, Code::Blocks ou Dev-C++, e de conhecimento na linguagem C++.

 Primeira Janela:
 Acredito que a maioria das pessoas utilizam o Visual Studio, por isso vou começar explicando nessa IDE.
 Primeiramente, abra o Visual Studio (No meu caso estou usando o Visual Studio 2012), clique no menu File -> New -> Project, neste momento vai abrir uma janela para criar seu projeto, na parte Esquerda da janela escolha em Templates a opção Visual C++, e na lista do meio selecione Win32 Console Application, digite um nome na barra Name, dessa forma:

  Clique em OK, neste momento vai abrir uma janela com o texto "Welcome to the Win32 Application Wizard", apenas clique no botão Next, após isso você devera selecionar o tipo de aplicação, então em "Application type" selecione Windows Application e em "Additional options" selecione Empty Project e clique em finish, a janela deve ficar dessa maneira dessa forma:


 Agora você talvez pergunte, "Porque criar um projeto vazio se o Visual Studio já tem um template com uma janela pronta?", como eu disse acima, eu vou explicar da maneira mais simples possível, e o código que vem com o Visual Studio não é a maneira mais simples. E porque eu teria escolhido a opção Windows Application se o projeto vai vir vazio? Qual seria a diferença?, Esta opção foi escolhida para que o ponto de entrada do projeto seja a Função WinMain, se eu tivesse escolhido a opção Console Application o ponto de entrada seria a função main() que todos já conhecem.
 Se você não sabe o que é Ponto de Entrada (EntryPoint) não tem problema, isso não te impedira de aprender Win32 API.
 Agora vamos adicionar o arquivo de código fonte principal, para isso vamos no Solution Explorer, clique com o botão direito do mouse em Source Files, escolha Add -> New Item, na janela que vai abrir escolha a opção "C++ File (.cpp)" e no campo name escreva Main.cpp, coloque este nome apenas para sabermos que este é o arquivo que contem o ponto de entrada.
Agora copie e cole o seguinte codigo no Main.cpp:
 #include <windows.h>  
   
 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);  
   
 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,   
   LPSTR pCmdLine, int nCmdShow)  
 {  
  MSG msg;    
  HWND hwnd;  
  WNDCLASSW wc;  
        
  wc.style     = CS_HREDRAW | CS_VREDRAW;  
  wc.cbClsExtra  = 0;  
  wc.cbWndExtra  = 0;  
  wc.lpszClassName = L"WINDOW";  
  wc.hInstance   = hInstance;  
  wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);  
  wc.lpszMenuName = NULL;  
  wc.lpfnWndProc  = WndProc;  
  wc.hCursor    = LoadCursor(NULL, IDC_ARROW);  
  wc.hIcon     = LoadIcon(NULL, IDI_APPLICATION);  
    
  RegisterClassW(&wc);  
  hwnd = CreateWindowW(L"WINDOW", L"Janela",  
         WS_OVERLAPPEDWINDOW | WS_VISIBLE,  
         100, 100, 800, 600, NULL, NULL, hInstance, NULL);   
   
  ShowWindow(hwnd, nCmdShow);  
  UpdateWindow(hwnd);  
   
  while( GetMessage(&msg, NULL, 0, 0)) {  
   DispatchMessage(&msg);  
  }  
   
  return (int) msg.wParam;  
 }  
   
 LRESULT CALLBACK WndProc(HWND hwnd, UINT msg,   
   WPARAM wParam, LPARAM lParam)  
 {  
  switch(msg)   
  {  
   case WM_DESTROY:  
    PostQuitMessage(0);  
    return 0;     
  }  
   
  return DefWindowProcW(hwnd, msg, wParam, lParam);  
 }  
 Primeiramente, antes que você queira desistir eu já vou falar, você não precisa decorar todo esse código agora, afinal, todas as IDEs trazem esse código como modelo padrão para criar uma janela básica. Sim, realmente o código é muito grande para apenas uma janela, mas como eu disse, não é necessário decorar isso agora, então apenas vou explicar o que este código significa:

 Explicação de cada linha:
 - Na primeira linha incluímos o arquivo "windows.h", isso foi para termos o acesso as funções, classes e valores do Win32 API.
 - Na terceira linha tem o código, "LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);", este código explicarei depois, mas quem já conhece C++ já deve imaginar que este é o protótipo de uma função, no caso da função WndProc que explicarei depois.
 - Na quinta linha temos o código "int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR pCmdLine, int nCmdShow)", esta função é o ponto de entrada do nosso projeto, por isso que escolhemos a opção Windows Application no momento de criar o projeto, mas você também pode escolher o ponto de entrada nas configurações do projeto. A função WinMain pode ser declarada de varias maneiras que sejam aceitas, por exemplo, poderíamos colocar o nome wWinMain, mas nesse caso o parametro pCmdLine teria que ser do tipo PWSTR,  se você não entendeu, não tem problema pois não falei ainda de strings ao usar Win32 API, apenas quero mostrar que podemos criar o ponto de entrada de varias maneiras.
 - Na linha  "MSG msg;", criamos objeto da estrutura MSG, para isso primeiramente precisamos saber o que é a estrutura MSG. A estrutura MSG contem a informação da mensagem obtida após a ocorrencia de um evento, os valores dela são alterados pois ela que armazenas as informações do evento no Loop de mensagens, e basicamente os valores dela são geralmente obtidos na própria função de procedimento de retorno de chamada que veremos depois.
 - Na linha "HWND hwnd;" criamos o objeto do manipulador de janelas, basicamente sempre que criamos uma janela ou qualquer controle para a janela obtemos o HWND do controle ou janela para poder alterar propriedades e até para adicionar um controle a uma janela, para resumir ele praticamente representa um controle ou janela. Nesse caso ele será usado como manipulador da janela.

 Classe da janela:
 - Na linha "WNDCLASSW wc;", isto é algo muito importante que merece uma boa explicação. Ao criar uma janela é como se estivéssemos criando um novo controle que não existe, portanto devemos criar uma classe para definir algumas de suas informação e qual será seu loop de mensagens que vai tratar dos eventos que ocorrer com esse controle, porem ao usar um controle já criado pelo sistema como um botão, checkBox, TextBox não precisamos criar uma nova classe pois ele já tem sua classe criada pelo próprio sistema com seus loops de mensagens e características padrões. Cada classe é identificada por um nome, e após esta classe ser registrada no sistema, independente do local do código podemos criar um controle baseado da classe já registrada.
 - Na linha " wc.style = CS_HREDRAW | CS_VREDRAW;", estamos alterando o valor de uma das propriedades da classe que será nossa janela, nesse caso style que colocamos flags de estilos, nesse caso o valor "CS_HREDRAW | CS_VREDRAW" significa que ao modificar o tamanho da janela, tanto em largura quanto em largura, devera ser chamado o evento de redesenhar a tela.
- Na linha "wc.cbClsExtra = 0;", colocamos o valor extra de bytes que devem ser alocados para a classe de estrutura da janela, como não precisamos de tamanho extra colocamos em 0.
- Na linha "wc.cbWndExtra = 0;", também para alocar bytes extra, mas neste caso para a instancia da janela.
 - Na linha "wc.lpszClassName = L"WINDOW";", esta propriedade é a que colocamos o nome da nossa classe, o nome da classe é muito importante pois é através dela que vamos identifica-la.
 - Na linha "wc.hInstance = hInstance;", passamos para a classe o manipulador de instancia do nosso programa.
 - Na linha "wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);"  esta propriedade é do tipo HBRUSH e nela definimos qual será a cor do fundo da nossa janela, é possivel também usar cores solidas mas vermos isso posteriormente.
 - Na linha "wc.lpszMenuName = NULL;" aqui colocaríamos em qual resource esta a barra de menu do formulario (se existir algum menu nos resources), mas também há outras formas para colocar barras de menu que também veremos posteriormente.
 - Na linha "wc.lpfnWndProc = WndProc;" aqui colocamos qual será a Window Procedure da classe que basicamente é o loop de mensagens da nossa janela, observe que no topo do código fonte tem uma linha escrito "LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);",  nesta linha tem o prototipo da função WndProc na qual é o nosso loop de mensagem, basicamente está função esta mais abaixo no nosso código e logo chegaremos lá.
 - Na linha "wc.hCursor = LoadCursor(NULL, IDC_ARROW);" aqui carregamos o cursor que será usado na janela, neste caso esta sendo carregado um padrão, mas é possivel carrega-los através das resources;
 - Na linha "wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);"aqui carregamos o icone que será usado na nossa janela, aqui também esta sendo usado um icone padrão e também é possivel carregar icones contido nas resources.
 - Na linha "RegisterClassW(&wc);" aqui é a parte final da criação da classe, nesta linha enviamos a classe para que seja registrada com as informações colocadas anteriormente, a partir daqui não vamos usar o nome do objeto "wc", toda essa classe será identificada com o seu nome colocado anteriormente na linha "wc.lpszClassName = L"WINDOW"" no caso a classe se chama "WINDOW", mas a nossa janela ainda não foi criada, apenas criamos sua classe que contém suas informações, basicamente essa é apenas uma das classes que é criada pelo próprio usuário pois classes como botões são criados pelo próprio sistema. Para criar a janela precisamos criar o objeto que vai herdar essa classe, dessa forma com uma unica classe podemos criar vários objetos com o mesmo loop e propriedades.

 Criando a Janela:
 Observe a linha abaixo:
  hwnd = CreateWindowW(L"WINDOW", L"Janela",   
      WS_OVERLAPPEDWINDOW | WS_VISIBLE,   
      100, 100, 800, 600, NULL, NULL, hInstance, NULL);  
 Nesta linha que criamos a janela, apesar do nome da função ser CreateWindowW ela não serve apenas para criar janelas, mas serve também para criar praticamente todos os controles possíveis como botões, textBox, ComboBox e etc.
 Observe que CreateWindowW retorna um objeto do tipo HWND e ele esta sendo armazenado no objeto com o nome hwnd, basicamente este não é o objeto em si, mas sim a copia do manipulador da janela para que possamos acessar ou alterar a janela com mais fácilidade, é possivel criar a janela sem pegar a copia da janela, mas caso você queira alterar algo posteriormente seria necessario usar funções para buscar o HWND da janela novamente.
 Em relação ao nome do tipo HWND ele é a abreviação de Handle WiNDow, ou seja, manipulador da janela, por isso ele é usado ao alterar algo dos controles, mas HWND não é usado apenas para janelas, mas também para outros controles.
 Agora vamos aos parêmetros passados na função:
 O primeiro deles é o L"WINDOW", aqui passamos o nome da classe criada acima, e é nessa classe que tem as informações como cursor, icone, menu, loop de mensagens e etc.
 O parâmetro L"Janela" será o título da nossa janela, quando a janela for exibida esse será o titulo que aparecera na janela.
  O parâmetro WS_OVERLAPPEDWINDOW | WS_VISIBLE são os estilos visuais da janela, basicamente este é o nome do parâmetro mas aqui passamos as caracteristicas que o controle de ter, o simbolo '|' é usado para usar vários estilos visuais, no caso o "WS_OVERLAPPEDWINDOW" é obrigatório quando se esta criando uma janela, ele passa as principais informações para uma janela padrão, para resumir ele é a abreviação de "WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX", ou sejá, ele facilita um pouco nossa vida para não precisar de passar tantos flags de estilos visuais, o WS_VISIBLE é usado para a criação de todo controle que seja visível.
 O parâmetro "100, 100, 800, 600" é a posição X e Y da nossa janela, e em seguida a Largura e a Altura;
 Os parâmetros nulos em seguida são o hWndParent e o hMenu, o hWndParent seria usado se este fosse um controle que possa pertencer a outro, como botões, comboBox que para ser criados precisam estar dentro de uma janela, e o hMenu seria a identidade do controle, no caso de janelas deixamos nulo pois só colocamos id em controles herdados como botoes e outros.
 O parâmetro "hInstance" é onde passamos o hIntance do nosso programa, ele é obtido em um dos parâmetros da função WinMain.
 O ultimo parâmetro que também é nulo seria o lParam passado no loop de mensagens na mensagem WM_CREATE, nesse caso também é nulo pois isso não teria utilidade agora.

 Função ShowWindow, UpdateWindow e Loop de mensagens:
 Observe o código abaixo:
  ShowWindow(hwnd, nCmdShow);   
  UpdateWindow(hwnd);   
     
  while( GetMessage(&msg, NULL, 0, 0)) {   
   DispatchMessage(&msg);   
  }   
 A função ShowWindow ao contrario do que se pensa ela não serve exatamente para mostrar a janela na tela, pois mesmo sem chamar essa função a janela será mostrada da mesma maneira, esta função serve apenas para mudar o modo de visualização como por exemplo, Maximizar, Minimizar, Ocutar e outros, no caso, o nCmdShow é um argumento que vem com a função WinMain, ele traz a forma que o programa deve ser mostrado, minimizado ou maximizado, isso porque ao criar atalhos no Windows, uma das opções contidas é o modo de visualização, então isso basicamente define a forma que o windows quer que a janela seja mostrada.
 A função UpdateWindow apenas chama a mensagem WM_PAINT do loop, para que todos os recursos gráficos que precisam ser renderizados na janela, sejam renderizados.
 Este loop While é o que mantem o programa em execução pois enquanto ele tiver recebendo mensagens mais ele vai despachar mensagens, basicamente é o seguinte, enquanto não for chamada a função PostQuitMessage que envia a mensage para finalizar o loop ele vai continuar despachando mensagens para o loop de mensagens (aquela função que passamos na hora de criar a classe).
 Cada vez que se chama o DispatchMessage o programa chama a função de procedimento, no caso, o loop da classe para que você possa tratar todos os eventos enquanto o programa estiver em execução.

 A ultima linha da função WinMain contem o return "return (int) msg.wParam;", aqui ele apenas retorna o resultado do programa, é aquele valor enviado junto com o PostQuitMessage.

 O Loop de Mensagens (Window Procedure)
 Agora vamos começar a falar do loop de mensagens, aqui vamos ver como tratar eventos, vamos começar explicando o prototipo da função.
 Você se lembra daquela linha bem no topo do código fonte, "LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);", essa é a primeira linha da função do Loop de Mensagens, ela é usada apenas para indicar que em algum lugar do código essa função será criada, pois na criação da classe precisamos de passar o nome da função e no C++ o código é lido de cima para baixo, essa primeira linha usada mostrar que essa função existe em algum lugar do código é chamado de protótipo. Observe que no protótipo não é necessário que os parâmetros tenham nome, apenas é necessário os tipos dos parametros.
 Agora vamos ver a primeira linha completa da função de Loop (Window Procedure):
 LRESULT CALLBACK WndProc(HWND hwnd, UINT msg,    
   WPARAM wParam, LPARAM lParam)  
 O primeiro parâmetro, o "HWND hwnd" é o manipulador do objeto que chamou o evento, se você precisa modificar algo no controle através de um evento, basta usar este HWND.
 O parâmetro "UINT msg" é o parâmetro que contem a mensagem que foi chamada, no meio dessa função você compara este parâmetro com constantes da Win32 API para saber qual evento ocorreu, por exemplo, o evento de pressionar tecla é o WM_KEYDOWN, o de soltar a tecla é o WM_KEYUP e etc.
 Os parâmetros "WPARAM wParam, LPARAM lParam" contém informações sobre o evento ocorrido, por exemplo, ao pressionar uma tecla ou alterar o tamanho da janela serão estes parâmetros que você vai obter a tecla pressionada e o novo tamanho da janela.
 Agora veja o que tem dentro da função WndProc:
  switch(msg)    
  {   
   case WM_DESTROY:   
   PostQuitMessage(0);   
   return 0;     
  }   
  return DefWindowProcW(hwnd, msg, wParam, lParam);   
 Este switch faz comparação com o parâmetro msg que como já foi dito, é este parâmetro que traz a mensagem sobre o event ocorrido.
 Acima podemos ver que foi comparado msg com WM_DESTROY, este é o evento ocorrido ao fechar o programa, ao ocorre-lo é chamado o PostQuitMessage que retorna um mensagem para o windows informando para fechar o programa. Isso é necessario pois se não chama-lo o programa continuará aberto mesmo sem ter nenhuma janela aberta.
 No final é chamado a função DefWindowProcW, esta função chama uma função de procedimento padrão para processar a mensagem que não tenha sido resolvida, por isso é enviado todos os parâmetros recebidos na nossa função de procedimento.

 Informações importantes:
 Acima expliquei todas as linhas do nosso código, primeiramente, vou dizer novamente o que disse no inicio desse post, NÃO PRECISA DECORAR ESSE CÓDIGO AGORA, todas as IDEs trazem um código como modelo o suficiente para mostrar uma janela, o código pode variar de uma IDE para outra, mas não muda muito. Segundo, não tem problema se você não entendeu este código agora, eu também não entendia inicialmente, mas no decorrer do uso de Win32 API você começara a entender mais.

 Uma duvida importante:
 - Por que usar o "L" antes das strings no Win32 API?
  Talvez você tenha visto no código que locais onde se digita texto, nós colocamos o caractere L antes da string, isso é bem simples e ao mesmo tempo chato, o Win32 API trabalha com diversos tipos de strings, nesse caso que usamos o L é usado o tipo LPCWSTR, no geral, as strings que somos acostumados a usar são array de chars ou a std::string, no caso do LPCWSTR ele é uma array de wchar_t, ou para ser mais especifico ele é um "const wchar_t*", este tipo de chars são chamados de wide character, e tem este nome pois tem um tamanho maior que o char padrão e tem uma variação de códigos maior. E disse que ao mesmo tempo é chato pois algumas funções usam tipos diferentes de caracteres, mas o Win32 API geralmente tem copia de suas funções com nomes terminados em W para funções que recebem strings do tipo LPCWSTR.
 Pretendo fazer um post somente falando sobre a conversão destes tipos de string e quando isso é necessário.

 Espero que tenha ajudado.