Konu ister Delphi ister başka bir programlama dili olsun Windows mesajları zor anlaşılan konulardan birisidir. Buna rağmen daha önce Delphi ile birkaç satır kod yazdıysanız bu metni ilk okumanızda mesajlar konusunu anlayacağınızdan eminim. Mesajlar konusunun kolay anlaşılmasını amaçladığım için en başından anlatacağım. Bu amaçla projenin formuna Edit ve Button nesnesi yerleştirdim.

Bu kitaptan önce yayınlanan Delphi konulu bir kitapta ya bir yazar bu konuyu anlatırken benzer şekilde forma Edit yerleştirdiyse, Pencere_no adında bir değişken tanımladıysa yine yandım demektir, yine çalıntı yapma suçu ile hakim karşısına çıkmam garanti olur.
Çalışma anında imleç Edit’in içinde iken klavyenin karaktersel tuşlarından birisine basıldığında basılan tuşu temsil eden karakter Edit’e aktarılır. Bir an için durup düşünmenizi istiyorum; klavyenin tuşuna basılması ile, tuşu temsil eden karakterin Edit’e aktarılması işleminin geri planında neler oluyor?
Tahmin edeceğiniz gibi bilgisayarın klavye ve diğer donanımları Windows’un kontrolündedir. Kullanıcı klavyenin karaktersel bir tuşuna bastığında basılan tuşu temsil eden kod o sırada aktif durumda olan pencereye gönderilir. Bizim örnekte aktif pencere Edit olduğu işin basılan tuşu temsil eden kod veya karakter Edit’e aktarılır.
Kullanıcı klavyenin bir tuşuna bastığında Windows o sırada aktif durumda olan uygulamaya(burada Delphi uygulaması) bir mesaj gönderir. Bu mesaj sayesinde Edit’e basılan tuşun kodu yazılır. Mesajlara, Windows’un programlara verdiği emirlerdir denilebilir.
Şimdi şeytanın avukatlığını yapıp formdaki düğme tıklandığında sanki kullanıcı klavyenin “A” tuşuna basmış gibi Delphi uygulamasına Windows’un mesaj göndermesini sağlayacağım. Delphi uygulamasından Windows’a klavyenin bir tuşuna basıldığı mesajını gönderirsem; Windows sanki ilgili tuşa kullanıcı basmış gibi işlem yapacaktır. Bu amaçla Button1 nesnesinin Click olayını temsil eden metodu aşağıdaki gibi düzenledim.
procedure TForm1.Button1Click(Sender: TObject);
var
Pencere_no : Integer;
begin
Pencere_no := Edit1.Handle;
PostMessage(Pencere_no, WM_KEYDOWN, 65, 0);
end;
Şimdi bu koda biraz açıklama getirelim. Bildiğiniz gibi Windows pencereli veya focus olabilen Edit ve Memo gibi nesnelere otomatik olarak bir numara vermektedir. Bu numara ilgili nesnenin Handle özelliğinde saklanmaktadır. Bu nedenle bu kodda integer bir değişken tanımlayıp Edit1’in Handle özelliğinin içeriğini bu değişkene aktardım.
Windows’a mesaj gönderme PostMessage ve SendMessage fonksiyonları ile gerçekleştirilmektedir. Yukarıda kullandığım PostMessage() fonksiyonu 4 parametreye sahiptir. İlk parametrede mesaja konu edilen pencere veya nesne işaret edilmektedir. İkinci parametrede ise Handle numarası belirtilen pencereye gönderilecek mesaj işaret edilmektedir. Windows’a tuşa basma mesajını göndermek istediğim için 2. parametre olarak WM_KEYDOWN mesajının adını yazdım.
Windows’un tanımlı mesajları “WM_” ön eki ile başlamaktadır. PostMessage() fonksiyonun 3. ve 4. parametrelerinde mesajın parametreleri verilmektedir. Edit’e “A” harfini yazmak istediğimden dolayı WM_KEYDOWN mesajına 1. parametre olarak 65 sayısını verdim. WM_KEYDOWN gibi bazı mesajların 2. parametresi olmadığı için 0 kullandım.
Anlayacağınız çalışma anında formadaki bu düğme tıklanıp yukarıda verilen kod işletilirse Edit1’e “A” harfi yazılır. Bu sırada CapsLock tuşu kilitli ise Edit1’e büyük “A” değilse küçük “a” yazılır. Aşağıda verdiğim ekran görüntüsünü düğmeyi 2 kez tıkladıktan sonra aldım.

Bu sırada CapsLock tuşu devrede olsaydı Edit’e yazılan harfler büyük olurdu. Şimdi ise Edit’e WM_KEYDOWN mesajı ile aktardığım “A” harflerini yine aynı mesajla sileceğim. Bu amaçla forma 2. bir düğme yerleştirip aşağıda verdiğim kodu yazdım.
procedure TForm1.SilClick(Sender: TObject);
var
Pencere_no : Integer;
begin
Pencere_no := Edit1.Handle;
PostMessage(Pencere_no, WM_KEYDOWN, 8, 0);
end;
Geriye silme tuşunun ASCII kodu 8 olduğu için WM_KEYDOWN mesajının 1. parametresi olarak 8’i kullandım. Edit’te silinecek bir şey yokken bu kodu işletirseniz Windows size bir tepkide bulunmaz. Tahmin edebileceğiniz gibi Windows’un tanımlı yüzlerce mesajı bulunmaktadır. Her mesaj gerçekte bir numara ile temsil edilmektedir. Ancak mesajlar sayısal bilgi yerine WM_KEYDOWN ve WM_PAINT gibi akılda kalması kolay olan sabitlerle ifade edilmektedir.
Örneğin WM_KEYDOWN sabiti 16 tabanlı 100 sayısını temsil etmektedir. Bu durumda PostMessage() fonksiyonuna 2. parametre olarak 16 tabanlı $0100 sayısını veya bu sayının 10 tabanlı karşılığı olan 256’yı vermeniz halinde Windows’un aktif pencereye bir tuşa basma mesajını göndermesine neden olursunuz. Aşağıda verilen her 3 kullanım aynı etkiye sahiptir.
PostMessage(Pencere_no, WM_KEYDOWN, 65, 0);
PostMessage(Pencere_no, 256, 65, 0);
PostMessage(Pencere_no, $0100, 65, 0);
Windows’un tanımlı mesajları hakkında bilgi edinmek veya hangi mesajın hangi sabit ile temsil edildiğini öğrenmek istiyorsanız Messages.pas adlı unit’e bakabilirsiniz. Bu hazır Unit projeye dahil ettiğiniz her kod dosyasına Uses bloğunda otomatik olarak dahil edilmektedir.

Yukarıda kullandığım Windows’un 256 sayılı veya WM_KEYDOWN olarak anılan mesajının 2. parametresi olmadığı için mesajın 2. parametresi olarak 0 kullandım. Sıfır sayısı yerine WM_NULL sabitini kullanabilirsiniz.
procedure TForm1.Button1Click(Sender: TObject);
var
Pencere_no : Integer;
begin
Pencere_no := Edit1.Handle;
PostMessage(Pencere_no, $0100, 65, WM_NULL);
end;
SendMessage() veya PostMessage() fonksiyonlarından yararlanarak Delphi uygulamasına mesaj göndererek istediğiniz işlemi yapabilirsiniz. Doğrusunu söylemek gerekirse genellikle mesajlarla işlem yapmaya gerek yoktur. Çünkü mesajlara işlem yaptırtmak yerine Delphi’nin imkanlarını kullanmanız önerilir. Ne zaman ki Delphi’nin imkanları yeterli gelmedi o zaman işletim sistemine başvurmak gerekir. Örneğin SendMessage() fonksiyonunu aşağıdaki gibi kullanarak formdaki Edit1 ve Edit2’ye girilebilinecek karakter sayısını sınırlayabilirsiniz.
procedure TForm1.FormCreate(Sender: TObject);
Var
Pencere_no : Integer;
begin
Pencere_no := Edit1.Handle;
SendMessage(Pencere_no, EM_LIMITTEXT, 10, 0);
Pencere_no := Edit2.Handle;
SendMessage(Pencere_no, EM_LIMITTEXT, 10, 0);
end;
Formdaki Edit1 ve Edit2’ye EM_LIMITTEXT mesajını gönderirken SendMessage fonksiyonunu kullandım. Bu mesajı Windows’a PostMessage fonksiyonu ile gönderebilirsiniz. Bu şekilde mesajlarla Edit’lere girilecek karakter sayısını sınırlamak yerine aşağıda yapıldığı gibi bu Edit’lerin Maxlength özelliklerinde ayarlama yapabilirsiniz.
procedure TForm1.FormCreate(Sender: TObject);
begin
Edit1.MaxLength := 10;
Edit2.MaxLength := 10;
end;
Benzer şekilde SendMessage() veya PostMessage() fonksiyonu ile formdaki Edit’leri veya başka nesnelere ReadOnly özelliklerini verebilirsiniz. Aşağıda verdiğim kodda önde Edit1’in ReadOnly özelliğini Delphi’nin imkanlarını kullanarak False, Edit2’nin bu özelliğini ise True yaptım. Devamında Delphi uygulamasından Windows’a PostMessage fonksiyonu ile mesaj gönderip Edit1’in ReadOnly özelliğini True, Edit2’nin bu özelliğini ise False yaptım.
procedure TForm1.FormCreate(Sender: TObject);
Var
Pencere_no : Integer;
begin
Edit1.ReadOnly := False;
Edit2.ReadOnly := True;
Pencere_no := Edit1.Handle;
PostMessage(Pencere_no, EM_SETREADONLY, 1, 0);
Pencere_no := Edit2.Handle;
PostMessage(Pencere_no, EM_SETREADONLY, 0, 0);
end;
Şimdi örnek olması için Delphi projesinin formundaki Edit’e yazılan bilgiyi Not Defteri penceresine yapıştıracağım. Bu amaçla Windows’un FindWindow ve FindWindowEx fonksiyonlarından yararlanıp o sırada Not Defteri programı çalışıyorsa penceresinin Handle numarasını öğreneceğim. Ardından Not Defteri penceresine Edit1’in içeriğini aktaracağım. Bu amaçla hazırladığım kodu aşağıda görebilirsiniz.
procedure TForm1.Button1Click(Sender: TObject);
var
Pencere_no, i: Integer;
Str: string;
begin
Pencere_no := FindWindow('Notepad', nil);
if handle <> 0 then
Begin
Str := Edit1.Text;
Pencere_no := FindWindowEx(Pencere_no, 0, 'Edit', nil);
for i := 1 to Length(Str) do
SendMessage(Pencere_no, WM_CHAR, Word(Str[i]), 0);
end;
end;
Windows’un Delphi uygulamalarına gönderdiği mesajları kolay bir şekilde inceleyebilirsiniz. Windows’un Delphi uygulamasına gönderdiği mesajı yakalayıp mesaj hakkında bilgi edinmek istiyorsanız Delphi’nin TMsg tipinden yararlanabilirsiniz. Windows adlı hazır unit’te tanımlı olan bu tipi aşağıda görebilirsiniz.
tagMSG = packed record
hwnd: HWND;
message: UINT;
wParam: WPARAM;
lParam: LPARAM;
time: DWORD;
pt: TPoint;
end;
TMsg = tagMSG;
Bu yapının ilk elemanına mesajı alacak pencerenin Handle numarası, 2. elemana ise mesajın adı; başka bir deyişle numarası aktarılmaktadır. TMsg adlı yapının wParam ve lParam adlı elemanlarında ise mesajın işletilmesi sırasında gereken bilgiler olmaktadır. time adlı elemana mesajın gönderildiği anki zaman bilgisi TPoint tipindeki “pt” elemanında ise mesajın gönderildiği sırada imlecin bulunduğu yer işaret edilmektedir.
Bildiğiniz gibi yazıcılara gönderilen dokümanın yazdırılması sonra ermeden başka dokümanlar gönderildiğinde dokümanlar sıraya veya yazıcı kuyruğuna konulur. Başka bir deyişle ilk gönderilen doküman ilk yazılır prensibi(FIFO) geçerlidir. Benzer durum bazı Windows mesajları için de geçerlidir.
Windows’un yazıcıya hizmet eden bir yazdırma kuyruğu olduğu gibi mesajları kuyruklayan veya sıraya sokan bir hizmete sahiptir. Kuyruğa sokulmuş veya kuyruklanmış mesajlar sıra ile işletilir. Kuyruklanmış mesajların arasında WM_PAINT mesajı varsa bu mesajın kuyruktaki sırasına bakılmadan en son olarak işletilir.
Windows’un ayrıca mesaj kuyruğuna sokulmayıp hemen işletilen mesajları da bulunmaktadır. Örneğin kullanıcı fare ile ekrandaki açık bir pencereden diğerine geçtiğinde Windows aktif yapılan pencereye WM_ACTIVATE mesajını gönderir ve bu mesajın gereği hemen yerine getirilir. Delphi projeleri dahilinde Windows’a mesaj gönderirken kullanabileceğiniz fonksiyonların orijinal yapılarını aşağıda görebilirsiniz.
LRESULT SendMessage
(
HWND hWnd,
UINT Msg,
WPARAM wParam,
LPARAM lParam );
BOOL PostMessage
(
HWND hWnd,
UINT Msg,
WPARAM wParam,
LPARAM lParam );
SendMessage fonksiyonu ile Windows’a gönderilen mesaj işlenmeden geriye değer gönderilmez. Başka bir anlatımla SendMessage() fonksiyonu ile Windows’a gönderilen mesajın cevabını beklenir. Parametre ve işlevleri benzer olan PostMessage() fonksiyonu ise mesajın işlenmesini beklemeden geriye değer göndermektedir.
PostMessage() fonksiyonu ile Windows’a gönderilen mesaj kuyrukta kendisine yer bulamazsa işletilmez. Gönderilen mesajın gereğinin hemen yapılması isteniyorsa mesaj PostMessage fonksiyonu ile Windows’a gönderilmelidir. Yukarıdaki sayfalarda Edit’e bilgi aktarma işlemini yapmak üzere WM_KEYDOWN mesajını PostMessage() fonksiyonu ile gönderdim. PostMessage yerine SendMessage() ile mesaj gönderseydim sonuç alamazdım. Bu nedenle aşağıda verilen kod işletildiğinde Edit1’e bilgi aktarılmaz.
procedure TForm1.Button1Click(Sender: TObject);
var
Pencere_no : Integer;
begin
Pencere_no := Edit1.Handle;
SendMessage(Pencere_no, WM_KEYDOWN, 65, 0);
end;
Çünkü bu kodu işletmek üzere Button1 nesnesi tıklandığında Delphi uygulaması zaten Windows’a mesaj gönderir. Devamında SendMessage fonksiyonu ile ikinci mesaj gönderildiğinde ise mesajın gereği yerine getirilemez. Formun üzerindeki herhangi bir nesneyi aktif nesne yapmak üzere Windows’a mesaj göndermek istiyorsanız ister SendMessage ister PostMessage fonksiyonu kullanılsın aynı sonuç alınır. Bu nedenle aşağıda verilen her 2 satır da aynı işleve sahiptir.
var
Pencere_no : Integer;
begin
Pencere_no := Edit2.Handle;
SendMessage(Pencere_no, WM_SETFOCUS, 0, 0);
PostMessage(Pencere_no, WM_SETFOCUS, 0, 0);
end;
TEdit sınıfı hakkında bilgi verilen bölümde Edit’in MouseEnter ve MouseLeave olaylarını temsil eden Procedure’leri aşağıdaki gibi düzenleyerek fare işareti söz konusu Edit’in üzerinde iken farklı renge boyanmasını sağlamıştım. Fare işareti Edit’in üzerinden uzaklaştırıldığında ise Edit’in eski rengini almasını sağlamıştım. Ayrıca her 2 yordamda kullanmak amacıyla Unit’in Var bloğunda TColor tipinde bir değişken tanımlamıştım.
var
Form1: TForm1;
Renk : TColor;
procedure TForm1.Edit1MouseEnter(Sender: TObject);
begin
Renk := Edit1.Color;
Edit1.Color := TColor($00FF00);
end;
procedure TForm1.Edit1MouseLeave(Sender: TObject);
begin
Edit1.Color := Renk;
end;
Şimdi bu işlemi Windows mesajları ile yapmayı deneyeceğim. Bu amaçla projenin formuna 3 Edit ve 2 düğme yerleştirdim. Kullanıcı çalışma anında fareyi hangi Edit’in üzerine götürürse o Edit farklı renge boyanacaktır. Bu işlemi gerçekleştirebilmek için WM_MOUSEMOVE mesajını yakalayıp istenen işlemleri yapmak gerekir. Bu amaçla ilk olarak formu temsil eden Unit’in Type bloğunda aşağıdaki gibi Mesaj_gor() adında bir yordam deklare ettim.

Bu yordamın WM_MOUSEMOVE mesajı Delphi uygulamasına gönderildiği zaman işletilmesini istediğim için yordamın deklerasyon satırında önce message deyimine ardından WM_MOUSEMOVE mesajına yer verdim.
Bu yordam, işaret edilen Windows mesajı Delphi uygulamasına gönderildiğinde işletileceği için TMessage tipinde bir parametreye sahip olmalıdır. “Mesaj_gor” adını verdiğim yordamı bu şekilde deklare ettikten sonra yordamın kendisini aşağıdaki gibi düzenledim.
procedure TForm1.Mesaj_gor(var Mesaj: TMessage);
Var
Nokta : TPoint;
Sayi, i : Integer;
TextBox : TEdit;
begin
Nokta.X := Mesaj.LParamLo;
Nokta.Y := Mesaj.LParamHi;
Sayi := self.ComponentCount;
for i := 0 to Sayi - 1 do
Begin
if Self.Components[i] is TEdit then
Begin
end;
end;
end;
2004 yılı başında yayınlanan C# kitabımda Point tipindeki değişkene “Nokta” adını verdiğim için “değişken adı benzerliği” suçlaması ile sayıdeğer 2 yazar tarafından dava edilmiştim. Bu örnekte de alışkanlıktan olsa gerek değişkene nokta dedim. Sizler bu değişkenin adının ölü_nokta, kör_kuyu, son_nokta vs. olduğunu düşünebilirsiniz.
Bu kodda ilk olarak TPoint tipinde bir değişken tanımladım. TPoint tipinde değişken tanımlamak yerine 2 integer değişken tanımlayabilirsiniz. Ardından yordama gönderilen mesajın(burada WM_MOUSEMOVE) LPramLo ve LParamHi özelliklerinin içeriğini TPoint değişkenin X ve Y elemanlarına aktardım.
Böylece farenin güncel konumunu öğrenmiş oldum. Devamında formun üzerindeki nesnelerin sayısını öğrenip TEdit tipinde olup olmadıklarını öğrendim. Söz konusu nesne TEdit tipinde ise ve fare işareti bu nesnenin üzerinde ise farklı renge boyanması için bu koda aşağıdaki gibi ekleme yaptım.
procedure TForm1.Mesaj_gor(var Mesaj: TMessage);
Var
Nokta : TPoint;
Sayi, i : Integer;
TextBox : TEdit;
begin
Nokta.X := Mesaj.LParamLo;
Nokta.Y := Mesaj.LParamHi;
Sayi := self.ComponentCount;
for i := 0 to Sayi - 1 do
Begin
if Self.Components[i] is TEdit then
Begin
TextBox := TEdit(Components[i]);
if (Nokta.X >= TextBox.Left-2) And (Nokta.Y >= TextBox.Top-2) And
(Nokta.X <= TextBox.Left + TextBox.Width+2) And
(Nokta.Y <= TextBox.Top + TextBox.Height+2) Then
TextBox.Color := clBlue
else
TextBox.Color := clWindow;
end;
end;
end;
Bu kodu dikkatlice incelerseniz fare işaretinin ilgili Edit nesnesinin üzerinde olup olmadığını araştırırken sol ve üst kenardan 2 çıkardım, alt ve sağ kenara ise 2 ekledim. Bunun açıklaması şudur: Dikkat ederseniz yukarıda WM_MOUSEMOVE mesajını yakalamak için hazırladığım yordamı TForm1 sınıfının Type bloğunda tanımlamıştım.
Dolayısıyla “Mesaj_gor” adını verdiğim yordama gönderilen TMessage tipindeki parametrede formla ilgili bilgi olur. Fare işareti çalışma anında tam olarak Edit’in üzerine götürüldüğünde Windows’un nazarında aktif pencere Edit olur ve formla ilgili WM_MOUSEMOVE mesajı yakalanamaz. Bunun önüne geçmek için koordinatlara 2 ekleyip ve çıkarmayı tercih ettim. Hemen belirtmeliyim ki bu kodun pratik bir değeri yoktur.