Artículo técnico

Formato de TDateTimePicker en Delphi 5: DateTime_SetFormat

TDateTimePicker obtuvo una propiedad Format en algún momento entre Delphi 5 y Delphi 6. El límite exacto de la versión rara vez se documenta de una manera en la que pueda confiar, y cuando se topa con él, el compilador simplemente le dice que el identificador no existe. La línea que se compila y funciona en cada versión más nueva de Delphi falla silenciosamente o por completo en la antigua, dependiendo de cómo esté estructurado el proyecto

El control subyacente es el control común de Windows DATETIMEPICK_CLASS de comctl32.dll, y siempre ha aceptado una cadena de formato a través del mensaje DTM_SETFORMAT. La VCL de Delphi envuelve ese mensaje en la propiedad Format en versiones más recientes. En Delphi 5, usted mismo envía el mensaje a través de la envoltura (wrapper) de la macro DateTime_SetFormat declarada en CommCtrl

El patrón de compilación condicional

El enfoque estándar es un bloque condicional del compilador basado en un símbolo de versión. Delphi 5 define VER130; puede probar contra él directamente o definir su propio símbolo D5 en las opciones del proyecto para hacer que la etiqueta de la bifurcación sea más legible en toda la base de código. De cualquier manera, la bifurcación de Delphi 5 llama a DateTime_SetFormat con el manejador (handle) del control; todas las demás bifurcaciones asignan la propiedad:

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

Agregue CommCtrl a la cláusula uses para la unidad que contiene la bifurcación de Delphi 5. No hay necesidad de agregarlo incondicionalmente; si prefiere mantener la dependencia con un alcance limitado, envuelva la entrada uses en el mismo condicional:

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

Sintaxis de la cadena de formato

La cadena de formato pasada a DateTime_SetFormat sigue el formato del selector de fecha y hora de Windows, no los tokens de FormatDateTime de Delphi. Ambos parecen similares pero no son intercambiables. Windows usa d para el día del mes (sin cero inicial), dd para el día rellenado con ceros, M para el número de mes, MM para el mes rellenado con ceros, yy para el año de dos dígitos y yyyy para el año de cuatro dígitos. Los campos de tiempo usan h/hh para las horas de 12 horas, H/HH para las 24 horas, m/mm para los minutos, s/ss para los segundos. El texto literal va dentro de comillas simples dentro de la cadena de formato, por lo que 'dd/MM/yyyy' ya es correcto, pero 'dd '<literal>' MM' requeriría pares de comillas simples incrustadas

Por el contrario, el FormatDateTime de Delphi usa d/dd/ddd/dddd para formas de día con diferentes semánticas, y m para el mes (no M). Si copia una cadena de formato de una llamada a FormatDateTime y se la pasa directamente a DateTime_SetFormat, es probable que los campos de mes y minuto se intercambien en la salida. Escriba la cadena de formato del control desde cero basándose en la documentación de Windows en lugar de convertirla desde un formato de Delphi

Configuraciones regionales y el ShortDateFormat global

Cambiar la variable global ShortDateFormat de Delphi no afecta a un TDateTimePicker que ya está creado. El selector lee las configuraciones regionales de Windows en el momento de su creación y, a partir de entonces, se renderiza de acuerdo con los mensajes que recibe, no con las variables globales de la RTL de Delphi. Eso significa que configurar ShortDateFormat en FormCreate no cambiará lo que muestra el control. Se necesita DateTime_SetFormat (o la propiedad Format en versiones más recientes de Delphi) para anular la cadena de formato propia del control después de que exista el manejador (handle)

Para las aplicaciones implementadas internacionalmente, pruebe la cadena de formato en un equipo con una configuración regional que no sea de EE. UU. Windows puede anular una cadena de formato en blanco o nula con el valor predeterminado de la región, pero una cadena explícita no nula reemplaza por completo la configuración regional. Eso es generalmente lo que se desea, pero significa que un patrón centrado en EE. UU. como 'MM/dd/yyyy' aparecerá tal cual para los usuarios que esperan dd/MM/yyyy por convención. Si el selector de fecha es visible para el usuario en lugar de un campo de entrada de datos puro, considere dejar el control por defecto (pasando nil a DateTime_SetFormat) para que siga automáticamente la configuración regional del usuario

Recreación del manejador (handle)

La configuración de formato pertenece al manejador de la ventana, no a ningún campo del objeto de la VCL. Si el manejador del control se destruye y se vuelve a crear, lo cual la VCL puede hacer cuando cambia el control principal (reparent) o cambia ciertas propiedades de estilo en tiempo de ejecución, el formato se revierte al valor predeterminado de Windows. El lugar seguro para aplicar DateTime_SetFormat es en un manejador de eventos que se ejecuta después de la creación del manejador de la ventana: anule (override) CreateWnd en una clase descendiente y llámelo allí, o aplíquelo en el evento OnEnter u OnShow solo después de confirmar que el manejador está activo. Configurarlo una vez en FormCreate suele ser suficiente para formularios simples, pero no es confiable si el formulario realiza cambios dinámicos de estilo o cambia el control principal

La misma restricción se aplica a la propiedad Format de la VCL en versiones más nuevas de Delphi; internamente llama al mismo mensaje de Windows y se enfrenta al mismo ciclo de vida del manejador. La diferencia es que el configurador (setter) de la propiedad de la VCL comprueba si el manejador está asignado antes de llamar, y vuelve a aplicar el formato automáticamente en CreateWnd. La llamada cruda a DateTime_SetFormat no tiene tal protección, por lo que la bifurcación de Delphi 5 debe ser más deliberada sobre cuándo se ejecuta

Eliminando la solución alternativa

Cuando un proyecto deja de ser compatible con Delphi 5, el bloque condicional se convierte en un peso muerto. Elimine la bifurcación {$IFDEF D5} y la importación de CommCtrl de alcance limitado, dejando solo la asignación directa de la propiedad Format. Mantenga la eliminación en un solo commit para que sea fácil de identificar si algo se rompe inesperadamente en un compilador que no probó. La cadena de formato en sí sigue siendo la misma; solo cambia el mecanismo de entrega