Cocoa Subclassing: Creazione di un HUD
scritto da Francesco Germinara
Introduzione
Come promesso ecco la seconda ed ultima parte
relativa alla personalizzazione dei controlli e delle finestre di Cocoa.
Le classi che seguono devono essere usate come
degli esempi pratici per poter effettuare il subclassing di altre classi del
framework Cocoa.
Visto che siamo in tema, diciamo anche che
normalmente si procede al subclassing in due differenti modi:
1) Derivando una nuova classe ed
implementando una propria versione delle funzioni “sovrascritte” ; utilizziamo
quindi l’ereditarietà tipica della programmazione ad oggetti
2) Incapsulando in una nuova classe un
controllo standard ed estendendone le capacità con nostri nuovi metodi e
variabili
Le classi ottenute
incapsulando altri controlli, si chiamano anche classi composte
Cocoa ha inoltre un altro
sistema per estendere (quindi personalizzare/subclassare) il funzionamento
delle classi base ed è quello di utilizzare una classe DELEGATA
In pratica ad un oggetto standard, è possibile
associare una classe delegata, ed a questa classe, l’oggetto invierà
messaggi “di
delegazione” o di notifica , in modo che la classe delegata possa
intervenire sul comportamento dell’oggetto che ha segnalata un particolare
evento.
Per maggiori dettagli sull’uso dei messaggi di notifica
e di delega, si rimanda alla documentazione Apple.
Nelle varie classi incluse in questo progetto, ho
fatto uso delle varie tecniche sopra descritte, quindi, a chi fosse
interessato, consiglio di dare uno sguardo approfondito al codice sorgente delle
classi.
Prima di procedere con la presentazione delle
varie classi (aggiunte rispetto alla Parte 1) del progetto, ecco come si
presenta ora la finestra del programma di test realizzato per verificare il
funzionamento delle classi stesse.

Ho continuato a seguire il procedimento già descritto la scorsa volta e
quindi ho generalmente subclassato il controllo e successivamente la cella
attaccata a quel controllo.
Le classi che ho creato nella prima parte (e che sono comunque incluse nel
progetto completo) sono:
FGHUDView
FGHUDSlider
FGHUDButton
LE NUOVE CLASSI
FGHudCircle
In questo caso, è un controllo creato da zero, nel senso che non è stato
subclassato un controllo standard di Cocoa, ma è un controllo che deriva dal un NSControl
Lo scopo di questo controllo è quello di visualizzare/consenrire all’utente
di impostare un valore usando una specie di diagramma a “torta” se utilizziamo
solo i colori o creando controlli “circolari” usando delle immagini (es.
Manopole)
I metodi base che ho implementato, consentono di impostare il colore di
riempimento setFillColor il colore del bordo setBorderColor e se usare o meno gli effetti di gradiente setUseGradient
I metodi avanzati consento invece di utilizzare una immagine e di farne
ruotare la posizione in funzione del movimento del mouse, in tal modo, puo’
essere implementato un controllo simile ad una manopola.
I metodi sono seImageForNormalState e
setUseCustomKnob
Nella classe, di default e per esempio, ho inserito una immagine che è una freccia,
quindi semplicemte impostando setUseCustomKnob:YES
sarà visibile l’immagine e cliccandoci sopra muovendo il mouse, si ruota
la freccia.
Questa classe usa una cella che è di tipo FGHudCircleCell
FGHudCircleCell
La classe in questo caso è derivata da NSActionCell all’interno della classe
sono memorizzate delle variabili che servo sostanziamente per il disegno del
controllo.
Tra i metodi sovrascritti troviamo
il solito
-
(void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView
*)controlView
Usato appunto per disegnare l’aspetto del nostro controllo
Mentre sono di particolare interesse i metodi
-
(BOOL)startTrackingAt:(NSPoint)startPoint inView:(NSView
*)controlView
-
(BOOL)continueTracking:(NSPoint)lastPoint at:(NSPoint)currentPoint
inView:(NSView *)controlView
-
(void)stopTracking:(NSPoint)lastPoint at:(NSPoint)stopPoint
inView:(NSView *)controlView mouseIsUp:(BOOL)flag
-
Usati per ricalcolare il valore interno del controllo in funzione della
posizione e del movimento del mouse, in pratica tali metodi sono
automaticamente chiamati dal framework quando l’utente fa clic sul controllo,
muove il mouse mantenendo il tasto sinistro premuto (tracking) e rilascia il
tasto del mouse.
FGHudDisplay
Per questa classe ho pensato di
realizzare un controllo se “simulasse” un display alfanumenico e,
opzionalmente, un display numerico di 2 digit.
I metodi base implementati consento di
attivare o meno il display numerico (setDisplayNumericVisible), di impostare il
valore numerico nel display usando un valore compreso tra 0 e 99
(setNumericDisplayValue) oppure di scrivere un testo nel display alfanumerico
(formato da due righe) usando i metodi
setTitle e setSubTitle.
Un metodo particolare è invece quello che
ho deciso di implementare per “connettere” questo controllo (FGHudDisplay) con
un altro controllo che descrivero’ piu’ avanti (FGHudLevelIndicator), in modo
che automaticamente il valore interno presente nel controllo
FGHudLevelIndicator sia visualizzato nel display NUMERICO; tale metodo è setOutLetIndicator:(FGHudLevelIndicator
*)theIndicator;
Ovviamente anche in questo caso ho creato il controllo Cella
FGHudDisplayCell
La cosa piu’ interessante in questa classe è l’utilizzo dei messaggi di
notifica per far comunicare i due oggetti (FGHudLevelIndicator e FGHudDisplayCell)
Vediamo come funziona il meccanismo:
Quando viene chiamato il metodo -(void) setOutLetIndicator:(FGHudLevelIndicator
*)theIndicator avvengono due cose;
viene memorizzato internamente il
puntato dell’oggetto (aIndicator=theIndicator;) e viene registrata l’intenzione
di controllare se durante l’esecuzione del programma, qualche oggetto invia un
messaggio di notifica con nome FGHudLevelIndicatorValueChanged
.
Quindi tramite questa chiamata
[[NSNotificationCenter defaultCenter]
addObserver:self selector:@selector(indicatorValueChanged:)
name:@"FGHudLevelIndicatorValueChanged" object:nil];
dico, ogni volta che ricevo un messaggio di notifica denominato FGHudLevelIndicatorValueChanged devo chiamare il mio metodo che
si chiama indicatorValueChanged
Di conseguenza, quando viene chiamato il metodo indicatorValueChanged della classe FGHudDisplayCell non faccio altro che ricavare il valore
dell’oggetto Indicatore (di cui in precedenza ho salvato il puntatore), lo
imposto come valore per il display numerico
e dico di ridisegnare il controllo.
Visto che ne abbiamo parlato ecco il
FGHudLevelIndicator
Lo scopo di tale controllo è quello di implemenare, il controllo standard NSLevelIndicator
I metodi implementati consentono di impostare il tipo di indicatore (ci
sono quelli stadard cocoa e due nuovi stili: FGContinuousCapacityLevelIndicatorStyleCustomColor e
FGContinuousLedsLevelIndicatorStyle) uno per usare colori personalizzati e
l’altro per usare una immagine che si colora (es. Leds)
Per il controllo standard è possibile impostare un valore di “Attenzione” e
uno di “Allarme”, nel mio controllo sono visualizzati in colore diverso (nel momento
il cui il valore interno raggiunge e supera la soglia tutto il controllo cambia
colore).
Ho inoltre aggiunto a tale controllo la possibilità di essere usato come
una barra di progresso, e quindi sono disponibili anche i metodi startAnimation, stopAnimation e setEnableReverse
Non poteva certo mancare
FGHudLevelIndicatorCell
Usata per disegnare il controllo, tale
classe è derivata dalla classe standard NSLevelIndicatorCell
Vi ricordate del messaggio di notifica FGHudLevelIndicatorValueChanged ?
Bene, il metodo setDoubleValue della classe, invia ogni volta
il messaggio al “centro di notifica” tramite l’istruzione
[[NSNotificationCenter defaultCenter]
postNotificationName:@"FGHudLevelIndicatorValueChanged" object:self];
in modo che tutti i controlli che hanno “registrato la richiesta di
notifica” vengano informati del fatto che il valore del controllo e’ cambiato.
FGHudSegmented
Ho deciso di personalizzare il
NSSegmentedControl, semplicemente utilizzando delle immagini personali per la
rappresentazione di tale controllo.
Per ottenere questo ho creato il metodo
setImageButtonWithName che mi consente di specificare il prefisso nei nomi
delle immagini che voglio usare
In pratica per rappresentare
graficamente il controllo ho la necessità di avere 9 differenti immagini che
rappresentano diversi stati e posizione del controllo, per evitare di dover
specificare i 9 nomi, occorre chiamare le immagini con uno stesso prefisso (es.
Immbase) e utilizzare la seguente convenzione
// gestisco un
massimo di 9 differenti immagini associate ai rispettivi stati e deve essere
TIFF
// il nome
effettivo dell'immagine è la radice (newImageButton) + N + L (Normale con stato a Left)
// il nome
effettivo dell'immagine è la radice (newImageButton) + N + M (Normale con stato a Middle)
// il nome
effettivo dell'immagine è la radice (newImageButton) + N + R (Normale con stato a Right)
// il nome
effettivo dell'immagine è la radice (newImageButton) + P + L (Premuto con stato a Left)
// il nome effettivo
dell'immagine è la radice (newImageButton) + P + M (Premuto con stato a Middle)
// il nome
effettivo dell'immagine è la radice (newImageButton) + P + R (Premuto con stato a Right)
// il nome
effettivo dell'immagine è la radice (newImageButton) + S + L (Selezionato con stato a Left)
// il nome
effettivo dell'immagine è la radice (newImageButton) + S + M (Selezionato con stato a Middle)
// il nome
effettivo dell'immagine è la radice (newImageButton) + S + R (Selezionato con stato a Right)
E’ necessario specificare differenti
immagini per un segmento in quando il primo pulsante e l’ultimo sono
“chiusi” in modo differente.
Tutto il lavoro “duro” è svolto dalla
classe
FGHudSegmentedCell
Tale classe nel metodo di disegno
drawWithFrame
Deve considerare il ridisegno di tutti i segmenti, quindi
viene ricavato il numero di segmenti impostati e viene chiamato per ogni
segmento il metodo
drawSegment
Ultimo controllo subclassato è un
NSPopUpButton
FGHudPopUp
Lo scopo del subclass di tale controllo
è quello di poter assegnare un look diverso alla casella di controllo su cui
l’utente effettua il clic per aprire il menu con gli elementi da selezionare, è
anche possibile modificare il colore ed il font delle voci di tale menu.
L’unico metodo sovrascritto è +(Class)cellClass in modo da restituire la
cella di tipo
FGHudPopUpCell
E’ una personalizzazione del controllo
standard NSPopUpButtonCell visualizza la casella di controllo con i colori di
foreground e background della finestra principale e il menu con il testo in
blu.
Non ho implementato alcun metodo che
consenta di personalizzare il colori o il font o il colore del font. Lo lascio
come esercizio a chi vuole cimentarsi.
Tra l’altro una cosa che mi sarebbe
piaciuto fare ma NON CI SONO RIUSCITO era quella di personalizzare lo sfondo
del menu o il colore di background del menu ... se qualcuno conosce come fare
... me lo dica!!! (eh eh eh).
Per concludere, ho creato anche la FGHUDSubView che è molto simile alla FGHUDView ma da
usarsi come vista base dentro altri
controlli Es. NSTabView su cui successivamente posizionare i controlli
FGHub.....
Saluti e buona lettura a tutti.
F.Germinara
Il link del progetto è il seguente http://www.germinara.it/FGBseHUD_1_3.zip