BilgiTeknoloji.net    
b i l g i   t e k n o l o j i   y a z ı l ı m

Ana Sayfa

Marjinal XML Access Pratik Uygulamalar Projeler Ekonometri Dilimiz Editörden Çetrefil İletişim
 

Windows'un Pencereleri - II
Windows Mesajları

• Windows mesajları
• Mesajların gönderilmesi
• Çalışma anında taşıma
• Boyutlandırma için hesaplar
• Tasarım paneli
• Gelen mesajları yakalamak
• Örnek uygulamalar


Windows mesajları arayüzde programlama yaparken doğrusu çok büyük faydalar sağlıyor. Pencereler üzerinde her türlü işlemi yapmak mesajlar ile yeterince kolaylaşıyor.

Bu yazıda mesajların nasıl gönderildiği ile birlikte uygulama içindeki pencerelere gelen mesajların nasıl yakalandığı, çalışma anında taşıma ve boyutlandırma işlemlerinin mesaj gönderme yoluyla nasıl yapıldığı anlatılıyor.


WİNDOWS MESAJLARI

Her Windows mesajı, en az dört özniteliğe sahiptir. Mesajın türünü belirten mesaj kodu (Msg), ilk parametre (wparam), ikinci parametre (lparam) ve sonuç değeri (result). Bu özniteliklerin dördü de 4-bayt (32-bit) tamsayıdır.

Bir mesajı göndermek SendMessage, PostMessage ve daha birkaç API işlevi ile mümkündür. Bazı işlemler için özelleştirilmiş işlevler mevcut olsa da bu yazıda SendMessage işlevini kullanacağız.

Tabii SendMessage ile PostMessage arasındaki farkı söyleyelim. Her ikisi de aynı şekilde mesaj gönderir, fakat SendMessage sonucu bekleyebilirken, PostMessage mesajı iletip beklemeksizin hemen geri döner.

Mesajların pencerelere gönderildiğinden geçen yazımızda bahsetmiştik. Mesajları işleyen bir kapalı döngünün uygulama başlangıcından bitimine kadar sürekli çalıştığını da hatırlatalım.

Bir mesajın gönderilmesi için en az iki bilgi gerekiyor. Mesajın gönderileceği pencerenin adresi (belirteci - HWND), ve mesajın kodu. Bunun dışında yukarıda belirttiğimiz iki geçiş değeri de (wparam, lparam) tercihe ve mesaj türüne bağlı olarak kullanılabilmektedir.

Windows, sınırsız mesaj gönderilmesine izin verse de pencerenin mesaj kuyruğunda biriktirilecek mesaj adedinin bir sınırı vardır. Windows 2000 ve üzeri işletim sistemlerinde geçerli olarak, bir mesaj kuruğunda en fazla 10000 mesaj bulunabilir. Tabii ki, normal şartlarda bu sayıya çıkmak çok zordur.

Gerektiği hallerde kayıt defterinin aşağıda belirtilen yolunda bu değer değiştirilebilir.

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\USERProcessHandleQuota


MESAJ GÖNDERMEK

Mesaj göndermek için yukarıda belirttiğimiz gibi SendMessage ile birkaç API işlevini kullanabilirsiniz. Bu yazıda ise sadece SendMessage kullanılmaktadır.

Bir pencere sol fare tuşu ile tıklatılıp bırakıldığında pencereye en az iki mesaj gönderilir. Birisi farenin basıldığını (WM_LBUTTONDOWN), diğeri de bırakıldığını (WM_LBUTTONUP) işaret eder.

Fare kullanmadan, doğrudan mesaj göndermek yoluyla da aynı iş yapılabilir. Spy++ ya da Spymon++ ile Başlat düğmesinin HWND değerini bulduktan sonra aşağıdaki Delphi ve VB kodlarını 12345 yerine bulduğunuz değeri yazarak çalıştırmayı deneyin.


[Delphi]

SendMessage(12345, WM_LBUTTONDOWN,0,0);
SendMessage(12345, WM_LBUTTONUP,0,0);


[VB]

Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long

Private Const WM_LBUTTONUP = &H202
Private Const WM_LBUTTONDOWN = &H201

Private Sub Command1_Click()
  SendMessage 12345, WM_LBUTTONDOWN, 0, 0
  SendMessage 12345, WM_LBUTTONUP, 0, 0
End Sub


Bu iki Windows mesajı aslında pencerenin belirli koordinatlarını kullanmaktadır ve bu bilgi SendMessage'nin lparam geçiş değerinde belirtilir. CTRL, ALT gibi tıklama anında özel tuş basılıymış gibi işlem yapılmak isteniyorsa da wparam değişkenine geçerli bir değer atamak gerekiyor.


ÇALIŞMA ANINDA TAŞIMA

Çalışma anında basit bir taşıma işlemi Windows mesajları kullanılarak çok kolay yapılır. Bir pencerenin MouseDown (fare basıldığında) eylemini aşağıdaki gibi yapmak bunun için yeterlidir.


[Delphi]

procedure TForm1.Button1MouseDown(Sender: TObject; Button: TMouseButton;
    Shift: TShiftState; X, Y: Integer);
begin
  ReleaseCapture;
  SendMessage(Button1.Handle, WM_SYSCOMMAND, SC_SIZE+9,0);
end;


[VB]

Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long

Private Const WM_SYSCOMMAND = &H112
Private Const SC_SIZE = &HF000&

Private Declare Function ReleaseCapture Lib "user32" () As Long

Private Sub Command1_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
  ReleaseCapture
  SendMessage Command1.hwnd, WM_SYSCOMMAND, SC_SIZE + 9, 0
End Sub

Fare ile ilgili bazı işlemlerde ReleaseCapture kullanmak gerekiyor. Bu, hareketin SendMessage satırında algılanması içindir.


BOYUTLANDIRMA İÇİN HESAPLAR

Çalışma anında boyutlandırma işlemi, taşımaya benzer şekilde yapılır. Bunun için SC_SIZE sabiti 1-9 arası değer alır ve herbir değer pencerenin farklı kenarından ya da köşesinden boyutlandırma yapılacağını ifade eder. 9 ise pencerenin sürüklenip taşınmasını sağlar.

Önce MouseDown yordamında SC_SIZE+8 kullararak düğmeyi taşımayı deneyin.

ReleaseCapture
SendMessage Command1.hwnd, WM_SYSCOMMAND, SC_SIZE + 8, 0



Bu haliyle 1-9 arası değerleri deneyip herbiri için yapılan hareketi gözleyebilirsiniz. Bu işlemler bir form penceresini taşımak ve kenarlarından tutup boyutlandırmak gibidir. Öyleyse boyutlandırma için farenin bulunduğu noktanın ilgili kenara ya da köşeye yakın olması gerekir. SC_SIZE'ye eklenecek sayısal değerler, farklı yönlere göre alttaki kutuda gösteriliyor.

4 3 5
1 9 2
7 6 8

Her yön için aynı zamanda fare işaretçisini belirtmek size kalmıştır.

Taşımayı ve her yöndeki boyutlandırmayı uygun şekilde sağlamak için aşağıdaki gibi MouseDown ve MouseMove olay yordamlarını kullanmak yeterli. MouseDown'da SC_SIZE ile hareket kodu kullanılırken MouseMove'de farenin konumuna göre işaretçinin (cursor) değiştirildiğine dikkat edin.


[Delphi]

var
Hareket: Byte;

procedure TForm1.Button1MouseDown(Sender: TObject; Button: TMouseButton;
    Shift: TShiftState; X, Y: Integer);
begin
  if button=mbleft then
  begin
    ReleaseCapture;
    SendMessage(Button1.Handle, WM_SYSCOMMAND, SC_SIZE+Hareket,0);
  end;
end;

procedure TForm1.Button1MouseMove(Sender: TObject; Shift: TShiftState; X,
    Y: Integer);
const parca=8;
begin
  if ( (x>=Button1.Width-parca) and (y>=Button1.Height-parca) ) then Hareket:=8
  else if ( (x>=Button1.Width-parca) and (y> parca) ) then Hareket:=2
  else if ( (x>=Button1.Width-parca) and (y<=parca) ) then Hareket:=5
  else if ( (x> parca) and (y>=Button1.Height-parca) ) then Hareket:=6
  else if ( (x<=parca) and (y>=Button1.Height-parca) ) then Hareket:=7
  else if ( (x> parca) and (y<=parca) ) then Hareket:=3
  else if ( (x<=parca) and (y> parca) ) then Hareket:=1
  else if ( (x<=parca) and (y<=parca) ) then Hareket:=4
  else Hareket:=9;

  case Hareket of
    4,8: Button1.Cursor:=crSizeNWSE; // kuzey batı - güney doğu
    7,5: Button1.Cursor:=crSizeNESW; // kuzey doğu - güney batı
    3,6: Button1.Cursor:=crSizeNS; // kuzey - güney
    1,2: Button1.Cursor:=crSizeWE; // doğu - batı
    else Button1.Cursor:=crSizeAll;
  end;

end;


[VB]

Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
Private Const WM_SYSCOMMAND = &H112
Private Const SC_SIZE = &HF000&
Private Declare Function ReleaseCapture Lib "user32" () As Long

Dim Hareket As Byte

Private Sub Command1_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
  ReleaseCapture
  SendMessage Command1.hwnd, WM_SYSCOMMAND, SC_SIZE + Hareket, 0
End Sub

Private Sub Command1_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)

  Const parca = 100

  If ((X >= Command1.Width - parca) And (Y >= Command1.Height - parca)) Then
    Hareket = 8
  ElseIf ((X >= Command1.Width - parca) And (Y > parca)) Then
    Hareket = 2
  ElseIf ((X >= Command1.Width - parca) And (Y <= parca)) Then
    Hareket = 5
  ElseIf ((X > parca) And (Y >= Command1.Height - parca)) Then
    Hareket = 6
  ElseIf ((X <= parca) And (Y >= Command1.Height - parca)) Then
    Hareket = 7
  ElseIf ((X > parca) And (Y <= parca)) Then
    Hareket = 3
  ElseIf ((X <= parca) And (Y > parca)) Then
    Hareket = 1
  ElseIf ((X <= parca) And (Y <= parca)) Then
    Hareket = 4
  Else
    Hareket = 9
  End If

  Select Case Hareket
    Case 4, 8: Command1.MousePointer = vbSizeNWSE 'kuzey batı - güney doğu
    Case 7, 5: Command1.MousePointer = vbSizeNESW 'kuzey doğu - güney batı
    Case 3, 6: Command1.MousePointer = vbSizeNS 'kuzey - güney
    Case 1, 2: Command1.MousePointer = vbSizeWE 'doğu - batı
    Case Else: Command1.MousePointer = vbSizeAll
  End Select

End Sub


TASARIM PANELİ

İçine yerleştirilen her bileşene taşıma ve boyutlandırma özelliği katan bir Delphi nesnesi CD'mizde yer alıyor. Kaynak kodu da verilen TTasarimPanel'in geliştirilmesine, okuyucularımızdan istek geldiği takdirde devam edeceğiz. İsteyenler bunu alıp değiştirebilir, yazar bilgilerini silmeden kendi isimlerini ekleyip, yapılan değişiklikleri de belirterek ve ücretsiz olmak kaydıyla dağıtabilirler. Açık kodlu geliştirmeye katılmak isteyen Delphi programcılarının hazırladıkları TTasarimPanel sürümlerini ya da türevlerini, bize gönderdikleri takdirde yayınlayacağız.


MESAJLARI YAKALAMAK

Windows mesajlarının yakalanması zor bir işlem değildir. Fakat Visual Studio araçlarındaki Spy++'da olduğu gibi başka uygulamalardaki pencerelere giden mesajların yakalanması yeterince zor ve masraflıdır.

Uygulama içindeki mesajları yakalamak ise Delphi'de zor değil, VB 6'da uğraştırıcıdır. Yine de her ikisi, buna aşina olmayanlar tarafından genellikle kafa karıştırıcı olarak görülür. Bu başlık altında sadece geçen ay verdiğimiz Boyut uygulamasının VB kodlarını kısaca inceleyeceğiz.

Uygulamanın çalışma yöntemine bağlı olarak, pencerelerin kendilerine ait, mesajları işleyen kapalı bir döngünün bulunduğu genel yordamları vardır. Uygulamanın kendi içinden bu yordamların adresi değiştirildiğinde tüm mesajlar artık yeni yordama gönderilmeye başlanır.

Bir pencerenin ana çalışma yordamını bulmak GetWindowLong ile mümkündür.

Yordam_Adresi:=GetWindowLong(Hwnd, GWL_WNDPROC);

Bu yordamın adresini değiştirip başka bir yordamın adresini vermek, mesajlara müdahale edebileceğimiz anlamına gelir. Bunu SetWindowLong ile yapmadan önce yeni yordamın uygun şekilde hazırlandığından emin olmak lazımdır.

[Delphi]

function Yeni_Yordam(..);
begin
..
end;

Eski_Yordam:= SetWindowLong(Hwnd, GWL_WNDPROC,integer(@Yeni_Yordam));


[VB]

Public Function Yeni_Yordam (..)
..
End Sub

Eski_Yordam_Adresi = SetWindowLong(Hwnd, GWL_WNDPROC, AddressOf Yeni_Yordam)


Neyse ki SetWindowLong yordam adresini değiştirdikten sonra sonuç olarak eski yordamın adresini döndürüyor.

Yeni yordamı genel (public) bir yordam olarak tanımlamak gerekir. VB'de bunu genel bir modülün içinde Public ile belirtmek yeterlidir.

SetWindowLong ile ayarlandıktan sonra artık pencere ile ilgili tüm mesajlar bu yordama, geçiş değişkenleri aracılığıyla gönderilecektir. Bu durumda ilgilendiğimiz mesajlar için gerekli işlemleri yapıp diğer mesajları çöpe atmaktansa, eski pencere yordamını ilgili mesajları belirterek CallWindowProc ile çalıştırmak gerekir. Zira bizim ilgilenmediğimiz mesajlar aslında pencerenin ilk yordamı için değerli olabilir.

CallWindowProc(Eski_Yordam_Adresi, HWND, Mesaj, wParam, lParam)

Yeni yordamı aşağıdakine benzer şekilde düzenlemek gerekiyor:

Public Function Yeni_Yordam(ByVal hwnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long

Dim PR As Long

Select Case uMsg
Case Mesaj1:
  ...
Case Mesaj2:
  ...
Case Else:
  'Diğer mesajların çalışması için.
  PR = CallWindowProc(Eski_Yordam_Adresi, HW, uMsg, wParam, lParam)
End Select

Yeni_Yordam = PR

End Function

SetWindowLong ile pencere yordamının adresi değiştirilmişse uygulama kapatılmadan önce mutlaka eski yordamı bağlamak gerekir.

SetWindowLong HWND, GWL_WNDPROC, Eski_Yordamın_Adresi

Boyut uygulamasının VB kodu CD'mizde bulunuyor. Bu uygulamada yakalanan mesajlara ait bilgilerin liste kutusuna SendMessage ile eklendiğine dikkat edin.

'Listeye metin ekle
SendMessage anaform.Liste.hwnd, LB_ADDSTRING, 0, ByVal metin

'Satırı seç
SendMessage anaform.Liste.hwnd, LB_SETCURSEL, ListeAdedi, 0

'Satıra değer ver
SendMessage anaform.Liste.hwnd, LB_SETITEMDATA, ListeAdedi, ByVal uMsg


Doğrudan ekleme yapıldığında farenin hareketi engellendiği için listeyi mesaj gönderme yoluyla doldurmayı tercih ettik.

Gelecek ayki uygulamalarımızdan biri mesaj gönderme yoluyla Windows'taki listelere ait bilgilere ulaşılması ve listelerde değişiklik yapılması ile ilgili olacak. O zamana kadar ekteki Liste Matik'i inceleyebilirsiniz. Liste Matik'i düz listelerde ve Windows Gezgini'ndeki gibi ağaç listelerde kullanmayı deneyin.

Düzeltme: Önceki yazıda geçen "4-bit tamsayı ile ifade edilen HWND'nin sayısal değeri .." ifadesinin doğrusu "4-bayt (32-bit) tamsayı ile ifade edilen HWND'nin sayısal değeri .." şeklindedir.
 

DOSYALAR:
boyut.exe
(36 kb.)
boyut_kod.zip
listematik.exe
(zip, 250 kb.)
spyfinder.zip
(Delphi için TSpyFinder bileşeni)
tasarim.exe
(zip, 206 kb.)
tasarim_kod.zip
tasarim_panel.zip
(Delphi 6 için açık kodlu TTasarimPanel bileşeni)


Serkan ŞAHİNOĞLU
(Chip Dergisi, Aralık 2002)


http://BilgiTeknoloji.net