Rapid-Q by William Yu (c)1999-2000 .

Back to holyguard.net

Capitolo 10: Creazione di componenti personalizzati

10. Creazione di componenti personalizzati

La creazione di componenti personalizzati può essere di aiuto se si desidera che i propri form abbiano un aspetto particolare (e per altre cose). Per esempio, invece delle checkbox standard di Windows, potreste preferire creare checkbox a forma di diamante (vedi fine del capitolo). Dopo aver letto questo capitolo, sarete in grado di creare i vostri componenti totalmente riutilizzabili, perfino form personalizzati! Cominciamo da un semplice esempio di una checkbox a diamante.




10.1 Introduzione alla creazione di oggetti
Nel creare oggetti personalizzati non reinventerete la ruota. Per creare i vostri oggetti usate il comando EXTEND su un componente (come QCanvas). Ciò significa che tutte le sue proprietà, i suoi metodi ed eventi saranno ereditati. Successivamente, potrete scegliere di ignorare le sue proprietà e/o metodi, o crearne di nuovi. Il concetto è veramente facile da capire, una volta presa un po' di confidenza. Il metodo di creazione dei componenti di Rapid-Q non ricorda alcun altro linguaggio, ma non è nemmeno troppo strano. Dal momento che la creazione di oggetti non è materia per principianti, vi consiglio di familiarizzare con i concetti di proprietà, metodi ed eventi. Avrete anche bisogno di una buona comprensione dei Tipi, dal momento che eventi e metodi compresi i tipi sono concetti nuovi. Non confondetevi con altri linguaggi OOP, Rapid-Q non adotta membri sovraccaricanti, protetti o virtuali, ma deriva diverse idee da altri linguaggi OOP come Delphi, C++ e Java.

10.2 Estendere QCanvas
QCanvas è un utile componente 'foglio bianco'. Permette di disegnarvi qualunque cosa desideriate, e ciò lo rende il componente più adatto per essere esteso. Prima di tutto, come si fa esattamente ad estenderlo?
     TYPE QDiamondBox EXTENDS QCanvas
     END TYPE
Wow, è stato facile. Il codice sopraindicato è corretto, per quanto non faccia niente di utile. L'unica cosa che ha fatto è stato creare un nuovo oggetto chiamato QDiamondBox che ha ereditato tutte le proprietà, metodi, ed eventi di QCanvas. Adesso possiamo dichiararlo con DIM ed assegnarvi dei valori:
     DIM DBox AS QDiamondBox
         DBox.Top = 10
Okay, questo non è molto utile. Qualche volta vorremo aggiungere delle proprietà ad un componente esistente, e nell'esempio precedente aveva probabilmente bisogno di una proprietà Caption (Titolo), e forse anche Checked (Selezionato), dal momento che stiamo implementando un componente di tipo checkbox. L'aggiunta di proprietà non presenta niente di nuovo:
     TYPE QDiamondBox EXTENDS QCanvas
        Caption AS STRING
        Checked AS INTEGER
     END TYPE
Come vedete, sembra un qualsiasi UDT (tipo definito dall'utente), ben conosciuto dalla maggior parte dei programmatori Basic. A questo punto abbiamo aggiunto due proprietà addizionali al nostro nuovo componente, e possiamo accedervi come a qualunque altra proprietà. Bene, e se volete che i valori siano inizializzati quando viene creato? Non è un problema, non serve un costruttore come in C++, è sufficiente inserire questa informazione in questo modo:
     TYPE QDiamondBox EXTENDS QCanvas
        Caption AS STRING
        Checked AS INTEGER

        CONSTRUCTOR
          Caption = 'DiamondBox'
          Checked = 0
        END CONSTRUCTOR
     END TYPE
Dopo ogni DIM (cioè quando viene creato l'oggetto), vengono inizializzati i valori di default. Rapid-Q non richiede alcun DESTRUCTOR, in quanto tutto viene automaticamente ripulito alla fine del programma. Se non siete convinti che questo funzioni, provate:
     DIM DBox AS QDiamondBox
     
     ShowMessage(DBox.Caption)
Se non restituisce 'DiamondBox' qualcosa non ha funzionato, controllate il codice e provate con $TYPECHECK ON.

10.3 Aggiungere/Superare Metodi
In Rapid-Q, tutti i metodi, proprietà, ed eventi possono essere superati. Infatti, è possibile superare un metodo e trattarlo come se fosse una proprietà o un evento personalizzato. Una volta superato un metodo/proprietà non sarà più possibile accedere a quelli ereditati (dal momento che sono nascosti). Per accedere a questa proprietà o metodo, è necessario chiamare esplicitamente la classe SUPER come sotto indicato. Fate attenzione all'utilizzo di WITH Super ... END WITH, dal momento che tutte le proprietà e i metodi saranno riferiti alla classe superiore. Il concetto di superare le proprietà o i metodi è abbastanza semplice, invece di utilizzare le proprietà ereditate è possibile semplicemente ridefinirle.
     TYPE QDiamondBox EXTENDS QCanvas
        Left AS STRING
        Top AS BYTE

        SUB Pset
            PRINT Super.Left
        END SUB
     END TYPE
In questo esempio stiamo superando due proprietà di QCanvas, ed un metodo. Come vedrete, QDiamondBox ha una proprietà denominata Left che supera la proprietà Left di QCanvas. Per utilizzare le proprietà Left della classe superiore, utilizziamo semplicemente la chiamata Super.Left anziché QDiamondBox.Left. Il superamento di metodi e proprietà può essere utile in molti casi. Considerate una listbox filtrata. È possibile superare il metodo AddItems e creando uno ad hoc filtrando determinate parole chiave che non volete che vengano incluse. È utile anche quando trasferite vostro codice Windows in Linux e viceversa. In questo modo, potete avere il vostro componente 'standard' per entrambe le versioni. Comunque, non approfondiremo ulteriormente questo argomento. Passiamo alla creazione di nuovi metodi per i nostri componenti:
     TYPE QDiamondBox EXTENDS QCanvas
       Caption AS STRING
       Checked AS INTEGER

       FUNCTION TextSize AS INTEGER
         Result = LEN(QDiamondBox.Caption)
         'QDiamondBox.TextSize = LEN(QDiamondBox.Caption)
       END FUNCTION        
     END TYPE
Questo esempio è un buon punto di partenza. Abbiamo appena definito un nuovo metodo per il nostro componente QDiamondBox. Come avrete notato, dobbiamo comprendere le nostre funzioni o sub nella nostra dichiarazione di TYPE. Questo è chiamato codice INLINE, e dovrebbe essere familiare a chi ha utilizzato C++ o altro linguaggio OOP. Rapid-Q richiede il codice INLINE, non è possibile definire la vostra SUB e puoi avere la SUB stessa al di fuori della definizione del TYPE. Bene, l'unica cosa da notare è QDiamondBox.Caption; che cosa fa esattamente? Bene, vi starere probabilmente chiedendo perché non abbiamo semplicemente scritto
      Result = LEN(Caption)
Bene, questo non funziona, in quanto nella nostra funzione avremmo potuto facilmente aggiungere
      DIM Caption AS INTEGER
A questo punto Rapid-Q sarebbe piuttosto confuso...Quale devo utilizzare, la variabile Caption locale o la nuova proprietà? Per semplificare le cose, quando volete utilizzare una proprietà o un metodo dovete anche farvi riferimento come fareste normalmente. Il nostro oggetto in questo caso è QDiamondBox (potete anche utilizzare la parola riservata this), e vogliamo fare riferimento alla proprietà Caption. Dal momento che il metodo sopraindicato è praticamente inutile nel nostro componente, lo ignoriamo semplicemente. Ciò di cui abbiamo veramente bisogno è un metodo per disegnare il nostro componente, in questo modo:

  SUB DrawComponent
    IF QDiamondBox.Checked THEN
      QDiamondBox.Line(QDiamondBox.Height/2,0,0,QDiamondBox.Height/2,0)
      QDiamondBox.Line(0,QDiamondBox.Height/2,QDiamondBox.Height/2,QDiamondBox.Height,0)
      QDiamondBox.Line(QDiamondBox.Height/2,0,QDiamondBox.Height,QDiamondBox.Height/2,0)
      QDiamondBox.Line(QDiamondBox.Height,QDiamondBox.Height/2,QDiamondBox.Height/2,QDiamondBox.Height,0)
      QDiamondBox.Paint(QDiamondBox.Height/2, QDiamondBox.Height/2, QDiamondBox.HiLightColor, 0)
      QDiamondBox.Line(QDiamondBox.Height/2,0,QDiamondBox.Height,QDiamondBox.Height/2,&HFFFFFF)
      QDiamondBox.Line(QDiamondBox.Height,QDiamondBox.Height/2,QDiamondBox.Height/2,QDiamondBox.Height,&HFFFFFF)
    ELSE
      QDiamondBox.Line(QDiamondBox.Height/2,0,0,QDiamondBox.Height/2,0)
      QDiamondBox.Line(0,QDiamondBox.Height/2,QDiamondBox.Height/2,QDiamondBox.Height,0)
      QDiamondBox.Line(QDiamondBox.Height/2,0,QDiamondBox.Height,QDiamondBox.Height/2,0)
      QDiamondBox.Line(QDiamondBox.Height,QDiamondBox.Height/2,QDiamondBox.Height/2,QDiamondBox.Height,0)
      QDiamondBox.Paint(QDiamondBox.Height/2, QDiamondBox.Height/2, &HBBBBBB, 0)
      QDiamondBox.Line(QDiamondBox.Height/2,0,0,QDiamondBox.Height/2,&HFFFFFF)
      QDiamondBox.Line(0,QDiamondBox.Height/2,QDiamondBox.Height/2,QDiamondBox.Height,&HFFFFFF)
    END IF
    QDiamondBox.TextOut(QDiamondBox.Height + 5, QDiamondBox.Height/2-QDiamondBox.Height/4, QDiamondBox.Caption, 0, -1)
  END SUB

Può sembrare orribile, ma un esame attento rivela che appare complicato a causa delle righe lunghe. Questo codice disegna un diamante e dimensiona il componente in modo appropriato cosicchè appaia correttamente quando viene ridimensionato. Ciò è molto importante, per evitare che il vostro componente sembri una miniatura quando l'utente specifica Height = 100, Width = 100, ecc... È sufficiente inserire il codice sopra indicato all'interno della dichiarazione del TYPE. È necessario inoltre definire un'altra proprietà, HiLightColor. Ogni qualvolta viene selezionata la nostra check box, probabilmente vorremmo che il nostro componente cambi colore per avvisare l'utente che è stato selezionato.

10.4 Definizione di eventi
È possibile definire nuovi eventi per i nostri componenti, ma di questo parleremo più avanti. Per il momento abbiamo tutto quello che serve per il nostro componente QDiamondBox, OnPaint, e OnClick. Che bisogno abbiamo di definire eventi? Perché non lasciamo che lo faccia l'utente? Certo, ma dal momento che questo è il nostro componente personalizzato, vogliamo che succedano alcune cose. Per esempio, il componente QCheckBox diventa automaticamente selezionato e non selezionato quando viene cliccato; non è stato necessario prevedere un evento per fare ciò. Questo è ciò che vogliamo che accada nel nostro componente personalizzato. Per cominciare, avremo bisogno di un evento OnPaint. Questo evento è chiamato ogni volta che il form o il componente vengono aggiornati.
     TYPE QDiamondBox EXTENDS QCanvas
       'Inserire qui le proprietà

       'Inserire qui il metodo DrawComponent

       EVENT OnClick
         IF QDiamondBox.Checked THEN
           QDiamondBox.Checked = 0
         ELSE
           QDiamondBox.Checked = 1
         END IF
         QDiamondBox.DrawComponent
       END EVENT

       EVENT OnPaint
         QDiamondBox.DrawComponent
       END EVENT
     END TYPE
Praticamente, abbiamo già spiegato perché bisogna fare riferimento a DrawComponent, ma è l'identificatore EVENT è nuovo. Dice semplicemente che stiamo creando un gestore EVENT per il nostro componente. Scrivete il vostro codice come fate normalmente. Ogni qualvolta viene creato (DIM) il nuovo componente, gli eventi vengono registrati automaticamente per noi. Vengono chiamati quando si verifica lo specifico evento. Fin qui tutto bene, e se ho bisogno di scrivere un nuovo gestore di evento? Nel nostro esempio QDiamondBox, dovremo sicuramente ridefinire OnClick, in quanto vogliamo sapere se l'utente ha selezionato o deselezionato la DiamondBox. A questo punto le cose si complicano. Siccome non vogliamo scartare la nostra precedente dichiarazione di OnClick, dovremo ereditarla.
     DIM DBox AS QDiamondBox

     SUB NewDBoxOnClick
         DBox.InheritOnClick
         ' inserire codice qui
     END SUB

     DBox.OnClick = NewDBoxOnClick
Come avrete notato, per evitare l'evento OnClick vi facciamo riferimento utilizzando QObject.Inherit, dove EventName è l'evento che vogliamo ereditare. In realtà è possibile ereditare OnPaint in questo esempio, ma ciò non avrebbe alcuno scopo. Se l'evento ereditato non viene trovato (ad esempio perché non vi era alcuna dichiarazione precedente), riceverete un messaggio di errore in compilazione.

10.5 Aggiunta di componenti al vostro componente (composizione)
Nel nostro esempio, non è necessario includere componenti nel nostro nuovo componente QDiamondBox, ma dal momento che questo è spesso utile, ne parliamo in questa sezione. È possibile aggiungere solo componenti Rapid-Q, e non quelli personalizzati. Anche in questo caso, non c'è niente di nuovo nel metodo per aggiungere componenti o proprietà:
     TYPE QGenericForm AS QForm
       Panel AS QPanel
     END TYPE

     DIM GF AS QGenericForm
         GF.Panel.Left = 123
Notate come facciamo riferimento ad un oggetto all'interno di un oggetto. Il primo punto serve per fare riferimento all'oggetto Panel, ed il secondo per fare riferimento alle proprietà e metodi di Panel. Se desiderate che vostro Panel abbia dei valori di default, utilizzate il metodo CONSTRUCTOR come prima:
     TYPE QGenericForm AS QForm
       Panel AS QPanel

       CONSTRUCTOR
          Panel.Parent = QGenericForm
          Panel.Width = QGenericForm.ClientWidth
          Panel.Height = QGenericForm.ClientHeight
       END CONSTRUCTOR
     END TYPE
Avete notato probabilmente che anche se abbiamo in qualche modo sottointeso che il Panel sarà parte del form, è comunque necessario assegnargli la proprietà Parent, altrimenti non sarà visibile. Ciò è abbastanza semplice, e per quanto riguarda la definizione dei suoi eventi? Non c'è problema:
     TYPE QGenericForm AS QForm
       Panel AS QPanel

       EVENT Panel.OnClick
         ShowMessage('L'utente ha cliccato sul pannello')
       END EVENT

       EVENT OnClick
         ShowMessage('L'utente ha cliccato sul form')
       END EVENT
     END TYPE
Notate che c'è un riferimento (punto) in più. Se questo viene omesso, l'evento OnClick sarà associato al vostro QGenericForm. Come prima, per ereditare eventi dovrete fare così:
     GF.Panel.OnClick = GF.Panel.InheritOnClick
Normalmente, non ci sarà quasi mai bisogno di accedere ad un componente all'interno di un altro componente. Per esempio, potete creare il vostro form personalizzato, con i bottoni per chiuderlo e ridimensionarlo senza riscrivere i gestori degli eventi.

10.6 Estensione di un componente vuoto
In alcuni casi non avrete bisogno di estendere alcun componente, e dovrete solo scrivere il vostro oggetto interfaccia. Ad esempio si può incrementare un oggetto interfaccia QIniFile. Magari prevedere proprietà e metodi per accedere e scrivere facilmente nel vostro file .INI nel modo che preferite. Per fare ciò Rapid-Q fornisce un oggetto vuoto:
    TYPE QIniFile EXTENDS QObject
       Size AS INTEGER
       FileName AS STRING

       SUB SaveINI
       END SUB
       SUB LoadINI
       END SUB
    END TYPE
Questo genere di oggetto si comporta come QSocket. Non è un componente visibile, è essenzialmente un oggetto interfaccia. Gli oggetti forniscono un'interfaccia più semplice e più elegante (non che questo mi interessi molto in realtà). Concludendo, questo è praticamente tutto quello che dovete sapere per aggiungere i vostri componenti a Rapid-Q, è piuttosto semplice da capire.

10.7 Proprietà e metodi pubblici e privati
Tutte le proprietà e metodi sono PUBLIC per default. Ecco come definire l'area di validità delle vostre proprietà e metodi:
     TYPE QText EXTENDS QObject
       PUBLIC:
         I AS INTEGER
         X AS INTEGER

       PRIVATE:
         Y AS INTEGER

       PUBLIC:
         SUB Test
            QText.X = QText.Y + 1
         END SUB
     END TYPE
Le proprietà I e X sono PUBLIC, cioè possono essere utilizzate al di fuori della loro area di validità (per esempio al di fuori di TYPE). Lo stesso vale per il metodo Test. Tuttavia, la proprietà Y è PRIVATE, e non può essere utilizzata al di fuori della sua area di validità. Dal momento che SUB Test è all'interno dell'area di Y (cioè all'interno di TYPE), è possibile utilizzarla lì. È possibile definire proprietà e metodi PROTECTED, ma funzionano esattamente come proprietà e metodi PUBLIC in quanto (per il momento) non è possibile estendere un tipo personalizzato. Per chi non ha una cultura OOP, la ragione per cui esistono membri PUBLIC e PRIVATE è essenzialmente a beneficio dell'utente finale. Saprete certamente quali membri non debbano essere utilizzati al di fuori della loro area di validità, ma definendo membri PRIVATE istruite i vostri utenti a non toccarli nei vostri programmi in quanto vengono utilizzati 'internamente'.

10.8 Schemi e gruppi di proprietà
Non abbiamo ancora parlato di come vengono creati gli eventi personalizzati, ci arriveremo presto. Tuttavia, ci sono alcune altre tecniche interessanti, una detta schema che potete usare nelle vostre definizioni di TYPE, ed un'altra detta guppo di proprietà. Arriveremo ai gruppi di proprietà più avanti questa sezione, cominciamo da gli schemi. Uno schema di definizione del tipo può sembrare strano, ma sono modellati più o meno in base a C++.
    TYPE NewClass EXTENDS QOBJECT
        N AS DataType
    END TYPE
Subito dopo aver dichiarato il nuovo tipo, si può definire lo schema . Nell'esempio precedente, il nostro solo parametro è DataType, ma se possono utilizzare un numero infinito. A differenza di C++, non si specifica il tipo di parametro, ma solo il nome. Quando si effettua la DIM del nuovo tipo, è necessario passargli il parametro extra previsto dallo schema:
    DIM MyClass1 AS NewClass
    DIM MyClass2 AS NewClass
Notate che la proprietà N di MyClass1 è legata ad un dato di tipo INTEGER, mentre MyClass2 ha una proprietà N legata ad un dato di tipo STRING. Ecco alcuni altri esempi:
    TYPE Arrays EXTENDS QOBJECT
        Item(Size) AS DataType

        SUB Clear
            DIM N AS DataType
            DIM I AS LONG
            DIM V AS VARIANT

            WITH Arrays
                V = .Item(0)
                IF VARTYPE(V) = 2 THEN
                    '-- String data type
                    FOR I = 0 TO Size
                        .Item(I) = ''
                    NEXT
                ELSE
                    '-- Integer/Float
                    FOR I = 0 TO Size
                        .Item(I) = 0
                    NEXT
                END IF
            END WITH
        END SUB
    END TYPE

    DIM IntArray AS ARRAYS
    DIM StrArray AS ARRAYS

    IntArray.Clear
    IntArray.Item(1) = 99
    StrArray.Clear
    StrArray.Item(1) = 'Ciao mondo'

    PRINT IntArray.Item(1)
    PRINT StrArray.Item(1)
L'utilizzo di schemi può aiutare a ridurre la quantità di codice riutilizzando lo stesso TYPE per le nostre matrici INTEGER e STRING. Ora che abbiamo parlato di schemi, passiamo ai gruppi di proprietà. Un gruppo di proprietà non è altro che una raccolta di proprietà con la differenza che è possibile fare delle elaborazioni particolari quando un utente assegna la proprietà. Cominciamo con un esempio abbastanza inutile:
    TYPE TForm EXTENDS QFORM
        Focus AS LONG PROPERTY SET Set_Focus

        PROPERTY SET Set_Focus (Handle AS LONG)
            WITH TForm
                .Focus = Handle
                IF .Focus THEN
                    SetFocus(Handle)
                END IF
            END WITH
        END PROPERTY
    END TYPE

    CREATE Form AS TForm
        CREATE Edit AS QEDIT
        END CREATE
        Focus = Edit.Handle
    END CREATE
Non considerate la funzione SetFocus,si tratta solo di una chiamata API. Nell'esempio precedente, Focus è un gruppo di proprietà, ogni qualvolta viene assegnato un valore a questa proprietà viene automaticamente chiamata la routine Set_Focus come specificato nel parametro SET dove il parametro e il valore che viene assegnato. Naturalmente, potete chiedervi perché si possa aver bisogno di questa in quanto è equivalente a:
    TYPE TForm EXTENDS QFORM
        SUB Focus (Handle AS LONG)
            IF Handle THEN
               SetFocus(Handle)
            END IF
        END SUB
    END TYPE

    CREATE Form AS TForm
        CREATE Edit AS QEDIT
        END CREATE
        Focus(Edit.Handle)
    END CREATE
Come avrete notato che quest'esempio, dal momento che Focus è una SUB, non è possibile recuperare il valore (come nel nostro esempio precedente). In questo caso, dovrete usare un altro nome come GetFocus per verificare quale maniglia sia attiva. Nell'esempio presente è possibile leggere e scrivere la proprietà ed eseguire elaborazioni particolari quando la proprietà viene assegnata, che è ciò che volevamo. Prendete ad esempio come è cambiato BorderStyle in un QFORM: quando assegnamo un nuovo valore a BorderStyle, inizia una sequenza di eventi, ma dal momento che BorderStyle è un gruppo di proprietà non abbiamo bisogno di definire una SUB per determinare lo stile ed una FUNCTION per recuperare il valore.
    TYPE TForm EXTENDS QFORM
        GetStyle AS LONG
        SUB BorderStyle (Style AS LONG)
            TForm.GetStyle = Style
            SendMessage(TForm.Handle, etc...)
        END SUB
    END TYPE

    CREATE Form AS TFORM
        BorderStyle(bsNone)
        Caption = STR$(Form.GetStyle)
    END CREATE
Come vedete è un vero spreco, in questo caso è opportuno utilizzare gruppi di proprietà. Ovviamente questi non serviranno se non avete bisogno di alcun processo particolare quando assegnate un valore ad una proprietà. Se decidete di utilizzare gruppi di proprietà ci sono alcune regole da seguire: 1. La proprietà ed il parametro devono essere dello stesso tipo; 2. Solo tipi semplici e QObject possono essere gruppi di proprietà, sono esclusi matrici e UDT. Il compilatore di avvertirà se violerete queste regole, ma a volte i messaggi di errore non sono molto chiari.

10.9 Eventi personalizzati
Gli eventi personalizzati sono stabiliti dal programmatore, cioè voi. Proviamo a immaginare una situazione in cui è necessario inventare un evento. Prendiamo ad esempio il componente QSOCKET. È completamente privo di qualsiasi evento; e se vogliamo sapere quando il server è pronto? Ovviamente possiamo continuare ad interrogarlo per vedere quando ciò accade, ma non sarebbe meglio se fosse rilevato automaticamente, per poi scrivere un gestore di evento per fare quello che vi serve? Non scriverò in realtà l'esempio completo, ma solo la parte necessaria per dimostrare come creare eventi personalizzati:
    '-- Definire, ma non implementare, una funzione schema
    DECLARE SUB ServerReady_EventTemplate (Socket AS LONG)

    TYPE TSocket EXTENDS QSOCKET
        OnServerReady AS EVENT(ServerReady_EventTemplate)
        Timer1 AS QTIMER
        Sock AS LONG

        EVENT Timer1.OnTimer
            WITH TSocket
                IF .IsServerReady(.Sock) AND .OnServerReady > 0 THEN
                    '-- OnServerReady  > 0 serve per controllare se il puntatore è nullo
                    '-- per controllare se c'è un gestore di evento assegnato
                    '-- se assegnato, realizza l'evento
                    CALLFUNC(.OnServerReady, .Sock)
                END IF
            END WITH
        END EVENT

        CONSTRUCTOR
            OnServerReady = 0
            Timer1.Enabled = 1
            Timer1.Interval = 500
        END CONSTRUCTOR
    END TYPE

    SUB ServerReady (Sock AS LONG)
        PRINT Sock;' is ready.'
    END SUB

    CREATE Socket AS QSOCKET
        OnServerReady = ServerReady
    END CREATE
Può essere utile leggere il prossimo capitolo sui puntatori alle funzioni, dal momento che fondamentalmente è così che funzionano gli eventi personalizzati. OnServerReady è in pratica un puntatore ad una funzione, che punta ad una SUB (se assegnato, altrimenti è equivalente a zero). È richiesta una dichiarazione dello schema SUB, allo scopo di fornire a CALLFUNC i parametri necessari. Può sembrare un po' complicato all'inizio, e serve un po' di tempo per abituarsi alla sintassi e all'idea di realizzare un evento. Tuttavia, la creazione di eventi personalizzati presenta parecchi vantaggi ed utilizzi, ed apprendere come creare i vostri eventi potrà semplificare considerevolmente l'utilizzo dei vostri componenti o perfino estendere le funzionalità di altri (come nell'esempio sopra).

10.10 Codice sorgente di QDiamondBox
$APPTYPE GUI
$TYPECHECK ON

TYPE QDiamondBox EXTENDS QCanvas     '' È possibile estendere qualsiasi QObject
  '-- Nuove proprietà, si possono anche aggiungere componenti
  Caption AS STRING
  Checked AS INTEGER
  HiLightColor AS INTEGER

  '-- Non esistono metodi protetti, ma è meglio informare l'utente
    '-- PROTECTED (nel senso che è meglio non chiamarlo direttamente nel vostro programma).
  SUB DrawComponent
    IF QDiamondBox.Checked THEN
      QDiamondBox.Line(QDiamondBox.Height/2,0,0,QDiamondBox.Height/2,0)
      QDiamondBox.Line(0,QDiamondBox.Height/2,QDiamondBox.Height/2,QDiamondBox.Height,0)
      QDiamondBox.Line(QDiamondBox.Height/2,0,QDiamondBox.Height,QDiamondBox.Height/2,0)
      QDiamondBox.Line(QDiamondBox.Height,QDiamondBox.Height/2,QDiamondBox.Height/2,QDiamondBox.Height,0)
      QDiamondBox.Paint(QDiamondBox.Height/2, QDiamondBox.Height/2, QDiamondBox.HiLightColor, 0)
      QDiamondBox.Line(QDiamondBox.Height/2,0,QDiamondBox.Height,QDiamondBox.Height/2,&HFFFFFF)
      QDiamondBox.Line(QDiamondBox.Height,QDiamondBox.Height/2,QDiamondBox.Height/2,QDiamondBox.Height,&HFFFFFF)
    ELSE
      QDiamondBox.Line(QDiamondBox.Height/2,0,0,QDiamondBox.Height/2,0)
      QDiamondBox.Line(0,QDiamondBox.Height/2,QDiamondBox.Height/2,QDiamondBox.Height,0)
      QDiamondBox.Line(QDiamondBox.Height/2,0,QDiamondBox.Height,QDiamondBox.Height/2,0)
      QDiamondBox.Line(QDiamondBox.Height,QDiamondBox.Height/2,QDiamondBox.Height/2,QDiamondBox.Height,0)
      QDiamondBox.Paint(QDiamondBox.Height/2, QDiamondBox.Height/2, &HBBBBBB, 0)
      QDiamondBox.Line(QDiamondBox.Height/2,0,0,QDiamondBox.Height/2,&HFFFFFF)
      QDiamondBox.Line(0,QDiamondBox.Height/2,QDiamondBox.Height/2,QDiamondBox.Height,&HFFFFFF)
    END IF
    QDiamondBox.TextOut(QDiamondBox.Height + 5, QDiamondBox.Height/2-QDiamondBox.Height/4, QDiamondBox.Caption, 0, -1)
  END SUB

  '-- Eventi ereditati (spiacente, non è possibile creare nuovi eventi)
  '-- L'utente può comunque superare questi eventi, ma non è una buona idea.
    EVENT OnClick
    IF QDiamondBox.Checked THEN
      QDiamondBox.Checked = 0
    ELSE
      QDiamondBox.Checked = 1
    END IF
    QDiamondBox.DrawComponent
  END EVENT

  EVENT OnPaint
    QDiamondBox.DrawComponent
  END EVENT

  '-- Valori di default
  CONSTRUCTOR
    Height = 30
    Width = 100
    HiLightColor = &H00FF00
    Caption = 'DiamondBox'
    Checked = 0
  END CONSTRUCTOR
END TYPE



'----- Collaudiamo il nostro nuovo componente

DECLARE SUB DBox2Click

DIM Font AS QFont
    Font.Name = 'Arial'
    Font.Size = 10

CREATE Form AS QForm
  Center
  Height = 120
  Caption = 'Custom Check Boxes'
  CREATE DBox1 AS QDiamondBox
    Caption = 'Diamond Box 1'
    Left = 100
    Height = 20
  END CREATE
  CREATE DBox2 AS QDiamondBox
    Caption = 'Diamond Box 2'
    Top = 30
    Left = 100
    Height = 20
    Width = 140
    HiLightColor = &H0000FF
    Font = Font
    ShowHint = 1 ' True
    Hint = 'Click me'
    OnClick = DBox2Click
  END CREATE
  CREATE DBox3 AS QDiamondBox
    Caption = 'Diamond Box 3'
    Top = 60
    Left = 100
    Height = 20
  END CREATE
  ShowModal
END CREATE


SUB DBox2Click
  DBox2.InheritOnClick    '' Inherit event
  ShowMessage('Diamond Box 2 clicked')
END SUB