Artigo Técnico

Formato de TDateTimePicker no Delphi 5: DateTime_SetFormat

O TDateTimePicker ganhou uma propriedade Format em algum lugar entre o Delphi 5 e o Delphi 6. O limite exato da versão raramente é documentado de uma forma em que você possa confiar e, quando você se depara com ele, o compilador simplesmente diz que o identificador não existe. A linha que compila e funciona em todo Delphi mais recente falha silenciosamente ou completamente no antigo, dependendo de como o projeto está estruturado

O controle subjacente é o controle comum do Windows DATETIMEPICK_CLASS da comctl32.dll e ele sempre aceitou uma string de formato por meio da mensagem DTM_SETFORMAT. A VCL do Delphi encapsula essa mensagem na propriedade Format nas versões mais recentes. No Delphi 5, você mesmo envia a mensagem por meio do wrapper de macro DateTime_SetFormat declarado na CommCtrl

O padrão de compilação condicional

A abordagem padrão é um bloco condicional de compilador chaveado em um símbolo de versão. O Delphi 5 define VER130; você pode testar em relação a ele diretamente ou definir o seu próprio símbolo D5 nas opções do projeto para tornar o rótulo do ramo mais legível em toda a base de código. De qualquer maneira, a ramificação do Delphi 5 chama DateTime_SetFormat com o handle do controle; todas as outras ramificações atribuem a propriedade:

O ramo do Delphi 5 chama DateTime_SetFormat no handle do controle enquanto versões VCL mais novas atribuem a propriedade Format, ambos convergindo na mensagem DTM_SETFORMAT
A condicional do compilador mantém uma única árvore de código-fonte para Delphi 5 e versões modernas da VCL; ambos os ramos acabam enviando a mesma mensagem DTM_SETFORMAT ao controle
{$IFDEF D5}
  DateTime_SetFormat(DateTimePicker1.Handle, PChar('MM/dd/yyyy'));
{$ELSE}
  DateTimePicker1.Format := 'MM/dd/yyyy';
{$ENDIF}

Adicione CommCtrl à cláusula uses para a unidade que contém o ramo do Delphi 5. Não há necessidade de adicioná-la incondicionalmente; se você preferir manter a dependência no escopo, encapsule a entrada uses na mesma condicional:

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

Sintaxe da string de formato

A string de formato passada para DateTime_SetFormat segue o formato de seletor de data e hora do Windows, não os tokens FormatDateTime do Delphi. Os dois parecem semelhantes, mas não são intercambiáveis. O Windows usa d para o dia do mês (sem zero à esquerda), dd para o dia com preenchimento de zero, M para o número do mês, MM para o mês com preenchimento de zero, yy para o ano com dois dígitos e yyyy para o ano com quatro dígitos. Os campos de hora usam h/hh para horas no formato de 12 horas, H/HH para formato de 24 horas, m/mm para minutos, s/ss para segundos. O texto literal vai entre aspas simples dentro da string de formato, então 'dd/MM/yyyy' já está correto, mas 'dd '<literal>' MM' exigiria pares de aspas simples embutidos

O FormatDateTime do Delphi, em contrapartida, usa d/dd/ddd/dddd para formas de dia com diferentes semânticas e m para o mês (não M). Se você copiar uma string de formato de uma chamada FormatDateTime e passá-la diretamente para DateTime_SetFormat, os campos de mês e minuto provavelmente serão trocados na saída. Escreva a string de formato do controle do zero com base na documentação do Windows, em vez de converter de um formato Delphi

Tokens de formato do seletor Windows versus tokens do FormatDateTime do Delphi, em que mm significa minutos em um e mês no outro
O seletor de data e hora do Windows e o FormatDateTime do Delphi usam tokens sobrepostos com significados diferentes, então uma string copiada transforma o campo de mês em minutos

Configurações regionais e a global ShortDateFormat

Aterar a variável global ShortDateFormat do Delphi não afeta um TDateTimePicker que já foi criado. O seletor lê as configurações regionais do Windows no momento da criação e, a partir de então, renderiza de acordo com as mensagens que recebe, não as variáveis globais da RTL do Delphi. Isso significa que definir ShortDateFormat no FormCreate não mudará o que o controle exibe. Você precisa de DateTime_SetFormat (ou a propriedade Format no Delphi mais recente) para substituir a string de formato do próprio controle depois que o handle existir

Para aplicativos implantados internacionalmente, teste a string de formato em uma máquina com uma configuração regional não americana. O Windows pode substituir uma string de formato em branco ou nil pelo padrão de localidade, mas uma string não nil explícita substitui a configuração regional inteiramente. Isso geralmente é o que você deseja, mas significa que um padrão centrado nos EUA como 'MM/dd/yyyy' aparecerá como está para usuários que esperam dd/MM/yyyy por convenção. Se o seletor de data for voltado para o usuário, em vez de um puro campo de entrada de dados, considere deixar o padrão do controle (passe nil para DateTime_SetFormat) para que ele siga o local do usuário automaticamente

Recriação do handle

A configuração de formato pertence ao handle da janela, não a nenhum campo de objeto da VCL. Se o handle do controle for destruído e recriado, o que a VCL pode fazer quando você repassa um controle ou altera certas propriedades de estilo em tempo de execução, o formato reverte para o padrão do Windows. O lugar seguro para aplicar DateTime_SetFormat é em um manipulador que é executado após a criação do handle: substitua CreateWnd em uma classe descendente e chame-o lá, ou aplique-o no evento OnEnter ou OnShow somente após confirmar que o handle está ativo. Defini-lo uma vez em FormCreate é frequentemente suficiente para formulários simples, mas não é confiável se o formulário fizer comutação dinâmica de estilo ou reparentalidade de controle

A mesma restrição se aplica à propriedade Format da VCL nas versões mais recentes do Delphi; internamente ela chama a mesma mensagem do Windows e enfrenta o mesmo ciclo de vida do handle. A diferença é que o setter da propriedade da VCL verifica se o handle está alocado antes de chamar por meio dele e reaplica o formato automaticamente no CreateWnd. A chamada DateTime_SetFormat simples não tem tal proteção, então a ramificação do Delphi 5 precisa ser mais deliberada sobre quando é executada

Removendo a solução alternativa

Quando um projeto deixa de suportar o Delphi 5, o bloco condicional se torna peso morto. Remova a ramificação {$IFDEF D5} e a importação com escopo do CommCtrl, deixando apenas a atribuição direta da propriedade Format. Mantenha a remoção em um único commit, de modo que seja fácil identificar se algo quebrar inesperadamente em um compilador que você não testou. A própria string de formato permanece a mesma; apenas o mecanismo de entrega muda