Articolo tecnico

Formato TDateTimePicker in Delphi 5: DateTime_SetFormat

TDateTimePicker ha ottenuto la proprietà Format in un momento imprecisato tra Delphi 5 e Delphi 6. La soglia esatta della versione è documentata raramente in modo affidabile e, quando la si raggiunge, il compilatore indica semplicemente che l'identificatore non esiste. La riga che viene compilata e funziona su ogni versione di Delphi più recente fallisce in modo silenzioso o esplicito su quella vecchia, a seconda di come è strutturato il progetto.

Il controllo sottostante è il controllo comune Windows DATETIMEPICK_CLASS proveniente da comctl32.dll, che ha sempre accettato una stringa di formato tramite il messaggio DTM_SETFORMAT. La VCL di Delphi racchiude quel messaggio nella proprietà Format nelle versioni più recenti. In Delphi 5, si invia il messaggio manualmente tramite la macro wrapper DateTime_SetFormat dichiarata in CommCtrl.

Il pattern di compilazione condizionale

L'approccio standard prevede un blocco di compilazione condizionale basato su un simbolo di versione. Delphi 5 definisce VER130; è possibile testarlo direttamente o definire il proprio simbolo D5 nelle opzioni del progetto per rendere l'etichetta del ramo più leggibile all'interno del codice base. In entrambi i casi, il ramo di Delphi 5 chiama DateTime_SetFormat con l'handle del controllo; tutti gli altri rami assegnano la proprietà:

{$IFDEF D5}
  DateTime_SetFormat(DateTimePicker1.Handle, PChar('MM/dd/yyyy'));
{$ELSE}
  DateTimePicker1.Format := 'MM/dd/yyyy';
{$ENDIF}

Aggiungere CommCtrl alla clausola uses dell'unità che contiene il ramo Delphi 5. Non è necessario aggiungerlo in modo incondizionato; se si preferisce limitare l'ambito della dipendenza, racchiudere la voce in uses nella stessa condizione:

uses
  ...,
{$IFDEF D5}
  CommCtrl,
{$ENDIF}
  ...;

Sintassi della stringa di formato

La stringa di formato passata a DateTime_SetFormat segue il formato del date-time picker di Windows, non i token di FormatDateTime di Delphi. I due sembrano simili ma non sono intercambiabili. Windows usa d per il giorno del mese (senza zero iniziale), dd per il giorno con zero iniziale, M per il numero del mese, MM per il mese con zero iniziale, yy per l'anno a due cifre e yyyy per l'anno a quattro cifre. I campi dell'ora usano h/hh per le ore nel formato a 12 ore, H/HH per il formato a 24 ore, m/mm per i minuti, s/ss per i secondi. Il testo letterale va inserito tra virgolette singole all'interno della stringa di formato, quindi 'dd/MM/yyyy' è già corretto, ma 'dd '<literal>' MM' richiederebbe coppie di virgolette singole nidificate.

FormatDateTime di Delphi, al contrario, usa d/dd/ddd/dddd per le diverse forme del giorno con semantiche differenti e m per il mese (non M). Se si copia una stringa di formato da una chiamata a FormatDateTime e la si passa direttamente a DateTime_SetFormat, i campi del mese e dei minuti verranno probabilmente scambiati nell'output. Si consiglia di scrivere la stringa di formato del controllo da zero facendo riferimento alla documentazione di Windows, piuttosto che convertirla da un formato Delphi.

Impostazioni regionali e la variabile globale ShortDateFormat

La modifica della variabile globale ShortDateFormat di Delphi non influisce su un TDateTimePicker già creato. Il picker legge le impostazioni regionali da Windows al momento della creazione e successivamente esegue il rendering in base ai messaggi ricevuti, non alle variabili globali della RTL di Delphi. Ciò significa che l'impostazione di ShortDateFormat in FormCreate non cambierà ciò che il controllo visualizza. È necessario utilizzare DateTime_SetFormat (o la proprietà Format nelle versioni di Delphi più recenti) per sovrascrivere la stringa di formato del controllo stesso dopo che l'handle esiste.

Per le applicazioni distribuite a livello internazionale, testare la stringa di formato su una macchina con impostazioni regionali non statunitensi. Windows può sovrascrivere una stringa di formato vuota o nil con quella predefinita della lingua locale, ma una stringa esplicita diversa da nil sostituisce completamente l'impostazione regionale. Di solito questo è ciò che si desidera, ma significa che un pattern incentrato sugli Stati Uniti come 'MM/dd/yyyy' apparirà così com'è per gli utenti che per convenzione si aspettano dd/MM/yyyy. Se il selettore di data è rivolto all'utente piuttosto che essere un puro campo di immissione dati, considerare l'opzione di lasciare il comportamento predefinito del controllo (passare nil a DateTime_SetFormat) in modo che segua automaticamente la localizzazione dell'utente.

Ricreazione dell'handle

L'impostazione del formato appartiene all'handle della finestra, non a qualche campo oggetto della VCL. Se l'handle del controllo viene distrutto e ricreato, operazione che la VCL può eseguire quando si cambia il parent di un controllo o si modificano alcune proprietà di stile a runtime, il formato ripristina i valori predefiniti di Windows. Il punto sicuro in cui applicare DateTime_SetFormat è in un gestore eseguito dopo la creazione dell'handle: eseguire l'override di CreateWnd in una classe discendente e chiamarlo lì, oppure applicarlo nell'evento OnEnter o OnShow solo dopo aver verificato che l'handle sia attivo. Impostarlo una sola volta in FormCreate è spesso sufficiente per moduli semplici, ma non è affidabile se il modulo esegue cambi di stile dinamici o ri-assegnazioni del parent del controllo.

Lo stesso vincolo si applica alla proprietà Format della VCL sulle versioni più recenti di Delphi; internamente richiama lo stesso messaggio di Windows e affronta lo stesso ciclo di vita dell'handle. La differenza è che il setter della proprietà VCL verifica se l'handle è allocato prima di effettuare la chiamata e riapplica il formato automaticamente in CreateWnd. La chiamata diretta a DateTime_SetFormat non ha tale protezione, quindi il ramo di Delphi 5 deve essere programmato in modo più ponderato riguardo al momento della sua esecuzione.

Rimozione del workaround

Quando un progetto abbandona il supporto per Delphi 5, il blocco condizionale diventa un peso morto. Rimuovere il ramo {$IFDEF D5} e l'importazione limitata di CommCtrl, lasciando solo l'assegnazione diretta della proprietà Format. Mantenere la rimozione in un singolo commit in modo che sia facile identificare se qualcosa si interrompe in modo imprevisto su un compilatore non testato. La stringa di formato stessa rimane la stessa; cambia solo il meccanismo di invio.