segunda-feira, 22 de agosto de 2011

Sockets de rede com wxWidgets

Introdução
    Em computação sockets são basicamente o elo de ligação entre duas máquinas na rede. O socket pode ser entendido como sendo IP:Porta (Protocolo TCP) onde os dados são transmitidos entre cliente e servidor como uma cadeia de bytes. Existe também a transmissão em forma de datagramas não confiáveis (Protocolo UDP).
   O endereço IP identifica a máquina à qual eu quero me conectar e a porta o serviço executando nesta máquina. Para que um cliente se conecte à determinada máquina em determinada porta é preciso que um servidor esteja 'escutando' nesta porta.
   Em wxWidgets nós temos algumas classes usadas com o propósito de comunicação em rede através de sockets. A classe wxSocketBase, de onde descendem as demais classes relacionadas com sockets, como wxSocketClient e wxSocketServer. Para mais informações acerca destas classes visite: http://docs.wxwidgets.org/stable/wx_wxsocketbase.html#wxsocketbase.

Exemplo
    Em nosso exemplo vamos escrever uma aplicação que será o cliente e outra que será o servidor. Para que o cliente se conecte é preciso que o servidor esteja em execução, 'escutando' na porta definida. O cliente e o servidor podem ser executados na mesma máquina ou em máquinas separadas, conectadas à mesma rede. No primeiro caso fornecemos para o cliente como nome do host servidor a string 'localhost', no segundo caso fornecemos uma string com o número ip da máquina onde está o aplicativo servidor. A aplicação cliente enviará uma string ao servidor que retornará esta mesma string para o cliente, então, o cliente compara a string recebida de volta pelo servidor com a string enviada. Uma mensagem é mostrada dependendo do resultado da comparação (sucesso ou falha).



Vamos ao exemplo:

//cliente.cpp
#include <wx/wx.h>
#include <wx/socket.h>
#include <wx/wfstream.h>

class MeuFrame : public wxFrame {
    public:
        MeuFrame(const wxString &titulo);
        void Conectar(wxCommandEvent &evt);
        void Enviar(wxCommandEvent &evt);
        void Desconectar(wxCommandEvent &evt);
        void EscreveStatus(wxString status, int onde);
    private:
        wxBoxSizer *box;
        wxButton *btn_conectar;
        wxButton *btn_enviar;
        wxButton *btn_desconectar;
        wxTextCtrl *txt_status;     
        wxSocketClient *m_socket;
};

MeuFrame::MeuFrame(const wxString &titulo):
          wxFrame(NULL, wxID_ANY, titulo, wxDefaultPosition, wxSize(360,200))
{
     wxPanel *painel = new wxPanel(this);
     box = new wxBoxSizer(wxVERTICAL);
     wxBoxSizer *box_texto = new wxBoxSizer(wxVERTICAL);
     wxGridSizer *grid_botoes = new wxGridSizer(1,3,5,5);
     txt_status = new wxTextCtrl(painel, -1, _(""),wxDefaultPosition,wxDefaultSize,wxTE_MULTILINE | wxTE_READONLY); 
     box_texto->Add(txt_status,1,wxEXPAND); 
     btn_conectar = new wxButton(painel,1000,_("Conectar"));
     Connect(1000,wxEVT_COMMAND_BUTTON_CLICKED,wxCommandEventHandler(MeuFrame::Conectar));
     btn_enviar = new wxButton(painel,1001,_("Enviar"));
     Connect(1001,wxEVT_COMMAND_BUTTON_CLICKED,wxCommandEventHandler(MeuFrame::Enviar));
     btn_desconectar = new wxButton(painel,1002,_("Desconectar"));
     Connect(1002,wxEVT_COMMAND_BUTTON_CLICKED,wxCommandEventHandler(MeuFrame::Desconectar));
     grid_botoes->Add(btn_conectar,1,wxALL,5);
     grid_botoes->Add(btn_enviar,1,wxALL,5);
     grid_botoes->Add(btn_desconectar,1,wxALL,5);
     box->Add(box_texto,1,wxEXPAND);
     box->Add(grid_botoes,1,wxEXPAND);
     painel->SetSizer(box);
     //criamos o socket
     m_socket = new wxSocketClient();
     //definimos o manipulador de eventos para o socket
     m_socket->SetEventHandler(*this,1003);
     //
     m_socket->SetNotify(wxSOCKET_CONNECTION_FLAG | wxSOCKET_INPUT_FLAG | wxSOCKET_LOST_FLAG);
     m_socket->Notify(true);
     #if wxUSE_STATUSBAR
        //barra de status da aplicação
        CreateStatusBar(2);
     #endif
     EscreveStatus(_("Desconectado"),1);
   
     Centre();
}

void MeuFrame::Conectar(wxCommandEvent &evt)
{
    wxIPV4address end;
    wxString host = wxGetTextFromUser(_("Digite o endereço do servidor"),
                                      _("Conectar"),
                                      _("localhost")
                                      );
    //atribuimos o nome do host digitado pelo usuario
    //no nosso caso 'localhost' (o host no qual o servidor está executando),
    //mas poderia ser um numero de ip de uma máquina na rede
    end.Hostname(host);
    //o numero da porta do serviço ao qual queremos nos conectar
    //utilizamos um numero alto para evitar conflitos com portas que já são utilizadas por outros serviços
    end.Service(3000);
    //fazemos uma tentativa de conexão
    //quando o segundo parametro é true a aplicação é paralisada
    //enquantro a conexão não se estabelece
    //quando é false a conexão é tentada e retorna-se logo em seguida
    //mesmo sem sucesso
    //em caso de sucesso o método retorna true, caso contrário, retorna false
    m_socket->Connect(end,false);
    //esperamos 10 segundos tentando estabelecer a conexão
    m_socket->WaitOnConnect(10);
    if(m_socket->IsConnected())
    {
        EscreveStatus(_("Conexão realizada com sucesso!"),0);
        EscreveStatus(_("Conectado"),1);       
    }
    else
    {
        EscreveStatus(_("Não foi possível realizar a conexão!"),0);  
        EscreveStatus(_("Desconectado"),1);     
        wxMessageBox(_("Não é possível estabelecer uma conexão com o host especificado!"),_("Alerta"));
    }

}

void MeuFrame::Enviar(wxCommandEvent &evt)
{
    unsigned char tam;
    wxString str_envio;
    wxChar *str_resposta;
    m_socket->SetFlags(wxSOCKET_WAITALL);
    str_envio = wxGetTextFromUser(_("Digite um texto para enviar ao servidor"),
                                  _("Texto"),
                                  _("")
                                 );  
                                
    str_resposta = new wxChar[wxStrlen(str_envio)+1];
    //tamanho da string enviada em bytes
    tam = (unsigned char) ((wxStrlen(str_envio)+1) * sizeof(wxChar));
    txt_status->AppendText(_("\nEnviando um buffer de texto para o servidor..."));
  
    //o primeiro byte é o tamanho da string o restante é a string em si
    m_socket->Write(&tam,1);
    m_socket->Write(str_envio,tam);
    if(m_socket->Error())
    {
        txt_status->AppendText(_("\nFalha!"));
        return;
    }
    else
    {
        txt_status->AppendText(_("\nSucesso!"));
    }
 
    txt_status->AppendText(_("\nRecebendo o texto de volta do servidor..."));
  
    //armazenamos a string de resposta do servidor em str_resposta
    m_socket->Read(str_resposta,tam);
 
    txt_status->AppendText(_("\nComparando as duas strings..."));
  
    //comparamos a string enviada com a string de resposta do servidor
    if(memcmp(str_resposta,str_envio,tam) == 0)
    {
       wxString s;
       s.Printf(_("\nSucesso! String Recebida: %s"), str_resposta);
       txt_status->AppendText(s);
    }
    else
    {
       txt_status->AppendText(_("\nFalha na recepção!"));
    }
    delete[] str_resposta;
}

void MeuFrame::Desconectar(wxCommandEvent &evt)
{
    //desconectamos do servidor
    m_socket->Close();
    EscreveStatus(_(""),0);
    EscreveStatus(_("Desconectado"),1);
}

void MeuFrame::EscreveStatus(wxString status, int onde)
{
    #if wxUSE_STATUSBAR
        SetStatusText(status,onde);
    #endif
}

class MinhaApp : public wxApp
{
    public:
        virtual bool OnInit();
};

IMPLEMENT_APP(MinhaApp)

bool MinhaApp::OnInit()
{
    MeuFrame *frame = new MeuFrame(_("Sockets com wxWidgets: Cliente"));
    frame->Show(true);
    return true;
}

//servidor.cpp
#include <wx/wx.h>
#include <wx/socket.h>

enum 
{
    ID_SERVER = 1000,
    ID_SOCKET   
};

class MeuFrame : public wxFrame
{
    public:
        MeuFrame(const wxString titulo);
        void OnSocketEvent(wxSocketEvent &evt);
        void OnServerEvent(wxSocketEvent &evt);
        void Recebe(wxSocketBase *socket);
        void EscreveStatus();
    private:
        wxTextCtrl *texto;
        wxSocketServer *m_server;
        int num_clientes;
};

MeuFrame::MeuFrame(const wxString titulo):
          wxFrame(NULL, wxID_ANY, titulo, wxDefaultPosition, wxSize(360,200))
{
  
    texto = new wxTextCtrl(this,-1,_(""), wxDefaultPosition,wxDefaultSize,
                           wxTE_MULTILINE | wxTE_READONLY);
    wxIPV4address end;
    end.Service(3000);

    m_server = new wxSocketServer(end);

    //verificamos se o servidor está 'escutando'
    if(m_server->Ok())
    {
        texto->AppendText(_("Servidor escutando...\n"));
    }
    else
    {
        texto->AppendText(_("Não é possével escutar na porta especificada\n"));
        return;
    }
    
    //conectamos os eventos de socket aos seus respectivos manipuladores
    Connect(ID_SERVER,wxEVT_SOCKET,wxSocketEventHandler(MeuFrame::OnServerEvent));
    Connect(ID_SOCKET,wxEVT_SOCKET,wxSocketEventHandler(MeuFrame::OnSocketEvent));

    //configura manipulador de eventos do servidor
    m_server->SetEventHandler(*this,ID_SERVER);
    //especifica quais eventos de socket serão enviados ao manipulador de eventos
    m_server->SetNotify(wxSOCKET_CONNECTION_FLAG);
    //Notify(true) habilita os eventos de socket especificados
    m_server->Notify(true);
    num_clientes = 0;
    #if wxUSE_STATUSBAR
       //barra de status da aplicação
       CreateStatusBar(1);
    #endif
    EscreveStatus();
}

void MeuFrame::Recebe(wxSocketBase *socket)
{
    unsigned char tam;
    char *str;
    texto->AppendText(_("Recebendo dados...\n"));
    socket->SetFlags(wxSOCKET_WAITALL);
    //lemos o tamanho da string
    socket->Read(&tam,1);  
    str = new char[tam];
    //lemos a string recebida e armazenamos em str
    socket->Read(str,tam);   
    texto->AppendText(_("Enviando a string de volta...\n"));
    //enviamos a string recebida de volta ao cliente
    socket->Write(str,tam);
    delete[] str;
    texto->AppendText(_("String enviada ao cliente...\n"));
}

void MeuFrame::OnServerEvent(wxSocketEvent &evt)
{
    wxSocketBase *sock;
    //aceita uma nova conexão e cria um novo objeto wxSocketBase que representa o lado servidor da conexão
    sock = m_server->Accept(false);
    if(sock)
    {
       texto->AppendText(_("Novo cliente aceito\n"));
    }
    else
    {
       texto->AppendText(_("Não foi possível aceitar uma nova conexão\n"));
       return;
    }

    sock->SetEventHandler(*this, ID_SOCKET);
    sock->SetNotify(wxSOCKET_INPUT_FLAG | wxSOCKET_LOST_FLAG);
    sock->Notify(true);
    num_clientes++; 
    EscreveStatus();  
}

void MeuFrame::OnSocketEvent(wxSocketEvent &evt)
{
    wxSocketBase *sock = evt.GetSocket();

    switch(evt.GetSocketEvent())
    {
        case wxSOCKET_INPUT:
        {
           //desabilitamos novos eventos de entrada temporariamente
           sock->SetNotify(wxSOCKET_LOST_FLAG);
           //recebemos e reenviamos a string para o cliente
           Recebe(sock);
           //habilitamos os eventos de entrada novamente
           sock->SetNotify(wxSOCKET_LOST_FLAG | wxSOCKET_INPUT_FLAG);
           break;
        }
        case wxSOCKET_LOST:
        {
            num_clientes--;
            EscreveStatus();
            texto->AppendText(_("Deletando socket.\n\n"));
            //destruimos o socket
            sock->Destroy();
            break;
        }
        default: ;
    }

}

void MeuFrame::EscreveStatus()
{
     wxString s;
     s.Printf(_("Numero de clientes conectados: %d"), num_clientes);
     #if wxUSE_STATUSBAR
        SetStatusText(s,0);
     #endif
}

class MinhaApp : public wxApp
{
    public:
        virtual bool OnInit();
};

IMPLEMENT_APP(MinhaApp)

bool MinhaApp::OnInit()
{
    MeuFrame *frame = new MeuFrame(_("Sockets com wxWidgets: Servidor"));
    frame->Show(true);
    return true;
}




domingo, 8 de maio de 2011

GDI (Graphics Device Interface)

Introdução
    O GDI (Graphics Device Interface) - Interface para dispositivos gráficos - abstrai a comunicação entre um programa e os dispositivos gráficos de saída, como monitor e impressora, por exemplo. Devido à esta abstração um programador não precisa conhecer detalhes específicos sobre o hardware para escrever um programa que desenhe primitivas gráficas. Através do GDI podemos trabalhar com gráficos vetoriais 2D, imagens e fontes. Para isso nós precisamos criar um Contexto de Dispositivo (DC - Device Context), em wxWidgets esta classe é a wxDC (que não pode ser usada diretamente). A classe wxDC é uma classe genérica que possui classes derivadas para diferentes tarefas, estas classes são:
  • wxBufferedDC
  • wxBufferedPaintDC
  • wxPostScriptDC
  • wxMemoryDC
  • wxPrinterDC
  • wxScreenDC
  • wxClientDC
  • wxPaintDC
  • wxWindowDC
      A classe wxScreenDC é usada para desenhar em qualquer lugar na tela. A classe wxWindowDC é usada para desenhar em qualquer local da janela (apenas Windows). A classe wxClientDC é usada para desenhar na área cliente da janela, isto é, menos barra de título e bordas. A classe wxPaintDC faz o mesmo que wxClientDC exceto pelo fato de que wxPaintDC só pode ser usada no evento wxPaintEvent e wxClientDC não pode ser usada neste evento. A classe wxMemoryDC é usada para desenhar gráficos em bitmap. A classe wxPostScriptDC é usada para criar arquivos PostScript. A classe wxPrinterDC é usada para acessar a impressora (apenas no Windows).

wxColour
   A classe wxColour representa uma cor RGB (Red, Green, Blue). Seu construtor define a intensidade de cada uma dessas três cores que varia de 0 a 255. Todas as outras cores são variações de intensidade das combinações das cores RGB.

wxPen
    A classe wxPen é utilizada para desenhar linhas e bordas de primitivas, como retângulos, elipses, etc. Em seu construtor podemos definir a cor, largura (default = 1) e o estilo (default = wxSOLID) da linha ou borda.

wxBrush
    A classe wxBrush é utilizada para definir a cor de preenchimento das primitivas gráficas, o brush pode ter uma cor e um estilo (default = wxSOLID).

Exemplo
    Para desenhar um retângulo de cor vermelha com borda branca faríamos da seguinte forma:

    void OnPaint(wxPaintEvent &evt)
    {
         int x = 10, y = 10, largura = 10, altura = 20;
         wxPaintDC dc(this);
         wxBrush brush(wxColour(255,0,0), wxSOLID);
         dc.SetBrush(brush);
         wxPen pen(wxColour(255,255,255);
         dc.SetPen(pen);
         dc.DrawRectangle(x, y, largura, altura);
    }

O jogo da "cobrinha"
    Quem nunca viu o famoso jogo da cobrinha? Se você nunca viu (o que eu duvido), abaixo segue uma imagem do jogo, que eu fiz utilizando wxWidgets (com alguns pequenos "bugs ", mas, bugs à parte, o objetivo foi dar um exemplo prático de utilização da GDI com wxWidgets e outras classes que serão abordadas em posts futuros). O projeto pode ser baixado em: http://www.4shared.com/file/b0KZqryl/JogoCobrinha.html









sexta-feira, 29 de abril de 2011

Sliders

Introdução
    Sliders são controles que nós podemos manipular movendo para frente e para trás (horizontal) ou para cima e para baixo (vertical) alterando seu valor. Em wxWidgets um slider é representado pela classe wxSlider. Podemos obter o valor de um wxSlider através do método GetValue() que nos retorna um número inteiro e definir seu valor através do método SetValue(int valor). No construtor da classe podemos definir seu valor inicial, valor mínimo, valor máximo, tamanho, orientação, etc. Para uma referência completa dos métodos da classe wxSlider visite: http://docs.wxwidgets.org/stable/wx_wxslider.html

Exemplo
    Como exemplo vamos nos basear no post sobre caixas de texto onde fizemos um programa para cálculo do volume do cilindro, mas ao invés de fornecer o raio e a altura através de caixas de texto vamos fornecer estes valores através de sliders (um para o raio e outro para altura). À medida que movemos os sliders os valores de raio e altura são alterados e o volume é calculado sendo mostrado através de um wxStaticText.



Código:

#include <wx/wx.h>

class Slider : public wxFrame
{
    private:
        wxSlider *sliderRaio;
        wxSlider *sliderAltura;
        wxStaticText *txtVolume;
        wxGridSizer *grid;
        double raio, altura, volume;

    public:
        Slider(const wxString &titulo);
        double Volume(double raio, double altura);
        void OnSlide(wxScrollEvent &evt);

};

const int ID_SL_RAIO = 1000;
const int ID_SL_ALTURA = 1001;


#include "slider.h"

Slider::Slider(const wxString &titulo) :
wxFrame(NULL, -1, titulo, wxDefaultPosition, wxSize(240,240))
{
   wxPanel *painel = new wxPanel(this, -1);
   grid = new wxFlexGridSizer(2,3,5,5);
   //criamos um slider com valor inicial = 0, valor minimo = 0, valor maximo = //100, 200 px de tamanho, orientado verticalmente e mostrando o valor //selecionado
   sliderRaio = new wxSlider(painel, ID_SL_RAIO, 0, 0, 100, wxDefaultPosition, wxSize(-1,200), wxSL_VERTICAL | wxSL_LABELS);
   //conectamos o metodo OnSlide ao evento do slider
   //ao movimentar o slide este metodo e chamado
   Connect(ID_SL_RAIO, wxEVT_COMMAND_SLIDER_UPDATED, wxScrollEventHandler(Slider::OnSlide));
   //criamos um slider com valor inicial = 0, valor minimo = 0, valor maximo = //100, 200 px de tamanho, orientado verticalmente e mostrando o valor //selecionado
   sliderAltura = new wxSlider(painel, ID_SL_ALTURA, 0, 0, 100, wxDefaultPosition, wxSize(-1,200), wxSL_VERTICAL | wxSL_LABELS);
   //conectamos o metodo OnSlide ao evento do slider
   //ao movimentar o slide este metodo e chamado
   Connect(ID_SL_ALTURA, wxEVT_COMMAND_SLIDER_UPDATED, wxScrollEventHandler(Slider::OnSlide));
   txtVolume = new wxStaticText(painel, -1, wxT("0"));
   grid->Add(new wxStaticText(painel, -1, wxT("Raio")), 1, wxLEFT, 20);
   grid->Add(new wxStaticText(painel, -1, wxT("Altura")), 1, wxLEFT, 20);
   grid->Add(new wxStaticText(painel, -1, wxT("Volume")), 1, wxLEFT, 38);
   grid->Add(sliderRaio, 1, wxLEFT, 22);
   grid->Add(sliderAltura, 1, wxLEFT, 24);
   grid->Add(txtVolume, 1, wxTOP | wxLEFT, 48);
   painel->SetSizer(grid);
   Centre();
}

double Slider::Volume(double raio, double altura)
{
    this->raio = raio;
    this->altura = altura;
    volume = 3.14 * (this->raio*this->raio) * altura;
    return volume;
}

void Slider::OnSlide(wxScrollEvent &evt)
{
   //o metodo GetValue retorna o valor do slider
   double v = Volume(sliderRaio->GetValue(), sliderAltura->GetValue());
   txtVolume->SetLabel(wxString() << v);
}


#include "slider.h"

class MinhaApp : public wxApp
{
    public:
       virtual bool OnInit();
};

#include "main.h"

IMPLEMENT_APP(MinhaApp)

bool MinhaApp::OnInit()
{
    Slider *slider = new Slider(wxT("Slider com wxWidgets"));
    slider->Show(true);
    return true;
}

segunda-feira, 25 de abril de 2011

CheckBox com wxWidgets

Introdução
     Um checkbox é um widget que pode ter dois estados: ligado ou desligado (checado ou não). Quanto um checkbox está checado ele apresenta um "tick" na caixa que representa o widget. Esta caixa pode ter um label associado a ela do lado esquerdo ou direito. Um checkbox pode ser usado em diversas situações, como marcar ou desmarcar opções, aceitar ou não termos de uso, mostrar ou esconder outros widgets, entre outras. Em wxWidgets um checkbox é representado pela classe wxCheckBox.

Exemplo
    Em nosso exemplo vamos fazer um programa que mostra ou esconde o título da aplicação dependendo do estado do checkbox. Quanto o checkbox estiver checado o título é mostrado, do contrário o título não aparece.


Código

#include <wx/wx.h>
//checkbox.h

class CheckBox : public wxFrame
{
public:
    CheckBox(const wxString& title);

    void Alternar(wxCommandEvent& event);

    wxCheckBox *m_cb;
};

#include "checkbox.h"
//checkbox.cpp

CheckBox::CheckBox(const wxString& title) : wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(270, 150))
{
  wxPanel *panel = new wxPanel(this, wxID_ANY);

  m_cb = new wxCheckBox(panel, 100, wxT("Mostra o título"), wxPoint(20, 20));
  m_cb->SetValue(true);
  Connect(100, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler(CheckBox::Alternar));
  Centre();
}

void CheckBox::Alternar(wxCommandEvent& WXUNUSED(event))
{

  if (m_cb->GetValue()) {
      this->SetTitle(wxT("CheckBox com wxWidgets"));
  } else {
      this->SetTitle(wxT(" "));
  }
}

#include <wx/wx.h>
//main.h
class MyApp : public wxApp
{
  public:
    virtual bool OnInit();
};

#include "main.h"
#include "checkbox.h"
//main.cpp

IMPLEMENT_APP(MyApp)

bool MyApp::OnInit()
{

    CheckBox *cb = new CheckBox(wxT("CheckBox com wxWidgets"));
    cb->Show(true);

    return true;
}

terça-feira, 19 de abril de 2011

Menus com wxWidgets

Introdução
    Através dos menus nós podemos agrupar os comandos de nossa aplicação para que possamos executá-los de maneira rápida e intuitiva. Em wxWidgets os menus são compostos basicamente de uma barra de menu (wxMenuBar), os menus propriamente ditos (wxMenu) e os items pertencentes a cada menu (wxMenuItem). Através do método SetMenuBar() nós adicionamos uma barra de menu à aplicação. O método Append() adiciona um menu à barra de menu e um item de menu ao menu.
   No nosso exemplo criaremos uma aplicação com uma barra de menu que tem o menu 'Arquivo' que por sua vez tem os items de menu: 'Novo', 'Abrir', 'Salvar' e 'Sair'. Ao clicarmos no item de menu 'Sair' a aplicação é fechada.


Código:

#include <wx/wx.h>
//menu.h

class Menu : public wxFrame
{
        private:
            wxMenuBar *barramenu;
            wxMenu *arquivo;
            wxMenuItem *novo;
            wxMenuItem *abrir;
            wxMenuItem *salvar;
            wxMenuItem *sair;

        public:
            Menu(const wxString &titulo);
            void Sair(wxCommandEvent &evt);
};

#include "menu.h"
//menu.cpp

Menu::Menu(const wxString &titulo) :
      wxFrame(NULL, -1, titulo, wxDefaultPosition, wxSize(500, 400))
      {
            //criamos a barra de menu
            barramenu = new wxMenuBar;
            //criamos o menu Arquivo
            arquivo = new wxMenu;
            novo = new wxMenuItem(arquivo, 1000, wxT("&Novo"));
            abrir = new wxMenuItem(arquivo, 1001, wxT("A&brir"));
            salvar = new wxMenuItem(arquivo, 1002, wxT("&Salvar"));
            sair = new wxMenuItem(arquivo, 1003, wxT("Sai&r"));
            //conectamos o método Sair() ao item de menu de id = 1003
            Connect(1003, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(Menu::Sair));
            //adicionamos os items de menu ao menu arquivo
            arquivo->Append(novo);
            arquivo->Append(abrir);
            arquivo->Append(salvar);
            arquivo->Append(sair);
            //adicionamos o menu Arquivo à barra de menu
            barramenu->Append(arquivo, wxT("&Arquivo"));
            //adicionamos a barra de menu 'barramenu' à aplicacao
            SetMenuBar(barramenu);
            Centre();
      }

void Menu::Sair(wxCommandEvent &evt)
{
    //fechamos a aplicacao
    Close();
}

#include "menu.h"
//main.h

class MinhaApp : public wxApp
{
    public:
        virtual bool OnInit();
};

#include "main.h"
//main.cpp

IMPLEMENT_APP(MinhaApp)

bool MinhaApp::OnInit()
{
        Menu *menu = new Menu(wxT("Menus com wxWidgets"));
        menu->Show(true);
        return true;
}

sábado, 16 de abril de 2011

Caixas de Texto

Introdução
      Caixas de texto são utilizadas em sistemas com interface gráfica para capturar entrada de dados do usuário. Através de caixas de texto podemos entrar com nomes de usuário e senhas para validação, gravar informações em um banco de dados e setar variáveis de nossos programas para fazer cálculos, por exemplo. Em wxWidgets a classe que representa uma caixa de texto é a wxTextCtrl.
    Para capturar o valor de um wxTextCtrl utilizamos o método GetValue() que retorna um wxString.     
    Para setar o valor de um wxTextCtrl utilizamos o método SetValue(wxString valor).

Cálculo do volume do cilindro

     Neste post vou mostrar um programa para calcular o volume de um cilindro. O usuário entrará com o raio da base e a altura do cilindro. A fórmula para calculo do volume do cilindro é dada por: A = PI * r^2 * h, onde r é o raio e h é a altura. O programa avisará através de uma caixa de mensagem (wxMessageBox) se o usuário deixar algum campo vazio ou se os valores digitados não forem números.


     


     

Segue abaixo o código da aplicação:

#include <wx/wx.h>
//cilindro.h

class Cilindro : public wxFrame {

    public:

        Cilindro(const wxString &titulo);

        double Volume(double raio, double altura);

        void Calcula(wxCommandEvent &evt);
        void Limpa(wxCommandEvent &evt);

    private:

       double raio, altura, volume;

       wxTextCtrl *txtraio;
       wxTextCtrl *txtaltura;
       wxTextCtrl *txtvolume;
       wxStaticText *stcraio;
       wxStaticText *stcaltura;
       wxStaticText *stcvolume;
       wxButton *calcular;
       wxButton *limpar;

};

#include "cilindro.h"
//cilindro.cpp

Cilindro::Cilindro(const wxString &titulo) : wxFrame(NULL, -1, titulo, wxDefaultPosition, wxSize(290, 180))
{
    wxPanel *painel = new wxPanel(this, -1);
    wxBoxSizer *boxprincipal = new wxBoxSizer(wxVERTICAL);
    wxGridSizer *boxtexto = new wxGridSizer(3, 2, 5, 5);
    wxBoxSizer *boxbotoes = new wxBoxSizer(wxHORIZONTAL);
    txtraio = new wxTextCtrl(painel, -1, wxT(""));
    txtaltura = new wxTextCtrl(painel, -1, wxT(""));
    txtvolume = new wxTextCtrl(painel, -1, wxT(""));
    stcraio = new wxStaticText(painel, -1, wxT("Raio:"));
    stcaltura = new wxStaticText(painel, -1, wxT("Altura:"));
    stcvolume = new wxStaticText(painel, -1, wxT("Volume:"));
    calcular = new wxButton(painel, 1000, wxT("Calcular"));
    Connect(1000, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(Cilindro::Calcula));
    limpar = new wxButton(painel, 1001, wxT("Limpar"));
    Connect(1001, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(Cilindro::Limpa));
    boxtexto->Add(stcraio, 1, wxALL, 10);
    boxtexto->Add(txtraio, 1, wxALL, 5);
    boxtexto->Add(stcaltura, 1, wxALL, 10);
    boxtexto->Add(txtaltura, 1, wxALL, 5);
    boxtexto->Add(stcvolume, 1, wxALL, 10);
    boxtexto->Add(txtvolume, 1, wxALL, 5);
    boxbotoes->Add(calcular, 1, wxALL, 10);
    boxbotoes->Add(limpar, 1, wxALL, 10);
    boxprincipal->Add(boxtexto, 1, wxEXPAND);
    boxprincipal->Add(boxbotoes, 1, wxEXPAND);
    painel->SetSizer(boxprincipal);
    txtraio->SetFocus();
    Centre();
}

double Cilindro::Volume(double raio, double altura)
{
    this->raio = raio;
    this->altura = altura;
    volume = 3.14 * (this->raio*this->raio) * altura;
    return volume;
}

void Cilindro::Calcula(wxCommandEvent &evt)
{
    double r, h, a;
    //verificamos se algum dos campos está vazio
    //se estiver mostramos uma mensagem de alerta
    if((txtraio->IsEmpty()) || (txtaltura->IsEmpty())) {
        wxMessageBox(wxT("Nao podem haver campos vazios!"), wxT("Aviso"));
        return;
    }
    //verificamos se os valores digitados sao numeros
    //se nao forem exibimos uma mensagem de alerta
    if((!txtraio->GetValue().ToDouble(&r)) || (!txtaltura->GetValue().ToDouble(&h))) {
        wxMessageBox(wxT("Os valores dos campos devem ser numericos!"), wxT("Aviso"));
        return;
    }
    //chamamos o metodo Area() para calcular o volume do cilindro
    a = Volume(r, h);
    //setamos o textbox volume com o valor calculado
    txtvolume->SetValue(wxString() << a);
}

void Cilindro::Limpa(wxCommandEvent &evt)
{
    //limpamos os campos com o método Clear()
    txtraio->Clear();
    txtaltura->Clear();
    txtvolume->Clear();
}

#include "cilindro.h"
//main.h


class MinhaApp : public wxApp
{
    public:
        virtual bool OnInit(); 
   
};

#include "main.h"
//main.cpp

IMPLEMENT_APP(MinhaApp)

bool MinhaApp::OnInit()
{
    Cilindro *cilindro = new Cilindro(wxT("Calculo da area do cilindro"));
    cilindro->Show(true);
    return true;
}





sexta-feira, 15 de abril de 2011

A classe wxString

Introdução
     wxWidgets nos fornece uma classe que representa uma cadeia de caracteres, a classe wxString. Esta classe é usada em várias outras classes da wxWidgets, como para setar ou retornar um valor em um wxTextCtrl, o label de um wxButton, etc. A classe wxString implementa cerca de 90% dos métodos da classe string presente na STL (Standard Template Library) de C++. Um bom conhecimento sobre esta classe é imprescindível para desenvolver programas utilizando wxWidgets.

Alguns Métodos
    Len() ou Length() - Ambos retornam o tamanho da string
    IsEmpty() - Retorna true se a string está vazia
    c_str() - Retorna um ponteiro para uma string ANSI, muito útil para passar uma wxString como argumento para uma função que espera por uma string no estilo C (const char*).
    Exemplo: func_c(minhaString.c_str())
    MakeUpper() - Converte todos os caracteres para maiúsculo e retorna o resultado.
    MakeLower() - Converte todos os caracteres para minúsculo e retorna o resultado.
    FromAscii(str_ascii) - Converte uma string normal para wxString.
    Exemplo: wxString minhaString = wxString::FromAscii(str_ascii)
    Convertendo um número para wxString
    wxString minhaString = wxString::Format(wxT("%d"), 10); //inteiro
    wxString minhaString = wxString::Format(wxT("%f"), 10.5); //float
    wxString s; s.Printf(wxT("%d", 10); //inteiro
    wxString s; s.Printf(wxT("%f", 10.5); //float
    wxString minhaString = wxString() << 10;
    wxString minhaString = wxString() << 10.5;
    Convertendo wxString para números
    long numero;
    wxString num(wxT("1000000"));
    num.ToLong(&numero);   //retorna falso em caso de erro
    double numero_d;
    wxString num_d(wxT("1234.89"));
    num_d.ToDouble(&numero_d); // retorna falso em caso de erro
    Estes são apenas alguns dos métodos da classe wxString, para uma referência completa visitem: http://docs.wxwidgets.org/2.6/wx_wxstring.html
  

quinta-feira, 14 de abril de 2011

Um pouco mais sobre eventos

Uma "quase" calculadora
      O exemplo do post anterior retirado do site zetcode onde é montado o "esqueleto" de uma calculadora não faz nada além de nos dar uma idéia de como trabalhar com wxGridSizer. Que tal implementarmos um método para detectar qual botão foi clicado e o que fazer dependendo de qual botão foi clicado? Isso mesmo!! UM método só para todos os botões!
    Para isso vamos atribuir um ID diferente para cada botão e utilizar o método Connect para conectar todos eles ao nosso método OnClick(). O nosso método OnClick() receberá como parâmetro uma referência para um objeto do tipo wxCommandEvent que descende de wxEvent. Este objeto tem um método chamado GetId() que retorna o ID do objeto que originou o evento e é através deste ID que identificamos qual botão foi clicado.
    Abaixo seguem as modificações no código do esqueleto da calculadora que vimos no post anterior.
    No arquivo gridsizer.h vamos declarar um objeto do tipo wxString (veremos mais sobre esta classe em outro post) na declaração da classe, abaixo de wxTextCtrl *display;:
    wxString textodisplay;
    Agora no construtor da classe GridSizer:

    //inicializamos o texto do display como vazio
    textodisplay = "";
   //atribuímos um ID diferente para cada botão (de 1 a 20), tomando cuidado para que nenhum outro widget 
  //tenha o mesmo ID
  gs->Add(new wxButton(this, 1, wxT("Cls")), 0, wxEXPAND);
  gs->Add(new wxButton(this, 2, wxT("Bck")), 0, wxEXPAND);
  gs->Add(new wxStaticText(this, 3, wxT("")), 0, wxEXPAND);
  gs->Add(new wxButton(this, 4, wxT("Close")), 0, wxEXPAND);
  gs->Add(new wxButton(this, 5, wxT("7")), 0, wxEXPAND);
  gs->Add(new wxButton(this, 6, wxT("8")), 0, wxEXPAND);
  gs->Add(new wxButton(this, 7, wxT("9")), 0, wxEXPAND);
  gs->Add(new wxButton(this, 8, wxT("/")), 0, wxEXPAND);
  gs->Add(new wxButton(this, 9, wxT("4")), 0, wxEXPAND);
  gs->Add(new wxButton(this, 10, wxT("5")), 0, wxEXPAND);
  gs->Add(new wxButton(this, 11, wxT("6")), 0, wxEXPAND);
  gs->Add(new wxButton(this, 12, wxT("*")), 0, wxEXPAND);
  gs->Add(new wxButton(this, 13, wxT("1")), 0, wxEXPAND);
  gs->Add(new wxButton(this, 14, wxT("2")), 0, wxEXPAND);
  gs->Add(new wxButton(this, 15, wxT("3")), 0, wxEXPAND);
  gs->Add(new wxButton(this, 16, wxT("-")), 0, wxEXPAND);
  gs->Add(new wxButton(this, 17, wxT("0")), 0, wxEXPAND);
  gs->Add(new wxButton(this, 18, wxT(".")), 0, wxEXPAND);
  gs->Add(new wxButton(this, 19, wxT("=")), 0, wxEXPAND);
  gs->Add(new wxButton(this, 20, wxT("+")), 0, wxEXPAND);

  //No loop for nós conectamos o click nos botões ao método OnClick() (poderia ter qualquer outro nome)
  for(int i = 1; i < 20; i++) {
    Connect(i, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(GridSizer::OnClick));     
  }

 Agora, eis o  método que vai tratar o click nos botões da "calculadora":

void GridSizer::OnClick(wxCommandEvent &evt) {
   //criamos um ponteiro para um objeto da classe wxButton e fazemos um cast para que
   //ele receba o objeto que gerou o evento
   wxButton *btn = (wxButton*) evt.GetEventObject();
   //o método GetId() da classe wxCommandEvent retorna o id do objeto que gerou o evento
   if(evt.GetId() != 2)
   //se o botão clicado for diferente de Bck atribuímos o label do mesmo ao objeto wxString textodisplay
      textodisplay += btn->GetLabelText();
   else
      //senão removemos o último caracter da string
      textodisplay.RemoveLast();
     
   if(evt.GetId() == 1)
      //se clicarmos no botão Cls atribuimos uma string vazia ao texto do display
      textodisplay = "";
  
   if(evt.GetId() == 4)
      //fechamos a aplicação
      this->Close(); 
   //setamos o valor do display      
   display->SetValue(textodisplay);  
}

quarta-feira, 13 de abril de 2011

Gerenciamento de Layout com wxWidgets

Introdução
      Uma aplicação em wxWidgets pode consistir de vários widgets (controles), como botões, caixas de texto, etc dentro de um container (um painel, por exemplo). Estes controles podem ser posicionados dentro do container de duas formas possíveis: com posicionamento absoluto ou através dos sizers.

Posicionamento Absoluto
     O programador especifica o tamanho e posicionamento de cada controle em pixels, desta forma o tamanho dos controles não se altera, ou seja, se eu redimensionar a janela os controles permanecerão na mesma posição e com o mesmo tamanho, além disso a aparência da aplicação pode ficar diferente dependendo da plataforma em que for compilada e executada.

Utilizando Sizers

    Com sizers nós podemos resolver os problemas de posicionamento absoluto. Em wxWidgets nós temos os seguintes sizers: wxBoxSizer, wxStaticBoxSizer, wxGridSizer, wxFlexGridSizer, wxGridBagSizer.

  • wxBoxSizers 
      Com um wxBoxSizer nós podemos colocar vários widgets em linha ou em coluna e podemos colocar um sizer dentro de outro criando assim layouts bem complexos.
      wxBoxSizer(int orientacao)
      wxSizerItem* Add(wxWindow* janela, int proporcao = 0, int flag = 0, int borda = 0)
      A orientação pode ser wxVERTICAL ou wxHORIZONTAL. Para adicionarmos os widgets dentro do wxBoxSizer nós utilizamos o método Add().
     O parâmetro proporcao define a taxa com que os widgets mudam na orientação definida, o parâmetro flag define o comportamento dos widgets dentro de um wxBoxSizer e o parâmetro borda adiciona bordas ao redor dos widgets. Para definirmos as bordas precisamos declarar os lados em que as bordas serão aplicadas no parâmetro flag. Os flags usados podem ser: wxLEFT, wxRIGHT, wxBOTTOM, wxTOP e wxALL. Nós podemos combiná-los através do operador '|' (ou).

  
Abaixo segue em exemplo retirado de:  http://zetcode.com/tutorials/wxwidgetstutorial/layoutmanagement/
    
#include <wx/wx.h>
//border.h

class Border : public wxFrame
{
public:
  Border(const wxString& title);

};
#include "border.h"
//border.cpp
Border::Border(const wxString& title)
       : wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(250, 200))
{

  wxColour col1, col2;
  col1.Set(wxT("#4f5049"));
  col2.Set(wxT("#ededed"));

  wxPanel *panel = new wxPanel(this, -1);
  panel->SetBackgroundColour(col1);
  wxBoxSizer *vbox = new wxBoxSizer(wxVERTICAL);

  wxPanel *midPan = new wxPanel(panel, wxID_ANY);
  midPan->SetBackgroundColour(col2);
  //colocamos uma borda de 20 pixels ao redor do painel midPan
//wxALL aplica a borda ao redor de todo o painel
  //wxEXPAND faz com que a borda ocupe todo o espaço disponível
  vbox->Add(midPan, 1, wxEXPAND | wxALL, 20);
  panel->SetSizer(vbox);

  Centre();
}
#include <wx/wx.h>
//main.h
class MyApp : public wxApp
{
  public:
    virtual bool OnInit();
};
#include "main.h"
#include "border.h"
//main.cpp
IMPLEMENT_APP(MyApp)

bool MyApp::OnInit()
{

    Border *border = new Border(wxT("Border"));
    border->Show(true);

    return true;
}

  • wxGridSizer
wxGridSizer estabelece os widgets em uma tabela de duas dimensões onde cada célula da tabela tem o mesmo tamanho. 
wxGridSizer(int linhas, int colunas, int vgap, int hgap)
No construtor nós especificamos o número de linhas e colunas de nossa tabela e o espaço horizontal e vertical entre cada célula.
No próximo exemplo nós criaremos o esqueleto de uma cálculadora. 
Este exemplo também foi retirado de: http://zetcode.com/tutorials/wxwidgetstutorial/layoutmanagement/
 
#include <wx/wx.h>
//gridsizer.h

class GridSizer : public wxFrame
{
public:
  GridSizer(const wxString& title);

  wxMenuBar *menubar;
  wxMenu *file;

  wxBoxSizer *sizer;
  wxGridSizer *gs;
  wxTextCtrl *display;

};
#include "gridsizer.h" //gridsizer.cpp
GridSizer::GridSizer(const wxString& title)
       : wxFrame(NULL, -1, title, wxPoint(-1, -1), wxSize(270, 220))
{
  menubar = new wxMenuBar;
  file = new wxMenu;

  SetMenuBar(menubar);

  sizer = new wxBoxSizer(wxVERTICAL);
 
  display = new wxTextCtrl(this, -1, wxT(""), wxPoint(-1, -1),
     wxSize(-1, -1), wxTE_RIGHT);

  sizer->Add(display, 0, wxEXPAND | wxTOP | wxBOTTOM, 4);
  gs = new wxGridSizer(4, 4, 3, 3);

  gs->Add(new wxButton(this, -1, wxT("Cls")), 0, wxEXPAND);
  gs->Add(new wxButton(this, -1, wxT("Bck")), 0, wxEXPAND);
  gs->Add(new wxStaticText(this, -1, wxT("")), 0, wxEXPAND);
  gs->Add(new wxButton(this, -1, wxT("Close")), 0, wxEXPAND);
  gs->Add(new wxButton(this, -1, wxT("7")), 0, wxEXPAND); 
  gs->Add(new wxButton(this, -1, wxT("8")), 0, wxEXPAND);
  gs->Add(new wxButton(this, -1, wxT("9")), 0, wxEXPAND);
  gs->Add(new wxButton(this, -1, wxT("/")), 0, wxEXPAND);
  gs->Add(new wxButton(this, -1, wxT("4")), 0, wxEXPAND);
  gs->Add(new wxButton(this, -1, wxT("5")), 0, wxEXPAND);
  gs->Add(new wxButton(this, -1, wxT("6")), 0, wxEXPAND);
  gs->Add(new wxButton(this, -1, wxT("*")), 0, wxEXPAND);
  gs->Add(new wxButton(this, -1, wxT("1")), 0, wxEXPAND);
  gs->Add(new wxButton(this, -1, wxT("2")), 0, wxEXPAND);
  gs->Add(new wxButton(this, -1, wxT("3")), 0, wxEXPAND);
  gs->Add(new wxButton(this, -1, wxT("-")), 0, wxEXPAND);
  gs->Add(new wxButton(this, -1, wxT("0")), 0, wxEXPAND);
  gs->Add(new wxButton(this, -1, wxT(".")), 0, wxEXPAND);
  gs->Add(new wxButton(this, -1, wxT("=")), 0, wxEXPAND);
  gs->Add(new wxButton(this, -1, wxT("+")), 0, wxEXPAND);

  sizer->Add(gs, 1, wxEXPAND);
  SetSizer(sizer);
  SetMinSize(wxSize(270, 220));

  Centre();
}
#include <wx/wx.h>
//main.h
class MyApp : public wxApp
{
  public:
    virtual bool OnInit();
};
#include "main.h"
#include "gridsizer.h"
//main.cpp
IMPLEMENT_APP(MyApp)

bool MyApp::OnInit()
{

    GridSizer *gs = new GridSizer(wxT("GridSizer"));
    gs->Show(true);

    return true;
}
gs->Add(new wxButton(this, -1, wxT("Cls")), 0, wxEXPAND);
Nós chamamos o método Add() várias vezes. Os widgets são colocados dentro da tabela na ordem em que eles são adicionados.
Após o preenchimento de todas as colunas da primeira linha passasse para a segunda linha e assim por diante.
  • wxFlexGridSizer
   Este sizer é muito parecido com o wxGridSizer, todas as células tem a mesma largura em uma coluna e a mesma altura em uma linha, 
mas suas linhas e colunas não precisam ser da mesma largura ou altura.
 wxFlexGridSizer(int linhas, int colunas, int vgap, int hgap)
    No construtor nós especificamos o número de linhas e colunas de nossa tabela e o espaço horizontal e vertical entre cada célula. 
 
 

segunda-feira, 11 de abril de 2011

Eventos

Introdução
       Manipulação de eventos é uma parte indispensável em qualquer sistema que utilize  interface gráfica. Eventos normalmente vêm de ações executadas pelo usuário sobre o sistema, como o clique em um botão, o pressionamento de uma tecla, etc, mas também podem ser gerados de outras formas, como pelo manipulador de janelas, o timer, etc.
      Tradicionalmente wxWidgets trabalha com eventos através da manipulação de uma tabela de eventos estática, diferentemente do modo como foi apresentado no post anterior com o método Connect, que é uma forma mais moderna e prática de tratar eventos com wxWidgets.

Exemplo
     Utilizando tabela de eventos o exemplo do post anterior ficaria assim:

#include <wx/wx.h>
//frame.h
class MeuFrame : public wxFrame {
        private:
            wxPanel *painel;
            wxButton *btnsair;
            wxStaticText *label;
            DECLARE_EVENT_TABLE() // Declaramos a tabela de eventos com a macro  
                                                                //DECLARE_EVENT_TABLE()
        public:
            MeuFrame(const wxString &titulo);
            void Sair(wxCommandEvent &evt);
};

#include "frame.h"
//frame.cpp
MeuFrame::MeuFrame(const wxString& titulo):
          wxFrame(NULL, wxID_ANY, titulo, wxDefaultPosition, wxSize(500, 400))
{
     painel = new wxPanel(this, wxID_ANY);
     label = new wxStaticText(painel, -1, wxT("Meu Primeiro Programa com wxWidgets"), wxPoint(100, 200));
     btnsair = new wxButton(painel, wxID_EXIT, wxT("Sair"), wxPoint(200, 250), wxSize(50, 30));
     //  Connect(wxID_EXIT, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MeuFrame::Sair));
     btnsair->SetFocus();
     Centre();
}

void MeuFrame::Sair(wxCommandEvent &evt) {
    //fecha a aplicação
    Close(true);
}

//Esta macro inicia a tabela de eventos
BEGIN_EVENT_TABLE(MeuFrame, wxFrame)
    /*Aqui através da macro EVT_BUTTON nós atribuímos o método sair ao botão com id =
       wxID_EXIT, isto é, o método sair será chamado quando o botão for clicado, onde 
      EVT_BUTTON indica o tipo de evento. */
    EVT_BUTTON(wxID_EXIT, MeuFrame::Sair)

//Esta macro finaliza a tabela de eventos
END_EVENT_TABLE()