|
|
|||||||||||||
|
|
Windows'un Pencereleri - I Bu ay Windows'un pencerelerini biraz olsun aralamaya çalıştık. Bu yazıda Windows'un pencereleri nasıl yönettiği hakkında temel bilgilerle birlikte Windows mesajları ile pencerelerin ilişkisi hakkında bilgiler bulacaksınız. Hazırladığımız örnek uygulamalarda ise VB'de Windows mesajlarının yakalanması, bileşenleri çalışma anında taşıma ve boyutlandırma, Windows'taki pencerelerin Delphi ile listelenmesi, pencerelerin birbirleri içine yerleştirilmesi gibi teknikler yer alıyor. • Windows'ta pencereler WİNDOWS'TA PENCERELER Windows'ta kullanıcı etkileşimi sağlayan bileşenler genel olarak pencere (window) şeklinde adlandırılır. Bunları içeriyi gösteren, açık ya da kapalı pencereler gibi düşünmek yanlış olmaz. Öyle ki, kullanıcının ekranda verdiği bir komutu pencereden içeri uzanmakmış gibi düşünmek lazım gelir. Pencereler kullanıcı etkileşimini sağlarken birden çok halde olabilirler. Bir pencere, kullanıcının bilgi girmesini sağlayan, ekranda çerçeve ile sınırlandırılmış bir bileşen olabilir. Bununla birlikte ekranda görüntülenmeyen pencereler de kullanıcı ile etkileşimde bulunabilirler. Birincil ve ikincil diye ayrılan pencereler Windows'ta "arayüz bileşenleri" sınıfına dahildir. Birincil pencereler kenarlıkları, başlık çubuğu ve sağ üst köşesinde küçültme, kapatma gibi düğmeleri olan genelde dört köşeli Windows formlarıdır. İkincil pencereler ise bu formların, yani birincil pencerelerin içinde yer alan bileşenlerdir. Komut düğmeleri, liste ve metin kutuları, menü çubukları gibi nesneler ikincil pencere olarak adlandırılır. Pencereler Windows'taki her uygulamada bulunabilir. Delphi'de, VB'de, diğer uygulamalarda tasarladığımız ekranlar da çoğunlukla pencerelerden oluşur. Windows'un Pencereleri, çok geniş olan bu konuyu özetlemekten ziyade, pencelerin birbirleri ile ilişkilerini ve pencerelere gönderilen Windows mesajlarını birkaç yönüyle irdelemektedir.
Windows'taki her pencere bileşeni (form, düğme, metin kutuları ve sair) benzersiz bir sayı değeri ile tanımlanmaktadır. Bu değer HWND (Handle WiNDow - pencere belirteci) türünde bir değişkende saklanır. Pencerelerin en önemli özelliği HWND değeri almalarıdır kuşkusuz. Bir bileşenin HWND değeri yoksa o bileşen pencere olarak kabul edilmez.
HWND değişken türünün VB'deki karşılığı Long'dur. Delphi'de ise HWND tipi yerleşik olarak gelmiştir. 4-bayt tamsayı ile ifade edilen HWND'nin sayısal değeri en çok 4 milyar (256 üzeri 4) olabilir. Bu, aynı zamanda Windows'taki aktif pencere adedinin ulaşabileceği en büyük değeri işaret eder. Windows API tanımlarında HWND değişkeni çokça yer alır. API işlevlerinin bir çoğu Handle (pencereler için HWND, menüler için HMENU vs.) değeri bilinen bir pencere üzerinde işlem yapabilir. Genel olarak bu değerin bilinmesi zorunludur. Aksi durumlarda farklı özniteliklere göre arama yaparak Windows'taki belli pencerelerin HWND değerini bulmak gerekir. Örnek olması açısından, pencerelerin başlığını değiştiren SetWindowText işlevinin Delphi'deki tanımlaması aşağıda gösterilmiştir: function SetWindowText(hWnd: HWND; lpString: PChar): BOOL; stdcall; İlgili pencere başlığını değiştirmek için bu işlev aşağıdaki şekilde kullanılır: SetWindowText(123456,PChar('Yeni başlık')); Bu işlevi VB'de kullanmak, öncelikle işlevin tanımını kod sayfasına eklemeyi gerektirir. API Viewer ile elde edilen tanım metni, forma ait özel kod sayfasının üst tarafına ya da bir genel kod sayfasına (modül) eklenmelidir. Private Declare Function SetWindowText Lib "user32" Alias "SetWindowTextA" (ByVal hwnd As Long, ByVal lpString As String) As Long Private Sub Command1_Click()
Tanım bilgileri formun özel kod sayfasına ekleniyorsa işlev adının başında "Private" kullanılmalıdır. Genel kod sayfasına ekleme yaparken bunu "Public" şeklinde değiştirmek gerekir.
Windows'ta pencereler bir ağacın dalları gibi basamaklı olarak yer alırlar. En üstte masaüstü penceresi yer alır ve diğer pencereler masaüstü içinde kabul edilir. Windows'ta bir andaki tüm pencerelerin adreslerini (yani HWND değerlerini) basit birkaç API işlevi ile listelemek mümkündür. Eldeki geçerli bir HWND değerini kullanarak üst, alt, komşu pencerelere ulaşılabilir. Bunun için başlangıç noktası olarak masaüstünü seçmek akıllıcadır. Zira doğrudan masaüstünün HWND değerini veren GetDesktopWindow işlevi mevcuttur. Public Declare Function GetDesktopWindow Lib "user32" () As Long Diğer pencerelerin adreslerine göreceli olarak ulaşmak GetWindow ile mümkündür. GetWindow'u döngü içinde kullanmak bilgisayarda dosya araması yapmak gibidir. Masaüstünün HWND değeri bulunduktan sonra GetWindow'a GW_CHILD değeri gönderilerek ilk alt pencere bulunur. Diğer pencereleri bulmak için ilk alt pencereden başlayarak GW_HWNDNEXT ile sonraki pencereler bulunur. GetWindow'un ilk parametresi geçerli bir HWND değeri, ikinci parametresi de göreceli olarak bu pencerenin hangi yönünde arama yapılacağını belirtir. Burada, ilk alt pencereyi bulmak için GW_CHILD, sonraki pencereyi bulmak için GW_HWNDNEXT kullanılıyor. var ch:=GetWindow(h,GW_CHILD);
Örnek olması açısından ekte verdiğimiz Pencereler adlı Delphi uygulamasını inceleyebilirsiniz. Yukarıda bahsettiğimiz üzere bir andaki en çok pencere adedi 4 milyar ile sınırlı olsa da bu uygulamada göreceğiniz gibi pencere adedi genellikle birkaç yüzden fazla olmayacaktır.
GetWindow, GW_CHILD ve GW_HWNDNEXT dışında farklı parametreler alıp diğer pencerelere de ulaşabilir. Public Declare Function GetWindow Lib "user32" Alias "GetWindow" (ByVal hwnd As Long, ByVal wCmd As Long) As Long Public Const GW_HWNDFIRST = 0
Pencerelerin bir ağaç yapısında olduğunu söylememizin sebebi içiçe olmalarından dolayıdır. Masaüstü en başta olacak şekilde her pencere mutlaka başka bir pencerenin içinde (altında) yer alır. Bu, bir kısıtlama değildir. Bir pencereyi içinde bulunduğu pencereden alıp bir diğerine yerleştirmek mümkündür. Taşınabilir paneller bunun güncel bir örneğidir. Bunu görmek için yeni bir Delphi formuna bir Panel ve Buton yerleştirin ve aşağıdaki kodları da ekledikten sonra uygulamayı çalıştırın. procedure TForm1.Panel1MouseDown(Sender:
TObject; Button: TMouseButton; procedure TForm1.Button1Click(Sender: TObject); Ekrandaki panel nesnesini taşıyabilirsiniz. (Bileşenleri çalışma anında taşıma ile ilgili sonraki yazımızda daha ayrıntılı bilgiler bulacaksınız.) Düğme tıklatıldığında panel formun dışına çıkacak. Geri getirmek için paneli sağ tuşla tıklatın. İki durumda ağaç yapısı farklıdır. Şu halde, pencereler her zaman oluşturuldukları pencere içinde kalmayabilir, yukarıda olduğu gibi başka pencere içine taşınabilirler. Peki bir pencere ilk uygulama penceresinden çıkarıldıktan sonra uygulama kapatılırsa ne olur? Bunu ayrıntılı bir şekilde izlemek için, bu yazı için hazırladığımız ekteki Sahip Değiştir uygulamasını kullanabilirsiniz.
Üstteki SpyFinder aracını ekran üzerinde dolaştırıp bir pencereyi (alt bileşenler vs.) seçmek gerekiyor. Forma Yerleştir komutu ile bu pencere formun sağ tarafındaki bölgeye taşınır.
SetParent komutunu kullanmak basit olduğu kadar yeterince serbesttir. Ekrandaki pencereleri anlamsız bir şekilde içiçe koymak da mümkündür.
WİNDOWS MESAJLARI Pencereler oluşturulma aşamasından sonra çoğunlukla mesaj kuyrukları ile idare edilirler. Aktif bir uygulamanın başlamasından bitimine kadar dakikada yüzlerce mesaj, bir kuyruğa girerek sırayla işletilir. Peki mesajlar nasıl oluşur? Mesajları çalıştıran mekanizma nasıl işler? Windows'ta uygulamaların bazı işleri yapması mesaj yoluyla gerçekleştirilir. Bu, aslında uygulamayı bir yığın işten kurtarır ve işin en zor kısmını işletim sistemi (Windows) yapmaktadır. Her pencerenin kendine ait genel bir mesaj kuyruğu vardır ve pencerelere farklı uygulamalardan mesaj gönderilebilir. Mesajlar temelde olaylara bağlıdır. Bir olay olduktan sonra bu olayı tanımlayan bir mesaj üretilir ve ilgili uygulamanın mesaj kuyruğuna bu mesaj eklenir. Bu, yapılan işten sonra çok kısa da olsa (ön sıralardaki mesajların işlenme süresine göre uzun da olabilir) biraz zaman geçebileceği anlamına gelir. Örneğin farenin hareketini anlamak Windows'un görevidir. Bu hareket sonrasında Windows aktif uygulamaya, farenin yeni koordinatı ile ilgili mesajları gönderir. Fare hareket ettirildiğinde, tıklatıldığında, pencere kapatılmak, küçültülmek, büyültülmek istendiğinde ve daha yüzlerce olayda uygulamaya olayı ifade eden mesajlar gönderilir. Örneğin farenin en küçük bir hareketinde ortalama 10 civarında mesaj işlenir. Bu durumda fareyi ekranın solundan sağına kadar hareket ettirmek onlarca mesaj gönderilmesine neden olur. Fakat bu işlem o kadar hızlı olur ki insanların bunu hissetmesi, istisnalar haricinde imkansızdır. Uygulamaların mesajları karşılayabilmesi tamamen geliştiricilere bağlıdır. Geliştiren kişi istemiyorsa gelen mesajla ilgili bir işlem yapmak zorunda değildir. Eğer MouseMove gibi bir olay yordamını boş bırakıyorsanız, pencere üzerinde fare hareket ettirildiğinde oluşan yüzlerce mesaj çöpe gitmiş olur. (Bu, Windows pencerelerinin temelinde olan bir şeydir. Kaldı ki, Windows'ta işleme alınan mesajlar tüm mesajların çok düşük bir oranındadır.) Mesajların gönderilmesi ya da alınması için bir olayın olmasını beklemeye gerek yoktur. İstenirse bir olay türü için sanki o olay gerçekleşmiş gibi mesajlar gönderilebilir. Bu SendMessage API komutu ile mümkündür. SendMessage kullanılırken öncelikle bir pencere adresi ve gönderilecek mesajın kodu belirtilir. Bunun dışında uygulamaların kendi içlerinde mesaj kuyruğundaki işlemlerin yapılmasını beklemeden araya başka mesajlar girmek mümkündür. Delphi'de bu, Perform komutu ile yapılır. Doğrusu bu işlem aslında mesaj kuyruğuna müdahale etmek anlamına gelmeyip, yalnızca mesaj işleyen birime bir mesajın işlenmesi komutunu vermekten ibarettir. Genel Visual C++ belgeleri ilk oluşturulduklarında mesajların işlenmesi için yaklaşık bir sayfalık otomatik kod üretilir. Burada mesajları işleyen bir döngü vardır. Uygulama kapanana kadar bu döngü devamlı çalışır. Aynısı Delphi için de geçerlidir. Bu açıdan Delphi ile Visual C++ arasında bir fark yoktur. VC++'daki bloğun aynısı Delphi'de TApplication.ProcessMessage içinde çalıştırılır. Uygulamanın başlaması TApplication.Run ile olur ve döngü uygulamanın başlangıcından sona ermesine kadar çalışıp, mesaj geldiği takdirde ilgili işlemleri yaptırır. Aşağıdaki kod listesinde (2) ile işaretlenmiş satırın dış tarafındaki repeat-until bloğu, gelen mesajları işleyen kapalı döngüdür.
procedure TApplication.Run; (1) procedure TApplication.HandleMessage; (2) function TApplication.ProcessMessage(var Msg: TMsg): Boolean;
(3)
O zamana kadar, VB ile hazırladığımız mesaj yakalama, çalışma anında boyutlandırma ve taşıma ile ilgili uygulamayı inceleyebilirsiniz. (boyut.zip)
|
||||