Delphi Dünyası Facebook'ta

Kodbank İndir

! CODEBANK 2012 !

İNDİRMEK&DETAYLI BİLGİ ALMAK İÇİN BURAYI TIKLAYINIZ.

Gönderen Konu: Windows ve Mesajlar  (Okunma sayısı 3644 defa)

0 Üye ve 1 Ziyaretçi konuyu incelemekte.

Çevrimdışı Opt2000

  • Global Moderatör
  • *****
  • İleti: 263
  • Rep: +9/-1
  • Cinsiyet: Bay
Windows ve Mesajlar
« : 07 Ekim 2005 18:55:47 »
Windows ve Mesajlar
Aslında bu yazıyı, Kefukar arkadaşımızın bir önerisi üzerine yazıyorum. “Hergün 1 kişi yani sırayla falan değil de ilk başlık açan diyelim Makaleler kısmına herkesin faydalanabilecegi bir konu açsın ve bildiklerini önce kendi sonra da arkasından isteyen diğer kişiler bildiklerini/bildiği kadar yazsın” Bu arada bence çok mantıklı ve güzel bir öneri.  

Aklıma ilk gelen konu Windows Mesajları oldu. Bu öneriyi okuduğum sırada bir program için mesaj yazıyordum. Özellikle mesajlarla ilk ilgilenmeye başladığım sıralarda çektiğim sıkıntıları düşününce, en azından diğer programcılar bu basit konu üzerinde vakit kaybetmesinler diye giriş niteliğinde bir şeyler yazayım dedim.

Windows çalışan programlarla sürekli haberleşmektedir. Buradaki haberleşme sadece program açıldı, kapandı gibi basit şeyler değildir. Aslında her şey mesajlar üzerinden yürütülür. Örneğin kullanıcı fare ile butonun üstüne geldi, farenin sol tuşuna bastı. Farenin sol tuşu basılı iken fareyi biraz kenara kaydırdı, sol tuşu serbest bıraktı. Click olayı oluştu vs. Biz elbette Delphi ile program yazarken bu mesajların nasıl yakalandığı ile ilgilenmiyoruz. Delphi bütün bunları bizim için yapıyor ve bunları kullanabileceğimiz bir arabirim sunuyor. O zaman ben niye bu satırları yazıyorum: Çok basit. Delphi Windows’un gönderdiği bütün mesajları yakalamıyor ve kimi zaman kullanıcının bazı mesajları yakalaması gerekiyor. Delphinin böyle bir kısıtlamaya gitmesinin sebebi ise programı hızlandırmak. Çünkü her mesaj yakalama işlemi sırasında aynı zamanda kontrol de yapıldığı için program gereksiz yere yavaşlıyor.

Peki, Windows mesajları nasıl gönderiyor? Aslında çok basit bir mantığı var. Kodlama yaparken irçok bileşen için Handle özelliğini görmüşsünüzdür. Bu Handle bilgisi, Windows tarafından verilmiştir ve aslında bileşenin adresidir. Windows o bileşene herhangi bir mesaj göndermek istediğinde, Handle bilgisini adres olarak kullanır ve oraya gerekli mesajı gönderir. Handle bilgisi olmayan bileşenler (TLabel, TImage, TPaintBox gibi) parent bileşinin mesajlarını kullanır. Ama dikkat ederseniz o bileşenler focus alamazlar ve özellikle uzun ve ağır işlemlerde paint problemi çıkarırlar.

Windows’un mesajlarında aşağıdaki bilgiler bulunur.

Handle: Mesajın gideceği bileşenin adresi
Mssage: Gönderilecek mesaj
WParam: 1. Parametre. (Integer)
LParam: 2. Parametre (Integer)

Mesajlarla ilk ilgilendiğim zamanlarda, mesaj parametrelerinin bu kadar az olması beni bir hayli şaşırtmıştı. Ama zaman içinde bir takım küçük ipuçları sayesinde 2 tane parametrenin olmasının yeterli olduğunu öğrendim. Asıl ipucu da şu: Pointer’lar, aslında 32 bit sayılardır, yani integerdir (UYARI: Her zaman için Pointer = integer geçeliridir, ama integer=pointer geçerli değildir. Her sayı bellekte geçerli ve anlamlı bir adres ifade etmez, ama bellekteki her adres bir sayı olmak zorundadır.) 32 bit işletim sistemi, bütün adreslemenin 32 bit sayılar (yani integer) üzerinden yapıldığını gösterir. Tabii başka farklılıklar da var, ama bence en önemlisi bu. Bu da şu anlama geliyor. Siz mesajlar yardımı ile programlar arasında Megabytelarca veri transferi yapabilirsiniz.

Şimdi bu süper ipucundan sonra biraz da can sıkıcı bir şeylerden bahsedeyim. Mesajlarla adres göndermek biraz tehlikelidir. En azından Microsoft böyle düşünüyor ve aslında haklı da. Çünkü mesajlarla adres gönderildiğinde Memory Leak (Bellek kayıpları) olma olasılığı yüksektir. Özellikle acemi ve dikkatsiz programcılar, bellekten aldıkları yeri geri vermeyi unuturlar. Tanımlanan bir mesajda bunun yapılması ise çok risklidir. Çünkü sistem çok kısa bir sürede kilitlenebilir. Bu yüzden de Windows mümkün olduğunca adres göndermeden işlem yapmaya çalışır. Ayrıca eski sistemlerde (şimdinin eski sistemlerinden değil, 5-10 yıl öncesinin kötü bilgisayarlarından bahsediyorum) verinin mümkün olduğu kadar az yer tutarak gönderilmesi gerekiyordu. Malum, az ram, yavaş işlemci vs. Bu yüzden basit içerikli mesajlarda (Fare, klavye mesajları gibi) size rahatça kullanabileceğiniz bir record tipinde bilgi gelmez. Onun yerine WParam parametresinin 1-4 bit arası şu bilgi, 5-10 bit arası bu bilgi gibi okunması zahmetli formatta gelir. Gerçi bu kısım çok da önemli değil, çünkü Windows mesajlarını kullanmak ihtiyacı hisseden bir programcı, zaten bitwise işlemleri öğrenmiştir. (Belki ileride bu konuda da bir şeyler yazarım)

Mesajlar aslında sayıdır. Tahmin edebileceğiniz gibi hepsi constant değerlerdir aslında. WM_PAINT gibi isimler verilmesi de sadece kolay kullanmak içindir. Eğer ihtiyacını olursa siz de mesaj tanımlayabilirsiniz. Burada hemen aklınıza mesajların çakışma olasılığı gelebilir. MS bunun için şöyle diyor: Kendi mesajlarınızı tanımlarken, WM_USER + x değeri ile tanımlayın. Burada WM_USER, Windows da kullanılabilecek maksimum mesaj sayısıdır. Dolayısıyla mesajların çakışma olasılığı kalmaz. Tabii siz kafanıza göre programlara mesaj gönderirseniz, kötü yazılmış bir programın çökmesine neden olabilirsiniz :)

Mesajların aynı zamanda bir sonucu vardır. Buradaki sonuç genelde mesajın işlenip işlenmediğidir. Basit bir örnek: Herhangi bir bileşen üzerinde bir tuşa bastınız? Bunu Form mu, yoksa aktif bileşen mi yakalayacak? Form yakalarsa, aktif bileşenin hiçbir işlem yapmaması gerekecek. Ya da WM_PAINT mesajını düşünün. Eğer bileşen kendini boyamayı adam gibi bitirmişse, bunu Windows’a haber vermelidir. Yoksa Windows bileşene sürekli WM_PAINT mesajı gönderir. Önceki tecrübelerimden biliyorum, sonuç gerçek bir felaket oluyor. (Hatta Delphi ve Builder IDE’si bile çökmüştür birkaç kez :)) Bu yüzden herhangi bir mesajı yakaladığınızda, Windows SDK’ya göre mesaj sonucunu bildirmeyi unutmayın.

Şimdi size süper bir ipucu daha! Normalde program yazan birisinin, program yazdığı işletim sistemini çok iyi bilmesi gerekir, ama Delphi gibi görsel diller yüzünden bu ihmal edilir oldu. Önce kısa bir senaryo yazalım ve cevabı okumadan önce soruya kendi bilginizle cevap vermeye çalışın.

İki ayrı program yazdınız ve bu programlar ortak mesajlarla bilgi transferi yapacaklar. Göndereceği bilgi de büyük bir bilgi. Örneğin kişi bilgileri. Aşağıda buna örnek bir kod yazıyorum:

Kod: [Seç]
Const WM_KISIINFO=WM_USER + 1; //Kendi mesajımızı tanımlayalım
//Kişi bilgilerini tutacak yapıyı ve pointerını tanımlayalım
Type
  PKisiInfo=^TKisiInfo;
  TKisiInfo=record
  Ad:array[0…20] of char;
  Soyad:array[0..20] of char;
  DogumTarihi:TDateTime;
  Adres:[0..255] of char;
End;

//Örnek olsun diye basit bir kod
Var
  Tmp:PKisiInfo;
Begin
  New(Tmp);
  StrPCopy(Tmp.Ad,’Bahadır’);
  StrPCopy(Tmp.Soyad,’Alkaç’);
  Tmp.DogumTarihi:=EncodeDate(1979,11,4);
  StrPCopy(Tmp.Adres,’Ankara’);
  PostMessage(ProgramHandle, WM_KISIINFO,integer(Tmp),0);
End;

Şimdi aşağıdaki cevabı okumadan dürüstçe cevap verin. Bu kod çalışır mı ve neden?

Cevap aslında basit. Bu kod çalışmaz. Nedeni de işletim sisteminin çalışma prensibinden kaynaklı. Windows uygulamaları çalıştırırken sanal bilgisayarlar oluşturur ve bu sayede (eski sürümlerinde başarılı değildi, ama XP’de gayet iyi) herhangi bir program çöktüğünde, aslında sadece o sanal bilgisayar imha edilir. Bu da diğer programların bundan etkilenmemesini sağlar. Siz bellekten yer alırken bunu Windows’a özel olarak söylemezseniz, Windows sizin programınızın sanal bilgisayarı içinde yer açacaktır ve ikinci program da bu sanal bilgisayarın içinde olmadığından dolayı sizin mesajınızda gönderdiğiniz adrese ulaşamayacaktır. Windows’a bunu özel olarak söylemenin yolu da GlobalAlloc fonksiyonunda yazıyor. Bu fonksiyon hakkında bilgi vermeyeceğim, bu zaten Microsoft’un işi. Eğer merak ederseniz Win32 SDK’dan okuyabilirsiniz.

Bütün bunları yazdıktan sonra mesajları nerede kullanacağım diye sorabilirsiniz kendinize? Çok basit bir örnek vereceğim.

Bazı bileşenlerde OnMouseEnter (Örneğin Label) diye bir event vardır, ama bazılarında yoktur. Şansa bakın ki, sizin kullandığınız bileşende bu yok, ama sizin de mutlaka o bileşeni kullanmanız gerekiyor. İşte size mesajları kullanabileceğiniz bir yer. CM_MOUSEENTER ve CM_MOUSELEAVE mesajlarını yakalamanız yeterli. Hatta bunu istediğiniz zaman tanımlayabilirsiniz. Örneğin programın bütün görsel tasarımını bitirdiniz, 10 tane TBitBtn koydunuz, eventlerini yazdınız. Sonra bu fonksiyonlara ihtiyacını oldu. İşte size Delphi ile ilgili süper bir örnek kod. Yeni bir proje açın ve formun üstüne bir tane Label (Adı Label1 ve Visible özelliği False olacak), bir tane BitBtn (adı BitBtn1 olacak) koyun ve aşağıdaki kodu yazın (ya da kopyalayın:)).

Kod: [Seç]
unit Unit1;
interface
uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Buttons;

type
  TBitBtn=class(Buttons.TBitBtn)
  private
    FOnMouseEnter: TNotifyEvent;
    FOnMouseLeave: TNotifyEvent;
    procedure FMouseEnter(var Msg:TMessage);message CM_MOUSEENTER; //Mesajı yakalama
    procedure FMouseLeave(var Msg:TMessage);message CM_MOUSELEAVE; //Mesajı yakalama
  public
    property OnMouseEnter:TNotifyEvent read FOnMouseEnter write FOnMouseEnter; //Event tanımı
    property OnMouseLeave:TNotifyEvent read FOnMouseLeave write FOnMouseLeave;//Event tanımı
end;

type
  TForm1 = class(TForm)
    Label1: TLabel;
    BitBtn1: TBitBtn;
    procedure FormCreate(Sender: TObject);
  private
    procedure OnMouseEnter(Sender:TObject);
/ //   procedure OnMouseLeave(Sender:TObject);
/    { Private declarations }
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
/implementation

{$R *.dfm}
{ TBitBtn }

procedure TBitBtn.FMouseEnter(var Msg: TMessage);
begin
  if Assigned(FOnMouseEnter) then
    FOnMouseEnter(Self);
end;

procedure TBitBtn.FMouseLeave(var Msg: TMessage);
begin
  if Assigned(FOnMouseLeave) then
    FOnMouseLeave(Self);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  BitBtn1.OnMouseEnter:=OnMouseEnter;
  BitBtn1.OnMouseLeave:=OnMouseLeave;
end;

procedure TForm1.OnMouseEnter(Sender: TObject);
begin
  label1.Visible:=true;
end;
procedure TForm1.OnMouseLeave(Sender: TObject);
begin
  Label1.Visible:=false;
end;

end.

Bu kodu incelediğinizde, eğer daha önce buna benzer bir şey görmediyseniz, olur mu öyle şey diyebilirsiniz. Delphi’nin gücü sayesinde oluyor.

Windows’da mesaj göndermek için iki temel yöntem var. SendMessage ve PostMessage. SendMessage, gönderilen mesajın cevabı gelene kadar bekler. Oysa PostMessage mesajı gönderir ve sonucu beklemez. Aslında tahmin edebileceğiniz gibi Windows’da mesajlarla ilgili daha gelişmiş fonksiyonlar da var, ama bu komutları ihtiyaç duyduğunuzda Win32 SDK’dan okumanız daha iyi :)

Kolay gelsin

Bahadır Alkaç

Çevrimdışı aligel54

  • Delphi 2006 Level 4
  • ****
  • İleti: 481
  • Rep: +1/-0
Ynt: Windows ve Mesajlar
« Yanıtla #1 : 17 Şubat 2011 15:42:29 »
Son örneği anlayamadım. Olması gereken neydi? Zaten bazı "/" işaretleri yanlış kullanılmış. Bu komutlar Delphi7 de kullanılır mı? Çünkü OnMouseLeave gibi bir event göremedim.

Çevrimdışı Opt2000

  • Global Moderatör
  • *****
  • İleti: 263
  • Rep: +9/-1
  • Cinsiyet: Bay
Ynt: Windows ve Mesajlar
« Yanıtla #2 : 17 Şubat 2011 16:07:53 »
Selam,

Eski bir yazı, ama bozuk olan kod aşağıdaki gibi olacak:

Kod: [Seç]
unit Unit1;
interface
uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Buttons;

type
  TBitBtn=class(Buttons.TBitBtn)
  private
    FOnMouseEnter: TNotifyEvent;
    FOnMouseLeave: TNotifyEvent;
    procedure FMouseEnter(var Msg:TMessage);message CM_MOUSEENTER; //Mesajı yakalama
    procedure FMouseLeave(var Msg:TMessage);message CM_MOUSELEAVE; //Mesajı yakalama
  public
    property OnMouseEnter:TNotifyEvent read FOnMouseEnter write FOnMouseEnter; //Event tanımı
    property OnMouseLeave:TNotifyEvent read FOnMouseLeave write FOnMouseLeave;//Event tanımı
end;

type
  TForm1 = class(TForm)
    Label1: TLabel;
    BitBtn1: TBitBtn;
    procedure FormCreate(Sender: TObject);
  private
    procedure OnMouseEnter(Sender:TObject);
    procedure OnMouseLeave(Sender:TObject);
    { Private declarations }
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
implementation

{$R *.dfm}
{ TBitBtn }

procedure TBitBtn.FMouseEnter(var Msg: TMessage);
begin
  if Assigned(FOnMouseEnter) then
    FOnMouseEnter(Self);
end;

procedure TBitBtn.FMouseLeave(var Msg: TMessage);
begin
  if Assigned(FOnMouseLeave) then
    FOnMouseLeave(Self);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  BitBtn1.OnMouseEnter:=OnMouseEnter;
  BitBtn1.OnMouseLeave:=OnMouseLeave;
end;

procedure TForm1.OnMouseEnter(Sender: TObject);
begin
  label1.Visible:=true;
end;
procedure TForm1.OnMouseLeave(Sender: TObject);
begin
  Label1.Visible:=false;
end;

end.

Örneğin amacı zaten OnMouseLeave eventi olmayan bir bileşene bu eventi eklemek.

Kolay gelsin,
Bahadır Alkaç