Tutorial - creare un componente: il frame

Da Gambas-it.org - Wikipedia.

Introduzione

Questo tutorial è di completamento alla guida di Come creare un componente e vuol essere di esempio per la realizzazione di un oggetto Frame, che andrà ad integrarsi come componente nell'ambiente di sviluppo del linguaggio di programmazione Gambas versione 3.

L'oggetto Frame, farà utilizzo di una classe genitore di tipo Container il quale verrà utilizzato per ereditare tutte le proprietà, metodi ed eventi che può un contenitore, di una Form che conterrà a sua volta una DrawingArea per il disegno dei bordi e del titolo, e di un Panel che sarà il reale contenitore di oggetti del Frame.

Creazione del Progetto

Una volta avviato Gambas, andiamo a selezionare le seguenti voci:

  • Tipo Progetto: Applicazione Grafica
  • Opzioni: Creazione di un componente

Dopo aver scelto la cartella che andrà a contenere il Progetto, inseriamo il titolo: gb.framebox.

A questo punto il nostro Esploratore di Progetti conterrà due classi di esempio che vengono generate di default, una Form di test e la cartella control dove risiederanno le icone dei vari oggetti del componente. Andiamo ad eliminare le due classi e le due icone, creando una nuova classe FrameBox, con opzione 'Esportato', e una Form a cui daremo il nome di FFrameBox.

Form FFrameBox

L'utilizzare una Form per la realizzazione di un oggetto grafico non è strettamente indispensabile in quanto si possono creare tutti gli oggetti grafici all'interno di una classe. Il solo scopo del suo utilizzo è per semplificare le operazioni di posizionamento e ridimensionamento degli oggetti che andranno a comporre il Frame.

Prima di tutto andiamo a posizionare la DrawingArea come primo strato del nostro oggetto, in quanto l'immagine che andrà a creare dovrà essere obbligatoriamente sul fondo, altrimenti non sarà possibile selezionare gli oggetti contenuti nel Frame. Dopodiché andremo ad inserire il Panel, senza preoccuparci della sua posizione.

Impostazioni degli oggetti

Le modifiche verranno apportate solamente alle proprietà sotto elencate, alle altre verranno lasciati i valori di default.

  • FFrameBox
Name = FFrameBox
Arrangemet = Fill

Impostando questa proprietà, gli oggetti figli con proprietà Expand = TRUE si andranno a ridimensionare in automatico alle dimensioni del Form.

  • DrawingArea
Name = DrawingArea1
Expand = TRUE
  • Panel
Name = Panel1
Expan = TRUE
Stesura del Codice

Come prima cosa si dovranno decidere le varie proprietà che andranno a variare graficamente il Frame:

  • Titolo
  • Colore testo
  • Tipo di carattere
  • Bordi visibili
  • Colore bordi

Dichiarazione delle Variabili Private

Private $Text As String
Private $TextColor As Integer
Private $Font As New Font
Private $Border As Boolean = True
Private $BorderColor As Integer

Essendo il codice 'di uso interno' andremo a creare solamente dei metodi per il settaggio di proprietà grafiche:

Public Sub SetText(Value As String)
  $Text = Value
  Me.DrawingArea1.Refresh()
End

Public Function GetText() As String
  Return $Text
End
 
Public Sub SetTexColor(Value As Integer)
  $TextColor = Value
  Me.DrawingArea1.Refresh()
End 

Public Function GetTextColor() As Integer
  Return $TextColor
End

Public Sub SetFont(Value As Font)
  $Font = Value
  Me.DrawingArea1.Refresh()
End

Public Function GetFont() As Font
  Return $Font
End

Public Sub SetBorder(Value As Boolean)
  $Border = Value
  Me.DrawingArea1.Refresh()
End

Public Function GetBorder() As Boolean
  Return $Border
End

Public Sub SetBorderColor(Value As Integer)
  $BorderColor = Value
  Me.DrawingArea1.Refresh()
End

Public Function GetBorderColor() As Integer
  Return $BorderColor
End

Da notare che per ogni variazione delle variabili, si richiama il metodo DrawingArea.Refresh() per poter applicare al volo le rispettive modifiche grafiche del frame.
A questo punto creiamo il disegno del Frame all'interno dell'evento Draw della DrawingArea:

Public Sub DrawingArea1_Draw()

  Paint.Begin(Me.DrawingArea1)
    Paint.Font = $Font
    Paint.Brush = Paint.Color($TextColor)
    Paint.Text($Text, 0, 0, Me.DrawingArea1.W, Paint.Font.TextHeight($Text) + 3, Align.Center)
    Paint.Fill()
    If $Border Then
      Paint.LineWidth = 0.5
      Paint.Brush = Paint.Color($BorderColor)
      Paint.MoveTo((Me.DrawingArea1.W - 5 - Paint.Font.TextWidth($Text)) / 2, Paint.Font.TextHeight($Text) / 2)
      Paint.LineTo(0, Paint.Font.TextHeight($Text) / 2)
      Paint.LineTo(0, Me.DrawingArea1.H)
      Paint.LineTo(Me.DrawingArea1.W, Me.DrawingArea1.H)
      Paint.LineTo(Me.DrawingArea1.w, Paint.Font.TextHeight($Text) / 2)
      Paint.LineTo(Me.DrawingArea1.W - ((Me.DrawingArea1.W - 5 - Paint.Font.TextWidth($Text)) / 2), Paint.Font.TextHeight($Text) / 2)
      Paint.Stroke()
    Endif
  Paint.End()

End

E per finire creiamo i metodi per variare la Proprietà Arrangement del Panel, in quanto sarà il vero contenitore del Frame:

Public Sub SetArrange(Value As Integer)
  Me.Panel1.Arrangement = Value
End

Public Function GetArrange() As Integer
  Return Me.Panel1.Arrangement
End

Classe FrameBox

Se nel Form appena creato abbiamo avuto una certa 'libertà' nella stesura del codice, adesso dobbiamo procedere con 'rigidità' per poter avere un componente pienamente funzionante nell'ide di Gambas.

Dichiarazione Proprietà, Costanti e Variabili Private

Per prima cosa andremo a dichiarare la classe Genitore:

Export

Inherits UserContainer

In questo modo abbiamo ereditato proprietà, metodi ed eventi dell'oggetto UserContainer
Poi andremo a dichiarare le costanti che serviranno all'ide di Gambas per l'integrazione del controllo:

Public Const _Properties As String = "*,Text,Foreground{Color}=&H000000&,BorderColor{Color}=&H000000H,Border=True"
Public Const _Group As String = "Gambas-it.org"
Public Const _Arrangement As Integer = 0
Public Const _Size As String = "36;36"

Dichiarazione delle Proprietà:

Property Text As String
Property Foreground As Integer
Property BorderColor As Integer
Property {Border} As Boolean
Property {Font} As Font
Property Arrangement As Integer

La Proprietà Arrangement è già compresa nelle Proprietà della classe genitore UserContainer. Dichiarandola nella classe figlia verrà automaticamente gestita da quest'ultima.

Private Obs As Observer

L'oggetto Observer intercetta gli eventi dell'oggetto a lui legato, prima che passino alla classe che ha istanziato il medesimo oggetto. Questo è utile per creare condizioni nell'oggetto, prima che scateni un evento. Nel nostro esempio non sarà indispensabile, ma siccome è un oggetto molto usato nei componenti, è bene sapere come funzioni.
Ed ora la FFrameBox che genererà la parte grafica del controllo:

Private F As FFrameBox
Metodo speciale _new()

Questo metodo viene richiamato al momento della creazione dell'oggetto Frame. All'interno della sub quindi, vengono istanziati i vari oggetti che compongono il Frame e si danno i valori di default per alcune variabili.

Public Sub _new()

  Obs = New Observer(Me) As "Event"
  F = New FFrameBox(Me)
  Me._Container = F.Panel1
  F.SetText(Me.Name)

End

Il metodo speciale UserContainer._Container serve per trasferire le proprietà di contenitore vero e proprio, dall'oggetto UserContainer ad un altro oggetto, a patto che quest'ultimo sia a sua volta un contenitore. In questo caso passiamo l'onere di oggetto contenitore dalla classe genitore al Panel1 della FFrameBox, quindi tutti gli oggetti inseriti nel nostro Frame saranno contenuti dall'oggetto Panel1.

Metodi Read/Write delle Proprietà

A questo punto non ci rimane altro che scrivere i metodi Read e Write delle varie proprietà, che andranno a modificare le caratteristiche del nostro Frame.

Private Function Text_Read() As String
  Return F.GetText()
End
Private Sub Text_Write(Value As String)
  F.SetText(Value)
End

Private Function Foreground_Read() As Integer
  Return F.GetTextColor()
End
Private Sub Foreground_Write(Value As Integer)
  F.SetTexColor(Value)
End

Private Function BorderColor_Read() As Integer
  Return F.GetBorderColor()
End
Private Sub BorderColor_Write(Value As Integer)
  F.SetBorderColor(Value)
End

Private Function Border_Read() As Boolean
  Return F.GetBorder()
End
Private Sub Border_Write(Value As Boolean)
  F.SetBorder(Value)
End

Private Function Font_Read() As Font
  Return F.GetFont()
End
Private Sub Font_Write(Value As Font)
  Super.Font = Value
  F.SetFont(Value)
End 

Private Function Arrangement_Read() As Integer
  Return F.GetArrange()
End
Private Sub Arrangement_Write(Value As Integer)
  F.SetArrange(Value)
End
Esempio per l'oggetto Observer

Mettiamo il caso in cui volessimo che il nostro Frame 'accendesse' i bordi al posizionamento del mouse su di esso, dovremmo intercettare l'evento Enter del Frame, che, in questo caso, viene generato dall'oggetto UserContainer. Siccome quest'ultimo viene istanziato nella classe contenitore di un progetto, i vari eventi vengono eseguiti nella classe istanziatrice e non all'interno del medesimo oggetto. Quindi l'onere di cambiare il colore ai bordi spetta al programmatore che ha richiamato l'oggetto nel proprio Progetto.
Per rendere l'oggetto autonomo da eventuale codice esterno, si richiama appunto l'oggetto Observer, il quale viene legato all'oggetto che si vuole monitorare. In questo esempio, e da come visto nel metodo _new(), si andrebbe semplicemente a scrivere questo codice:

Private $BorderColor as Integer
Public Sub Event_Enter()

  $BorderColor = Me.BorderColor
  Me.BorderColor = Color.Lighter(Me.BorderColor)

End

E, a sua volta, per far tornare i bordi ai suoi colori originali:

Public Sub Event_Leave()

  Me.BorderColor = $BorderColor

End