Sviluppo multipiattaforma con QT

A settembre 2011 Embarcadero annunciava il suo Delphi XE2 con la libreria firemonkey che permettevano una compilazione di un eseguibile tra piattaforme diverse. Un solo sorgente per Windows e OSX.
In realtà esiste già da tempo una libreria che permette di fare compilazioni cross-platform e si chiama QT un solo sorgente per i sitemi Linux/OSX/Windows e inoltre embedded Linux e Symbian. Così giusto per fare una qualche prova e scrivere un po’ di codice in C++ ho provato a fare un programma che si collegasse ad un database sqlite 🙂
Con le librerie Qt viene anche fornito un IDE, QT Creator, che permette di sviluppare i propri applicativi. Esiste una versione opensource per i progetti non commerciali altrimenti bisogna investire qualche euro (per sapere i prezzi bisogna contattare il loro team vendite).
Per la creazione della for ho usato il designer visuale integrato con il QT Creator. 3 bottoni, connessione al DB, esecuzione query e uscita dall’applicativo e una griglia per visualizzare i dati. La griglia è una QTableWidget (non ‘è l’oggetto consigliato per i dati che arrivano da un database, ma per la prova va benissimo).

Per usare le librerie per i database ho dovuto aggiungere sql nel file di progetto “.pro” Che di default aveva questo contenuto:

#-------------------------------------------------
#
# Project created by QtCreator 2013-02-07T11:50:15
#
#-------------------------------------------------

QT += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = DBTest
TEMPLATE = app

SOURCES += main.cpp\
 mainwindow.cpp

HEADERS += mainwindow.h

FORMS += mainwindow.ui

Che io ho cambiato in:

QT += core gui sql

Il file header (mainwindow.h) è il seguente:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QtSql/QSqlDatabase>

namespace Ui {
  class MainWindow;
}

class MainWindow : public QMainWindow
{
 Q_OBJECT

  public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

  private slots:
    void on_btnDBConnection_clicked();
    void on_btnQuery_clicked();

  private:
    Ui::MainWindow *ui;
    QSqlDatabase db;
};

#endif // MAINWINDOW_H

Il file sorgente è invece questo.

#include <QMessageBox>
#include <QSqlQuery>
#include <QSqlError>
#include <QSqlRecord>
#include <QTableWidgetItem>
#include <QDir>
#include <QSqlQuery>
#include <QtSql>

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
 QMainWindow(parent),
 ui(new Ui::MainWindow)
{
 ui->setupUi(this);
}

MainWindow::~MainWindow()
{
 db.close();
 delete ui;
}

void MainWindow::on_btnDBConnection_clicked()
{
  //Connessione al DB
  db = QSqlDatabase::addDatabase("QSQLITE");
  QString path(QDir::home().path());
  path.append(QDir::separator()).append("qtTest.sqlite");
  path = QDir::toNativeSeparators(path);
  db.setDatabaseName(path);
  bool ok = db.QSqlDatabase::open();
  if (!ok)
    QMessageBox::critical(0, QObject::tr("Errore database"), db.lastError().text());
}

void MainWindow::on_btnQuery_clicked()
{
  if (db.isOpen())
  {
    QSqlQuery query("Select * from tbllocalita");

    int codNo = query.record().indexOf("cod");
    int desNo = query.record().indexOf("des");
    ui->tblData->insertColumn(0);
    ui->tblData->insertColumn(1);
    while (query.next())
    {
      QString sCod = query.value(codNo).toString();
      QString sDes = query.value(desNo).toString();
      int row = ui->tblData->rowCount();
      ui->tblData->insertRow(row);
      ui->tblData->setItem(row, 0, new QTableWidgetItem(sCod));
      ui->tblData->setItem(row, 1, new QTableWidgetItem(sDes));
    }
  }
}

E’ da notare la definizione del tipo di database a cui connettersi:

db = QSqlDatabase::addDatabase("QSQLITE");

I database supportati sono i seguenti (copio e incollo dal sito):

“IBM DB2 (version 7.1 and above)Borland InterBase
MySQL
Oracle Call Interface Driver
Open Database Connectivity (ODBC) – Microsoft SQL Server and other ODBC-compliant databases
PostgreSQL (versions 7.3 and above)
SQLite version 2
SQLite version 3
SQLite version 3 for Symbian SQL Database
Sybase Adaptive Server Note: obsolete from Qt 4.7”

Trovate più informazioni sui database qui.

Dopo la scelta del tipo di database, bisognerà definire i parametri di connessione. Nel caso in questione avendo scelto sqlite si dovrà definire la path del file (l’ultima riga), ed ecco comparire alcune utili funzioni integrate in QT che permettono di otterene la path della propria home e di riscrivere i separatori delle cartelle rispettando lo standard del sistema operativo su cui gira il programma (le prime 3 righe).

QString path(QDir::home().path());
path.append(QDir::separator()).append("qtTest.sqlite");
path = QDir::toNativeSeparators(path);
db.setDatabaseName(path);

Di seguito potete vedere come nella finestra di debug le path siano corrette:

Path in ambiente windows

Path in ambiente windows

Path in ambiente Linux

Path in ambiente Linux

Una volta aperto il DB, potremo fare la nostra estrazione dei dati:

if (db.isOpen())
{
  QSqlQuery query("Select * from tbllocalita");

  int codNo = query.record().indexOf("cod");
  int desNo = query.record().indexOf("des");
  ui->tblData->insertColumn(0);
  ui->tblData->insertColumn(1);
  while (query.next())
  {
    QString sCod = query.value(codNo).toString();
    QString sDes = query.value(desNo).toString();
    int row = ui->tblData->rowCount();
    ui->tblData->insertRow(row);
    ui->tblData->setItem(row, 0, new QTableWidgetItem(sCod));
    ui->tblData->setItem(row, 1, new QTableWidgetItem(sDes));
  }
}

Sempre in questo esempio la tabella era banale e aveva solo 2 colonne, ma nel caso ve ne siano molte di più si può vedere che è possibile abbinare un identificativo menmonico (una variabile di tipo intero) alla colonna fisica della query risultante in maniera da poterla usare per ottenere il valore del campo. Si è vero si poteva scrivere anche così:

QString sCod = query.value(query.record().indexOf("cod")).toString();
QString sDes = query.value(query.record().indexOf("des")).toString();

Infine i dati vengono salvati nella griglia, oggetto tbldata.

int row = ui->tblData->rowCount();
ui->tblData->insertRow(row);
ui->tblData->setItem(row, 0, new QTableWidgetItem(sCod));
ui->tblData->setItem(row, 1, new QTableWidgetItem(sDes));

La funzione insertRow inserisce una riga vuota alla posizione specificata da row, per cui nel nostro caso ci viene comodo impostarla sempre al numero di righe inserite che partono da 0.

Si compila quindi il tutto e lo stesso sorgente genera due eseguibili diversi funzionanti nello stesso modo su Windows e Linux.

Binario Linux

Binario Linux

Binario Windows

Binario Windows

Si lo so… i più bravi saranno già li a dire che con QT esisteno le QTableView a cui è possibile abbinare direttamente un modello dati di tipo QSqlQueryModel (che è in sola lettura) o QSqlTableModel (anche in scrittura). Per cui il codice in on_btnQuery_clicked diventerà molto semplicemente:

QSqlQueryModel *model = new QSqlQueryModel;
QSqlQuery query("Select cod, des from tbllocalita");

model->setQuery(query);
model->setHeaderData(0, Qt::Horizontal, tr("Codice"));
model->setHeaderData(1, Qt::Horizontal, tr("Descrizione"));

ui->tblData->setModel(model);

Speriamo che Nokia non decida in futuro di farci pagare questo gioiellino 🙂

Posta un commento o usa questo indirizzo per il trackback.

Commenti

  • juhan  Il 13 febbraio 2013 alle 12:41

    OPS! sono saltati gli #include

    • dikiyvolk  Il 13 febbraio 2013 alle 12:54

      Adesso ci sono 🙂 grazie per la segnalazione

  • b3h3m0th (@b3h3m0th)  Il 13 febbraio 2013 alle 13:54

    Le QT son o fantastiche, si sposano davvero bene con SQLite peraltro.

    Per dovere di cronaca devo far notare che le QT non sono più di nokia da questa estate, le ha vendute a digia.

    • dikiyvolk  Il 13 febbraio 2013 alle 16:47

      Davvero? Grazie della info… la notizia mi era proprio sfuggita. Googlando molte volte sono stato rimandato all’help della Nokia e non sono mai andato a vedere chi fosse il nuovo proprietario quando venivo rimandato sulle pagine con il logo verde.
      Adesso sto guardando come fare un collegamento master detail tra 2 modelli dati… se qualcuno sa indirizzarmi, molti suggeriscono di usare Predicate…

  • glipari  Il 13 febbraio 2013 alle 15:05

    Bel post! Si dovrebbe parlare di più delle QT. Tra le librerie C++ che conosco è forse quella fatta meglio e meglio supportata. Spero non muoia mai!

  • Roberto  Il 24 aprile 2013 alle 12:40

    Bell’articolo. Un errata corrige: librerie ha una sola “b” (riga 17)

    • dikiyvolk  Il 24 aprile 2013 alle 12:57

      Ciao Roberto,

      Quando si comincia a fare errori di italiano le cose si fanno serie 😦 ho fatto troppo affidamento sul correttore ortografico. Ho già corretto il testo. Grazie per la segnalazione.
      A breve dovrei uscire con un pezzo su qt quick… il problema è sempre trovare il tempo per tutto.

      Buona giornata

Trackback

Rispondi

Inserisci i tuoi dati qui sotto o clicca su un'icona per effettuare l'accesso:

Logo di WordPress.com

Stai commentando usando il tuo account WordPress.com. Chiudi sessione /  Modifica )

Google photo

Stai commentando usando il tuo account Google. Chiudi sessione /  Modifica )

Foto Twitter

Stai commentando usando il tuo account Twitter. Chiudi sessione /  Modifica )

Foto di Facebook

Stai commentando usando il tuo account Facebook. Chiudi sessione /  Modifica )

Connessione a %s...

Questo sito utilizza Akismet per ridurre lo spam. Scopri come vengono elaborati i dati derivati dai commenti.

%d blogger hanno fatto clic su Mi Piace per questo: