Archivio autore: dikiyvolk

Un altro modo di fare le tabelle… in HTML R. 2

Rieccomi!!

Nel fine settimana ho ricevuto alcuni commenti al mio articolo sull’HTML, potete leggere tutto nei commenti qui.
Per cui ritenendo alcune considerazioni esposte valide ho deciso di cambiare i sorgenti.
Ecco qua la nuova versione del css:

<style type="text/css">
  #search_results{
    width: 100%;
    padding: 10px 0;
    margin: 0 0 10px 0;
  }
 
  #search_results_inside_box{
    border: 2px solid #C8CDD2;
  }

  #search_results dl{
    padding: 0;
    color: #000000;
    font-weight: bold;
    margin: 0 0 0 0;
 }
 
  #search_results dd{
    margin: 5px 0 5px 0;
    padding: 0;
    line-height: 1.5em;
    color: #000000;
    float: left;
    word-break: break-all;
    font-weight: normal;
  }
 
  #search_results .caption dd{
    font-weight: bold;
  }
 
  #search_results .clear: {
    visibility: hidden;
  }
     
  #search_results  .caption_div{
    border-bottom: 2px solid black; 
  }
 
  #search_results  .rows_div{
  }
 
  #search_results  .rows_bottom_div{
    border-bottom: 1px solid LightGray; 
  }
 
  #search_results  .rows_even_div{
    background-color: LightGray;
  }
     
  #search_results  .clearfix:after{
    content:".";
    display:block;
    height:0;
    clear:both;
    visibility:hidden
  }

  #search_results  .clearfix{
    display:block
  }

/* Holly Hack Targets IE Win only \*/
* html .clearfix {height: 1%;}
.clearfix {display: block;}
/* End Holly Hack */

</style>

e qui dell’HTML:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
 
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
 
    <!-- HTTP 1.1 -->
    <meta http-equiv="Cache-Control" content="no-store">
 
    <!-- HTTP 1.0 -->
    <meta http-equiv="Pragma" content="no-cache">
 
    <!-- Prevents caching at the Proxy Server -->
 
    <meta http-equiv="Expires" content="0">
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
 
    <link href="css/dltable.css" rel="stylesheet">
 
    <title>Classica table</title>
  </head>
 
  <body>
 
    <div id="search_results">
	  <div id="search_results_inside_box">
		  <div class="caption_div clearfix">
			<dl class="caption">
			  <dd style="width: 10%;">#</dd>
			  <dd style="width: 30%;">Codice</dd>
			  <dd style="width: 60%;">Descrizione</dd>
			</dl>
		  </div>
		  <div class="rows_div clearfix">
			<dl>
			  <dd style="width: 10%;"><input id="selectedRecord1" name="selectedRecord" value="1" type="radio" height="100%"></dd>
			  <dd style="width: 30%;">1</dd>
			  <dd style="width: 60%;">descrizione 1</dd>
			</dl>
		  </div>
		  <div class="rows_even_div clearfix">
			<dl>
			  <dd style="width: 10%;"><input id="selectedRecord1" name="selectedRecord" value="2" type="radio"></dd>
			  <dd style="width: 30%;">2</dd>
			  <dd style="width: 60%;">descrizione 2</dd>
			</dl>
		  </div>
		  <div class="rows_div  clearfix">
			<dl>
			  <dd style="width: 10%;"><input id="selectedRecord1" name="selectedRecord" value="3" type="radio" height="100%"></dd>
			  <dd style="width: 30%;">3</dd>
			  <dd style="width: 60%;">descrizione 3</dd>
			</dl>
		  </div>
		  <div class="rows_even_div clearfix">
			<dl>
			  <dd style="width: 10%;"><input id="selectedRecord1" name="selectedRecord" value="4" type="radio"></dd>
			  <dd style="width: 30%;">4</dd>
			  <dd style="width: 60%;">descrizione 4</dd>
			</dl>
		  </div>
		</div>
    </div> 
  </body>
</html>

Questi sono i link al materiale di riferimento che ho usato. html.it e css Zibaldone.

Come al solito spero che il codice sia utile o almeno d’ispirazione a qualcuno per il proprio studio/lavoro.

Un altro modo di fare le tabelle… in HTML

Ciao a tutti… dopo mesi di latitanza sono tornato… spero che nessuno scappi per questo motivo 🙂 L’argomento di oggi è l’html. No! Nessuna pozione magica per superare le incompatibilità dei vari browser e la loro indifferenza nei confronti dello standard, ma bensì la risoluzione di un problema avuto in ufficio. In fondo questa foto parla chiaro:

ie

Partiamo dall’inizio! Ennensimo sito per un’applicazione intranet, ennesima pagina di ricerca e lista dei record trovati. Partendo modello armata Brancaleone costruiamo la pagina e ci mettiamo la “sempre verde” table per contenere i dati. Passiamo tutto al nostro “grafico” che trasforma il nostro Quasimodo nel “Principe azzurro” o almeno lo veste da principe perché pur utilizzando un ambiente “liquido” la nostra tabella continuava ad essere presente. Sto usando il plurale non in preda a delirio di onnipotenza ma perché il progetto non era sviluppato solo da me. 🙂
Questo era il codice della tabella (ho tolto per ovvi motivi il resto):

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">


<html xmlns="http://www.w3.org/1999/xhtml">
  <head>

    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

    <!-- HTTP 1.1 -->
<meta http-equiv="Cache-Control" content="no-store">

    <!-- HTTP 1.0 -->
<meta http-equiv="Pragma" content="no-cache">

    <!-- Prevents caching at the Proxy Server -->

    <meta http-equiv="Expires" content="0">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

    <link href="css/dltable.css" rel="stylesheet">

    <title>Classica table</title>
  </head>

  <body>
    <table class="table">
					  	
      <thead>
        <th>#</th>
							
        <th><label>Codice</label></th>

        <th><label>Descrizione</label></th>
					  	
      </thead>
      <tbody>
        <tr>
          <td><input id="selectedRecord1" name="selectedRecord" value="1" type="radio"></td> 
		          
          <td><label>1</label></td>
					  <td><label>descrizione 1</label></td>
        </tr>
        <tr>
          <td><input id="selectedRecord1" name="selectedRecord" value="2" type="radio"></td> 
		          
          <td><label>2</label></td>
          <td><label>descrizione 2</label></td>
        </tr>
        <tr>
          <td><input id="selectedRecord1" name="selectedRecord" value="3" type="radio"></td> 
		          
          <td><label>3</label></td>
					  <td><label>descrizione 3</label></td>
        </tr>
        <tr>
          <td><input id="selectedRecord1" name="selectedRecord" value="4" type="radio"></td> 
		          
          <td><label>4</label></td>
          <td><label>descrizione 4</label></td>
        </tr>
      </tbody>
					
    </table>

  </body>

</html>

Brutta vero? Non è che l’ho paragonata a Quasimido per pregiudizio 😛 Diciamo che fa il suo sporco lavoro 🙂

Table classica in HTML

Table classica in HTML

La domanda è stata… “come renderla più cool?” e più “moderna”. La risposta non è stata così immediata anche se la strada mi era già stata indicata da un libro, Web Design Bulletproof (fa sempre bene leggere). In un capitolo veniva fatto vedere come sostituire una tabella con degli elenchi (dd, dl, dd…).
Il risultato è stato questo:

Righe con colore di sfondo alternato

Righe con colore di sfondo alternato

Butto giù il css che è questo:

<style type="text/css">
  #search_results{
    width: 100%;
    padding: 10px 0;
    border: 2px solid #C8CDD2;
    margin: 0 0 10px 0;
    overflow: hidden;
  }

  #search_results dl{
    padding: 0;
    color: #000000;
    font-weight: bold;
    margin: 0 10px 10px 10px;
  }

  #search_results dd{
    margin: 5px 0 5px 0;
    padding: 0;
    line-height: 1.5em;
    color: #000000;
    float: left;
    word-break: break-all;
    font-weight: normal;
  }

  #search_results .caption dd{
    font-weight: bold;
  }

  #search_results .clear: {
    clear: both;
    visibility: hidden;
  }
	
  #search_results  .caption_div{
    border-bottom: 2px solid black; 
    overflow: hidden;
  }

  #search_results  .rows_div{
    overflow: hidden;
  }

  #search_results  .rows_bottom_div{
    border-bottom: 1px solid LightGray; 
    overflow: hidden;
  }

  #search_results  .rows_even_div{
    background-color: LightGray;
    overflow: hidden;
  }
	
</style>

e l’html.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
  <head>

    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

    <!-- HTTP 1.1 -->
    <meta http-equiv="Cache-Control" content="no-store">

    <!-- HTTP 1.0 -->
    <meta http-equiv="Pragma" content="no-cache">

    <!-- Prevents caching at the Proxy Server -->

    <meta http-equiv="Expires" content="0">
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

    <link href="css/dltable.css" rel="stylesheet">

    <title>Classica table</title>
  </head>

  <body>

    <div id="search_results">
	  <div class="caption_div">
        <dl class="caption" width=100%>
          <dd style="width: 10%;">#</dd>
          <dd style="width: 30%;">Codice</dd>
          <dd style="width: 60%;">Descrizione</dd>
        </dl>
      </div>
      <div class="rows_div">
        <dl>
          <dd style="width: 10%;"><input id="selectedRecord1" name="selectedRecord" value="1" type="radio" height="100%"></dd>
          <dd style="width: 30%;">1</dd>
          <dd style="width: 60%;">descrizione 1</dd>
        </dl>
      </div>
      <div class="rows_even_div">
        <dl>
          <dd style="width: 10%;"><input id="selectedRecord1" name="selectedRecord" value="2" type="radio"></dd>
          <dd style="width: 30%;">2</dd>
          <dd style="width: 60%;">descrizione 2</dd>
        </dl>
      </div>
      <div class="rows_div">
        <dl>
          <dd style="width: 10%;"><input id="selectedRecord1" name="selectedRecord" value="3" type="radio" height="100%"></dd>
          <dd style="width: 30%;">3</dd>
          <dd style="width: 60%;">descrizione 3</dd>
        </dl>
      </div>
      <div class="rows_even_div">
        <dl>
          <dd style="width: 10%;"><input id="selectedRecord1" name="selectedRecord" value="4" type="radio"></dd>
          <dd style="width: 30%;">4</dd>
          <dd style="width: 60%;">descrizione 4</dd>
        </dl>
      </div>
    </div>

  </body>

</html>

in evidenza nel CSS:

overflow: hidden;

Questo è fondamentale affinchè gli elmenti float vengano “disegnati” correttamente e non come in figura:

Senza overflow=hidden nel css.

Senza overflow=hidden nel css.

Dove si vede che il background color è sparito. Questo capita perché gli elementi definiti come float escono dalla sequenza standard di ridisegno del browser e non compaiono dove vorremmo (trovate ulteriori informazioni “qui” e “qui”.

word-break: break-all;

Che serve per mandare a capo eventuali testi troppo lunghi.

La struttura è molto semplice. Dopo aver definito l’elemento dd di tipo float con allineamento a sinistra ho messo ogni singola voce in un div in modo da poter mettere o un colore o una riga di separazione. Infatti basta cambiare le classi rows_even_div e rows_div con la classe rows_bottom_div per avere il seguente risultato:

Righe separate da una linea

Righe separate da una linea

L’ho provata con successo su:

– IE 11/9/8
– Chrome 30.0
– Firefox 25
– Konqueror 4.10.5

Tutto qua 🙂 pagina più leggera e moderna… Ohps… dimenticavo e i miei colleghi un po’ incarogniti perché devono cambiare le pagine jsp 😛

Puntatori alberi e ricorsività parte ][

Rieccomi 🙂 con la seconda parte dei miei mini articoli su Delphi. I simpatizzanti della mela morsicata mi perdoneranno per aver usato ][ al posto del numero 2, ma il mio primo computer è stato un Apple ][ e la tentazione è stata troppo grande 🙂
La prima puntata della saga è qui. Partendo dal primo articolo ho pensato di aggiungere l’implementazione di un design pattern, il singleton.
Lo scopo del singleton, che è un design pattern creazionale, è quello di garantire la creazione di una sola istanza di una particolare classe. Il link a wikipedia è qui. Nell’esempio precedente il singleton potrebbe esserci utile perché pensando alla nostra classe StandardTreeGest in un contesto più ampio, quindi come parte di un progetto più complesso scritto da molti programmatori, ci troveremmo a doverla istanziare in più punti. Certo potremmo dichiarare una bella variabile globale 🙂 (male… molto male chi lo ha pensato?) ma questa non ci garantirebbe dall’instanziarla più volte e quindi poi trovarci con una struttura dati inconsistente e con anomalie molto molto “stravaganti”.

Cercando in giro non ho trovato molti esempi di singleton in Delphi. I primi risultati della mia ricerca sono stati:

questo che però è stato risolto usando un’interfaccia e dove si elogiava una soluzione C# definendola elegante… Delphi si basa sul Pascal, ma non è un linguaggio vecchio 🙂 abbiamo anche i tipi generici da Delphi 2009
– Altra soluzione è questa (per Delphi 5) ma utilizza un singleton “strano”… con una tecnica di reference count.

Avendo comprato un bel libro sui design pattern (da non leggere la sera 🙂 specialmente dopo una pizza ai 4 formaggi) ho voluto fare (o almeno ci ho provato) il “preciso” copiando il codice C++

#include <iostream>
 
#define null 0
 
class singleton {
  private:
    static singleton* instance_ptr;

  protected:
    singleton() { };

  public:
    ~singleton() {};
    static singleton* get_instance() {
      if (instance_ptr == null) {
        instance_ptr = new singleton;
      }
      return instance_ptr;
    }
};
 
// initialize pointer
singleton* singleton::instance_ptr = null;

Che in Delphi verrà scritto invece così:

  Singleton = class
    strict private
      // Singleton
      class var
        pInstance: Singleton;

    strict protected
      constructor Create; virtual;

    public
      destructor Destroy; override;
      class function getInstance: StandardTreeGest;
  end;

Ok descriviamo il codice Delphi. Per prima la bellissima parola chiave strict che serve a rendere veramente privati e protetti i dati membro… perché le parole private e protected non valgono per classi all’interno della stessa unit. Tanto per intenderci potete vedere questo codice sorgente che ho copiato da Stack Overflow:

type
  TFather = class
  private
    FPriv : integer;
  strict private
    FStrPriv : integer;
  protected
    FProt : integer;
  strict protected
    FStrProt : integer;
  public
    FPublic : integer;
  end;

  TSon = class(TFather)
  public
    procedure DoStuff;
  end;

  TUnrelated = class
  public
    procedure DoStuff;
  end;

procedure TSon.DoStuff;
begin
  FProt := 10;       // Legal, as it should be. Accessible to descendants.
  FPriv := 100;      // Legal, even though private. This won't work from another unit!
  FStrictPriv := 10; // <- Compiler Error, FStrictPrivFather is private to TFather
  FPublic := 100;    // Legal, naturally. Public members are accessible from everywhere.
end;

procedure TUnrelated.DoStuff;
var
  F : TFather;
begin
  F := TFather.Create;
  try
    F.FProt := 10;     // Legal, but it shouldn't be!
    F.FStrProt := 100; // <- Compiler error, the strict keyword has "made the protection work"
    F.FPublic := 100;  // Legal, naturally.
  finally
    F.Free;
  end;
end;

Qui si parla di un bug del compilatore… ma nell’help ufficiale è una features 🙂 ovviamente!!!
Poi incontriamo un’altra parolina magica class var questa ci permette di definire dei dati membro statici, infatti per il singleton sia il dato membro che la funzione di istanziazione della classe devono essere statici (per poter essere richiamati senza istanziare una classe, ma dico una cosa ovvia vero…?).

La Unit TreeGest del nostro esempio risulterà quindi essere questa:

unit TreeGest;

interface

type
  pElement = ^Element;

  Element = record
    iValue: integer;
    pLeftElement, pRightElement: pElement;
  end;

  StandardTreeGest = class
    strict private
      // Singleton
      class var
        pInstance: StandardTreeGest;

    strict protected
      constructor Create; virtual;

    private
      // Tree managment
      pRoot: pElement;

      function CreateNewElem(ext_iValue: integer): pElement;
      function InsertSorted(ext_iValue: integer; var pCurrElem: pElement): pElement;
      function ReadFromMinimum(pCurrElem: pElement): string;

      procedure DeleteAll_From(pCurrElem: pElement);

    public
      function AddElement(ext_iValue: integer): pElement;
      function SortAscending: string;

      procedure DeleteAll;

      destructor Destroy; override;

      // Singleton
      class function getInstance: StandardTreeGest;
  end;

implementation

{ StandardTreeGest }

uses
  System.SysUtils;

constructor StandardTreeGest.Create;
begin
  pRoot := nil;
end;

destructor StandardTreeGest.Destroy;
begin
  if assigned(pRoot) then
    DeleteAll;
end;

class function StandardTreeGest.getInstance: StandardTreeGest;
begin
  if (pInstance = nil) then
    pInstance := StandardTreeGest.Create;

  result := pInstance;
end;

procedure StandardTreeGest.DeleteAll;
begin
  DeleteAll_From(pRoot);
  pRoot := nil;
end;

procedure StandardTreeGest.DeleteAll_From(pCurrElem: pElement);
begin
  if pCurrElem <> nil then
  begin
    DeleteAll_From(pCurrElem.pLeftElement);
    DeleteAll_From(pCurrElem.pRightElement);
    Dispose(pCurrElem);
  end
end;

function StandardTreeGest.AddElement(ext_iValue: integer): pElement;
var
  pAddedElement: pElement;

begin
  if pRoot = nil then
  begin
    pRoot := CreateNewElem(ext_iValue);
    pAddedElement := pRoot;
  end
  else
    pAddedElement := InsertSorted(ext_iValue, pRoot);

  result := pAddedElement;
end;

function StandardTreeGest.CreateNewElem(ext_iValue: integer): pElement;
var
  pNewElement: pElement;
begin
  try
    new(pNewElement);
    pNewElement.iValue := ext_iValue;
    pNewElement.pLeftElement := nil;
    pNewElement.pRightElement := nil;
  except
    pNewElement := nil;
  end;

  result := pNewElement;
end;

function StandardTreeGest.InsertSorted(ext_iValue: integer; var pCurrElem: pElement): pElement;
var
  pResultElem: pElement;

begin
  if (pCurrElem = nil) then
  begin
    pResultElem := CreateNewElem(ext_iValue);
    pCurrElem := pResultElem;
  end
  else
    if (ext_iValue < pCurrElem.iValue) then
      pResultElem := InsertSorted(ext_iValue, pCurrElem.pLeftElement)
    else
      pResultElem := InsertSorted(ext_iValue, pCurrElem.pRightElement);

  result := pResultElem;
end;

function StandardTreeGest.SortAscending: string;
var
  sNode: string;

begin
  sNode := ReadFromMinimum(pRoot);
  if (sNode <> '') then
    result := Copy(sNode, 3, length(sNode) - 2)
  else
    result := '';
end;

function StandardTreeGest.ReadFromMinimum(pCurrElem: pElement): string;
var
  sOrderedTree: string;

begin
  if pCurrElem <> nil then
  begin
    sOrderedTree := ReadFromMinimum(pCurrElem.pLeftElement) + ' - ' + IntToStr(pCurrElem.iValue);
    sOrderedTree := sOrderedTree + ReadFromMinimum(pCurrElem.pRightElement);
  end
  else
    sOrderedTree := '';

  result := sOrderedTree;
end;

end.

Oltre ad aver implementato il singleton rispetto a quella dello scorso post ho aggiunto un blocco try…except al momento dell’allocazione della memoria del nuovo nodo dell’albero. Nel mondo ideale la memoria è illimitata, ma in quello reale finisce sempre sul più bello e la scorsa volta mi sono dimenticato di gestire l’eccezione.
Il programma avrà l’aspetto seguente:

pgm_singleton

Che a parte un bottone in più… è uguale al precedente… che farà il nuovo bottone?

procedure TfrmMain.btnAddElem2Click(Sender: TObject);
var
  pTreeGestLocal: StandardTreeGest;
  iValue, iCode: integer;

begin
  pTreeGestLocal := StandardTreeGest.getInstance;

  Val(txtValue.Text, iValue, iCode);

  if ((trim(txtValue.Text) <> '') and (iCode = 0)) then
  begin
    pTreeGestLocal.AddElement(StrToInt(txtValue.Text));
    txtValue.Clear;
    txtValue.SetFocus;
  end
end;

Semplicemente permette di verificare il singleton, aggiungendo direttamente un elemento all’albero. Notate che uso solo varibili locali e che la getInstance mi restituisce sempre il riferimento all’unica classe StandardTreeGest creata.

Il codice sorgente lo potete scaricare qui.

Alla prossima 🙂

Puntatori, alberi e ricorsività

Rieccomi dopo tanto. Questo è il primo articolo di una piccola serie… facciamo miniserie di 3 o forse 4 puntate veloci.
Qualche tempo fa ho fatto un colloquio per una ditta all’estero, il colloquio prevedeva anche un test pratico. 40 minuti di tempo per creare in Delphi un albero, popolarlo e stamparne gli elementi.
L’albero conteneva numeri, a partire dalla radice i numeri minori del nodo corrente venivano memorizzati nel ramo a sinistra, quelli maggiori del nodo corrente invece nel ramo di destra. La lettura ovviamente doveva restituire gli elementi in maniera ordinata.
Facendo un esempio pratico, dovendo inserire i numeri 10 – 6 – 4 – 3 – 8 -12 avremo avuto un albero come questo:

Tree

Insomma 🙂 il calssico esercizio di teoria sugli alberi, dov’era l’inghippo? Si avevano a disposizione solo 40 minuti! E il tempo vola.

Devo ammettere che il test era proprio ben fatto. Dopo averlo superato ho pensato che in realtà fosse troppo facile, ma non lo era affatto. Per poterlo passare o si conosceva la teoria o non si sarebbe potuto trovare nulla su google da copiare ed incollare. Gli elementi da inserire erano:

– Puntatori
– Gli alberi
– La ricorsione

I puntatori non sono così usati in Delphi e non ho mai capito bene perché sono indicanti da molti sviluppatori come il male di ogni software. Agli albori di Java una delle frasi che mi aveva divertito di più era:
“La gestione della memoria è troppo importante per lasciarla agli sviluppatori. La gestione della memoria è troppo importante per lasciarla al software”.
Chissà chi aveva ragione…

Sugli alberi dipende molto da che software sviluppate, io non li ho più usati dai tempi della scuola… troppo software gestionale 🙂 con le sue query SQL.
Riguardo la ricorsione… siamo quasi come i puntatori; un altro di quegli argomenti amati e odiati, la ricorsione (qui il link a wikipedia) risolve semplicemente alcune tipologie di problemi ma al tempo stesso non è performante.

Tornando a noi, il programmino finale è quello nella figura di seguito:

Il programma

Ok è esteticamente bruttino, ma abbiate pietà, comprensione, umanità un principio di cataratta 🙂 … fare le cornicette di notte non mi entusiasma 🙂 Il suo funzionamento è semplice. Si aggiungono gli elementi alla TListBox premendo il bottone “Add Element” che sposta l’elemento dalla TEdit “value” alla lista, una volta che abbiamo popolato a dovere quest’ultima il bottone “Create tree” genererà l’albero.
Il bottone “Sorted elements” scriverà gli elementi dal più piccolo al più grande 🙂 nel componente TEdit a fianco del bottone.

L’implementazione della classe StandardTreeGest prevederà le funzioni pubbliche:

public
  function AddElement(ext_iValue: integer): pElement;
  function SortAscending: string;

Per aggiungere un elemento e per estrarre la lista degli elementi inseriti in maniera ordinata. Queste 2 funzioni saranno le “interfacce” pubbliche delle seguenti funzioni private:

function InsertSorted(ext_iValue: integer; var pCurrElem: pElement): pElement;
function ReadFromMinimum(pCurrElem: pElement): string;

Che opereranno realmente sull’albero. Andando ad evidenziare i punti problematici elencati prima (puntatori, alberi e ricorsione) vediamo che ogni nodo di un albero è formato da 2 puntatori ad altri nodi (uno al ramo sinistro e uno al destro) e dal valore del nodo stesso. Questo in Delphi si dichiara così:

type
  pElement = ^Element;

  Element = record
    iValue: integer;
    pLeftElement, pRightElement: pElement;
  end;

Abbiamo quindi le funzioni pubbliche per l’accesso all’albero che richiameranno quelle private che saranno anche quelle ricorsive.

function StandardTreeGest.AddElement(ext_iValue: integer): pElement;
var
  pAddedElement: pElement;

begin
  if pRoot = nil then
  begin
    pRoot := CreateNewElem(ext_iValue);
    pAddedElement := pRoot;
  end
  else
    pAddedElement := InsertOrdered(ext_iValue, pRoot);

  result := pAddedElement;
end;

function StandardTreeGest.SortAscending: string;
var
  sNode: string;

begin
  sNode := ReadFromMinimum(pRoot);
  if (sNode <> '') then
    result := Copy(sNode, 3, length(sNode) - 2)
  else
    result := '';
end;

Come vedete fanno ben poche cose principalmente legate al controllo del nodo radice dell’albero. Il lavoro vero lo fanno le funzioni private:

function CreateNewElem(ext_iValue: integer): pElement;
function InsertSorted(ext_iValue: integer; var pCurrElem: pElement): pElement;
function ReadFromMinimum(pCurrElem: pElement): string;

Con il loro codice sorgente:

function StandardTreeGest.CreateNewElem(ext_iValue: integer): pElement;
var
  pNewElement: pElement;
begin
  new(pNewElement);
  pNewElement.iValue := ext_iValue;
  pNewElement.pLeftElement := nil;
  pNewElement.pRightElement := nil;

  result := pNewElement;
end;

function StandardTreeGest.InsertSorted(ext_iValue: integer; var pCurrElem: pElement): pElement;
var
  pResultElem: pElement;

begin
  if (pCurrElem = nil) then
  begin
    pResultElem := CreateNewElem(ext_iValue);
    pCurrElem := pResultElem;
  end
  else
    if (ext_iValue < pCurrElem.iValue) then
      pResultElem := InsertOrdered(ext_iValue, pCurrElem.pLeftElement)
    else
      pResultElem := InsertOrdered(ext_iValue, pCurrElem.pRightElement);

  result := pResultElem;
end;

function StandardTreeGest.ReadFromMinimum(pCurrElem: pElement): string;
var
  sOrderedTree: string;

begin
  if pCurrElem <> nil then
  begin
    sOrderedTree := ReadFromMinimum(pCurrElem.pLeftElement) + ' - ' + IntToStr(pCurrElem.iValue);
    sOrderedTree := sOrderedTree + ReadFromMinimum(pCurrElem.pRightElement);
  end
  else
    sOrderedTree := '';

  result := sOrderedTree;
end;

A parte la CreateNewElem le altre funzioni sono ricorsive cioè richiamano se stesse dal loro interno, questo permette ad esempio alla ReadFromMinimum di leggere tutti i nodi a sinistra e una volta arrivato all’ultimo elemento di “riavvolgersi” su se stessa e passare alla lettura del ramo destro del nodo corrente. Tutto questo perché le chiamate a funzione e le variabili locali in esse contenute vengono memorizzate sullo stack. Da qui la lentezza e la grande occupazione di memoria delle funzioni ricorsive a cui si contrappone la drammatica semplicità del codice scritto.

Volevo richiamare la vostra attenzione sulla creazione di un nuovo record nella funzione CreateNewElem

  new(pNewElement);

avete visto l’istruzione new… mica vorrete lasciarla senza la sua dispose!!! 🙂 I puntatori fanno casini quando ci dimentichiamo di deallocarli o magari li usiamo senza allocarli, ma non sono così cattivi in fondo.
La funzione di “ripulitura” è la DeleteAll richiamata anche da dentro il distruttore della classe StandardTreeGest. Il codice completo di quest’ultima è il seguente:

unit TreeGest;

interface

type
  pElement = ^Element;

  Element = record
    iValue: integer;
    pLeftElement, pRightElement: pElement;
  end;

  StandardTreeGest = class
    private
      pRoot: pElement;

      function CreateNewElem(ext_iValue: integer): pElement;
      function InsertSorted(ext_iValue: integer; var pCurrElem: pElement): pElement;
      function ReadFromMinimum(pCurrElem: pElement): string;

      procedure DeleteAll_From(pCurrElem: pElement);

    public
      function AddElement(ext_iValue: integer): pElement;
      function SortAscending: string;

      procedure DeleteAll;

      constructor Create; virtual;
      destructor Destroy; override;

  end;

implementation

{ StandardTreeGest }

uses
  System.SysUtils;

constructor StandardTreeGest.Create;
begin
  pRoot := nil;
end;

destructor StandardTreeGest.Destroy;
begin
  if assigned(pRoot) then
    DeleteAll;
end;

procedure StandardTreeGest.DeleteAll;
begin
  DeleteAll_From(pRoot);
  pRoot := nil;
end;

procedure StandardTreeGest.DeleteAll_From(pCurrElem: pElement);
begin
  if pCurrElem <> nil then
  begin
    DeleteAll_From(pCurrElem.pLeftElement);
    DeleteAll_From(pCurrElem.pRightElement);
    Dispose(pCurrElem);
  end
end;

function StandardTreeGest.AddElement(ext_iValue: integer): pElement;
var
  pAddedElement: pElement;

begin
  if pRoot = nil then
  begin
    pRoot := CreateNewElem(ext_iValue);
    pAddedElement := pRoot;
  end
  else
    pAddedElement := InsertSorted(ext_iValue, pRoot);

  result := pAddedElement;
end;

function StandardTreeGest.CreateNewElem(ext_iValue: integer): pElement;
var
  pNewElement: pElement;
begin
  new(pNewElement);
  pNewElement.iValue := ext_iValue;
  pNewElement.pLeftElement := nil;
  pNewElement.pRightElement := nil;

  result := pNewElement;
end;

function StandardTreeGest.InsertSorted(ext_iValue: integer; var pCurrElem: pElement): pElement;
var
  pResultElem: pElement;

begin
  if (pCurrElem = nil) then
  begin
    pResultElem := CreateNewElem(ext_iValue);
    pCurrElem := pResultElem;
  end
  else
    if (ext_iValue < pCurrElem.iValue) then
      pResultElem := InsertSorted(ext_iValue, pCurrElem.pLeftElement)
    else
      pResultElem := InsertSorted(ext_iValue, pCurrElem.pRightElement);

  result := pResultElem;
end;

function StandardTreeGest.SortAscending: string;
var
  sNode: string;

begin
  sNode := ReadFromMinimum(pRoot);
  if (sNode <> '') then
    result := Copy(sNode, 3, length(sNode) - 2)
  else
    result := '';
end;

function StandardTreeGest.ReadFromMinimum(pCurrElem: pElement): string;
var
  sOrderedTree: string;

begin
  if pCurrElem <> nil then
  begin
    sOrderedTree := ReadFromMinimum(pCurrElem.pLeftElement) + ' - ' + IntToStr(pCurrElem.iValue);
    sOrderedTree := sOrderedTree + ReadFromMinimum(pCurrElem.pRightElement);
  end
  else
    sOrderedTree := '';

  result := sOrderedTree;
end;

end.

Il sorgente di tutto il programma lo trovate qui

Come potete vedere ho creato il costruttore e il distruttore… ho fatto lo stesso anche con la form del main, so che con le form avrei potuto usare l’evento oncreate per fare le mie inizializzazioni ma sono un po “stagionato” 🙂

Concludo questo primo articolo dicendo che le poche righe scritte non sono certo esaustive, gli alberi sono materia complessa se non di più, ma la mia idea è di buttare una pietra nell’acqua e far muovere un po’ le cose. Spiegare quello che so e anche imparare per cui se avete commenti, suggerimenti o correzioni non esitate a scrivere 🙂

Sei un programmatore Ruby principiante se…

Rieccomi!!! Butto giusto uno spunto di riflessione sui post di Juhan riguardo Ruby. Un po’ di tempo fa su linkedin mi arrivò questo argomento di discussione:

“Sei un programmatore Ruby principiante se pensi che la seguente espressione:

ruby -e 'a=(true and false) or (true and true); puts a'

restituisca true.”

Leggendola così su due piedi… ho pensato perché non dovrebbe restituire true… la risposta è semplice, in ruby esistono degli operatori “ripetuti” (chiamiamoli così, gli esperti non mi insultino) che hanno lo stesso funzionamento, ma differente priorità. Per cui abbiamo che “or” può essere scritto come “or” o come “||” e l'”and” come “and” o come “&&”. Solo che gli opertori “and” e “or” hanno minore priorità dell’operatore “=” per cui nel nostro esempio avremo che l’assegnazione alla variabile “a” avverrà prima che venga eseguito l’or. A varrà quindi il risultato di (true and false).

Questa è una tabella che ho copiato da “Programming Ruby 1.9 & 2.0” di “The Pragmatic Bookshelf” con l’elenco degli operatori e della loro priorità:

Elenco degli operatori Ruby con le loro priorità

Elenco degli operatori Ruby con le loro priorità

Se si fosse scritto:

ruby -e 'a=(true && false) || (true && true); puts a'

o

ruby -e 'a=(true and false) || (true and true); puts a'

Avremmo ottenuto “true” come da copione 🙂 o da abitudinario come me 🙂

Ho faticato un po’ a capire il perché degli operatori “duplicati”, ovviamente la mia prima risposta è stata:

“Ruby è sviluppato da un giapponese e i giapponesi ogni tanto fanno delle scelte che a noi non giapponesi sembrano strane” 🙂 se non mi credete fatevi un giro per Tokyo (ci sono stato 2 volte) e vi ricrederete… 😀
La risposta invece logica è che gli operatori “&&” e “||” si usano per operazioni booleane (i nostri amici if e famiglia), mentre “and” e “or” si usano per il controllo del flusso (come in perl).
Per dettagli e approfondimenti qui è spiegato tutto benissimo.

Post brevissimo… diciamo un aperitivo per i prossimi post di Juhan 🙂

QT – WEBINAR: Getting Started with Qt/C++ Programming. Parte 2

La scorsa volta ci siamo lasciati alla gestione degli eventi. Ecco cosa ci propone QT.

QT gestione eventi

Le QT usano gli SLOT e i SIGNAL per la comunicazione ad alto livello con l’utente (ad esempio il movimento del mouse o la pressione di un bottone…proprio quello che vogliamo noi 🙂 ). I virtual method sono invece usati per interazioni a basso livello con il sistema operativo o ad esempio la pressione dei tasti… su una tastiera naturalmente.
Quindi il funzionamento di SLOT e SIGNAL è molto semplice. Un oggetto emette un segnale (il SIGNAL) e un altro risponde al segnale con una funzione particolare (lo SLOT).
La connessione tra i due viene fatta usando la funzione connect che appartiene alla classae QObject. I parametri della connect sono i seguenti (copiati dall’help QT):

bool QObject::connect ( const QObject * sender, const char * signal, const QObject * receiver, const char * method, Qt::ConnectionType type = Qt::AutoConnection ) [static]

Come farli funzionare nel nostro esempio della volta scorsa? Il sender e l’evento lo abbiamo, ci mancano il receiver e il metodo di risposta… almeno per il bottone “Ciao”. Perché per il bottone “Esci” useremo un artificio 🙂

Il codice è il seguente:

Eventibottoni.h

#ifndef EVENTIBOTTONI_H
#define EVENTIBOTTONI_H

#include <QObject>

class EventiBottoni : public QObject
{
    Q_OBJECT

public slots:
    void rispondiButton1();

};

#endif // EVENTIBOTTONI_H

Eventibottoni.cpp

#include <QMessageBox>

#include "eventibottoni.h"

void EventiBottoni::rispondiButton1()
{
    QMessageBox::information(0, QObject::tr("Messaggio"), QObject::tr("Ciao, mondo!!"));
}

main.cpp

#include <QApplication>
#include <QtGui>

#include "eventibottoni.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QWidget window;

    QPushButton *button1 = new QPushButton("Ciao", &window);
    QPushButton *button2 = new QPushButton("Esci", &window);
    QHBoxLayout *layout = new QHBoxLayout(&window);

    layout->addWidget(button1);
    layout->addWidget(button2);

    EventiBottoni eventi;

    QObject::connect(button1, SIGNAL(clicked()), &eventi, SLOT(rispondiButton1()));
    QObject::connect(button2, SIGNAL(clicked()), qApp, SLOT(quit()));

    //window.setLayout(layout);
    window.show();

    return a.exec();
}

E questo è il risultato:

Pulsante ciao

Allora come si vede abbiamo dovuto generare un oggetto derivante da QObject in cui definire il metodo di risposta alla pressione del tasto “Ciao”. L’oggetto deve forzatamente derivare da QObject pena il non poter definire la connect. Se vi chiedete il perché guardate la definizione della funzione 🙂 l’ho messa a posta all’inizio.

L’artificio per il bottone “Esci” invece è semplice 🙂 ho definito una connessione verso l’oggetto qApp (che non abbiamo definito noi) ma è definito dalle QT ed è un puntatore globale all’oggetto applicazione. Per cui diciamo all’applicazione di chiudersi chiamando lo slot quit().

Ok Siamo quasi arrivati alla fine, il webminar era di soli 45 minuti, manca solo un punto da chiarire. Quel “Q_OBJECT” che vedete in mezzo alla classe abilita il MOC, cioè il Meta object compiler. Diciamo che è il traduttore di alcune feature QT verso codice standard C++ ad esempio converte le funzioni signals e slots in codice C++ al momento della compilazione. Un sunto lo trovate nell’immagine di seguito:

MOC

Il mio prossimo webinar sarà il 13 di marzo 🙂 per cui potete stare tranquilli per un po’. 😛

QT – WEBINAR: Getting Started with Qt/C++ Programming. Parte 1

Che dire… con le QT (si pronuncia cute) è stato amore a prima vista 🙂 spero mia moglie non scopra con chi passo le notti 😀 Per cui dato che hanno risvegliato i miei “peggiori istinti Nerd” 🙂 mi sono iscritto ad un paio di webminar. Se vi interessano i webinar li trovate qui mentre i link ai miei precedenti post sono questo e questo.
Vi riporto di seguito alcune info/tips che ho ascoltato nel webinar “Getting Started with Qt/C++ Programming”.

Il segreto della portabilità è qmake che genera il makefile adatto al sistema opertativo usato. Ottenuto il make file si potrà passare alla compilazione. Questo è il mio output di qmake in ambiente windows, notate il parametro “-spec win32-g++”:

14:22:22: Running steps for project DBTest...
14:22:22: Starting: "C:\Qt\Qt5.0.1\5.0.1\mingw47_32\bin\qmake.exe" C:\Users\wildwolf\Desktop\DBTest\DBTest\DBTest.pro -r -spec win32-g++ "CONFIG+=debug" "CONFIG

+=declarative_debug" "CONFIG+=qml_debug"
14:22:25: The process "C:\Qt\Qt5.0.1\5.0.1\mingw47_32\bin\qmake.exe" exited normally.

La compilazione invece è stata questa. Notate l’utilizzo di “mingw32-make.exe” che è il “Minimalist GNU for Windows” cioè il compilatore GNU minimale per winzoz.

14:24:10: Running steps for project DBTest...
14:24:11: Starting: "C:\Qt\Qt5.0.1\Tools\MinGW\bin\mingw32-make.exe" clean
C:/Qt/Qt5.0.1/Tools/MinGW/bin/mingw32-make -f Makefile.Debug clean
mingw32-make[1]: Entering directory 'C:/Users/wildwolf/Desktop/DBTest/DBTest-build-Desktop_Qt_5_0_1_MinGW_32bit-Debug'
rm -f debug/moc_mainwindow.cpp
rm -f ui_mainwindow.h
rm -f debug/main.o debug/mainwindow.o debug/moc_mainwindow.o
mingw32-make[1]: Leaving directory 'C:/Users/wildwolf/Desktop/DBTest/DBTest-build-Desktop_Qt_5_0_1_MinGW_32bit-Debug'
C:/Qt/Qt5.0.1/Tools/MinGW/bin/mingw32-make -f Makefile.Release clean
mingw32-make[1]: Entering directory 'C:/Users/wildwolf/Desktop/DBTest/DBTest-build-Desktop_Qt_5_0_1_MinGW_32bit-Debug'
rm -f release/moc_mainwindow.cpp
rm -f ui_mainwindow.h
rm -f release/main.o release/mainwindow.o release/moc_mainwindow.o
mingw32-make[1]: Leaving directory 'C:/Users/wildwolf/Desktop/DBTest/DBTest-build-Desktop_Qt_5_0_1_MinGW_32bit-Debug'
14:24:37: The process "C:\Qt\Qt5.0.1\Tools\MinGW\bin\mingw32-make.exe" exited normally.
14:24:37: Configuration unchanged, skipping qmake step.
14:24:37: Starting: "C:\Qt\Qt5.0.1\Tools\MinGW\bin\mingw32-make.exe" 
C:/Qt/Qt5.0.1/Tools/MinGW/bin/mingw32-make -f Makefile.Debug
mingw32-make[1]: Entering directory 'C:/Users/wildwolf/Desktop/DBTest/DBTest-build-Desktop_Qt_5_0_1_MinGW_32bit-Debug'
C:/Qt/Qt5.0.1/5.0.1/mingw47_32/bin/uic.exe ../DBTest/mainwindow.ui -o ui_mainwindow.h
g++ -c -pipe -fno-keep-inline-dllexport -g -frtti -Wall -Wextra -fexceptions -mthreads -DUNICODE -DQT_QML_DEBUG -DQT_DECLARATIVE_DEBUG -DQT_WIDGETS_LIB -DQT_SQL_LIB -
DQT_GUI_LIB -DQT_CORE_LIB -DQT_OPENGL_ES_2 -DQT_OPENGL_ES_2_ANGLE -DQT_NEEDS_QMAIN -I../DBTest -I'../../../../../Qt/Qt5.0.1/5.0.1/mingw47_32/include' -
I'../../../../../Qt/Qt5.0.1/5.0.1/mingw47_32/include/QtWidgets' -I'../../../../../Qt/Qt5.0.1/5.0.1/mingw47_32/include/QtSql' -
I'../../../../../Qt/Qt5.0.1/5.0.1/mingw47_32/include/QtGui' -I'../../../../../Qt/Qt5.0.1/5.0.1/mingw47_32/include/QtCore' -I'debug' -I'.' -I'.' -
I'../../../../../Qt/Qt5.0.1/5.0.1/mingw47_32/mkspecs/win32-g++' -o debug/main.o ../DBTest/main.cpp
g++ -c -pipe -fno-keep-inline-dllexport -g -frtti -Wall -Wextra -fexceptions -mthreads -DUNICODE -DQT_QML_DEBUG -DQT_DECLARATIVE_DEBUG -DQT_WIDGETS_LIB -DQT_SQL_LIB -
DQT_GUI_LIB -DQT_CORE_LIB -DQT_OPENGL_ES_2 -DQT_OPENGL_ES_2_ANGLE -DQT_NEEDS_QMAIN -I../DBTest -I'../../../../../Qt/Qt5.0.1/5.0.1/mingw47_32/include' -
I'../../../../../Qt/Qt5.0.1/5.0.1/mingw47_32/include/QtWidgets' -I'../../../../../Qt/Qt5.0.1/5.0.1/mingw47_32/include/QtSql' -
I'../../../../../Qt/Qt5.0.1/5.0.1/mingw47_32/include/QtGui' -I'../../../../../Qt/Qt5.0.1/5.0.1/mingw47_32/include/QtCore' -I'debug' -I'.' -I'.' -
I'../../../../../Qt/Qt5.0.1/5.0.1/mingw47_32/mkspecs/win32-g++' -o debug/mainwindow.o ../DBTest/mainwindow.cpp
C:/Qt/Qt5.0.1/5.0.1/mingw47_32/bin/moc.exe -DUNICODE -DQT_QML_DEBUG -DQT_DECLARATIVE_DEBUG -DQT_WIDGETS_LIB -DQT_SQL_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_OPENGL_ES_2 -
DQT_OPENGL_ES_2_ANGLE -DQT_NEEDS_QMAIN -I../DBTest -I'../../../../../Qt/Qt5.0.1/5.0.1/mingw47_32/include' -I'../../../../../Qt/Qt5.0.1/5.0.1/mingw47_32/include/QtWidgets' 
-I'../../../../../Qt/Qt5.0.1/5.0.1/mingw47_32/include/QtSql' -I'../../../../../Qt/Qt5.0.1/5.0.1/mingw47_32/include/QtGui' -
I'../../../../../Qt/Qt5.0.1/5.0.1/mingw47_32/include/QtCore' -I'debug' -I'.' -I'.' -I'../../../../../Qt/Qt5.0.1/5.0.1/mingw47_32/mkspecs/win32-g++' -D__GNUC__ -DWIN32 
../DBTest/mainwindow.h -o debug/moc_mainwindow.cpp
g++ -c -pipe -fno-keep-inline-dllexport -g -frtti -Wall -Wextra -fexceptions -mthreads -DUNICODE -DQT_QML_DEBUG -DQT_DECLARATIVE_DEBUG -DQT_WIDGETS_LIB -DQT_SQL_LIB -
DQT_GUI_LIB -DQT_CORE_LIB -DQT_OPENGL_ES_2 -DQT_OPENGL_ES_2_ANGLE -DQT_NEEDS_QMAIN -I../DBTest -I'../../../../../Qt/Qt5.0.1/5.0.1/mingw47_32/include' -
I'../../../../../Qt/Qt5.0.1/5.0.1/mingw47_32/include/QtWidgets' -I'../../../../../Qt/Qt5.0.1/5.0.1/mingw47_32/include/QtSql' -
I'../../../../../Qt/Qt5.0.1/5.0.1/mingw47_32/include/QtGui' -I'../../../../../Qt/Qt5.0.1/5.0.1/mingw47_32/include/QtCore' -I'debug' -I'.' -I'.' -
I'../../../../../Qt/Qt5.0.1/5.0.1/mingw47_32/mkspecs/win32-g++' -o debug/moc_mainwindow.o debug/moc_mainwindow.cpp
g++ -Wl,-subsystem,windows -mthreads -o debug/DBTest.exe debug/main.o debug/mainwindow.o debug/moc_mainwindow.o  -lmingw32 -lqtmaind -LC:\Qt\Qt5.0.1\5.0.1\mingw47_32\lib 
-lQt5Widgetsd -lQt5Sqld -lQt5Guid -lQt5Cored -llibEGLd -llibGLESv2d -lgdi32 -luser32 
mingw32-make[1]: Leaving directory 'C:/Users/wildwolf/Desktop/DBTest/DBTest-build-Desktop_Qt_5_0_1_MinGW_32bit-Debug'
14:25:07: The process "C:\Qt\Qt5.0.1\Tools\MinGW\bin\mingw32-make.exe" exited normally.

Ed in ambiente GNU Linux? 🙂 Tutto uguale a parte che qmake imposta il make file per linux e quindi trovate il parametro “-spec linux-g++”

14:43:38: Running steps for project DBTest...
14:43:38: Starting: "/usr/bin/qmake" /home/wildwolf/Documenti/Projects/CPP/DBTest/DBTest/DBTest.pro -r -spec linux-g++ CONFIG+=debug CONFIG+=declarative_debug
14:43:40: The process "/usr/bin/qmake" exited normally.

Non vi riporto la sbrodolata della compilazione 🙂 … e non cercate di farmi credere che qualcuno ha letto quella di prima 😛
Ok andiamo oltre la compilazione… Per chi soffre di insonnia da memory leak e sindrome da delete, c’è una bella notizia. Quando si alloca un puntatore di un oggetto QT e questo ha un padre, cioè, vi riporto le parole del docente del webinar, “nel costruttore dell’oggetto viene passato come ultimo parametro un oggetto padre” allora questo si occuperà di deallocare la memoria, se poi noi siamo presi da forme di “delete” compulsivo, un oggetto figlio comunicherà comunque al padre la sua deallocazione e quindi non verrà eliminato 2 volte 🙂 come dire… “quasi salvi” 😛

Quindi il codice del nostro applicativo base con 2 bottoni sarà:

#include <QApplication>
#include <QtGui>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QWidget window;

    QPushButton *button1 = new QPushButton("Ciao", &window);
    QPushButton *button2 = new QPushButton("Esci",&window);
    QHBoxLayout *layout = new QHBoxLayout();

    layout->addWidget(button1);
    layout->addWidget(button2);

    window.setLayout(layout);
    window.show();

    return a.exec();
}

Il nostro mini applicativo… va beh finestra con 2 bottoni 🙂 farà quindi vedere 2 bottoni 🙂 come in figura:

Applicazione

e dato che abbiamo applicato un layout orizzontale, allargando la finestra otterremo un risultato simile:

Applicazione strechata

button1 e button2 avendo definito un parent saranno deallocati in automatico e il layout verrà sempre eliminato da window perché impostato con la setLayout. E window? Beh è allocato sullo stack 🙂 per cui alla chiusura del programma ci saluterà senza recriminare alcunchè 🙂

Avremmo potuto scrivere il codice anche così, assegnando un parent al nostro layout.

#include <QApplication>
#include <QtGui>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QWidget window;

    QPushButton *button1 = new QPushButton("Ciao", &window);
    QPushButton *button2 = new QPushButton("Esci", &window);
    QHBoxLayout *layout = new QHBoxLayout(&window);

    layout->addWidget(button1);
    layout->addWidget(button2);

    //window.setLayout(layout);
    window.show();

    return a.exec();
}

Per adesso è tutto. La prossima volta faremo fare qualche cosa con i 2 bottoni. Per concludere una notiziona… le QT 5.2 dovrebbero permettere di creare applicativi Android, si troverà una beta delle API con la versione 5.1

Errata Corrige

Ebbene si, mi sono sbagliato. Nel mio articolo dove descrivevo la migrazione da Oracle a Postgres ho sbagliato il sorgente della funzione get_sysdate. Nella definizione della funzione non andavano messe le parentesi tonde (). Altrimenti Oracle non compila la funzione… Questo è il codice giusto:

create or replace function get_sysdate return date is
 Result date;
begin
 Select sysdate into Result from dual;
 return(result);
end get_sysdate;

Il classico errore di copia e incolla 😦 o almeno spero non sia la vecchiaia 😉 ho già corretto il codice nell’articolo originale… a presto e senza errori spero!

Ancora QT e ancora database.

Comincio subito con il ringraziare b3h3m0th che mi ha segnalato che le QT non sono più di Nokia dalla scorsa estate e adesso appartengono a digia.

Dopo essere rimasto favorevolmente impressionato dalle QT ho deciso di approfondirle ulteriormente andando a complicare l’esempio di collegamento al database. L’idea era di avere una griglia con l’elenco dei record e un dettaglio separato (in modo da sincronizzare il record attivo sulla griglia con una parte separata ma dipendente)
La finestra ha l’aspetto in figura.

Il nostro primo risultato

Lo schema del database era il seguente:

CREATE TABLE tblProvincia
(cod character varying(2) NOT NULL,
 des character varying(40)
);

CREATE TABLE tbllocalita
(
  cod character varying(4) NOT NULL,
  des character varying(40),
  fk_provincia_cod character varying(2)
);

Il codice C++ invece è il seguente:

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

#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())
    {
        QSqlRelationalTableModel *model = new QSqlRelationalTableModel;

        model->setTable("tbllocalita");
        provIndex = model->fieldIndex("fk_provincia_cod");
        model->setRelation(provIndex, QSqlRelation("tblprovincia", "cod", "des"));
        model->setHeaderData(0, Qt::Horizontal, tr("Codice"));
        model->setHeaderData(1, Qt::Horizontal, tr("Descrizione"));
        model->setHeaderData(2, Qt::Horizontal, tr("Provincia"));
        model->select();

        ui->tblData->setModel(model);

        QSqlTableModel *relModel = model->relationModel(provIndex);
        ui->qcbProvincia->setModel(relModel);
        ui->qcbProvincia->setModelColumn(relModel->fieldIndex("des"));

        QDataWidgetMapper *mapper = new QDataWidgetMapper;
        mapper->setModel(model);
        mapper->setItemDelegate(new QSqlRelationalDelegate(this));
        mapper->addMapping(ui->pteCod, 0);
        mapper->addMapping(ui->pteDes, 1);
        mapper->addMapping(ui->qcbProvincia, provIndex);

        mapper->toFirst();
        connect(ui->tblData->selectionModel(), SIGNAL(currentRowChanged(QModelIndex,QModelIndex)), mapper, SLOT(setCurrentModelIndex(QModelIndex)));

    }
}

void MainWindow::on_btnExit_clicked()
{
    QApplication::quit();
}

Rispetto al mio precedente esempio ci sono poche modifiche. La prima è che ho dovuto usare un modello dati di tipo QSqlRelationalTableModel. In questo modo ho potuto impostare una relazione tra il campo fk_provincia_cod della tabella tbllocalita e il codice della tblprovincia in modo da salvare il codice come dato ma vedere la sua descrizione come visualizzazione. In parole povere la classica normalizzazione della base dati.
Usando un modello di tipo QSqlRelationalTableModel ho dovuto eliminare la query ed impostare una tabella di partenza:

model->setTable("tbllocalita");

ed impostarne la relazione con la tblprovincia:

provIndex = model->fieldIndex("fk_provincia_cod");
model->setRelation(provIndex, QSqlRelation("tblprovincia", "cod", "des"));

Attenzione… se siete tentati come me nella prima versione a non dichiarare una variabile provIndex di tipo int e ad usare ovunque direttamente

model->fieldIndex("fk_provincia_cod")

non fatelo… otterreste un bruttissimo errore di “QComboBox::setModel: cannot set a 0 model” a runtime alla riga:

ui->qcbProvincia->setModelColumn(relModel->fieldIndex("des"));

Sinceramente non capisco il perché dell’errore… se qualcuno ha dei suggerimenti e ben accetto 🙂

Dopo di che ho usato un QDataWidgetMapper per legare i widget alla sorgente dati:

QDataWidgetMapper *mapper = new QDataWidgetMapper;
mapper->setModel(model);
mapper->setItemDelegate(new QSqlRelationalDelegate(this));
mapper->addMapping(ui->pteCod, 0);
mapper->addMapping(ui->pteDes, 1);
mapper->addMapping(ui->qcbProvincia, provIndex);

E per ultimo per far si che allo spostarsi della cella attiva sulla griglia venissero aggiornati i dati dei widget sotto la stessa ho creato una connessione tra il segnale dell’oggetto mittente (sender) e il metodo dell’oggetto ricevente (receiver).

connect(ui->tblData->selectionModel(), SIGNAL(currentRowChanged(QModelIndex,QModelIndex)), mapper, SLOT(setCurrentModelIndex(QModelIndex)));

Come si vede nella finestra anche la combo è popolata con le descrizioni giuste.

Una bella combo box con i miei dati

Le istruzioni per caricare i dati nella combo sono state le seguenti:

ui->qcbProvincia->setModel(relModel);
ui->qcbProvincia->setModelColumn(relModel->fieldIndex("des"));
...
...
mapper->addMapping(ui->qcbProvincia, provIndex);

E se si volesse fare un collegamento master detail? Altra prassi comune in applicativi database oriented… le cose 🙂 rimangono ancora semplici.

Master - Detail

Le tabelle usate hanno la seguente struttura:

CREATE TABLE tblPersona (
  ID INTEGER,
  NOME character varying(40),
  PRIMARY KEY (ID)
);

CREATE TABLE tblFilm (
  ID INTEGER,
  FK_PERSONA_ID INTEGER,
  TITOLO character varying(40),
  PRIMARY KEY (ID),
  FOREIGN KEY (FK_PERSONA_ID) REFERENCES tblPersona
);

Il codice C++ è invece il seguente:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

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

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_btnExit_clicked();
    void updateDetailGrid();
    void on_btnOpenTable_clicked();

private:
    Ui::MainWindow *ui;

    QSqlDatabase db;
    QSqlRelationalTableModel *masterModel;
    QSqlRelationalTableModel *detailModel;

};

e poi,

#include <QDir>
#include <QMessageBox>
#include <QSqlError>
#include <QSqlRelationalTableModel>
#include <QSqlRecord>

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

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    masterModel = NULL;
    detailModel = NULL;

    ui->setupUi(this);
}

MainWindow::~MainWindow()
{
    db.close();
    if (masterModel == NULL)
        delete masterModel;
    if (detailModel == NULL)
        delete detailModel;

    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::updateDetailGrid()
{
    QModelIndex index = ui->vieGridMaster->currentIndex();
    if (index.isValid())
    {
        QSqlRecord record = masterModel->record(index.row());
        int id = record.value("id").toInt();
        detailModel->setFilter(QString("fk_persona_id = %1").arg(id));
    }
    else
        detailModel->setFilter("fk_persona_id = -1");

    detailModel->select();
    ui->vieGridDetail->horizontalHeader()->setVisible(detailModel->rowCount() > 0);
}

void MainWindow::on_btnExit_clicked()
{
    QApplication::quit();
}

void MainWindow::on_btnOpenTable_clicked()
{
    if (db.isOpen())
    {
        masterModel = new QSqlRelationalTableModel;
        masterModel->setTable("tblpersona");
        masterModel->setHeaderData(0, Qt::Horizontal, tr("ID"));
        masterModel->setHeaderData(1, Qt::Horizontal, tr("Nome"));
        masterModel->select();

        detailModel = new QSqlRelationalTableModel;
        detailModel->setTable("tblfilm");
        detailModel->setHeaderData(0, Qt::Horizontal, tr("ID"));
        detailModel->setHeaderData(1, Qt::Horizontal, tr("fk_Perosna_id"));
        detailModel->setHeaderData(2, Qt::Horizontal, tr("Titolo"));
        detailModel->select();

        ui->vieGridMaster->setModel(masterModel);
        ui->vieGridDetail->setModel(detailModel);

        connect(ui->vieGridMaster->selectionModel(), SIGNAL(currentRowChanged(QModelIndex,QModelIndex)), this, SLOT(updateDetailGrid()));
        ui->vieGridMaster->selectRow(0);
    }
}

Si creano 2 datamodel di tipo QSqlRelationalTableModel. Dopo di che si crea un evento che collega il cambio di riga selezionata sulla griglia master e lo comunica alla funzione che si occuperà di aggiornare il dettaglio, la funzione updateDetailGrid. Quest’ultima per aggiornare i propri dati crea un filtro sul suo modello dati e aggiorna l’estrazione. Veramente facile 🙂 dopo che si ha acquisito un po’ di esperienza.

Per concludere il mio parere personale è che QT sia un’ottima libreria per lo sviluppo di applicativi client multipiattaforma (insomma mi ha dato una bella soddisfazione usarla per questi test 🙂 ).E’ sicuramente ben studiata e soprattutto va studiata… e con l’avvento di internet (e Google) almeno io tendo a cercare la soluzione piuttosto che leggermi i manuali, per cui l’occhio sui QDataWidgetMapper mi è caduto dopo vari tentativi e ore perse a leggermi vari post. La parte di accesso all’SQL sembra meno curata del resto (ma è la mia prima impressione da utilizzatore di altri framework). L’utilizzo di un filtro per il master/detail funziona bene con pochi record… ma se questi aumentassero? Sicuramente ci sono altri framwork di sviluppo (ad esempio Delphi e la sua VCL) che sono più flessibili e sofisticati (anche nell’integrazione con l’IDE), ma evidentemente gli sviluppatori QT hanno deciso di porre l’enfasi su altri aspetti del framework e in tutta franchezza CHE ASPETTI 🙂

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 🙂