Delphi ile Generic Clock

Delphi ile Generic Clock

Son günlerde Delphi ve GDI+ ile ilgili oldukça mail almaktayım. Aslında uzun zamandır yazmak istediğim bir yazı bu. Yazının konusu Delphi bilen yazılımcı arkadaşları yakından ilgilendiriyor. Başlıktan da anlaşılacağı üzere Delphi ile GDI+ kullanımı hakkında. Geçen sene bir arkadaşımız bana mail göndermişti. Hava Cıva! kodunun karışık olduğunu söylüyodu. Hava Cıva! gibi bir arayüzün daha basit bir şekilde nasıl yapılacağını merak etmiş. Bende üşenmedim ona çok basit bir saat programı yazmıştım. Delphi ile temel GDI+ tekniklerinin nasıl kullanılacağını göstermiştim. İşte konumuz bu işlemin nasıl yapılacağını anlatıyor. Eğer Delphi biliyorsanız çok basit bir şekilde Hava Cıva! gibi bir arayüz tasarlayabileceksiniz. Hemen başlayalım...

Projenin adı Generic Clock. Adından da anlaşılacağı üzere temel bir saat projesi. Uygulama geliştirme ortamı olarak Delphi 7 kullandım. Ayrıca GDI+ kütüphanesi de gerekli bizim için. Gerçi projenin kaynak kodunda GDI+ kütüphanesi mevcut ama yine de orjinal kütüphaneyi indirmek isteyenler için adresi verelim: Delphi için GDI+ Kütüphanesi

1. TASARIM
Doğal olarak ilk önce ekran tasarımını yapmamız gerekiyor. Bunun için projenin ana formunda bazı değişiklikler yapılmalı. Bunlardan en önemlisi ana formun BorderStyle özelliğinin bsNone olarak ayarlanması. Neden mi? Widget (yada Gadget) tarzı arayüze sahip uygulamalarda pencerelerin kenarlı olması pek işimize yaramaz. Hatta işimizi daha da zorlaştırır. Bu uygulamada olduğu gibi zemin için bir resim seçeceğiz ve formumuzun zeminini seçtiğimiz resim ile kaplayacağız. Başlıksız ve kenarlıksız bir pencereye resmi uydurmak çok daha kolay olacaktır. Formun başlık bölümü olmadığına göre doğal olarak Kapat düğmeside olamıyor. Bu sorunu gidermek için formumuza bir Kapat düğmesi eklememiz gerekiyor. Bu düğmeyi ekledikten sonra bir StaticText ve bir de Timer bileşenin ekliyoruz. Son olarak pek önemli olmayan detaylar var. Örneğin ben ana formun Position özelliğini poScreenCenter olarak ayarladım. Ekranın ortasından başlasın diye. Diğer bileşenlerin boyutları ve konumları sizin isteğinize bağlı aslında. Ama örneğe sadık kalırsanız karışıklık olmadan anlayabilirsiniz. Tasarım kısmında hepsi bu. Tasarım için örnek bir resim aşağıda görüntüleniyor.

Delphi ile Generic Clock Tasarımı

Delphi ile Generic Clock Tasarımı

2. ALGORITMA
Bu bölümde GDI+ ile Delphi formlarının nasıl ilişkilendirileceğinden bahsedeceğim. İlk öğrenmemiz gereken kısım klasik GDI çizim tekniklerini formumuzda kullanamayacağımızdır. Eğer gölgeli, saydam ve şık bir arayüz istiyorsak GDI tarafından sağlanan araçlar işimizi görmez. En başta GDI' nin renk desteği zayıftır. Oysa GDI+ Alpha channel ile 32-bit renk desteği sağlar. Bu sayede saydam pencereler oluşturabiliriz.
Şimdi aklımızdan kesinlikle çıkarmamız gereken çok önemli bir kısma geldik: LayeredWindows! Katmanlı Pencereler olarak çevireceğiz bu terimi. Windows 2000 ile birlikte gelen bir pencere özelliğidir bu. Widget tarzı programların temelini oluşturur. Eğer formumuza katmalı pencere özelliği eklemezsek boşa uğraşmış oluruz. Peki nasıl yapacağız? O kısım oldukça kolay aslında. Formumuzun FormCreate olayına aşağıdaki kodu ekleyeceğiz.
SetWindowLong(Handle, GWL_EXSTYLE, GetWindowLong(Handle, GWL_EXSTYLE) or WS_EX_LAYERED);Bu kodu ekledikten sonra diğer önemli bir kısımdan bahsetmek gerek. Microsoft MSDN' de belirttiği üzere "eğer pencereniz katmanlı olarak ayarlandıysa WM_PAINT mesajı oluşturulmaz!" MSDN' de bu yazıyı okuduktan sonra ne yapacağımı şaşırmıştım açıkçası. Windows'ta WM_PAINT mesajı oluşmuyorsa Delphi' de OnPaint olayı da oluşmaz! Bu karmaşayı aşmak için yine Windows API' den faydalanmamız gerekecek. Çözüm için UpdateLayeredWindow fonksiyonunu kullanacağız. Bu fonksiyon oldukça karışıktır. Benden söylemesi. Detaylı açıklamayı MSDN' den mutlaka okuyunuz (MSDN: UpdateLayeredWindow). Ben bu fonksiyonun nasıl kullanılacağını kaynak kod içinde gösterdim. UpdateLayeredForm yordamına bakmanız yeterlidir.
procedure TForm1.UpdateLayeredForm;
var
SrcDC, DestDC: HDC;
BitmapHandle, PrevBitmap: HBITMAP;
BlendFunc: _BLENDFUNCTION;
Size: TSize;
P, S: TPoint;
begin
{ Desktop ile uyumlu bir DC olustur. (Yada 0 yerine GetDesktopWindow yazabiliriz) }
SrcDC := CreateCompatibleDC(0);
{ Olusturdugumuz DC ile uyumlu yeni bir DC daha olusturmaliyiz. Kopyalama icin. }
DestDC := CreateCompatibleDC(SrcDC);
{ Cizim yaptigimiz resmin Windows tarafidan anlasilan HBITMAP turunden degiskene ata. }
{ Bu kisim onemli. Cizim yaptigimiz resmi gercek dunyaya getiriyoruz. }
{ BitmapHandle degiskeni artik Windows tarafindan kullanilanilabilir duruma geldi. }
FBitmap.GetHBITMAP(0, BitmapHandle); { 0 = Background transparent, out BitmapHandle }
{ SrcDC de bitmap secilmeli }
PrevBitmap := SelectObject(SrcDC, BitmapHandle);
{ Boyutlar ve konum }
Size.cx := Width;
Size.cy := Height;
P := Point(Left, Top);
S := Point(0, 0);
with BlendFunc do
begin
BlendOp := AC_SRC_OVER;
BlendFlags := 0; { Sifir olmali }
SourceConstantAlpha := FOpacity; { Ana formun donukluk degeri. 0 = tam saydam, 255 = tam donuk. }
AlphaFormat := AC_SRC_ALPHA;
end;
{ Microsoft'un sihirli fonksiyonu! Oylesine onemli ki, aslinda butun kiyamet burada kopuyor. }
{ Detayli aciklamayi MSDN' den mutlaka okuyunuz. http://msdn.microsoft.com/en-us/library/ms633556(VS.85).aspx }
UpdateLayeredWindow(Handle, DestDC, @P, @Size, SrcDC, @S, 0, @BlendFunc, ULW_ALPHA);
{ SrcDC yi eski haline getir. }
SelectObject(SrcDC, PrevBitmap);
{ ve Bitmap'i yok et. Yoksa hafiza sizmasi olur. }
DeleteObject(BitmapHandle);
{ DC leri yok et. }
DeleteDC(DestDC);
DeleteDC(SrcDC);
end;
Karmaşa hala devam ediyor... Madem windows bizim için WM_PAINT olayını oluşturmuyor o zaman bizim elle bu olayı tetiklememiz gerekiyor! Kendinizi resim yapıyor gibi düşünün. Önce zemini çizeceğiz sonra diğer bileşenleri vs. Böylelikle oluşan resmi formumuzda göstereceğiz. Mantık basit: Resmi çiz (RepaintForm) ve ekranda güncelle (UpdateLayeredForm).
Örneğin bu projede saati saniye saniye ekranda göstermemiz gerekiyor. Bu durumda her saniye form alanını güncellemeliyiz. Bunun için Timer bileşeninden faydalanacağız.
procedure TForm1.Timer1Timer(Sender: TObject);
begin
{ Her saniye form alanini yeniden cizmek zorundayiz. }
RepaintForm;
{ Ayrica cizdigimiz form alanini guncellemeliyiz. }
UpdateLayeredForm;
end;

Bu proje hakkında yazacaklarım şimdilik bu kadar. Widget tarzı programlarda can alıcı noktaları açıklamaya çalıştım. Projenin kaynak kodunu aşağıdaki linkten indirebilirsiniz.

generic-clock-source.rar [290 KB]