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

www.germinara.it

info@germinara.it

 

Il link del progetto è il seguente http://www.germinara.it/FGBseHUD_1_3.zip