Delphi/FPC

Menu e shortcut in Delphi

Gestire voci di menu e shortcut in Delphi è un’operazione piuttosto semplice. La procedura più consueta prevede di disaccoppiare l’interfaccia grafica dal codice vero e proprio; per esempio, i componenti che gestiscono i menù sono collocati in una form, mentre in un datamodule troveranno posto le azioni, con proprietà ed eventi che saranno validi per tutti i componenti associati ad esse.

A volte, però, capita che il baco si nasconda nei dettagli e renda difficile trovare la chiave giusta per scrivere codice altamente riutilizzabile.

Consideriamo il caso di una voce di menù che può agire su un numero arbitrario di controlli, senza limitazioni rispetto al loro tipo. Un esempio sono le classiche voci di copia e incolla, cui tradizionalmente si associano gli shortcut CTRL+C e CTRL+V, che devono operare su tipi TEdit, TCombo, e così via. Il codice che implementa le operazioni di copia o incolla devono sapere quale componente contiene i dati da leggere o da modificare; saperlo a priori, nella maggior parte dei casi, è impossibile. Ogni volta che la voce di menù è usata dall’utente, deve determinare su quale controllo dovrà operare.

In generale, possiamo dire che, se la voce appartiene al menu principale, le operazioni andranno svolte sul controllo attivo nella form; ma come individuare il controllo giusto quando, invece, l’azione è invocata da un menu popup?

Vediamo un piccolo esempio. Attraverso una voce di menù, copieremo in una label il nome dei componenti sui quali la voce stessa agisce. Saranno proposte tre soluzioni, l’ultima delle quali sembra essere quella più completa. Il codice completo è disponibile nel nostro spazio di GitHub.

La form del programma dimostrativo.

La soluzione di base

Quando si scrive codice altamente generico, come per esempio BindAPI, non si possono conoscere gli oggetti sui quali la procedura dovrà lavorare. L’Object Pascal, così come il FreePascal, permette di sbrigare questa incombenza con la consueta eleganza:

procedure TForm1.Test1_Click(Sender: TObject);
var
  lParentMenu: TMenu;
  lComponent: TComponent;
begin
  lParentMenu := TMenuItem(Sender).GetParentMenu ;
  lComponent := TPopupMenu(lParentMenu).PopupComponent ;
  Label1.Caption := lComponent.name;
end;

Per provare questo piccolo blocco di codice, mettiamo in una form due componenti TEdit, un componente TLabel e un componente TPopupMenu con una sola voce. Questo menu popup sarà associato a tutti i componenti della form. “Test” e CRTL+M saranno caption del menu e shortcut associato.

Ora compiliamo ed eseguiamo il programma. Spostiamo il mouse sul primo controllo TEdit, schiacciamo il tasto destro e facciamo click sulla voce di menù: nella label vedremo il nome “Edit1”. Ora posizioniamo il mouse sulla label e ripetiamo l’operazione: la sua caption tornerà ad essere Label1. Collochiamo il mouse sul secondo controllo TEdit e ripetiamo l’operazione: il testo diventerà “Edit2”.

Il baco nascosto nello shortcut

Sembra funzionare tutto bene. Ora torniamo con il mouse su Edit1, gli diamo il focus premendo il tasto sinistro, e infine premiamo i tasti CTRL+M: questa è la combinazione che abbiamo scelto nell’editor delle proprietà.

L’editor delle proprietà per la voce di menù.

Ci aspetteremmo che il testo nell’etichetta torni ad essere “Edit1”, invece non cambia. Questo accade perché la proprietà PopupComponent conserva la memoria dell’ultimo controllo che ha richiamato il menu popup attraverso il mouse, anche se nel frattempo ha perso il focus.

Capire se la voce di menu è stata aperta con un click o richiamata da uno shortcut è senz’altro possibile, ma richiede di mettere mano a non poco codice; per questo, alcuni raccomandano di far agire le procedure legate a voci di menù sul controllo attivo:

procedure TForm1.Test2_Click(Sender: TObject);
var
  lComponent: TComponent;
begin
  lComponent := Screen.ActiveControl;
  Label1.Caption := lComponent.name;
end;

Il problema, però, è che se ora andate sull’etichetta, aprite il menu e fate click sulla sua voce, la procedura scriverà il nome del componente TEdit attivo, non quello della label. In generale, questo sistema fallirà per tutti i componenti che non possono essere selezionati.

La soluzione più semplice

Il giusto compromesso fra semplicità e funzionalità, che può essere poi personalizzato secondo le esigenze, ci pare qualcosa di questo tipo:

function TForm1.FindComponentFromMenu(Sender: TObject): TComponent;
var
  lParentMenu: TMenu;
  lControl: TComponent;
begin
  lControl := nil;
  if Sender is TMenuItem then
  begin
    lParentMenu := TMenuItem(Sender).GetParentMenu;
    if lParentMenu is TPopupMenu then
    begin
      lControl := TPopupMenu(lParentMenu).PopupComponent;
      TPopupMenu(lParentMenu).PopupComponent := nil;
    end;
  end;
  if lControl is TGraphicControl then
    Result := lControl
  else
    Result := Screen.ActiveControl;
end;

La logica è assai semplice. Per prima cosa si verifica se il Sender è di tipo TMenuItem e si trova in un componente TPopupMenu. Se è così, ricava il componente puntato da PopupComponent e se discende da TGraphicControl, lo restituisce come risultato; altrimenti, restituisce il controllo attivo. Notate che dopo aver memorizzato il controllo nella variabile lControl, dobbiamo resettare il valore di PopupComponent. Se non lo facessimo, ogni volta che con il tasto destro apriamo il menù su Label1 e richiamiamo la procedura, la proprietà PopupComponent punterà ancora a Label1 fino a quando non si cambia esplicitamente il focus. E’ ovvio che ciò creerebbe problemi se usassimo subito lo shortcut CTRL+M.

L’evento OnClick associato alla voce di menu cambierà di conseguenza:

procedure TForm1.Test3_Click(Sender: TObject);
var
  lComponent: TComponent;
begin
  lComponent := FindComponentFromMenu(Sender);
  if Assigned(lComponent) then
    Label1.Caption := lComponent.Name;
end ;

Ora tutti gli elementi della form ai quali si associa il menu permetteranno una corretta gestione delle informazioni. Da questa struttura estremamente semplice si potranno derivare controlli più sofisticati sul corretto componente da usare.

Conclusioni

Questo semplice esempio su voci di menu e shortcut in Delphi ci ricorda che spesso i problemi di programmazione più insidiosi si nascondono nei dettagli. Anche se può sembrare scontato che il funzionamento della procedura richiamata da mouse o da shortcut sia uguale, nella realtà questo risultato si ottiene solo con una buona conoscenza dei meccanismi che lo regolano. Nel prossimo post vedremo che cosa succede agli shortcut quando sono associati alle action che si trovano in un datamodule.

Paolo Morandotti

Professionista nel campo del software con trent'anni di esperienza, ama studiare le ricadute sociali delle tecnologie sulle quali ha realizzato vari programmi radiofonici.

Recent Posts

Riflessioni sulla filatelia tematica

Gli articoli sulla filatelia tematica, vista da una prospettiva semantica e semiotica, sono elencati sotto…

2 anni ago

Da Gliwice a Grigoriopol?

Il paragone tra Gliwice e Grigoriopol è spontaneo; ma quanto è giustificato? Analizziamo i fatti…

2 anni ago

La comunicazione russa e il conflitto in Ucraina

Alcune considerazioni sulla comunicazione russa che ha preceduto il conflitto in Ucraina spingono al pessimismo.

2 anni ago

L’unità narrativa nella filatelia tematica

Definire un'unità narrativa nella filatelia tematica può rendere lo sviluppo tematico più completo e coinvolgente,…

2 anni ago

Un orologio multiuso per Delphi

TplFmxClock è il nuovo componente per Delphi rilasciato come open source da morandotti.it. Si tratta…

2 anni ago

Filatelia tematica e multimedialità

Concludiamo la serie di articoli sulla filatelia tematica affrontando una delle sfide più urgenti: quella…

2 anni ago