segunda-feira, 12 de setembro de 2011

Tutorial wxWidgets e sqlite3 parte 10 - Selecionando valores e colocando em um wxGrid


Hoje vamos ver como exibir os valores de uma consulta SQL, em um wxGrid.

A explicação como de costume está nos comentários do fonte, basicamente, o que faremos, é selecionar todos os valores que estão na tabela contatos com um comando SQL que é:

SELECT ID, NOME, IDADE FROM contatos;
ou
SELECT * FROM CONTATOS;

Passar esta consulta como parâmetro para a função ExecuteQuery() da classe wxSQLite3Database().

Esta função irá retornar um wxSQLite3ResultSet(), que armazenará todos os valores contidos na consulta.

Vamos caminhar por todas as linhas do resultset e passaremos os valores para o wxGrid...

Para isto eu criei uma função chamada SelectInDB, que recebe como parâmetro um wxGrid(), nos arquivos sqlf.

Então abra o projeto e adicione os seguintes códigos:

em sqlf.h declare a função SelectInDB

sqlf.h
//Aqui a função que vai fazer um select no banco de dados e preencher o grid...
//Eu passo um ponteiro como parametro, para o grid que será preenchido...
void SelectInDB(wxGrid* grid);



em sqlf.cpp implemente a mesma função

sqlf.cpp
void SelectInDB(wxGrid* grid)
{
    //Nesta linha eu limpo o Grid, veja que, se o grid já estiver vazio
    //eu não faço nada, isto evita alguns erros...
    if(grid->GetNumberRows() > 0)
    {
        //Aqui uso a função DeleteRows() de wxGrid, ela vai deletar
        //as linhas do grid desde a posição passada no parametro 1 (no caso 0)
        //mais a quantidade de linhas passada no parametro 2...
        //no caso o número de linhas presentes no grid atual...
        //para sabermos este número usamos a função GetNumberRows() ou GetRows()...
        grid->DeleteRows(0, grid->GetNumberRows());
    }

    //Agora vamos escrever o código de seleção dos dados...
    wxString SQL = wxT("SELECT ID, NOME, IDADE FROM contatos;");
    //Veja que o banco de dados vai me retornar todas as colunas da tabela,
    //No caso, 3 colunas, e como não colocamos nenhuma condição no comando SQL,
    //ele vai retornar todas as linhas da tabela...

    //Criamos a conexão com o banco de dados...
    wxSQLite3Database *db = OpenDB(GetExecPath() + wxT("/agenda.db3"));

    //Agora vamos usar uma classe que não tinhamos usado ainda
    //que é wxSQLite3ResultSet...
    //Esta classe vai armazenar todos os valores que pedirmos em um comando SELECT
    //e é com ela que pegaremos estes dados e passaremos para o grid...

    wxSQLite3ResultSet result = db->ExecuteQuery(SQL);

    //Veja que aqui usamos outra função de wxSQLite3Database que é ExecuteQuery
    //Esta função, diferente de ExecuteUpdate, recebe um comando SQL como parametro,
    //e retorna um wxSQLite3ResultSet como os valores da consulta...

    //Agora vamos pegar todos estes valores e colocaremos no wxGrid...
    //Enquanto existirem linhas a serem lidas...
    while(result.NextRow())
    {
        //Adiciono uma linha ao grid...
        grid->AppendRows(1);

        //Aqui é a parte que temos que fixar bem...

        //Vou pegar o valor da primeira coluna da linha atual do resultset e passar para
        //a primeira coluna da linha atual do grid...
        grid->SetCellValue(grid->GetNumberRows() - 1, 0, result.GetAsString(wxT("ID")));

        //Vejam que mesmo eu tendo o ID declarado como INTEGER eu posso chamá-lo como String,
        //Isto ocorre porque quem controla o banco de dados é nossa aplicação...
        //Eu posso pegar este valor usando o indice do mesmo no resultset assim:
        //result.GetAsString(0);
        //Pois ele é a primeira coluna da seleção e em sqlite3 o indice sempre vai começar em 0

        //Fazemos a mesma coisa para pegar o nome...
        grid->SetCellValue(grid->GetNumberRows() - 1, 1, result.GetAsString(wxT("NOME")));

        //E a idade...
        grid->SetCellValue(grid->GetNumberRows() - 1, 2, result.GetAsString(wxT("IDADE")));
    }

    //Neste ponto fechamos o resultset...
    result.Finalize();

    //Fechamos o banco de dados...
    CloseDB(db);
}

em framep.cpp vamos chamar esta função no final da implementação do frame principal e no final da função OnClickSalvar

framep.cpp
//Vamos carregar os dados no grid...
SelectInDB(lista_contatos);

O tutorial de hoje é bem simples, tendo como maior importância vocês entenderem como os dados são manipulados pelo resultset, se fixarem bem este esquema, vocês verão que existem maneiras mais simples para implementar funções que agilizam o processo de seleção dos dados e preenchimento dos mesmos em um grid, relatórios e outros controles wxWdigets.

Os arquivos modificados ficaram assim:

sqlf.h
//ARQUIVO DO BLOG - http://wxnewbie.blogspot.com
//TUTORIAL wxWidgets + sqlite3 usando wxSqlite3

#ifndef SQLF_H_INCLUDED
#define SQLF_H_INCLUDED

#include <wx/wx.h>
#include <wx/wxsqlite3.h>
#include <wx/stdpaths.h>
#include <wx/grid.h>

wxString GetExecPath();
//Nesta primeira função, iremos abrir um banco de dados indicado pelo
//parâmetro caminho e retornar um ponteiro para wxSQLite3Database...
//Se o banco de dados não existir, será criado um...
wxSQLite3Database *OpenDB(wxString caminho);
//Aqui iremos apenas fechar o mesmo...
//Isto é necessário, pois liberaremos o arquivo para novos acessos...
void CloseDB(wxSQLite3Database* db);

//Esta função será usada para criar o nosso banco de dados
//e também para criar a tabela contatos.
//Ela será executada apenas uma vez...
void CreateDBAgenda();

//Aqui escrevo uma função que vai receber o nome e a idade como parametros...
void InsertInDB(wxString nome, wxString idade);

//Aqui a função que vai fazer um select no banco de dados e preencher o grid...
//Eu passo um ponteiro como parametro, para o grid que será preenchido...
void SelectInDB(wxGrid* grid);

#endif // SQLF_H_INCLUDED


sqlf.cpp
//ARQUIVO DO BLOG - http://wxnewbie.blogspot.com
//TUTORIAL wxWidgets + sqlite3 usando wxSqlite3

#include "sqlf.h"

//Função para pegar o diretório da aplicação, já explicada no blog...
wxString GetExecPath()
{
 wxString retorno;
 retorno = wxStandardPaths::Get().GetExecutablePath();
 retorno = wxPathOnly(retorno);
 return retorno;
}

//Vamos implementar nossa função OpenDB...
wxSQLite3Database *OpenDB(wxString caminho)
{
    //Aqui eu crio um ponteiro para um wxSQLite3Database chamado db...
    //É ele que nós retornaremos na função...
    wxSQLite3Database *db = new wxSQLite3Database();

    //Aqui uso a função Open, passando como parâmetro o caminho para o banco de dados...
    //O interessante aqui é vermos que se existir um banco de dados neste lugar a função
    //irá abrir o mesmo, se não existir o banco de dados será criado...
    db->Open(caminho);

    //Vocês poderão ter um problema ao executar esta função, dizendo que Open possui mais de
    //um parâmetro, se isto acontecer, basta usá-la desta maneira:

    //db->Open(caminho, wxEmptyString);

    //O segundo parametro seria a senha para o banco de dados,
    //se estivessemos usando criptografia no mesmo...

    //retornamos o db...
    return db;
}

//Aqui vamos fechar a "conexão" com o banco de dados...
void CloseDB(wxSQLite3Database* db)
{
    //Vemos se db é verdadeiro...
    assert(db != NULL);
    //Fechamos db com o comando Close();
    db->Close();

    //Deletamos db da memória...
    delete db;
}

void CreateDBAgenda()
{
    //Primeiro, vamos pegar o diretório da aplicação mais o nome do banco de dados...
    wxString conf_path = GetExecPath() + wxT("/agenda.db3");

    //Agora vamos verificar se o arquivo já existe...
    //Se existir, paro a execução da função...
    if(wxFileExists(conf_path))
    {
        return;
    }

    //Se ele não existir crio um wxSQLite3Database com este caminho...
    wxSQLite3Database *db = OpenDB(conf_path);

    //Ok, agora criamos um sql com o comando de criação da nossa tabela
    //CREATE TABLE contatos(
    //ID INTEGER PRIMARY KEY,
    //NOME VARCHAR(50),
    //IDADE INTEGER
    //);

    //Vamos adicionar isto em uma wxString

    wxString SQL = wxT("CREATE TABLE contatos(\n");
    SQL += wxT("ID INTEGER PRIMARY KEY,\n");
    SQL += wxT("NOME VARCHAR(50),\n");
    SQL += wxT("IDADE INTEGER\n");
    SQL += wxT(");");

    //Agora vamos usar ExecuteUpdate para executar este SQL...
    //Usamos ExecuteUpdate para criarmos tabelas, views, triggers...
    //Inserir registros no banco de dados...
    //Editar registros no banco de dados...
    //Excluir registros no banco de dados...
    //Não use ExecuteUpdate para seleções...
    db->ExecuteUpdate(SQL);

    //Fechamos a conexão com o banco de dados...
    CloseDB(db);
}

//Vamos Implementá-la
void InsertInDB(wxString nome, wxString idade)
{
    //Primeiro vejo se o nome ou idade estão em branco
    if(nome.IsEmpty() || idade.IsEmpty())
    {
        //Aviso ao usuário que algum dado está em branco:
        wxMessageBox(wxT("Nenhum campo pode estar em branco!"));
        //Cancelo a função...
        return;
    }

    //Agora preciso ver se a idade é um número...
    //Crio um long int
    long int confere_idade;

    //Tento converter a string idade em um número...
    if(!idade.ToLong(&confere_idade))
    {
        //Se não conseguir, significa que ele não é um inteiro...
        //Aviso o usuário...
        wxMessageBox(wxT("O valor da idade (") + idade + wxT(") não é um número"));
        //Cancelo a função...
        return;
    }

    //Se chegamos até aqui, é porque tudo está correto...
    //Vamos abrir o banco de dados:
    wxSQLite3Database *db = OpenDB(GetExecPath() + wxT("/agenda.db3"));

    //Agora vamos montar o sql para inserir os dados...
    wxString SQL = wxT("INSERT INTO contatos(ID, NOME, IDADE) VALUES(NULL, '");
    SQL += nome + wxT("', ");
    SQL += idade + wxT(");");

    //Reparem que adiciono os apóstrofos apenas nos campos VARCHAR...
    //Tenham muito cuidado quando montarem comandos SQL, para não adicionarem
    //valores incorretos, como virgulas após o ultimo campo
    //esquecerem-se de fechar parenteses, etc...

    //uma maneira simples é fazer o seguinte...
    //Escreva o comando todo SQL...

    //wxString SQL = wxT("INSERT INTO contatos(ID, NOME, IDADE) VALUES (NULL, 'NOME', IDADE);");

    //Agora adicione as variáveis no lugar dos valores com os sinais de concatenação:

    //wxString SQL = wxT("INSERT INTO contatos(ID, NOME, IDADE) VALUES (NULL, ' + nome + ', + idade + );");

    //Antes de cada + à esquerda da variável adicione ") e depois de cada + à direita da variável adicione wxT("

    //wxString SQL = wxT("INSERT INTO contatos(ID, NOME, IDADE) VALUES (NULL, '") + nome + wxT("',") + idade + (");");

    //Agora executo a função ExecuteUpdate, que vimos no tutorial anterior, passando como
    //parâmetro a wxString SQL...
    db->ExecuteUpdate(SQL);

    //Fecho db...
    CloseDB(db);
}

void SelectInDB(wxGrid* grid)
{
    //Nesta linha eu limpo o Grid, veja que, se o grid já estiver vazio
    //eu não faço nada, isto evita alguns erros...
    if(grid->GetNumberRows() > 0)
    {
        //Aqui uso a função DeleteRows() de wxGrid, ela vai deletar
        //as linhas do grid desde a posição passada no parametro 1 (no caso 0)
        //mais a quantidade de linhas passada no parametro 2...
        //no caso o número de linhas presentes no grid atual...
        //para sabermos este número usamos a função GetNumberRows() ou GetRows()...
        grid->DeleteRows(0, grid->GetNumberRows());
    }

    //Agora vamos escrever o código de seleção dos dados...
    wxString SQL = wxT("SELECT ID, NOME, IDADE FROM contatos;");
    //Veja que o banco de dados vai me retornar todas as colunas da tabela,
    //No caso, 3 colunas, e como não colocamos nenhuma condição no comando SQL,
    //ele vai retornar todas as linhas da tabela...

    //Criamos a conexão com o banco de dados...
    wxSQLite3Database *db = OpenDB(GetExecPath() + wxT("/agenda.db3"));

    //Agora vamos usar uma classe que não tinhamos usado ainda
    //que é wxSQLite3ResultSet...
    //Esta classe vai armazenar todos os valores que pedirmos em um comando SELECT
    //e é com ela que pegaremos estes dados e passaremos para o grid...

    wxSQLite3ResultSet result = db->ExecuteQuery(SQL);

    //Veja que aqui usamos outra função de wxSQLite3Database que é ExecuteQuery
    //Esta função, diferente de ExecuteUpdate, recebe um comando SQL como parametro,
    //e retorna um wxSQLite3ResultSet como os valores da consulta...

    //Agora vamos pegar todos estes valores e colocaremos no wxGrid...
    //Enquanto existirem linhas a serem lidas...
    while(result.NextRow())
    {
        //Adiciono uma linha ao grid...
        grid->AppendRows(1);

        //Aqui é a parte que temos que fixar bem...

        //Vou pegar o valor da primeira coluna da linha atual do resultset e passar para
        //a primeira coluna da linha atual do grid...
        grid->SetCellValue(grid->GetNumberRows() - 1, 0, result.GetAsString(wxT("ID")));

        //Vejam que mesmo eu tendo o ID declarado como INTEGER eu posso chamá-lo como String,
        //Isto ocorre porque quem controla o banco de dados é nossa aplicação...
        //Eu posso pegar este valor usando o indice do mesmo no resultset assim:
        //result.GetAsString(0);
        //Pois ele é a primeira coluna da seleção e em sqlite3 o indice sempre vai começar em 0

        //Fazemos a mesma coisa para pegar o nome...
        grid->SetCellValue(grid->GetNumberRows() - 1, 1, result.GetAsString(wxT("NOME")));

        //E a idade...
        grid->SetCellValue(grid->GetNumberRows() - 1, 2, result.GetAsString(wxT("IDADE")));
    }

    //Neste ponto fechamos o resultset...
    result.Finalize();

    //Fechamos o banco de dados...
    CloseDB(db);
}

framep.cpp
//ARQUIVO DO BLOG - http://wxnewbie.blogspot.com
//TUTORIAL wxWidgets + sqlite3 usando wxSqlite3

#include "framep.h"
#include "sqlf.h"

wxFramePrincipal::wxFramePrincipal():wxFrame(NULL, wxID_ANY, wxT("Teste Tutorial Sqlite3"))
{
    //Altero a cor do background do Frame
    this->SetBackgroundColour(*wxWHITE);

    //Meu BoxSizer Principal...
    wxBoxSizer *boxp = new wxBoxSizer(wxVERTICAL);

    //Inicio a construção do grid...
    lista_contatos = new wxGrid(this, ID_GRID);

    //Começo com 0 linhas e 3 colunas, lembre-se que o número de colunas deve ser igual ao numero de campos
    //Da view que iremos carregar ou do comando select que iremos carregar...
    lista_contatos->CreateGrid(0, 3);

    //Aqui altero o valor dos rótulos de cada coluna...
    //Nunca se esqueça, o vetor começa em 0, tanto para colunas como para linhas...
    lista_contatos->SetColLabelValue(0, wxT("ID"));
    lista_contatos->SetColLabelValue(1, wxT("Nome"));
    lista_contatos->SetColLabelValue(2, wxT("Idade"));

    //Altero a largura padrão do rótulo da linha para 25px...
    //Altero a altura padrão do rótulo da coluna para 25px...
    lista_contatos->SetRowLabelSize(25);
    lista_contatos->SetColLabelSize(25);

    //Desabilito a edição dos valores no grid, a alteração de valores será feita
    //pelos wxTextCtrl's
    lista_contatos->EnableEditing(false);

    //Adiciono o grid no BoxSizer principal...
    boxp->Add(lista_contatos, 1, wxEXPAND, 0);

    //Aqui eu adiciono um separador...
    boxp->Add(0, 0, 0, wxALL, 10);

    //Aqui crio um BoxSizer para arranjar os campos de preenchimento de valores...
    wxBoxSizer *box_texts = new wxBoxSizer(wxVERTICAL);

    //o rótulo para o campo nome...
    label_nome = new wxStaticText(this, wxID_ANY, wxT("Nome"));
    //adiciono o rótulo no box dos valores de preenchimento...
    box_texts->Add(label_nome, 0, wxALL, 2);

    //Faço o mesmo que fiz anteriormente para todos os outros campos...
    nome = new wxTextCtrl(this, wxID_ANY, wxEmptyString ,wxDefaultPosition, wxSize(200, -1));
    box_texts->Add(nome, 0, wxALL, 5);

    label_idade = new wxStaticText(this, wxID_ANY, wxT("Idade"));
    box_texts->Add(label_idade, 0, wxALL, 2);

    idade = new wxTextCtrl(this, wxID_ANY, wxEmptyString ,wxDefaultPosition, wxSize(120, -1));
    box_texts->Add(idade, 0, wxALL, 5);

    //Aqui adiciono o box dos campos de preenchimento no box principal...
    boxp->Add(box_texts, 0, wxLEFT, 40);

    //Agora um box para arranjar os botões...
    wxBoxSizer *box_btns = new wxBoxSizer(wxHORIZONTAL);

    //Um separador...
    box_btns->Add(0, 0, 0, wxLEFT, 40);

    //Botão salvar...
    salvar = new wxButton(this, ID_SALVAR, wxT("Salvar"));
    //Adiciono ao box dos botões...
    box_btns->Add(salvar, 0, wxALL, 5);

    limpar = new wxButton(this, ID_LIMPAR, wxT("Limpar Campos"));
    box_btns->Add(limpar, 0, wxALL, 5);

    deletar = new wxButton(this, ID_DELETAR, wxT("Deletar"));
    box_btns->Add(deletar, 0, wxALL, 5);

    //Adiciono o box dos botões ao box principal...
    boxp->Add(box_btns, 1, wxEXPAND, 0);

    //"Digo" ao meu frame que o boxp é o box principal...
    this->SetSizer(boxp);

    //Aqui apenas faço com que tudo seja arranjado de maneira correta...
    this->Layout();

    //Seto o ID como -1 para saber que inicio em modo de inserção...
    ID = -1;

    //Vamos carregar os dados no grid...
    SelectInDB(lista_contatos);
}

//Aqui minha tabela de eventos declarada...
BEGIN_EVENT_TABLE(wxFramePrincipal, wxFrame)
//Veja que digo para cada evento, que o controle que estiver com
//determinado id, é o que será responsável por executá-lo
//No exemplo de salvar:
//Eu digo que é um evento do tipo EVT_BUTTON, ou seja, um evento executado por um botão...
//O botão responsável pelo evento é o que tiver o identificador: ID_SALVAR
//e o evento que ele vai executar é o OnClickSalvar...

//É muito importante o entendimento desta parte para não acontecer nada de errado...
EVT_BUTTON(ID_SALVAR, wxFramePrincipal::OnClickSalvar)
EVT_BUTTON(ID_LIMPAR, wxFramePrincipal::OnClickLimpar)
EVT_BUTTON(ID_DELETAR, wxFramePrincipal::OnClickDeletar)

//Quando você for criar um evento para grid's repare que alguns levam um CMD no nome e outros não...
//Se você estiver criando uma classe derivada de wxGrid, vc não usa o comando que contém o CMD
//Veja que este comando sem CMD como é o caso de EVT_GRID_CELL_LEFT_DCLICK (comando que é executado
//quando dou 2 cliques em uma célula) não recebe ID nenhum como parâmetro...
//agora se eu for executar o mesmo comando usando um wxGrid como controle no meu frame ou dialogo
//eu uso o comando com CMD como é o caso do evento abaixo...
EVT_GRID_CMD_CELL_LEFT_DCLICK(ID_GRID, wxFramePrincipal::OnDoubleClickCell)
END_EVENT_TABLE()

//Aqui eu inicio a implementação do evento para salvar ou editar um registro...
void wxFramePrincipal::OnClickSalvar(wxCommandEvent& event)
{
    //Vamos, primeiro, verificar qual operação será feita
    //Inserção ou Edição
    if(ID == -1)
    {
        //Se a variável ID for = -1, é porque estamos em inserção...
        //Vamos pegar os valores do campo nome...
        wxString get_nome = nome->GetValue();
        //...agora pegamos a idade...
        wxString get_idade = idade->GetValue();

        //E passamos as duas como parâmetros para a função InsertInDB()...
        InsertInDB(get_nome, get_idade);
    }
    else
    {
    }

    //Após editarmos um valor, recarregamos os valores no grid...
    SelectInDB(lista_contatos);
}

//Aqui o evento que vai limpar os campos...
void wxFramePrincipal::OnClickLimpar(wxCommandEvent& event)
{
    //...Os comandos que usaremos aqui, será escrito depois...
}

//Aqui o evento quando eu clicar no botão deletar...
void wxFramePrincipal::OnClickDeletar(wxCommandEvent& event)
{
    //...Os comandos que usaremos aqui, será escrito depois...
}

//Aqui o evento que será exxecutado
//quando eu der 2 cliques em uma célula...
void wxFramePrincipal::OnDoubleClickCell(wxGridEvent& event)
{
    //...Os comandos que usaremos aqui, será escrito depois...
}

//Esta função eu vou usar para limpar os campos
//como eu precisarei fazer isto sempre que eu completar uma alteração
//vou escrever este código em uma função em separado...
void wxFramePrincipal::LimparCampos()
{
    //...Os comandos que usaremos aqui, será escrito depois...
}

//Aqui vou conferir os campos para ver se o usuário digitou algo errado...
bool wxFramePrincipal::ConfereCampos()
{
    //...Os comandos que usaremos aqui, será escrito depois...
    return true;
}



Até a próxima.

0 comentários:

Postar um comentário