Teknik Makale

Delphi 5'te TDateTimePicker Formatı: DateTime_SetFormat

TDateTimePicker, Delphi 5 ile Delphi 6 arasındaki bir noktada bir Format özelliği kazandı. Kesin sürüm sınırını güvenebileceğiniz biçimde belgelemek nadirdir; o sınıra çarptığınızda derleyici size yalnızca tanımlayıcının var olmadığını söyler. Peşinden koşulacak bir çalışma zamanı sürprizi yoktur: elinizdeki her yeni Delphi'de derlenip çalışan satır Delphi 5'te sert bir derleme hatası verir; asıl soru, tek bir kaynak ağacını her ikisinde de derlenebilir tutmaktır

Alttaki kontrol, comctl32.dll içindeki Windows DATETIMEPICK_CLASS ortak kontrolüdür ve her zaman bir biçim dizesini DTM_SETFORMAT mesajıyla kabul etmiştir. Delphi'nin VCL'i, daha yeni sürümlerde bu mesajı Format özelliğine sarar. Delphi 5'te mesajı, CommCtrl içinde tanımlanan DateTime_SetFormat makro sarmalayıcısı üzerinden kendiniz gönderirsiniz

Koşullu derleme kalıbı

Standart yaklaşım, bir sürüm sembolüne anahtarlanmış derleyici koşullu (compiler-conditional) bir bloktur. Delphi 5, VER130 tanımlar; doğrudan buna karşı test edebilir veya dallanma etiketini kod tabanında daha okunaklı hale getirmek için proje seçeneklerinde kendi D5 sembolünüzü tanımlayabilirsiniz. Her iki durumda da, Delphi 5 dalı kontrol tanıtıcısı (handle) ile DateTime_SetFormat işlevini çağırır; diğer tüm dallar ise özelliği atar:

Delphi 5 dalı denetim tanıtıcısı üzerinde DateTime_SetFormat çağırırken daha yeni VCL sürümleri Format özelliğini atar; her ikisi de DTM_SETFORMAT iletisinde birleşir
Derleyici koşullu ifadesi Delphi 5 ve modern VCL sürümleri için tek kaynak ağacı korur; her iki dal da denetime aynı DTM_SETFORMAT iletisini gönderir
{$IFDEF D5}
  DateTime_SetFormat(DateTimePicker1.Handle, PChar('MM/dd/yyyy'));
{$ELSE}
  DateTimePicker1.Format := 'MM/dd/yyyy';
{$ENDIF}

Delphi 5 dalını içeren unitin uses satırına CommCtrl'i ekleyin. Bunu koşulsuz eklemeniz gerekmez; bağımlılığı kapsamlı tutmayı tercih ederseniz uses girdisini aynı koşulun içine sarın:

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

Format dizisi sözdizimi (syntax)

DateTime_SetFormat işlevine iletilen format dizisi, Delphi'nin FormatDateTime belirteçlerini değil, Windows tarih-saat seçici formatını izler. İkisi benzer görünür ancak birbirinin yerine kullanılamaz. Windows, ayın günü için d (başında sıfır olmadan), sıfır dolgulu gün için dd, ay numarası için M, sıfır dolgulu ay için MM, iki haneli yıl için yy ve dört haneli yıl için yyyy kullanır. Saat alanlarında 12 saatlik format için h/hh, 24 saatlik format için H/HH, dakikalar için m/mm, saniyeler için s/ss kullanılır. Format dizisinin içinde metin değişmezleri (literal text) tek tırnak içine alınır, yani 'dd/MM/yyyy' zaten doğrudur, ancak 'dd '<literal>' MM' gömülü tek tırnak çiftleri gerektirir

Buna karşılık Delphi'nin FormatDateTime işlevi, farklı semantiklere sahip gün biçimleri için d/dd/ddd/dddd kullanır ve ay için m (M değil) kullanır. Bir format dizisini bir FormatDateTime çağrısından kopyalayıp doğrudan DateTime_SetFormat işlevine verirseniz, çıktıda büyük ihtimalle ay ve dakika alanları yer değiştirmiş olacaktır. Kontrolün format dizisini bir Delphi formatından dönüştürmek yerine Windows belgelerine bakarak sıfırdan yazın

Windows seçici biçim belirteçleri ile Delphi FormatDateTime belirteçleri: mm birinde dakikayı, diğerinde ayı belirtir
Windows tarih-saat seçici ile Delphi'nin FormatDateTime'ı farklı anlamlara gelen çakışan simgeler kullanır; bu yüzden kopyalanmış bir dizge ay alanını dakikalara çevirir

Bölgesel ayarlar ve küresel ShortDateFormat

Delphi'nin global ShortDateFormat değişkenini değiştirmek, zaten oluşturulmuş bir TDateTimePicker'ı etkilemez. Kontrol, oluşturulma anında bölgesel ayarları Windows'tan okur ve bundan sonra aldığı mesajlara göre çizer; Delphi RTL global'larına göre değil. Yani FormCreate içinde ShortDateFormat ayarlamak, kontrolün gösterdiklerini değiştirmez. Tanıtıcı var olduktan sonra kontrolün kendi biçim dizesini geçersiz kılmak için DateTime_SetFormat (ya da daha yeni Delphi'lerde Format özelliği) gerekir

Uluslararası dağıtılan uygulamalarda biçim dizesini, ABD dışında bir bölgesel ayarla çalışan bir makinede test edin. Windows, boş ya da nil bir biçim dizesini yerel ayarın varsayılanıyla değiştirebilir; ama açık bir non-nil dize bölgesel ayarı tamamen geride bırakır. Bu genellikle istediğiniz şeydir; yine de bu, 'MM/dd/yyyy' gibi ABD merkezli bir kalıbın, gelenek olarak dd/MM/yyyy bekleyen kullanıcılarda olduğu gibi görüneceği anlamına gelir. Tarih seçici salt veri girişi alanı değil de kullanıcıya dönükse, kontrolün varsayılana uymasına izin vermeyi (yani DateTime_SetFormat'a nil geçmeyi) düşünün; böylece kullanıcının yerel ayarını kendiliğinden izler

Handle (Tanıtıcı) yeniden oluşturulması

Format ayarı herhangi bir VCL nesne alanına değil, pencere tanıtıcısına (window handle) aittir. Çalışma zamanında bir kontrolün ebeveynini değiştirdiğinizde (reparent) veya belirli stil özelliklerini değiştirdiğinizde VCL'nin yapabildiği gibi, kontrolün tanıtıcısı yok edilip yeniden oluşturulursa format Windows varsayılanına döner. DateTime_SetFormat uygulamak için güvenli yer, tanıtıcı oluşturulduktan sonra çalışan bir işleyicidir (handler): alt bir sınıfta CreateWnd'yi geçersiz kılın (override) ve orada çağırın veya yalnızca tanıtıcının canlı olduğunu onayladıktan sonra OnEnter veya OnShow olayına uygulayın. Basit formlar için bunu FormCreate içinde bir kez ayarlamak genellikle yeterlidir, ancak form dinamik stil geçişleri veya kontrolün ebeveynini değiştirme (reparenting) işlemleri yapıyorsa güvenilir değildir

uses
  ComCtrls, CommCtrl;

type
  TFormattedDatePicker = class(TDateTimePicker)
  private
    FDisplayFormat: string;
    procedure ApplyDisplayFormat;
    procedure SetDisplayFormat(const Value: string);
  protected
    procedure CreateWnd; override;
  public
    property DisplayFormat: string read FDisplayFormat write SetDisplayFormat;
  end;

procedure TFormattedDatePicker.ApplyDisplayFormat;
begin
  if FDisplayFormat = '' then
    DateTime_SetFormat(Handle, nil)            // yerel ayarın varsayılanına geri döner
  else
    DateTime_SetFormat(Handle, PChar(FDisplayFormat));
end;

procedure TFormattedDatePicker.CreateWnd;
begin
  inherited CreateWnd;
  ApplyDisplayFormat;                          // her tanıtıcı yeniden oluşturulduğunda yeniden çalışır
end;

procedure TFormattedDatePicker.SetDisplayFormat(const Value: string);
begin
  FDisplayFormat := Value;
  if HandleAllocated then
    ApplyDisplayFormat;
end;

Biçim dizesi Delphi nesnesinin bir alanında yaşar, bu yüzden pencereden uzun ömürlüdür; CreateWnd her yeni tanıtıcıya bu değeri aktarır. Özellik, Format yerine DisplayFormat adını taşır; böylece aynı unit daha yeni Delphi sürümleriyle derlendiğinde kalıtsal Format ile çakışmaz

Aynı kısıtlama daha yeni Delphi sürümlerindeki VCL Format özelliği için de geçerlidir; dahili olarak aynı Windows mesajını çağırır ve aynı handle yaşam döngüsüyle karşı karşıyadır. Aradaki fark, VCL özellik ayarlayıcısının (setter) çağrı yapmadan önce tanıtıcının ayrılıp (allocated) ayrılmadığını kontrol etmesi ve CreateWnd anında formatı otomatik olarak yeniden uygulamasıdır. Ham (raw) DateTime_SetFormat çağrısı böyle bir korumaya sahip değildir, bu nedenle Delphi 5 dalının ne zaman çalışacağı konusunda daha dikkatli olması gerekir

Geçici çözümü kaldırma

Bir proje Delphi 5 desteğini bıraktığında koşullu blok ölü ağırlık haline gelir. {$IFDEF D5} dalını ve kapsamlı CommCtrl içe aktarımını (import) kaldırarak geriye yalnızca doğrudan Format özellik atamasını bırakın. Test etmediğiniz bir derleyicide beklenmedik bir şekilde bir şey bozulursa tanımlamayı kolaylaştırmak için kaldırma işlemini tek bir işlem (commit) içinde tutun. Format dizisinin kendisi aynı kalır; yalnızca teslim mekanizması değişir