Windows'un Tarihi
Windows üzerinde ilk çalışmalara 1985 yılında başlamıştır. İlk ürün Windows 1.0 idi. Windows 3.0 sürümü ile işletim sistemine benzeyen özellikler kazanmıştır. Windows'un en uzun süre pazarda kalan sürümü 3.1'dir. Windows 3.1 kadar(3.1 dahil) Windows sistemleri 16 bit mikro işlemcilerde çalışabilecek biçimdeydi. 1995'te Windows 95 piyasaya sürüldü ve bunu Windows 98 izledi. Win95/98/NT sistemleri 32 bitlik Windows sistemleridir. 32 bit Windows sistemleri birbirine çok benzer sistemleridir. Programlama bakımından bu sistemlerin hepsi yüksek düzeyde uyumludur(.obj ve .exe dosyaları da tamamen uyumludur). Windows sistemleri yalnızca Intel tabanlı mikro işlemciler için düşünülmemiş. Çeşitli risc tabanlı sistemlerde de Windows işletim sistemi kullanılabilmektedir. Windows CE, Windows sisteminin küçültülerek el bilgisayarlarına uyarlanmış biçimidir. Bu sistemin programlama biçimi diğer Windows sistemleriyle uyumludur.
Windows Sisteminin Temel Özellikleri
1. Windows grafik tabanlı bir işletim sistemidir.
Windows grafik mode'da çalışan bir sistemdir. Oysa DOS ve UNIX temelde text mode'da çalışır. Ancak bu işletim sistemlerinin de kullanımını kolaylaştırmak için grafik arabirimleri vardır. Ancak yalnızca Windows'da grafik mode'da program yazmak zorunlu değildir. Bir Windows programı Windows'un grafik özelliğini kullanabilir. Bu tür programlara GUI(graphical user interface) programlar denir. Zaten Windows programı denilince bu tür programlar anlaşılır. Bazı Windows programları text mod'da çalışabilir. Böyle programlara console programları denir. Console programları için özel bir bilgiye gereksinim yoktur. Programlar DOS ve UNIX'te olduğu gibi organize edilir.
2. Windows aygıt bağımsız grafik özelliğine sahiptir.
Windows'da oluşturulan grafiksel görüntüler ekran kartına, ekran moduna ve monitör tipine bağlı değildir. Yeni bir grafik kartı takıldığında önceki kart için yazılmış program aynı biçimde çalışır. Yani bu değişimlerden programlama sistemi etkilenmez.
3. Windows çok işlemli bir işletim sistemidir.
Windows aynı anda birden fazla programın bir arada çalıştırılabildiği çok işlemli(multi processing) bir işletim sistemidir. Çok işlemli çalışma zaman paylaşımlı bir biçimde yapılır. Yani her program bir süre çalıştırılarak bırakılır. Programların çalıştırılıp bırakılma işlemleri belli bir algoritma ile yürütülür. Bu algoritmaya çizelgeleme algoritması denir. Windows sistemleri döngüsel çizelgeleme(round robind scheduling) sistemi kullanır. Programların çalıştırılıp bırakılma süresine quanta denir. Windows sistemleri 20 ms'lik quanta sistemi kullanır. Bir programın bırakılarak diğer bir programın çalıştırılması işlemine görevler arası geçiş(task switch) denir. Ancak programlar çoğu zaman dışsal bir takım olayları beklerler(klavyeden bir tuşun basılmasını, porttan bir bilgi gelmesini, dosyadan veri okunması vs..). O zaman işletim sistemi ilgili programı çizelge dışı bırakır. Bu işleme programın bloke edilmesi denir. Bu dışsal olaylar gerçekleştiğinde işletim sistemi programı tekrar çizelgeye yeniden dahil eder. Ancak Windows işletim sisteminde olduğu gibi bazı sistemler öncelikli döngüsel çizelgeleme kullanabilirler. Her programın 0-31 arasında bir öncelik derecesi vardır. Sistem en yüksek öncelikteki programlar arasında çizelgeleme yapar. Ancak onlar bittikten sonra diğerleriyle uğraşılır.
Düşük öncelikli programlar yine de çalışır. Çünkü yüksek öncelikli programların hepsinin bloke olma olasılığı vardır. Tabii isterse programcı yazılım yoluyla da bloke olabilir. Windows'da bir programın minimize edilmesi bloke edilmesi anlamına gelmez.
4. Windows "preemtive" bir işletim sistemidir.
Preemtive sistemlerde işletim sistemi görevler arası geçiş işlemiyle bir programı çalıştırır, quanta süresi bittiğinde kontrolü tekrar ele geçirerek başka bir programı çalıştırır. Oysa preemtive olmayan sistemlerde bir program çalıştırıldığında görevler arası geçişin oluşması için programcının kontrolü işletim sistemine geri vermesi gerekir. Eğer vermezse tüm sistem olumsuz yönde etkilenir ve hatta kilitlenebilir. Windows 3.x sistemleri preemtive değildir. Ancak Windows 95/98/NT sistemleri preemtive'dir.
5. Windows alt işlemli çalışmanın mümkün olduğu bir sistemdir.
Alt işlem(thread) bir programın bağımsız olarak çizelgelemeye giren bir parçasına denir. C'de herhangi bir fonksiyon alt işlem olarak tanımlanabilir.Ancak bu fonksiyonun alt işlem olarak sisteme tanıtılması gerekir. Her program en azından bir alt işlem içerir, bu da programın kendisidir. Alt işlemler programın çalışma zamanı sırasında oluşturulur. Yani C programı main'den çalışmaya başladığında tek alt işleme sahiptir. Sonra programın akışı CreateThread fonksiyonuna gelir ve bir fonksiyon yeni bir alt işlem olarak devreye girer. Aslında bütün çizelgeleme alt işlem düzeyinde yapılır. Örneğin 3 proses söz konusu olsun, p1, p2, p3 adlı üç program çalışıyor olsun.
Çok işlemli çalışma pek çok algoritmanın gerçekleştirilmesini kolaylaştırmaktadır. Birden fala cpu'nun bulunduğu makinalarda hız ciddi biçimde artar. Aynı programın alt işlemleri arasındaki task switch zamanı farklı programların alt işlemleri arasındaki task switch zamanından daha azdır.
Çok işlemliliğin avantajları:
- Çok alt işlemden oluşan programlarda dışsal bir olay gerçekleştiğinde programın tamamı bloke olmaz, yalnızca bir alt işlem bloke olur. Bir döngü içerisinde bir olayın gerçekleşmesi ile ilgili işlemler yapan kodlar daha kolay tasarlanırlar.
6. Windows Intel sistemlerinde 4 Gb bellek kullanabilir.DOS işletim sistemi ancak 1 Mb belleği kontrol edebilir. Üstelik bu 1 MB belleğin yalnızca 640 Kb'lik bölümünü programlar için kullanabilir. Oysa programlar ayrıntılı özelliklere sahip oldukça bellek gereksinimi de artar. Bir Windows exe programı istenildiği kadar uzun olabilir.
7. Windows sanal bellek kullanan bir işletim sistemidir.
Sanal belek(virtual memory) programın belli bir kısmının RAM'e yüklenerek disk ile yer değiştirmeli olarak çalıştırılması için kullanılır. Programın belirli bir bölümü RAM'e yüklenerek çalışmaya başlar, program kod ya da data bakımından RAM'de olmayan kısma erişmek istediğinde işletim sistemi programın RAM'deki bir bölümünü disk'e diskteki bölümünü RAM'e çekerek kesiksiz çalışmayı sağlar. Bütün sanal bellek kontrolü; örneğin programın neresinin diskte tutulup tutulmayacağı gibi işlemler işletim sisteminin kontrolü altındadır. Programcının sanal bellek kullanımı üzerindeki kontrolü son derece azdır. Bütün bu işlemler işletim sistemi tarafından otomatik olarak gerçekleştirilir.
Sanal bellek kullanımı hem mikro işlemcinin hem de işletim sisteminin ortaklaşa sağladığı bir işlemdir. Intel işlemcileri 80286 ile birlikte segment tabanlı sanal bellek, 80386 ile birlikte sayfa tabanlı sanal bellek kullanımına uygun tasarlanmıştır. Yani Windows altında toplam bellek RAM + boş disk alanı kadardır. Programın disk ile RAM arasında taşınmasına yer değiştirme(swap) denir. Program uzun RAM azsa fazla sayıda yer değiştirme olur, program da yavaş çalışır. Fiyat/Performans bakımından optimum RAM Windows 3.1 için 16Mb, 32 bit Windows sistemleri için 64 Mb civarındadır. Dinamik bellek fonksiyonlarıyla heap üzerinde yapılan tahsisatlar da sanal bellek kullanılarak gerçekleştirilir. Örneğin Excel büyük bir tabloyu yükleyebilmek için malloc fonksiyonu ile 200 Mb bellek isteyebilir, sistem bunu RAM'de bulamazsa disk üzerinde yer değiştirme dosyası üzerinde tahsis eder. Bu alan kullanılmak istendiğinde işletim sistemi otomatik olarak yer değiştirme yapar.
8. Win32 sistemlerinde her program sanki belleği tek başına kullanıyormuş gibi çalışır.
Win32'de görevler arası geçiş sırasında bir program bellekten tamamen atılır, diğeri belleğe tamamen yüklenir. Yani bir program çalışırken diğer programlar bellekte değildir ve onlara erişilemez. Tipik bir Windows'daki bellek yönetimi düşük seviyeli 2Gb'lık sana bellek programlar için, yüksek anlamlı 2GB'lık sanal bellek ise işletim sistemi içindir.
Çalışma sırası programa geldiğinde sanal bellek desteğiyle program 2Gb'lık alana yüklenir. Win32'de bütün göstericiler 4 byte uzunluğundadır ve bütün belleği görebilir. Örneğin iki farklı programda malloc fonksiyonu aynı adresi vermiş olabilir, ancak bu iki program tamamen izole çalıştığına göre iki adresin birbirleriyle hiç ilişkisi yoktur. Yani Windows'da bir adres söz konusu olduğu zaman o adresin hangi programa ilişkin olduğunu da bilmek gerekir. Yüksek anlamlı 2 Gb'lık alan görevler arası geçişten etkilenmez.
9. Windows dosya formatları.
İlk obj formatı 1978'deki Intel OMF(dos) formatıdır. Win32 sistemleriyle beraber COFF formatına geçilmiştir.
İlk exe formatı DOS'ta MZ(Mark Zibikowski) formatı olarak, Windos 3.x ile beraber NE(new executable) formatı kullanılmaya başlanmıştır. Win32 sistemleriyle beraber PE(portable executable) formatı kullanılmaya başlanmıştır. PE formatı mikro işlemciden ve işletim sisteminden bağımsız bir format olma iddiasındadır.
10. Kaynak kullanımı.
Bir Windows programındaki bitmap, dialog penceresi, menu, icon, string gibi görsel elemanlara kaynak(resource) denir. Kaynaklar ya doğrudan kaynak dili kullanılarak yazılırlar ya da görsel olarak kaynak editörlerinde oluşturulurlar. Tabii kaynak editörü bu görsel belirlemelerden kaynak kodunu oluşturur. Kaynak dilinde yazılmış kaynak programının uzantısı rc'dir. Bu program ismine kaynak derleyicisi denilen özel bir derleyicide derlenir ve kaynak verisi res uzantılı bir dosya halinde oluşturlur. Bu res dosyası linker tarafından PE formatının kaynak bölümü denilen bir bölümüne yerleştirilir(resource binding). Rc dosyası derlenirken gerekli bütün grafik dosyalar bulundurulmalıdır. Ancak derlendikten sonra bu dosyaların içerisindeki veriler res dosyasına aktarılır, yani rs dosyası bütün grafik bilgilerini içerir. Bu res dosyası PE formatına yazıldıktan sonra grafik dosyalarına, rc dosyasına, res dosyasına programın çalışması için gereksinim kalmaz. PE formartının kaynak bölümü tamamen dokümante edilmiştir. Bu kaynak bölümünde değişiklik yapılmasına izin veren kaynak editörleri vardır. Kaynak kullanımından amaç bir programda hiç onu derleyip link etmeden görsel değişiklikleri kaynak editörü kullanarak yapabilmektir(res dosyası üzerinde doğrudan değişiklik yapılması uygun değildir. Bu kaynak kısmı PE formatından çıkartyılabilir ve yeni bir kaynak kodu oraya yerleştirilebilir.).
11. Windows sisteminde dinamik kütüphaneler yoğun olarak kullanılır.
Kütüphane dosyaları lib ve dll uzantılı olmak üzere ikiye ayrılır. Lib dosyası içerisinden bir fonksiyon çağırılmış olduğunda linker fonksiyonların kodlarını alarak exe dosyasının içerisine yazar. Yani dosyayı çalıştırmak için lib dosyasına ihtiyaç olmaz. Oysa dll içerisindeki bir fonksiyon çağırıldığında linker yalnızca fonksiyonun hangi dll içerisinde olduğunu PE formatının import bölümüne yazar. Program çalıştırılacağı zaman Windows, PE formatının import bölümünü okur, bu programın hangi dll dosyalarını kullandığını tespit eder, programla birlikte bu dll dosyaslarını da yükler. Bu durumda dinamik kütüphane kullanan bir program ancak bu dinamik kütüphanelerle birlikte taşınarak çalıştırılabilir. Dll dosyaları tamamen PE formatındadır. Yani bir exe dosya da bir dll'miş gibi kullanılabilir.
12. Windows sistemleri UNICODE tablosunu desteklemektedir.
Bir yazı ASCII tablosunda her karakteri 1 byte olacak biçimde temsil edilir. Bu durumda 256 değişik karakter bulunabilir. Oysa UNICODE tablosunda her karakter 2 byte ile belirtilir. Dünyada kullanılan bütün semboller bu tabloya yerleştirlimiştir. Bir yazının başlangıç adresini parametre olarak alan Windows API fonsiyonlarından 2 tane vardır. Bir tanesi yazıyı ASCII tablosuna bakarak yorumler, diğeri UNICODE tablosuna bakarak yorumlar. Eğer UNICODE sistemi kullanılırsa programdaki yazılar dilden bağımsız hale getirilir.
Macar Notasyonu
Macar notasyonu Microsoft çalışanlarından Charles Simoney tarafından geliştirilmiştir. Macar notasyonu değişkenlere isim verme ile ilgili belirlemelerden oluşur. Amaç bir değişkene bakıldığında onun türünün ve hangi amaçla kullanıldığının tespit edilmesidir.
Özellikleri:
- Değişken isimleri değişkenin türünü anlatan küçük harf ön eklerle başlar, daha sonra her sözcüğün ilk harfi büyük harfle başlar.
Önek
Anlamı
l
long
w
WORD (işaretsiz iki byte)
dw
DWORD (işaretsiz dört byte)
d
double
c
char
p
gösterici
pl
long gösterici
pv
void gösterici
psz
sonu NULL ile biten yazıya ilişkin char göstericisi
f
flag(aynı zamanda float)
b
bool
lp
uzak gösterici
u
unsigned int
int türü çok fazla kullanıldığı için ön ek almaz. Ancak küçük harfle başlatılır, sonraki sözcüğün ilk harfi büyük harfle başlatılır(totalCost gibi).
- Macar notasyonunda alt tire kullanılmaz. Fonksiyon isimlerinin her sözcüğünün ilk harfi büyük yazılır(CreateWindow gibi). Fonksiyon isimleri önce eylemle başlar nesneyle devam eder.
- Değişken isimleri uzun olma eğilimindedir.
Macar notasyonu Windows işletim sisteminin programlamaya ilişkin dokümantasyonlarında Microsoft tarafından yoğun bir biçimde kullanılmaktadır. Windows programlamada Macar notasyonunun kullanılması gelenek haline gelmiştir.
- Yapı türünden değişkenlere ön ek getirilmez. İlk sözcük küçük harf sonraki bütün sözcüklerin ilk harfleri büyük harfle yazılır.
Windows.h Dosyası
Windows programlamasında windows.h isimli başlık dosyası ön işlemci ve derleyici için include edilmelidir. Bu dosyanın içerisinde API fonksiyonlarının prototipleri, sembolik sabitler, typedef isimleri, yapı bildirimleri vs gibi bildirimler vardır. Bu dosya ön işlemci tarafından açıldığında çok büyük bir kod derleme işlemine sokulur.
API(Application Programming Interface) Fonksiyonları
Bir işletim sisteminin çalışırken kendisinin kullandığı, programcının da dışarıdan çağırabileceği fonksiyonları vardır. Bunlara sistem fonksiyonları denir. Sistem fonksiyonlarına unix'te System Call, Windows'da ise API denilmektedir. Sistem fonksiyonları işletim sistemiyle birlikte belleğe yüklenirler. API fonksiyonları bütün programlama dillerinde ve geliştirme ortamlarında işlemleri yapabilmek için derleyici sistemleri tarafından çağırılır. Örneğin Visual Basic'te programcı API kavramını bilmese de aslında derleyici sistemi bütün işlemlerini sistem fonksiyonlarını çağırarak gerçekleştirir. Windows API fonksiyonları dll dosyaları içerisindedir ve sistemle beraber otomatik olarak yüklenirler. API fonksiyonları üç gruba ayrılır:
1. Kernel API'ler: Kernel32.dll içerisindedir. Sistemin aşağı seviyeli çalışmalarıyla ilgili sistem fonksiyonları bulunur.
2. User API'ler: User32.dll içerisindedir. Programcının doğrudan üzerinde çalıştığı konularla ilgili olan sistem fonksiyonlarıdır. Örneğin pencere işlemleri için gereken API'ler, kontrollere ilişkin API'ler vs.
3. Graphics API'ler: Gdi32.dll içerisindedir. Bütün grafik çizimlerine ilişkin sistem fonksiyonlarını içerir.
Bir Windows Programının Derlenerek Çalıştırılması
Bir Windows programının exe uzantılı hale getirilebilmesi için birkaç aşama gereklidir. Önce uzantısı c olan dosyalar derleyici tarafından derlenerek obj dosyalar dönüştürülür, sonra uzantısı rc olan kaynak dosyaları kaynak derleyicisi tarafından derlenerek res uzantılı hale getirilir, en sonunda linker tarafından obj ve res dosyaları link edilerek exe uzantılı dosya elde edilir.
c dosyaları
rc dosyaları
Derleyici
Kaynak derleyicisi
obj dosyaları
res dosyaları
linker
exe (PE format)
Aslında linker programı exe dosyasının içerisine res dosyasını yeleştirebilecek biçimde yazılmıştır. Yani işlemler şöyle de yürütülebilir: Önce yalnızca obj'larla exe elde edilir, daha sonra res dosyası tekrar linker kullanılarak exe içerisine yerleştirilir(resource binding). Burada açıklanan işlemler komut satırından manual olarak yapılabilir. Ancak derleyici sistemleri proje dosyası kullanımıyla bu işlemleri kolaylaştırmaktadır. Bu çalışma biçimine göre önce bir proje dosyası açılır, sonra bu dosya içerisine c ve rc dosyaları yerleştirilir. Program çalıştırılacağı zaman derleyici sistemi önce c dosyalarını c derleyicisiyle, rc dosyalarını ise kaynak derleyicisi ile derler ve hep birlikte link eder.
Windows.h İçerisindeki typedef İsimleri
Windows programlamasında taşınabilirlik sağlamak amacıyla tür belirten anahtar sözcükler yerine typedef isimleri yoğun olarak kullanılır. Bu tür tanımlamalarının başlıcaları şunlardır:
typedef unsigned char BYTE
typedef unsigned short int WORD
typedef unsigned long int DWORD
typedef int BOOL
/*Örneğin BOOL bir fonksiyonun başarılı ya da başarısız olduğu bilgisi için kullanılabilir. BOOL demek aslında int demektir ancak okunabilirliği arttırır. Aynı zamanda BOOL türüne eşlik eden iki makro vardır. TRUE ve FALSE. #define FALSE 0 #define TRUE 1*/
typedef unsigned int UINT
/*Aslında Win32'de UINT ile DWORD arasında işlevsel bir fark yoktur. Çünkü int türüyle long türü aynı uzunluktadır.*/
typedef int INT
typedef float FLOAT
/*
DOS ve Win3.1 sistemlerinde yakın ve uzak gösterici kavramları vardır. Dolayısıyla near ve far anahtar sözcükleri geçerli bir biçimde kullanılır. Oysa 32 bit Windows sistemlerinde yakın ve uzak gösterici diye kavramlar yoktur. Bütün göstericiler 4 byte uzunluğundadır. Win32'de near ve far anahtar sözcükleri tanımlı değildir, kullanılması error oluşturur. Ancak Win3.1 sistemleriyle uyumu korumak için near ve far anahtar sözcükleri windows.h içerisindeki
#define far
#define near
satırlarıyla silinirler. Yani biz near ya da far anahtar sözcüklerini kullansak bile windows.h dosyası include edildiği için bu sözcükler koddan çıkartılır. Böylece error oluşmaz. Bu iki satır Win3.1 programlarının Win32'de hiç değişiklik yapılmadan kullanılabilmesini sağlamak için konulmuştur. Göstericilere ilişkin typedef tanımlamaları Win3.1'de oluşturulmuştur. Dolayısıyla yakın ve uzak gösterililer için farklı isimler uydurulmuştur.
*/
typedef char far *LPSTR;
typedef char *PSTR;
/*far anahtar sözcüğü silindiği için LPSTR ile PSTR aynı türdür, karakter türünden gösterici anlamına gelir. Ancak hala Win3.1'den alışkanlıkla LP ön ekli typedef isimleri daha fazla kullanılmaktadır.*/
typedef unsigned short int *PWORD, *LPWORD;
typedef int * PBOOL;
typedef unsigned int *LPUINT, *PUINT;
typedef const char *LPCSTR, *PCSTR;
typedef unsigned int WPARAM;
typedef long int LPARAM;
/*Win3.1'de WPARAM 2 byte LPARAM ise 4 byte uzunluğunda işaretsiz tam sayı biçimindeydi. Win32'de aralarında fark yoktur.*/
#define CONST const
/*CONST yerine const yerleştirilir.*/
typedef void *PVOID, *LPVOID;
Handle Türleri
İsmine handle denilen ve ilk harfi 'H' ile başlayan bütün tür isimleri gerçekte void türünden gösterici anlamındadır.
HWND
WICON
HCURSOR
Macar notasyonunda bu türde değişken tanımlanırken değişken isminin önüne 'h' getirilir. Örneğin: HWND hwnd; /*veya void *hwnd;*/
Bir Windows Programının Organizasyonu
Bir Windows programı DOS ve UNIX'te olduğu gibi main fonksiyonundan değil, WinMain fonksiyonundan çalışmaya başlar. WinMain fonksiyonunun parametreleri main fonksiyonundan farklıdır ve işletim sistemi tarafından geçirilir. WinMain fonksiyonunun geri dönüş değeri int olmak zorundadır.
Fonksiyonun geri dönüş değerinin soluna yazılan anahtar sözcüğe fonksiyonun çağırma biçimi(calling convention) denir. Bu çağırma biçimleri pascal, cdecl veya stdcall olabilir. Microsoft firması bu anahtar sözcükleri _pascal, _cdecl, _stdcall olarak tanımlamıştır. Ancak daha sonra ANSI uyuşumu sağlamak için çift alt tireli şekillerini standartlaştırmıştır. Bugün bütün derleyiciler üç biçimi de desteklemektedir. Bu anahtar sözcüklerden hiçbiri yazılmazsa C'deki fonksiyonlar için __cdecl yazılmış kabul edilir. C++'taki sınıfa ilişkin üye fonksiyonlar için varsayılan durum __stdcall biçimindedir. Windows.h içerisinde #define WINAPI __stdcall tanımlaması vardır. WinMain fonksiyonunun çağırılma biçimi __stdcall olmak zorundadır. WINAPI zaten bu demektir(Eğer burada WINAPI ya da __stdcall yazmazsak default olarak __cdecl anlaşılır, bu da programın hatalı çalışmasına yol açar).
WinMain Fonksiyonunun Parametreleri
Bu parametreler işletim sistemi tarafından geçirilir.
- HINSTANCE hInstance
Buraya geçirilen değer exe dosyanın çalıştırılma sırasında belleğe yüklenme adresidir. Programın yüklenme adresi aslında PE formatının içerisine yazılmaktadır. Yükleme adresi derleyici seçeneklerinden çeşitli biçimlerde değiştirilebilir. Ancak değiştirilmesi için ciddi bir neden yoktur. Win9x sistemlerinde bu yükleme adresi default olarak 0x400000(4 Mb), WinNT sistemlerinde 0x100000(1Mb) biçimindedir. Yani farklı programların hInstance değerleri farklı olabilir.
16 bit Windows'da hInstance değeri programın yüklenme adresi değil programın module database adresidir. Yani 16 bit Windows'da her programın hInstance değeri birbirinden farklıdır. hInstance değeri pekçok API fonksiyonuna parametre olarak geçirilmektedir. Örneğin kaynak(resource) üzerinde işlem yapan bir API fonksiyonu bu değeri parametre olarak ister, aslında fonksiyon bu değerden faydalanarak PE formatının yüklenme adresini alır. PE formatı içerisinde kaynak bölümünün nerede başladığı bilgisi vardır. Fonksiyon kaynak bilgilerine bu biçimde erişir. hInstance değeri WinMain içerisinde global bir değişkene atanırsa her fonksiyon içerisinden kullanılabilir.
- HINSTANCE hPrevInstance
Bu değer 32 bit Windows sistemlerinde her zaman NULL olarak geçirilir. 16 bit Windows'da hPrevInstance programın kopyası birden fazla çalıştırılıyorsa önceki kopyasının hInstance değeri olarak geçirilir. Tabii program ilk kez çalıştırıldığında hPrevInstance 0 olacaktır. 16 bit Windows'da bir programın birden fazla çalıştırılması istenmiyorsa aşağıdaki gibi bir işlem yapılabilir.
if (hPrevInstance)
return 0;Bazı işlemlerin yalnızca programın ilk çalıştırılmasında yapılması isteniyorsa:
if (!hPrevInstance) {
...........
........
........
........
}
32 bit Windows sistemlerinde hPrevINstance her zaman NULL olduğuna göre programın ilk kez çalıştırılıp çalıştırılmadığı bu yöntemle anlaşılamaz(Bu işlem için mutex nesneleri kullanılmalıdır).
- LPSTR lpszCmdParam
Komut satırı argümanlarını gösteren yazının başlangıç adresini belirtir. Komut satırı argümanları tek yazı olarak tutulur.
- int nCmdShow
Programın ana penceresinin program çalıştırıldığında hangi büyüklükte ilk kez görüntüleneceğini belirtir. Bu parametre 3 seçenek alabilir. Bunlar:
SW_MAXIMIZE
SW_MINIMIZE
SW_RESTORE
Bir Windows programı hiçbir pencere olmadan da çalışabilir. Ancak tabi istenen bu değildir.
Programın Ana Penceresinin Yaratılması
Programa ilişkin bütün pencerelerin yaratılması CreateWindow API fonksiyonu ile yapılmaktadır. Bu fonksiyonun birinci parametresi WNDCLASS türünden bir yapı değişkeninin adresini alır. Bu durumda önce WNDCLASS türünden bir yapı değişkeni tanımlanıp içinin doldurulması gerekir. CreateWindow bu yapının içerisindeki bilgileri kullanmaktadır.
WNDCLASS Yapısı
WNDCLASS yapısı bir pencerenin temel özelliklerini barındıran bir yapıdır.
WNDCLASS Yapsının Elemanları
- UINT style
Pencerenin genel türüyle ilgili bilgileri içerir. Bu eleman aslında her biti bir özellik olarak ele alınacak biçimde düşünülmüştür. Bu elemana genellikle CS_HREDRAW | CS_VREDAW değeri girilir(Windows.h içerisinde birçok sembolik sabit vardır. Bu sembolik sabitlerin hangi amaçla kullanıldığının kolay anlaşılabilmesi için isimlendirilmeleri de okunabilir bir biçimde yapılmıştır. Bir sembolik sabitin isimlendirilmesi genel olarak XX_ISIM biçimindedir. Burada XX sembolik sabitin hangi konuyla ilgili olduğunu anlatan bir ön ektir).
- WNDPROC lpfnWndProc
Pencere fonksiyonunun başlangıç adresini tutan göstericidir.
- int cbClsExtra
int cbWndExtra
Pencere biçimine ilişkin yapı için ayrılacak ekstra alanların byte cinsinden değeridir. Ekstra alana nadiren gereksinim duyulur, genelde değeri sıfırdır.
- HANDLE hInstance
Bu elemana WinMain fonksiyonuna geçirilen hInstance değeri geçirilmelidir.
- HICON hIcon
Program minimize edildiğinde gösterilecek icon görüntüsünü belirlemek için kullanılır. Genellikle bu elemana Windows'un önceden tanımlanmış standart icon'larından biri yerleştirilir. İskelet programda bu elemana LoadIcon API fonksiyonu çağırılarak değer atanmıştır.
LoadIcon(NULL, IDI_QUESTION);
- HCURSOR hCursor
Windows programlamasında cursor mouse'un hareket ettirildiğindeki gösterici şekili olarak kullanılır. Mouse oku bir pencerenin sınırları içerisinde gezdirildiğinde istenilen bir şekle dönüştürülebilir. Bu eleman bu şeklin belirlenmesinde kullanılır. İskelet Windows programında bu elemana LoadCursor API fonksiyonunun geri dönüş değeri atanmıştır.
LoadCursor(NULL, IDC_ARROW);
Burada belirlenen mouse şekli ok biçimindedir.
- HBRUSH hbrBackground
Bu eleman pencerenin zemin rengini belirlemekte kullanılır. Bu elemana iskelet Windows programında GetStockObject fonksiyonuyla elde edilen değer atanmıştır.
GetStockObject(WHITE_BRUSH);
- LPCTSTR lpszMenuName
Program menuye sahip olabilir ya da olmayabilir. Eğer olacaksa buraya menu kaynağının ismi girilmelidir.
- LPCTSTR lpszClassName
Her WNDCLASS yapısının bir ismi vardır. Bu isim CreateWindows fonksiyonuna parametre olarak geçirilir. Yani aslında CreateWindow fonksiyonu bu isimden hareketle bu yapının içerisindeki bilgileri elde eder.
WNDCLASS yapısının içi doldurulduktan sonra bu yapının sisteme tanıtılması gerekir. Bu tanıtma işlemi RegisterClass API fonksiyonu ile yapılır. Bu API fonksiyonu WNDCLASS türünden bir yapı değişkeninin adresini parametre olarak alır. WNDCLASS yapısının sisteme tanıtılması işlemi 32 bit Windows sistemlerinde programın her kopyası için yapılmak zorundadır. Ancak 16 bit Windows sistemlerinde bu işlem yalnızca ilk kopya için yapılmalıdır. Bunu yapan kod 16 bit Windows sistemleriyle uyumu korumak için if (!hPrevInstance) { }kod bloğunun içinde yapılır. RegisterClass fonksiyonu WNDCLASS yapısına ilişkin bu bilgileri sistem bölgesine kopyalar. Herhangi bir API fonkiyonu isterse bu bilgilere ulaşabilir(Win32'de program sonlandırıldığında o programın sisteme tanıttığı bütün WNDCLASS bilgileri sistemden boşaltılır).
Programın Ana Penceresinin Yaratılması
Pencerenin yaratılması CreateWindow API fonksiyonu ile yapılır.
CreateWindow Fonksiyonunun Parametreleri
- LPCTSTR lpszClassName
Bu parametre daha önce sisteme tanıtılmış olan WNDCLASS yapısının ismini verir. Bu ismi alan CreateWindow sistem alanına ulaşarak sisteme tanıtılmış olan bütün yapılar arasından uygun olanı alır.
- LPCTSTR lpszWindowName
Pencerenin başlık yazısını belirlemek için kullanılır.
- DWORD dwStyle
Pencerenin görüntü biçimini belirlemekte kullanılır. Bu değişken 32 bitten oluşmaktadır. Her bit bir özelliğin olduğunu ya da olmadığını belirlemekte kullanılır. Pencere biçimlerine ilişkin windows.h içerisinde tanımlanmış WS_ ile başlayan bir grup sembolik sabit vardır. Bu sembolik sabitlerin yalnızca bir biti 1 diğer bitleri 0'dır. Bu sembolik sabitler bit OR işlemine sokularak çeşitli özelliklerin birlikte sağlanması için kullanılabilirler.
Başlıca Pencere Biçimleri
- WS_VISIBLE
Yaratılır yaratılmaz pencerenenin görüntülenmesini sağlar.
- WS_BORDER
Penerenin sınır çizgilerinin görünmesini sağlar.
- WS_CAPTION
Başlık kısmının olmasını sağlar.
- WS_SYSMENU
Sistem menüsünün görüntülenmesini sağlar.
- WS_OVERLAPPED
Pencerenin başlık kısmı ve sınır çizgilerinin olmasını sağlar.
- WS_OVERLAPPEDWINDOWWindows.h içerisindeki bir sembolik sabit biçimindedir. WS_CAPTION | WS_OVERLAPPED | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX.
İskelet programda pencere biçimi olarak WS_OVERLAPPEDWINDOW olarak seçilmiştir. En sık kullanılan pencere biçimi budur.
- int x
int y
int nWidth
int nHeight
Pencerenin ekrandaki ilk araboyut(restore) koordinatlarıdır. x, y sol üst köşenin koordinatlarıdır. nWidth, nHeight yatay ve düşey uzunluklarıdır. İskelet programda bu değerler,
CW_USEDEFAULT
0
CW_USEDEFAULT
0
olarak girilmiştir. Pencerenin ilk görüntüsü masaüstünü ortalayacak biçimde yerleştirilir.
- HWND hWndParent
Pencereler üst pencere(parent window) ve alt pencere(child window) olmak üzere iki kısma ayrılır. Üst pencereler masa üstünde herhangi bir yere hareket ettirilebilirler. Oysa alt pencereler üst pencerelerin sınırları dışarısına çıkartılamaz. Alt pencerenin de alt penceresi olabilir(Aslında üst pencere masa üstünün alt penceresiymiş gibi de düşünülebilir). Aslında Windows'un push button, radio button, check box gibi görsel elemanları birer alt pencerdir. Bu tür özel alt pencerelere control denir. CreateWindow fonksiyonunun geri dönüş değeri HWND türündendir. Bu değere pencerenin handle değeri denir. Bir pencere yaratıldığında pencerenin bütün bilgileri sistem içerisinde gizli bir bölgeye aktarılır. Fonksiyon aslında bu bilginin aktarıldığı bölgenin başlangıç adresini verir. Pencereyle ilgili işlem yapan bütün API fonksiyonları bu handle değerini parametre olarak alırlar. Handle değerini alan API fonksiyonları ilgili pencerenin bütün bilgilerine erişebilir. Yani handle değeri hangi pencereyle ilgili işlem yapılacağını belirlemekte kullanılır. CreateWindow fonksiyonu hem üst pencere hem de alt pencere yaratılmasında kullanılmaktadır. Eğer üst pencere yaratılacaksa hWndParent parametresi NULL olarak, alt pencere yaratılacaksa hangi üst pencerenin alt penceresi olduğunu belirtmek için üst pencerenin handle değeri olarak girilmesi gerekir. İskelet programda üst pencere yaratılması söz konusu olduğu için NULL değeri girilmiştir.
- HMENU hMenu
Pencere menüye sahip olabilir. Eğer bu menü WNDCLASS yapısı içerisinde belirlenmemişse burada belirlenebilir. İskelet programda menü olmadığı için bu parametre NULL olarak girilmiştir.
- HANDLE hInstance
WinMain fonksiyonundan alınan hInstance değeri bu parametreye girilmelidir.
- LPVOID lpParam
Pencere yaratıldığında ek bir veri alanı oluşturmak istenebilir. Bu parametre bu alanın başlangıç adresini tutar. İskelet programda NULL olarak girilmiştir. Bu değer böyle bir alanın kullanılmayacağını belirtir.
CreateWindow Fonksiyonunun Geri Dönüş Değeri
Fonksiyonun geri dönüş değeri pencere başarılı bir biçimde yaratılmışsa pencerenin handle değeridir, başarısızlık durumunda fonksiyon NULL değeri ile geri döner. İskelet programda yaratılma işleminin başarısı kontrol edilmemiştir. Aslında bu işlemin başarısının kontrol edilmesi tavsiye edilir. Pencerenin yaratıldığında görüntülenip görüntülenmeyeceği pencere biçimindeki WS_VISIBLE parametresine bağlıdır. İskelet programda yaratılır yaratılmaz görüntülenmemektedir.
Yaratılmış Pencerenin Görüntülenmesi
Bir pencere WS_VISIBLE pencere biçimiyle yaratılmışsa yaratılır yaratılmaz zaten görüntülenir. Ancak değilse ShowWindow API fonksiyonu ile görüntülenir.
BOOL ShowWindow(HWND hWnd, int nExitCode);
Fonksiyonun birinci parametresi görüntülenecek pencerenin handle değeri, ikinci parametresi ise görüntülenme biçimidir. Görüntülenme biçimi SW_MAXIMIZE, SW_MINIMIZE, SW_RESTORE olabilir.
(Bir fonksiyonun geri dönüş değeri BOOL ise başarı bilgisinin verildiği anlaşılır. BOOL değer mantıksal olarak 0 ya da 0 dışı bir değer olarak değerlendirilir. Örneğin ShowWindow başarılı ise 1'e değil 0 dışı herhangi bir değere geri dönmektedir.)
Ana pencere yaratıldıktan sonra içsel görüntünün güncelleştirilmesi için WM_PAINT mesajı gerekebilir. Bunu sağlamak için UpdateWindow API fonksiyonu çağırılır.
İskelet Programın Aşamaları
Pencereli bir Windows programı tipik olarak 3 aşamadan geçilerek oluşturulur.
1. WNDCLASS yapısının içi doldurularak sisteme tanıtılması aşaması,
2. Pencerenin yaratılarak görüntülenmesi aşaması,
3. Mesaj dögüsünün oluşturulması ve pencere fonksiyonunun tasarımı.
Mesaj Kavramı
DOS işletim sisteminde klavye ve mouse bilgileri programcının koda yerleştirdiği fonksiyonlar yardımıyla elde edilir. Örneğin getchar() fonksiyonu klavyeden bir tuşa basılana kadar bekler, basılan tuşun bilgisini alarak işlemini sonlandırır. Oysa Windows sistemlerinde klavye ve mouse gibi giriş bilgileri programcının çağırdığı bir fonksiyonla değil, Windows sistemlerinin kendisi tarafından ilk elden alınır. Windows sistemlerinde sistem tarafından tespit edilen girdi bilgilerine mesaj denir. Her girdi bilgisinin yani mesajın hangi nedenden dolayı oluştuğunu açıklayan bir türü vardır. Örneğin mesaj klavyede bir tuşa basılmasından dolayı oluşmuş olabilir ya da mouse ile tek tıklama ya da çift tıklama sonucunda oluşmuş olabilir. Windows oluşan bu mesajı MSG yapısı biçiminde ifade ederek programın mesaj kuyruğuna yerleştirir. Her programın(32 bit Windows sistemlerinde aslında her alt işlemin) bir mesaj kuruğu vardır. Mesaj kuyruğu aslında MSG türünden bir yapı dizisi biçimindedir. MSG yapısı şöyledir:
typedef struct tagMSG { // msg
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
} MSG;
MSG Yapısının Elamanları
- HWND hWnd
Her pencerenin bir mesaj kuyruğu yoktur. Her programın bir mesaj kuyruğu vardır. Yani bir program 10 tane pencere yaratmış olsa bile bu pencerelerin herhangi birisinde oluşan girdi işlemi aynı kuyruğa yazılacaktır. Yapının bu elemanı programın hangi penceresine ilişkin bir mesaj oluştuğunu belirtir.
- UINT message
Bu eleman mesajın hangi sebepten kuyruğa yazıldığını anlatan bir sayı içerir. Bir mesaj yüzlerce sebepten dolayı kuyruğa yazılabilir. Ancak mesajın nedenleri sayısal düzeyde konuşulmaz. Windows.h içerisinde tüm bu sayılar WM_ ile başlayan sembolik sabitlerle tanımlanmıştır. Yani örneğin 108 numaralı mesaj yerine WM_XXXXX mesajı biçiminde durum ifade edilir.
- WPARAM wParam
LPARAM lParam
WPARAM aslında unsigned int türüdür. Unsigned int türü 16 bit Windows sistemlerinde 2 byte uzunluğundaydı. Ancak 32 bit Windows sistemlerinde 4 byte'lık bir bilgidir. Burada W ön eki 16 bit Windows sistemleri kullanılırken isimlendirilmiştir. 32 bit sistemlere geçildiğinde bu isimlendirmenin kullanılmasına devam edilmiştir. LPARAM unsigned long türüdür ve her iki sistemde de 4 byte uzunluğundadır. Bu elemanlara mesaja bağlı olan ek bilgiler yerleştirilir. Örneğin WM_CHAR mesajında wParam basılan tuşun ASCII sıra numarasını içerirken WM_LBUTTONDOWN mesajında basılan noktanın koordinat bilgisini içermektedir. Her mesaj için bu elemanlara hangi bilgilerin yerleştirildiği ayrıca öğrenilmelidir.
- DWORD time
Mesajın gönderildiği zaman bilgisidir.
- POINT pt
Bu eleman bazı mesajlarda ekrana ilişkin koordinat bilgisini tutmak amacıyla kullanılır.
Mesaj Döngüsü
Mesajlar sistem tarafından kuyruğa yazılır, ancak bu mesajların alınarak işlenmesi programcı tarafından yapılır. Programcı mesajları alarak önce bu mesajları anlamlandırır, daha sonra uygun işlemleri yapacak kodları çalıştırır. Programa ilişkin pencere kapatılıp program sonlandırılacağı zaman bu işlem de Windows tarafından WM_QUIT mesajı olarak kuyruğa yazılır. Yani bir döngü içerisinde mesajların sürekli alınarak işlenmesi WM_QUIT mesajı görüldüğünde de döngü sonlandırılarak programın bitirilmesi gerekir.
GetMessage Fonksiyonu
GetMessage kuyrukta bulunan ilk mesajı alır ve bu mesajı kuyruktan siler. Fonksiyonun prototipi:
BOOL GetMessage(
LPMSG lpMsg,
HWND hWnd,
UINT wMsgFilterMin,
UINT wMsgFilterMax
);
Fonksiyonun birinci paranmertesi MSG türünden bir göstericidir. Bu parametreye MSG türünden bir yapının adresi geçirilmelidir. GetMessage kuyrukta sırada bulunan mesajı alarak bu yapının içerisine yerleştirir. İkinci parametre programa ilişkin hangi pencerenin mesajlarının alınacağını belirtir. Yani GetMessage ile özellikle bir pencereye ilişkin mesajlar alınabilir. Bu parametre NULL girilirse bütün pencerelere ilişkin mesajlar alınır. Fonksiyonun üçüncü ve dördüncü parametreleri hangi aralıktaki mesajların kuyruktan alınacağını belirtir. Örneğin bu parametrelere 100 ve 120 girilirse yalnızca bu aralıkta numaralara sahip olan mesajlar alınır. Eğer bu iki parametreye 0 girilirse tüm mesajların alınacağı anlamına gelir. İskelet programda tüm mesajlar alınmaktadır. Fonksiyon eğer WM_QUIT mesajını aldıysa 0 değerine, WM_QUIT dışındaki bir mesajı aldıysa 0 dışı herhangi bir değere geri döner. GetMessage eğer kuyrukta mesaj yoksa programın çizelgeleme dışı bırakılıp bloke edilmesine yol açar. Yani programcı bakış açısıyla programın akışı GetMessage içerisinde beklemektedir. Kuyruktan sürekli olarak mesajı alan WM_QUIT görünce işlemi sonlandıran bir döngü şöyle kurulabilir:
while (GetMessage(&msg, NULL, 0, 0)) {
...........
..........
.........
}
Mesaj döngüsünden çıkıldığında WinMain fonksiyonu da biter, bu fonkiyon bitince de program biter. GetMessage fonksiyonu ile mesaj alındıktan sonra anlamlandırılıp işlenmelidir, ancak prensip olarak mesajın mümkün olduğu kadar çabuk bir biçimde işlenmesi istenir. Eğer mesajın işlenmesi uzun sürerse GetMessage bir sonraki mesajı kuyruktan alamaz. Böylece programda herhangi bir ilerleme söz konusu olamaz. Ancak yine de mesajın işlenmesini geciktirecek ciddi sebepler olabilir. O zaman programın akışının devam etmesi nasıl sağlanacaktır? İşte alt işlemli programlama sistemi(multi-threading) bu tür problemlere çözüm bulmaktadır.
Mesajların İşlenmesi ve Pencere Fonksiyonu
Mesajların işlenmesi ismine pencere fonksiyonu denilen bir fonksiyon tarafından yapılır. Kuyruktan mesaj alındığında DispatchMessage API fonksiyonu çağırılır. DispatchMessage fonksiyonuna parametre olarak kuyruktan alınan mesajın bulunduğu değişkenin adresi geçirilmektedir. DispatchMessage mesajın hWnd elamanına bakarak mesajın hangi pencereye ilişkin olduğunu anlar. O pencere ilişkin pencere fonksiyonunu çağırır. Bir pencere yapısının parametrik yapısı şöyle olmak zorundadır:
LRESULT CALLBACK WndProc(
HWND hWnd,
UINT message
WPARAM wParam
LPARAM lParam
);
Fonksiyonun geri dönüş değeri LRESULT yani long olmak zorundadır. Çağırma biçimi __stdcall olmak zorundadır. Zaten CALLBACK sembolik sabiti __stdcall anlamına gelmektedir. DispatchMessage fonksiyonu mesajı alıp mesaj içerisindeki hWnd, message, wParam, lParam elemanlarını ayrıştırarak bu elemanları pencere fonksiyonunun parametresi yapmak suretiyle pencere fonksiyonunu çağırır. Yani programın akışı pencere fonksiyonumuzdan çıktığında akış DispatchMessage içerisinden devam eder.
Mesaj döngüsünün içerisinde TranslateMessage API fonksiyonu da çağırılmaktadır. Bu fonksiyonun mesaj işlenme mekanizmasıyla ciddi bir bağlantısı yoktur. ASCII tuşlarına basıldığında WM_CHAR mesajının oluşumunu sağlar.
Pencere Fonksiyonunun Tasarımı
Bunun için önce mesajın ne mesajı olduğunun tespit edilmesi gerekir. Bu işlem bir switch deyimiyle yapılabilir. Daha sonra mesaj parametreleri yorumlanarak case ifadeleri işlenir.
Windows Programının Sonlandırılması
Bir Windows programı programın akışı mesaj döngüsünden çıkıp WinMain fonksiyonunun bitmesiyle sonlanır. Program akışının mesaj döngüsünden çıkması GetMessage fonksiyonunun 0 ile geri dönmesiyle sağlanır. GetMessage fonksiyonu WM_QUIT mesajını gördüğünde 0'a geri döner.
DefWindowProc API Fonksiyonu
Bir programın en basit işlemleri yapacak biçimde sağlıklı çalışabilmesi için bir takım kritik mesajların işlenmesi gerekir. Oysa bunların işlenmesi ve bu mesajlara karşılık default bazı işlemleri yapılması uzun bir işlem yükü gerektirir. İşte DefWindowProc fonksiyonu bir mesaj için default kritik işlemleri yapabilen bir fonksiyondur. Yani pencere fonksiyonu içerisinde mesajlar işlenir, işlenmeyen mesajlar bu API fonksiyon çağırılarak işletilir. O halde en basit bir pencere fonksiyonu şöyle tasarlanabilir:
LRESULT CALLBACK WindowProc(HWND hWnd, UINT, message, WPARAM wParam, LPARAM lParam)
{
return DefWindowProc(hWnd, message, wParam, lParam);
} /*Tüm mesajlar default olarak işlenecektir*/
DefWindowProc fonksiyonunun gelen mesajlara karşı hangi kritik işlemleri yaptığı bazı tür işlemlerde programcı tarafından bilinmek zorundadır.
İşlenen mesajlar için DefWindowProc fonksiyonu çağırılmamalıdır. O halde bu fonksiyon tipik olarak switch'in default kısmına yerleştirilmelidir.
Pencere Fonksiyonunun Geri Dönüş Değeri
Pencere fonksiyonunun geri dönüş değeri DispatchMessage fonksiyonu tarafından yorumlanır. Fonksiyonun geri dönüş değeri mesajdan mesaja değişir. Yani hangi mesaj geldiğinde neyle geri dönüleceği önceden bilinmek zorundadır. Ancak aksi söylenmediği sürece pencere fonksiyonu eğer mesajı işlemişse 0 ile geri dönmelidir. İşlememişse DefWindowProc fonksiyonunun geri dönüş değeriyle geri dönmelidir.
LRESULT CALLBACK WindowProc(HWND hWnd, UINT, message, WPARAM wParam, LPARAM lParam);
{
switch(message) {
case .......... :
........
........
break;
case .......... :
........
........
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
Pencerenin Kapatılması Sırasında Gerçekleşen İşlemler
Bir pencereye kapatmak için klik yapıldığında ya da klavye ile Alt+F4 tuşlarına basıldığında Windows yalnızca mesaj kuyruğuna WM_CLOSE mesajını bırakır. Bu mesaja karşılık DefWindowProc DestroyWindow API fonksiyonunu çağırmaktadır. Bu API fonksiyonu pencereyi kapar ve mesaj kuyruğuna WM_DESTROY mesajını koyar. WM_DESTROY mesajı genellikle programcı tarafından işlenir. Programcı bu mesaj karşılığında PostQuitMessage API fonksiyonunu çağırmalıdır(zaten DefWindowProc fonksiyonu da default olarak PostQuitMessage fonksiyonunu çağırır), bu fonksiyon yalnızca kuyruğa WM_QUIT mesajını yerleştirir. GetMessage fonksiyonu da bu mesajı alarak 0 ile geri döner ve WinMain sonlanır.
Visual C++ Geliştirme Sisteminin Kullanılması
Windows altında geliştirme ortamlarında programlama sistemi karmaşık olduğu için mutlaka proje dosyası oluşturularak çalışılır. Uygun bir proje dosyası oluşturabilmek için File => New seçilir. Eğer Win32 Console Application seçilirse konsol programı yazılabilir. Bir konsol programı tamamen DOS'teki bilgilerle yazılabilir. Ancak Windows'un grafik özellikleri kullanılamaz. Eğer Win32 Application seçilirse API düzeyinde programlama yapılabilir. 6.0 sürümünde iskelet programı oluşturan bir wizard da yerleştirilmiştir. Tüm projeler için ayrı bir dizin açılır ve proje dosyaları o dizin içerisine yerleştirilir. Dizin proje ismiyle aynı isimde olur. Açılan dizin içerisine projedeki bütün kaynak dosyalar yerleştirilir. Proje dizininin altında res ve debug/release isimli iki dizin oluşturulur(bu genellikle debug olur, program kaynak kullanmamışsa res dizini açılmaz). Res dizini içerisinde kaynak kullanımına ilişkin dosyalar bulunur. Debug/release'in içerisinde bütün obj modüller, exe dosyaları ve precompiled header dosyası bulunur. Precompiled header dosyası başlık dosyalarını çabuk analiz etmek için gerekir, çok büyük bir dosyadır. Bir projeyi diskete almak için önce debug/release dizini silinmeli ve proje tüm alt dizinleriyle beraber alınmalıdır. Bir proje yaratıldığında bütün proje işlemleri ismine proje çalışma alanı(project workspace) denilen bir pencere yardımıyla yürütülür. Proje çalışma alanı penceresi kapatılabilir(projenin kapandığı anlamına gelmez), daha sonra Alt+0 tuşları ile açılabilir. Bir projenin tamamen kapatılması için File=>Close Workspace seçilmelidir. Proje çalışma alanı penceresinin 4 alt penceresi vardır.
Class View:
Burada bütün C++'taki sınıflar C'deki fonksiyonlar listelenir. Buradaki bir elemanın üzerine double click yapılırsa o fonksiyonun tanımlamasının bulunduğu yere gidilebilir.
File View:
Burada projenin içindeki dosyaların listesi bulunur. Bu pencere aynı zamanda projeye dosya eklemek ve projeden dosya çıkarmak için de kullanılır. Eklemek için projenin üzerinde sağ fare tuşuna basılıp Add Files To Project seçilerek yapılır.
Resource View:
Bu pencere program kaynak kullanıyorsa yaratılır. Kaynak işlemleri bu pencereden izlenebilir.
Info View:
Yardım almakta kullanılır.
Programın Derlenmesi ve Çalıştırılması
Editördeki program Build=>Compile menüsüyle derlenir(Ctrl+F7). Proje içerisindeki bütün dosyaları ve kaynak dosyasını derleyip hep beraber link ederek exe yapmak için Build=>Build seçilir(F7). Bu seçenek yalnızca değişmiş olan dosyaları yeniden derler. Koşulsuz bütün dosyaları derleyip exe yapmak için Build=>Rebuild seçilir. Bir programı çalıştırmak için Build=>execute seçilir(Ctrl+F5). Tabii değişen dosyalar bu arada tekrar derlenir. Bir projeyi açmak için File=>Open Workspace.
Bir fonksiyonun pencere fonksiyonu olduğunu belirtmek için o fonksiyon isminin WndClass yapısının lpfnWndProc elemanına yazılması gerekir.
Windows Programlamada Yardım İşlemleri
Windows altında programlama için iyi bir yardım desteğinin olması gerekir. Visual C++ geliştirme ortamının Online Book diye isimlendirilen bir yardım desteği vardır. Visual C++ 6.0 sürümünde yardım desteği MSDN programının ayrıca install edilmesiyle sağlanır. Online Book içerisinde sistemin orijinal kitapları bulundurulmaktadır.
Mouse Mesajları
Bir pencerenin başlık kısmı çıkartılmış bölgesine çalışma alanı(client area) denir. Çalışma alanının sol üst köşegeni pek çok işlem için orijin noktası belirtir. Mouse pencerenin çalışma alanı içerisinde bir yerde tıklandığında ve çekildiğinde Windows mesaj kuyuruğuna WM_LBUTTONDOWN, WM_RBUTTONDOWN, WM_LBUTTONUP, WM_RBUTTONUP mesajlarını ekler. Bu mesajların lParam ve lParam parametreleri şu bilgileri içerir:
wParam: Click yapıldığında aynı zamanda bir tuşa basılıp basılmadığını belirtir. Şunlardan oluşur:
MK_CONTROL
MK_LBUTTON
MK_RBUTTON
MK_MBUTTON
MK_SHIFT
lParam: Mouse'un click yapıldığı pixel pozisyonudur.
xPos = LOWRD(lParam);
yPos = HIWORD(lParam);
LOWORD ve HIWORD makroları 32 bit bir bilginin yüksek anlamlı ve düşük anlamlı 2 byte'ını elde etmek için kullanılır.
#define HIWORD(x) ((WORD) (x) >> 16)
#define LOWORD(x) ((WORD) (x))
Bu makrolar windows.h içerisinde tanımlanmıştır.
MessageBox Fonksiyonu
Windows'ta acil bir mesaj basmak için MessageBox API fonksiyonu kullanılır. Bu fonksiyon ile pencere başlığı, pencerenin içerisindeki mesaj, istenen tuş takımı ve görüntülenecek ikon belirlenebilir.
int MessageBox(
HWND hWnd,
LPCTSTR lpText,
LPCTSTR lpCaption
UINT uType
);
Fonksiyonun birinci parametresi messagebox penceresinin çıkartılacağı üst pencerenin handle değeridir. Bu parametre NULL olarak girilebilir(messagebox yaratmak için bir pencere yaratmak gerekli değildir). Yani programın hiçbir pencereyi olmasa bile messagebox çıkartılabilir. İkinci parametresi pencerenin içine yazılacak yazının başlangıç adresini alır. Üçüncü parametresi pencerenin başlık yazısını belirtir. Dördüncü parametresi tuş takımı ve ikon görüntüsünü belirler. Tuş takımları şunlar olabilir:
MB_ABORTRETRYIGNORE
MB_OK
MB_OKCANCEL
MB_YESNO
MB_YESNOCANCEL
İkon görüntüsü şunlar olabilir:
MB_ICONEXCLAMATION
MB_ICONWARNING
MB_ICONINFORMATION
MB_ICONASTERIKS
MB_ICONQUESTION
MB_ICONSTOP
Tuş takımıyla ikon görüntüsü bit OR işlemine sokularak birleştirilebilir. Örneğin: MB_OK | MB_ICONEXCLAMATION
Tuş takımlarından bir tanesi default olarak seçilebilir. Default tuş enter tuşuna basıldığında seçilmiş kabul edilen tuştur. Default tuşu belirlemek için 3 tane sembolik sabit vardır:
MB_DEFBUTTON1
MB_DEFBUTTON2
MB_DEFBUTTON3
Bu sembolik sabitler de diğerleriyle birleştirilerek kullanılırlar. Örneğin:
MB_YESNOCANCEL | MB_ICON_EXCLAMATION | MB_DEFBUTTON3
Fonksiyonun geri dönüş değeri hangi tuşa basılarak pencerenin kapatıldığı bilgisini verir. Şunlardan bir tanesi olabilir:
ID_ABORT
ID_CANCEL
ID_IGNORE
ID_NO
ID_OK
ID_RETRY
ID_YES
Mouse'un sol tuşuna basıldığında messagebox çıkartan uygulama:
LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam)
{
switch (message) {
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_LBUTTONDOWN:
MessageBox(hwnd, "Deneme", "Deneme", MB_YESNOCANCEL | MB_ICONSTOP | MB_DEFBUTTON2);
break;
default:
return DefWindowProc(hwnd, message, wParam, lParam);
}
return 0;
}
Sınıf çalışması: Farenin sol tuşuna basıldığında yesnocancel messagebox'ı gözükecek, bunlardan bir tanesi seçildiğinde başka bi messagebox ile hangi tuş'a basıldığı yazdırılacak.
LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam)
{
switch (message) {
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_LBUTTONDOWN:
iMsgBoxReturnValue = MessageBox(hwnd, "Deneme yapıyom ben", "Deneme dedik aşşaada", MB_YESNOCANCEL | MB_ICONSTOP | MB_DEFBUTTON2);
switch (iMsgBoxReturnValue) {
case IDYES:
MessageBox(hwnd, "Yes'e bastın kardeş", "Deneme", MB_OK | MB_ICONSTOP);
break;
case IDNO:
MessageBox(hwnd, "No'ya bastın kardeş", "Deneme", MB_OK | MB_ICONSTOP);
break;
case IDCANCEL:
MessageBox(hwnd, "Cancel'a bastın kardeş", "Deneme", MB_OK | MB_ICONSTOP);
break;
}
break;
default:
return DefWindowProc(hwnd, message, wParam, lParam);
}
return 0;
}
DestroyWindow Fonksiyonu
CreateWindow fonksiyonu pencereyi yarattıktan sonra pencereye ilişkin bütün bilgileri dinamik olarak tahsis ettiği içsel bir yapıda saklar. Aslında CreateWindow fonksiyonunun vermiş olduğu bu geri dönüş değeri bu içsel yapının başlangıç adresidir. DestroyWindow fonksiyonu pencereyi ekrandan görüntüsel olarak siler ve CreateWindow fonksiyonu tarafından dinamik olarak oluşturulmuş olan bu içsel yapıyı da free hale getirir. DestroyWindow fonksiyonu DefWindowProc tarafından WM_CLOSE mesajına karşı otomatik olarak çağırılır. Tabii istenildiği zaman programın ana penceresi kapatılmak isteniyorsa DestroyWindow programcı tarafından çağırılmalıdır. DestroyWindow pencere görüntüsü ekrandan sildikten sonra programcıyı haberdar etmek için WM_DESTROY mesajını gönderir. DestroyWindow aynı zamanda pencerenin bütün alt pencerelerini de DestroyWindow fonksiyonunu çağırarak siler. WM_DESTROY mesajı geldiğinde pencere ve alt pencereler ekranda görünür değildir, ancak bunlara ilişkin veri yapıları hala durmaktadır.
BOOL DestroyWindow(HWND hWnd);
Fonksiyon silinecek pencerenin handle değerini parametre olarak alır.
WM_CREATE Mesajı
Bu mesaj CreateWindow fonksiyonu tarafından pencerenin yaratıldığını bildirmek amacıyla gönderilir. Pencere yaratıldığında yapılmak istenen ilk işlemler WM_CREATE mesajı işlenerek yapılabilirler. WM_CREATE mesajı gönderildiğinde pencere henüz görünür değildir(henüz CreateWindow fonksiyonundan çıkılmamıştır). lParam parametresinde CREATESTRUCT isimli bir yapının adresi bulunur. Bu yapının içi CreateWindow tarafından doldurulmuştur ve faydalı bilgiler içermektedir. Bu yapı içerisindeki bilgilere şu biçimde erişilebilir:
case WM_CREATE:
{
CREATESTRUCT *pStruct;
pStruct = (LPCREATESTRUCT ) lParam;
.............
.............
.............
}
break;
SendMessage ve PostMessage Fonksiyonları
Mesaj kuyruğuna mesajlar PostMessage fonksiyonuyla programcı tarafından bilinçli olarak da bırakılabilirler. Tabii bu mesaj normal olarak kuyruğun sonuna bırakılırlar. Mesajın kuyruğa bırakılmasıyla işlenmesi arasında belli bir zaman kaybı söz konusu olabilir. Yani PostMessage fonksiyonu pencere fonksiyonu içerisinde kullanıldığında programın akışı PostMessage fonksiyonundan çıkar, mesaj kuyruğuna girilerek sıradaki mesajlar işlenir. En sonunda kuyruğa PostMessage ile programcı tarafından bırakılan mesaj alınarak işlenir. Oysa SendMessage fonksiyonu mesajı kuyruğa bırakmaz, doğrudan pencere fonksiyonunu çağırarak hemen işler. Yani programın akışı SendMessage'den çıktığında mesaj işlenmiş olur. Her iki fonksiyonun da parametrik yapısı aynıdır.
LRESULT SendMessage(
HWND hWnd,
UINT Msg,
WPARAM wParam,
LPARAM lParam
);
BOOL PostMessage(
HWND hWnd,
UINT Msg,
WPARAM wParam,
LPARAM lParam
);
Fonksiyonların birinci parametresi mesajın gönderileceği pencerenin handle değeridir. Yani bu fonksiyonlar bu handle'a ilişkin pencerenin yaratılması sırasında belirtilen pencere fonksiyonunu çağırırlar. Fonksiyonların ikinci parametresi gönderilecek mesajın numarasıdır. Üçüncü ve dördüncü parametreler mesajın wParam ve lParam parametreleridir. PostMessage fonksiyonunun geri dönüş değeri BOOL türündendir, işlemin başarısını bildirir. Oysa SendMessage fonksiyonunun geri dönüş değeri çağırılan pencere fonksiyonunun geri dönüş değeridir.
Kaynakların Program İçerisinden Kullanılması
Normal olarak kaynaklar rc uzantılı bir text dosyası içerisine kaynak dili kullanılarak yazılırlar. Kaynak derleyicisi tarafından derlenip res uzantılı yapılırlar ve linker tarafından PE formatındaki exe dosyanın içindeki kaynak bölümüne yerleştirilirler. Visual C++ geliştirme sisteminde rc dosyası proje dosyasına eklenirse bu işlemler otomatik olarak yapılır.
Her kaynağın kaynak dilinde nasıl yazılacağı önceden belirlenmiştir ve belli bir syntax kuralı vardır. Ancak kaynakların programcı tarafından bu biçimde yazılması, yani kaynak dilinin öğrenilmesi çok zahmetlidir. Bunun için tüm geliştirme sistemlerinde kaynaklar ismine kaynak editörü(resource editor) denilen bir ortamda görsel olarak hazırlanırlar. Kaynaklar görsel olarak belirlendikten sonra geliştirme sistemi bunun kaynak dilindeki karşılığını rc dosyası biçiminde oluşturur. Her kaynağın bir ismi vardır. Bu isim alfabetik ya da sayısal biçimde verilebilir. Eğer kaynak isminin ilk harfi alfabetik bir karakterle başlıyorsa kaynak alfabetik olarak isimlendirilmiştir, sayısal bir karakterle başlıyorsa kaynak sayısal olarak isimlendirilmiştir(Eğer bir sayı alfabetik bir isim olarak kullanılacaksa çift tırnak içine alınmalıdır). Genel olarak sayısal biçimde isimlendirilmiş kaynaklar ile daha hızlı işlem yapılır. Bir kaynağı bir programın içinde kullanmak için kaynağı hazır hale getirmek gerekir. Bu işlem sırasında kaynak exe dosyanın kaynak bölümünden alınır, bellekte dinamik olarak yaratılan bir veri yapısının içerisine çekilir. kaynağı hazır hale getiren fonksiyonların isimleri Loadxxxx biçimindedir. Kaynağı hazır hale getiren Loadxxxx fonksiyonlarının iki parametresi vardır. Birincisi exe dosyanın bellekteki yükleme adresi olan hInstance adresidir, ikincisi ise kaynağın ismidir(Bu fonksiyonların exe dosyasının kaynak bölümüne erişebilmesi için exe dosyasının bellekteki yükleme adresini bilmeleri gereklidir). Loadxxxx fonksiyonlarının geri dönüş değerleri kaynağa ilişkin handle değerleridir. Örneğin HCURSOR, HICON, HMENU biçiminde olabilir(hepsi aslında void *).
Menü Kaynağının Kaynak Dilinde Oluşturulması
Kaynak dilinde her kaynak önce kaynak ismiyle başlar, sonra onu kaynağın türünü belirten bir anahtar sözcük izler, sonra kaynaklara göre değişebilen parametrik bilgiler yazılır. Bir menü menü çubuğu ve popup menülerden oluşur. Popup menünün içerisinde başka bir popup menü bulunabilir. Bir menü popup menülerden, popup menüler de menü elemanlarından oluşur. Kaynak derleyicileri C'nin ön işlemci komutlarını aynı biçimde kullanabilir. Popup menülerin bir numarası yoktur ama her menü elemanının bir numarası olmak zorundadır. Menü elemanlarının numaraları istenildiği gibi seçilebilir(Ancak 100'den büyük olması tavsiye edilir). Ancak her menü elemanının numarası diğerinden farklı olmalıdır.
Kaynak dilinde yazılmış kaynaklar çeşitli sayılar kullanabilirler. Bu sayıların ayrı bir başlık dosyasında sembolik sabit olarak tanımlanmaları önerilmektedir. Bu durumda kaynak kullanımı için bir rc dosyası, bir de h dosyası oluşturulur. Bu rc dosyası içerisinden h dosyası include edilir. Örneğin Visual C++ geliştirme sisteminde bir kaynak dosyası yaratıldığında otomatik olarak resource.h dosyası da yaratılır.
Menü Kaynağının Program İçerisinden Kullanılması
Bir menü kaynağını programa dahil edebilmek için üç yöntem vardır:
1. WNDCLASS yapısının lpszMenuName elemanına menü kaynak ismi girilirse menü otomatik olarak görüntülenir.
2. WNDCLASS yapısının lpszMenuName elemanı NULL olarak girilirse menü yine de CreateWindow fonksiyonunda yaratılabilir. Bunun için önce LoadMenu fonksiyonuyla menü kaynağı hazırlanır, sonra buradan alınan handle değeri CreateWindow fonksiyonunun hMenu parametresi olarak verilir.
HMENU hMenu;
hMenu = LoadMenu(hInstance, "SAMPLE_MENU");
CreateWindow(.........................., hMenu, ......);
3. Menü CreateWindow fonksiyonundan sonra programın herhangi bir yerinde SetMenu API fonsiyonuyla da yaratılabilir.
Alt Pencerelerle İşlemler
Alt pencereler CreateWindow fonksiyonuyla yaratılırlar. Verilen koordinatlar üst pencerenin çalışma alanı(client area) referans alınarak oluşturulurlar(Üst pencerelerin referans noktası masaüstü penceresinin sol üst köşesidir). Alt pencere yaratılırken üst pencerenin handle değerine gereksinim duyulur. Alt pencerelerin handle değerlerinin yanısıra bir de id değerleri vardır. Alt pencere id değerleri işaretsiz tam sayı biçimindedir ve normal olarak WORD sınırları içerisindedir. CreateWindow fonksiyonunun dokuzuncu parametresi HMENU türündendir. HMENU parametresi üst pencerelerde menü işlemleri için kullanılırken, alt pencerelerde pencere id değerinin belirlenmesi amacıyla kullanılır. Tabii bu parametrenin alt pencere id'si olarak girilmesi durumunda tür dönüştürmesi yapılması gerekir. Örneğin (HMENU) 100, ya da sembolik olarak (HMENU) CHILD_ID. Pencere id değeri WORD sınırlarında sıfırdan farklı herhangi bir değer olabilir. Alt pencerelerin id değerleri aslında tamamen programlamayı kolaylaştırmak amacıyla düşünülmüştür. Hem handle değeri hem de id değeri her ikisi de bir pencereyi teşhis etmek amacıyla kullanılabilir. Ancak id değerinin tam sayı türünden olması programlamada çeşitli biçimlerde kolaylık sağlamaktadır. Zaten alt pencerenin handle değeriyle id değerinin bir tanesinin bilinmesi durumunda diğerinin elde edilmesini sağlayan API fonksiyonları vardır. Alt pencerelerin ve üst pencerelerin farklı pencere fonksiyonları olabilir. Alt pencereyle üst pencerenin pencere fonksiyonlarının farklı olabilmesi için bu iki pencerenin farklı WNDCLASS yapılarıyla yaratılması gerekir. Çünkü pencere fonksiyonunun belirlenmesi WNDCLASS ile yapılmaktadır. Bir program içerisinde ne kadar çok alt pencere ve üst pencere yaratılmış olursa olsun bu pencerelere ilişkin mesajlar programın(aslında alt işlemin(thread)) mesaj kuyruğuna yerleştirilirler. Yani her programın(aslında alt işlemin) yalnızca bir tane mesaj kuyruğu vardır, o programın yarattığı bütün pencerelere ilişkin mesajlar aynı kuyruğa yerleştirilir. Programın tek bir mesaj döngüsü vardır. GetMessage fonksiyonu tüm pencereler için gönderilmiş olan mesajları alabilmektedir. Mesaj karşılığında hangi pencere fonksiyonunun çağırılacağı kararı DispatchMessage fonksiyonu tarafından verilir. DispatchMessage fonksiyonu mesajın hWnd parametresine bakarak pencerenin hangi WNDCLASS yapısıyla yaratılmış olduğunu tespit eder ve buradan ilgili pencere fonksiyonunu bularak çağırır. DestroyWindow fonksiyonu ile üst pencere yok edilince DestroyWindow fonksiyonu önce alt pencereleri yok eder, daha sonra ilgili pencereyi yok eder.
Uyarı: DefWindowProc fonksiyonu WM_CLOSE mesajı işlenmemişse default olarak DestroyWindow fonksiyonunu çağırır. Ancak WM_DESTROY için PostQuitMessage'i çağırmaz. Eğer WM_DESTROY mesajı ile PostQuitMessage çağırılmamışsa pencere silinir ama program sonlanmaz.
Üst ve Alt Pencereler Arası Mesaj Alış-Verişi
SendMessage ve PostMessage fonksiyonlarıyla üst pencere alt pencereye, alt pencere de üst pencereye mesaj gönderebilir. Aslında bu fonksiyonlarla handle değeri bilindikten sonra her türlü pencereye mesaj gönderilebilmektedir.
Alt Pencere Nerede Yaratılmalı?
Alt pencere üst pencere yaratıldıktan sonra herhangi bir yerde yaratılabilir. Ancak uygulamalarda bu işlem genellikle iki biçimde yapılır:
1. Önce CreateWindow ile üst pencere yaratılır. Bu kodun aşağısında yine CreateWindow ile alt pencere yaratılır.
2. Alt pencere üst pencerenin pencere fonksiyonu içerisinde WM_CREATE mesajında yaratılır. Tabii burada hInstance değerinin WinMain fonksiyonundan bir biçimde elde edilmesi gerekir. hInstance değerinin herhangi bir yerden elde edilmesi için birkaç yöntem vardır. En fazla kullanılan yöntem bu değerin WinMain içerisinde global bir değişkene taşınmasıdır.
WM_USER Mesajları
Windows'un önceden belirlenmiş olan WM_ mesajları 0 ile 0x0400 arasında bulunmaktadır. Ancak programcı isterse kendi mesaj numaralarıyla da işlem yapabilir. Bunun için windows.h içerisinde tanımlanmış olan WM_USER değerinden 0x7FFF'e kadar olan mesajlar kullanılmalıdır. WM_USER sistem tarafından işlenen bir mesaj değildir. Yalnızca programcının kullanabileceği mesaj numarasının nereden başlayacağını anlatan bir sembolik sabittir.
Alt Pencere Biçimleri
CreateWindow fonksiyonunun ikinci parametresi olan pencere biçimi eğer alt pencere söz konusuysa en azından WS_CHILD içermelidir. Ancak WS_BORDER eklenmezse pencerenin sınır çizgisi görünmez. Genellikle alt pencereler için başlık kullanılmaz. Ancak WS_CAPTION pencere biçimi eklenirse pencere başlık kısmına sahip olabilir.
WS_VISIBLE Pencere Biçimi
İster üst pencere ister alt pencere söz konusu olsun eğer pencere biçimi olarak WS_VISIBLE seçilirse pencere yaratılır yaratılmaz pencere görüntülenir. Eğer WS_VISIBLE kullanılmazsa görüntüleme işlemi için ShowWindow fonksiyonunun çağırılması gerekir.
GetClientRect API Fonksiyonu
Bir pencereyle ilgili işlem yapılırken örneğin alt pencere koordinatlarının belirlenmesi ya da çizimler söz konusu olduğunda orijin noktası çalışma alanının(client area) sol üst köşesidir. Pencere boyutunun değiştirilmesi durumunda çalışma alanının sağ alt köşegeninin koordinatları da değişir. GetClientRect fonksiyonu çalışma alanının sağ alt köşegeninin koordinatlarını elde etmekte kullanılır. Bu koordinatın elde edilmesiyle pencerenin genişlik ve yükseklik bilgisi elde edilmiş olur. Prototipi:
BOOL GetClientRect(
HWND hWnd,
LPRECT lpRect
);
Kullanımı:
LPRECT rect;
GetClientRect(hWnd, &rect);
Fonksiyonun ikinci parametresi RECT türünden bir yapı değişkeninin adresini alır. RECT yapısı windows.h içerisinde şöyle bildirilmiştir:
typedef struct tagRECT{
long left, top, right, bottom;
} RECT *LPRECT;
GetClientRect fonksiyonu ile elde edilen dikdörtgensel koordinatın sol üst köşegeni her zaman 0, 0 olur. Zaten bu fonksiyon sağ alt köşegenin elde edilmesi için kullanılır.
WM_SIZE Mesajı
Windows bir pencerenin boyutunun değiştirildiği bir durumda mesaj kuyruğuna bu mesajı bırakır. Pencerenin boyutu köşe noktalarından mouse ile çekildiğinde ya da maximize, minimize, restore tuşlarına basıldığında değişmektedir. Mesajın wParam ve lParam parametreleri şöyle yerleştirilir:
Width = LOWORD(lParam); /*rect.right*/
Hight = HIWORD(lparam); /*rect.bottom*/
wParam parametresi şu değerleri alabilir:
SIZE_MINIMIZED
SIZE_MAXIMIZED
SIZE_RESTORED
MoveWindow API Fonksiyonu
CreateWindow fonksiyonu ile belirlenen koordinat pencerenin ilk açılma görüntüsüne ilişkindir. Bunun dışında pencerenin boyutu çeşitli biçimlerde değiştirilebilir. MoveWindow fonksiyonu hem pencerenin boyutunu değiştirmekte hem de onu ötelemekte kullanılabilir. Prototipi:
BOOL MoveWindow(
HWND hWnd,
int x,
int y,
int nWidth,
int nHeight,
BOOL bRepaint
);
Fonksiyonun birinci parametresi ilgili pencerenin handle değeri, x, y, nWidth ve nHeight parametreleri pencerenin yeni konumunun bilgileridir. Fonksiyonun son parametresi taşınan pencerenin görüntüsünün güncellenmesiyle ilgilidir. Bu parametre genellikle TRUE olarak verilir. Bu durumda güncelleme işlemi hemen yapılır.
Kontroller
Windows sistemi tarafından tanımlanmış olan bir grup alt pencere sistemine kontrol denir. Kontroller; temel kontroller ve gelişmiş kontroller biçiminde ikiye ayrılır. Temel kontroller şunlardır:
Static kontrolü
Push button kontrolü
Edit box kontrolü
List box kontrolü
Check box kontrolü
Radio button kontrolü
Combo box kontrolü
Scroll bar kontrolü
Gelişmiş kontroller şunlardır:
Toolbar kontrolü
Tree kontrolü
Status bar kontolrü
Proggress bar kontrolü
Column kontrolü
Property sheet kontrolü
Tab kontrolü
...
Windows sistemi yüklenirken bir grup WNDCLASS yapısını çeşitli isimlerle sisteme register eder. Bu sınıf isimleri alt pencere yaratmakta kullanılırsa pencere fonksiyonu sınıf ile belirtilen fonksiyonlar olur. Bu sınıfların pencere fonksiyonları user32.dll içerisindedir ve programcıdan gizlenmiştir. Bu sınıflar kullanılarak alt pencere yaratıldığında bu pencere için gönderilen mesajlar programcının değil bu sınıfların pencere fonksiyonu tarafından işlenir. Örneğin kontrol sınıflarına ilişkin bir pencere yaratılıp mouse ile üzerine click yapılsa mouse mesajı programın mesaj kuyruğuna bırakılacak ancak DispatchMessage kotrolün pencere fonksiyonunu çağıracaktır.
Static Kontrolü
Bu kontrol yalnızca bir alt pencerenin üzerine yazı görüntülemek için kullanılır. Static kontrol penceresinin zemin ve yazı renkleri ileride ele alınacaktır. Static kontrol penceresinin zemin rengiyle üst pencerenin zemin rengi aynı alınırsa bu kontrol bir pencereye yazı yazmak amacıyla kullanılır. Bu kontrolü yaratmak için CreateWindow fonksiyonunda sınıf ismi yerine "static" girilir. Pencere kordinatları yazının yazılacağı pencerenin koordinatlarıdır. Pencerenin içerisine yazılacak yazı CreateWindow fonksiyonunun pencere başlığı yazısıdır.
(WM_SIZE mesajı bir pencere ilk kez görüntülendiği zaman da gönderilmektedir.)
Static Kontrolünün Özel Pencere Biçimleri
Her pencere için ortak olan pencere biçimlerinin yanı sıra her kontrolün ayrı yalnızca o kontrol için anlamlı olan özel pencere biçimleri de vardır. Static kontrolü için en önemlileri şunlardır:
SS_LEFT
SS_RIGHT
SS_CENTER
/*Bunlar yazının hizalanmasına ilişkindir.*/
SS_WHITERECT
SS_BLACKRECT
SS_GRAYRECT
/*Bunlar static kontrolünün zemin rengine ilişkindir.*/
Ancak bu renkler mutlak değil göreli renklerdir. Denetim masasındaki renklendirmeye bağlı olarak değişirler.
SS_WHITEFRAME
SS_BLACKFRAME
SS_GRAYFRAME
/*Kontrolün sınır çizgilerinin rengine ilişkindir.*/
Not: Bir pencerenin double-click mesajlarını kabul edebilmesi için sınıfın yaratıldığı WNDCLASS yapısının UINT Style elemanını CS_DBLCLKS olarak belirtmek gerekir.
Pushbutton Kontrolü
Bu kontrol bir işlemi başlatmak ya da iptal etmek gibi amaçlarla kullanılır. Bu kontrole yapılan en önemli hareket tek click işlemidir. Kontrole tek click yapıldığında görüntüsel olarak basma ve çekme hareketi elde edilir. Her ne kadar pushbutton kontrolü üzerine çift click işlemi tanımlanmış olsa da uygulamada kullanılması tavsiye edilmemektedir. Tuş görüntüsünün basılmış olması ya da çekilmiş olması görüntüsü programlama yoluyla ayrı ayrı elde edilebilir. Ancak basılmış durumda kalmış olan bir tuş görüntüsü tercih edilmemektedir.
Pushbutton kontrolünü yaratabilmek için sınıf ismi olarak "button" girilir. Ancak button sınıfı diğer bazı kontroller için de kullanıldığından yaratılan kontrolün bir pushbutton olup olmadığı CreateWindow fonksiyonunun pencere biçimi parametresiyle verilir. CreateWindow fonksiyonunun pencere başlığı yazısı tuş görüntüsünün üzerinde yer alır. Button sınıfı için özel pencere biçimleri şunlardır:
BS_PUSHBUTTON
Bu pencere biçimi pushbutton kontrolü için zorunlu olarak kullanılır.
BS_DEFPUSHBUTTON
Eğer bir grup pushbutton kontrolü arasında klavye odağı(input focus) bu pushbutton kontrollerinin hiçbirinde değilse enter tuşuna basıldığında DEFBUSHBUTTON ile belirtilen pushbutton kontrolüne click yapılmış varsayılır.
Pushbutton Kontrolünün Üst Pencereye Gönderdiği Mesajlar
(Pushbutton Notification Messages)
Pushbutton kontrolü mouse ile kontrolün üzerine click yapıldığında SendMessage fonksiyonuyla üst pencerenin pencere fonksiyonunu WM_COMMAND mesajıyla çağırır. Bunun dışında tavsiye edilmemesine karşın çift click işleminde de bu kontrol üst pencerenin pencere fonksiyonunu aynı biçimde çağırmaktadır.
WM_COMMAND Mesajı
WM_COMMAND mesajı iki durumda çağırılmaktadır.
1. Menüden bir eleman seçildiğinde lParam 0, HIWORD(wParam) 0 ve LOWORD(wParam) menü elemanının ID değeri olur.
2. Bu mesaj Windows sisteminin standart kontrollerinde bir olay gerçekleştiğinde üst pencereyi haberdar etmek için de kullanılır. Bu durumda lParam alt pencerenin handle değerine, HIWORD(wParam) mesajın ne sebeple gönderildiğine ait koda(notification code), LOWORD(wParam) alt pencerenin ID değerine sahip olur. Bu durumda alt pencerede bir olay gerçekleştiğinde bu olayın nedenini anlayarak uygun kodları çalıştırmak için WM_COMMAND mesajı işlenmelidir.
WM_COMMAND mesajı içerisinde HIWORD(wParam) ve LOWORD(wParam) değerlerine istenilen bir sırada bakılmalıdır. LOWORD(wParam) değerinin kontrol edilmesiyle mesajın hangi kontrolden geldiği tespit edilebilir. HIWORD(wParam)'a bakılarak da mesajın hangi nedenle geldiği tespit edilir.
Çok fazla kontrol için WM_COMMAND mesajının işlenmesi programlama açısından zordur. Bunun için WM_COMMAND mesajının işlenmesi daha sistematik bir biçimde yapılabilir. Örneğin iç içe switch'ler kullanılabilir.
case WM_COMMAND:
switch(LOWORD(wParam)) {
case ID1:
switch(HIWORD(wParam) {
.......
.......
.......
}
case ID2:
switch(HIWORD(wParam) {
.......
.......
.......
}
.......
.......
}
break;
Ancak iç içe switch'ler yerine fonksiyon çağırmalarıyla daha okunabilir bir yapı da elde edilebilir.
case WM_COMMAND:
switch(LOWORD(wParam)) {
case ID1:
HandleCtrl1(HIWORD(wParam);
break;
case ID2:
HandleCtrl2(HIWORD(wParam);
break;
.......
.......
}
break;
Bu örnekte programcı her kontrol için bir fonksiyon tanımlamıştır. Fonksiyonların parametresi kontrol üzerinde gerçekleştirilen eyleme ilişkindir.
Menüden bir elemanın seçilmesi sırasında ve bir kontrol üzerinde bir eylemin gerçekleştirilmesi durumunda her ikisi için de WM_COMMAND mesajı çağırıldığına göre bu durum bir karışıklığa yol açabilir. Eğer menü elemanının ID değeriyle kontrolün ID değeri aynı alınırsa mesajın ne sebepten geldiği anlaşılamayabilir. Bunun için en pratik çözüm bu ID değerlerinin farklı alınmasıdır. Eğer farklı alınmazsa bu kontrol WM_COMMAND mesajı içerisinde ayrıca yapılamlıdır.
case WM_COMMAND:
if (lParam) {
....../*Kontrol mesajı*/
}
else {
....../*Menü mesajı*/
}
WM_COMMAND mesajı işlendiğinde pencere fonksiyonu 0 değeriyle geri dönmelidir.
Pushbutton Kontrolünün Üst Pencereye Gönderdiği Mesajların İşlenmesi
Eğer HIWORD(wParam) aşağıdaki değerlerden bir tanesi ise ilgili olaylar gerçekleşmiştir:
BN_DBLCLK
Çift click yapılmıştır.
BN_CLICKED
Tek click yapılmıştır.
Üst Pencerenin Pushbutton Kontrolüne Gönderdiği Mesajlar
Pushbutton kontrolünün çeşitli görüntüsel ve işlevsel işlemleri yapabilmesi için üst pencere kontrole mesaj gönderebilir. Bu mesajlar şunlardır:
BM_CLICK Eğer bu mesaj pushbutton kontrolüne gönderilirse sanki mouse ile tek click yapılmış gibi bir işleme yol açar. Bu durumda pushbutton kontrolü yine WM_COMMAND ile üst pencerenin pencere fonksiyonunu çağıracaktır. Bu mesajın wParam ve lParam parametreleri 0 girilmelidir.
BM_SETSTATE Mesajı
Bu mesajla lParam parametresine 0 verilir, wParam ya 0 ya 1 olur.Eğer 1 verilirse basılma görüntüsü 0 verilirse çekilme görüntüsü oluşur. Yalnız bu durum sadece görüntü üzerinde etkili olur. Pushbutton bu mesajı alınca üst pencereye BN_CLICK bilgisi göndermez.
BM_GETSTATE Mesajı
Bu mesajla wParam ve lParam 0 olarak girilir. Bu mesaj gönderildiğinde SendMessage fonksiyonunun geri dönüş değeri pushbutton kontrolünün durumu hakkında bilgi verir.
Edit Kontrolü
Bu kontrol bir yazının klavyeden girilerek elde edilmesi için kullanılır. Edit kontrolünü yaratabilmek için CreateWindow fonksiyonunda sınıf ismi "edit" olarak girilmelidir. Edit kontrolü tek satırlı veya çok satırlı olabilir. Pencere başlığı olarak belirlenen yazı edit alanının içine başlangıçta yazılır. Edit kontrolü bir editörün yapabileceği her türlü işlemi gerçekleştirebilen bir kontroldür. Örneğin kendi içerisinde bütün özel tuşlara duyarlıdır, undo özelliği vardır, clipboard özelliği vardır.
Edit Kontrolünün Özel Pencere Biçimleri
Edit kontrolünü yaratırken WS_ biçimindeki genel pencere biçimlerine ek olarak ES_ ile başlayan çeşitli pencere biçimleri de kullanılabilir.
ES_LEFT, ES_RIGHT, ES_CENTER
Kontrol içerisindeki yazısın hizalanmasını sağlamak için kullanılırlar.
ES_UPPPERCASE, ES_LOWERCASE
Kontrol içerisine yazılanların tamamını büyük/küçük harf olacak şekilde yazılmasını sağlamak için kullanılır.
ES_READONLY
Klavye kullanılarak kontrol içerisine birşey yazılamaz.
ES_PASSWORD
Yazılanların görüntülenmemesini sağlayarak geri almakta kullanılır.
ES_AUTOHSCROL, ES_AUTOVSCROLL
Yatay ve dikey scroll işlemlerine olanak sağlar.
ES_MULTILINE
Eğer bu pencere biçimi kullanılmazsa default olarak edit kontrolü tek satırlı olur.
Üst Pencerenin Edit Kontrolüne Gönderdiği Mesajlar
1. Edit kontrolündeki yazının elde edilmesi
Bu işlem için edit kontrolüne EM_GETLINE mesajı gönderilir. Bu mesajın wParam parametresi 0 olaral girilmelidir. Bu mesajı alan edit kontrolü yazıyı lParam parametresiyle belirtilen adrese kopyalar. Ancak sonuna NULL karakter eklemez, NULL karakterin programcı tarafındanh eklenmesi gerekir. Bu mesaj gönderildiğinde SendMessage fonksiyonu kopyalama yaptığı yazının karakter uzunluğuyla geri döner. Ancak kullanıcı edit kontrolüne çok fazla bilgi girmiş olabilir. Bu durumda bilginin kopyalanacağı karakter dizisinin uzunluğu ne olacaktır? Kuşkusuz en iyi çözüm başka bir mesajla önce satırdaki karakter sayısını almak, sonra dinamik bir dizi tahsis ederek tüm bilgiyi çekmek olabilir. Ancak bu işlemin program yükü fazladır. Bir gösterici hatasına yol açmamak için edit kontrolüne kopyalanacak maksimum karakter sayısı belirtilebilir. Bunun için EM_GETLINE mesajını göndermeden önce kopyalanacak maksimum karakter sayısını belirtmek için kopyalanacak karakter dizisinin ilk iki byte'ına WORD formunda bir sayı girmek gerekir. Bu durumda bir edit kontrolündeki bilginin elde edilmesi için şu kalıp kullanılabilir.
#define SIZE 100
char buf[SIZE];
*(WORD *)buf = SIZE - 1;
n = SendMessage(hEdit, EM_GETLINE, 0, (LPARAM)buf);
buf[n] = '\0';
2. Çok Satırlı Edit Kontrolünde Toplam Satır Sayısının Elde Edilmesi
EM_GETLINECOUNT mesajı wParam 0, lParam 0 parametreleriyle çağırılır. SendMessage fonksiyonunun geri dönüş değeri olarak toplam satır sayısı olarak alınır.
n = SendMEssage(hEdit, EM_GETLINECOUNT, 0, 0L);
3. Bir Satırdaki karakter Sayısının Elde Edilmesi
İlk satırın sıra numarası 0 olmak üzere edit kontrolü satırlar biçiminde organize edilmiştir. Bir satırdaki karakter sayısının elde edilmesi iki işlemle gerçekleşir:
a. Karakter sayısı bulunacak satırın ilk karakterinin index numarasının buluınması. Edit kontrolü içerisindeki her karakterin bir index numarası vardır. Kontrol içeridindeki ilk karakterin index numarası 0 olmak üzere, her karakterin index numarası artan bir sıraya sahiptir. Bunun için EM_LINEINDEX mesajı kullanılır. Mesaj parametreleri:
wParam = satır no
lParam = kullanılmıyor(0)
Fonksiyonun geri dönüş değeri ilgili satırın ilk karakterinin index numarasıdır. Eğer satır numarası olarak -1 verilirse edit imlecinin bulunduğu satırın ilk karakterinin index numarası elde edilir.
b. Karakter sayısı bulunacak satırın ilk karakterinin index numarası elde edildikten sonra EM_LINELENGTH mesajıyla satırın karakter sayısı elde edilir. mesaj parametreleri:
wParam = index no
lParam = kullanılmıyor(0)
Eğer index numarası olarak -1 girliirse o zaman seçim yapılmış olan satırdaki seçilmemiş eleman sayısına geri döner. Eğer seçim yapılmamışsa imlecin bulunduğu satırdaki toplam karakter sayısına geri döner.
O halde n'inci satırdaki karakter uzunluğunu elde edilmesi için şunlar yapılmalıdır:
{
int index, numberOfChar;
index = SendMessage(hEdit, EM_LINEINDEX, n, 0);
numberOfChar = SendMessage(hEdit, EM_LINELENGTH, index, 0);
}
4. Pencerenin çalışma alanını tamamen kaplayacak bir edit kontrolünün oluşturulması.
Bunun için önce edit kontrolü CreateWindow ile yaratılır, yaratıldıktan hemen sonra WM_SIZE mesajı geleceğine göre koordinat bilgisi olarak (0, 0, 0, 0) girilebilir. Bundan sonra WM_SIZE mesajı ile MoveWindow fonksiyonu kullanılarak boyutlandırma tekrardan yapılır.
WM_SIZE:
MoveWindow(hEdit, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
break;
5. Edit kontrolünün içeriğinin değişip değişmediğinin tespit edilmesi.
Özellikle edit kontrolündeki bilginin saklanacağı zaman böyle bir bilgiye gereksinim duyulur. Bunun çin EM_GETMODIFY mesajı kullanılır. Mesajın parametreleri:
wParam = 0
lParam = 0
Fonksiyonun geri dönüş değeri 0 ise değişiklik yok, sıfır dışı herahngi bir değerse deişiklik var anlamına gelir.
6. Edit kontrolünün scroll yapılması.
Bunun için EM_SCROLL mesajı kullanılır. Bu mesaj ile edit kontrolü düşey olarak scroll yaptırılır. Mesajın parametreleri:
wParam = scroll sayısı
lParam = kullanılmıyor(0)
Bu scroll değerleri:
SB_LINEDOWN
SB_LINEUP
SB_PAGEDOWN
SB_PAGEUP
biçiminde olabilir.
7. Edit kontrolüne undo işleminin yaptırılması.
Bunun için EM_UNDO mesajı kullanılır. Mesajın parametreleri:
wParam = 0
lParam = 0
8. Diğer işlemler.
EM_GETSEL mesajı ile seçilmiş olan karakterlerin index numaraları alınabilir. EM_LINEFROMCHAR ile karakter index verilen bir karakterin hangi satırda olduğu bilgisini elde etmekte kullanılır. EM_REPLACESEL seçili olan alan yerine belirlenen bir yazıyı yerleştirir. EM_GETPASSWORDCHAR ve EM_SETPASSWORDCHAR ile password karakteri olarak çıkacak karakter get ve set edilir. EM_SETTABSTOP ile tab stop'ların yerleri belirlenir. EM_SETREADONLY ile edit kontrolü programın belirli bir aşamasından sonra read-only yapılabilir.
Edit Kontrolünün Üst Pencereye Gönderdiği Mesajlar
1. EN_CHANGE ve EN_UPDATE Mesajları
Bu iki mesaj edit kontrolüne bir karakter girildiğinde üst pencereye gönderilir. Tabii WM_COMMAND mesajı içerisinde HIWORD(wParam) EN_CHANGE ya da EN_UPDATE olacak biçimde gönderilir. Bir tuşa basıldığında karakter görüntülenmeden evvel EN_UPDATE mesajı bir üst pencereye gönderilir. Yani EN_UPDATE mesajı geldiğinde bir tuşa basılmıştır ama henüz görüntülenmemiştir. EN_CHANGE ise görüntülenmeden sonra üst pencereye gönderilir. Yani bir tuşa basıldığında önce EN_UPDATE sonra EN_CHANGE mesajları üst pencereye gönderilir.
2. EN_HSCROLL ve EN_VSCROLL
Edit kontrolünde yatay ya da düşey scroll işlemi oluştuğunda bu mesajlar gönderilir.
Edit kontrolü bir takım gereksinimleri karşılayacak biçimde özellikle EN_CHANGE ve EN_UPDATE mesajları kullanılarak değişik bir biçimde düzenlenebilir. Örneğin sayısal karakter dışında bir karakterin basılması engellenebilir. ancak bu kontrol her türlü bilgi giriş biçimlerini destekleyecek biçimde tasarlanmamıştır. Böylesi bir durumda edit kontrolüne benzer başka bir kontrol tasarlamak gerekir.
List Box Kontrolü
Bu kontrol belirli bilgilerin satırlar halinde listelenmesi için kullanılır. Örneğin 100 kişinin isim ve soyadları ekranda gösterilecekse list box içerisinde satırlar halinde gösterilebilir. List box satırlardan oluşur. List box kontrolüne yeni bir satırın eklenmesi ya da bir satırın silinmesi gibi işlemler kontrole mesjalar gönderilerek yapılır. List box kontrolünde aktif bir satır söz konusudur. Aktif satır ok tuşlarıyla ya da tek click işlemiyle değiştirilebilir. Bu kontrol üzerinde en sık yapılan hareket mouse ile belirli bir satıra double-click işlemidir. Geleneksel olarak böyle bir işlemden sonra double-click yapılan satıra ilişkin özel işlemler yapılır. List box kontrolü yatay ve düşey scroll çubuklarına sahip olabilir. List box kontrolü edit kontrolünde olduğu gibi pek çok işlemi kendi içerisinde yapmaktadır. Örneğin Ctrl+C tuş kombinasyonuyla aktif satır otomatik olarak clipboard içerisine alınır.
List Box Kontrolünün Pencere Biçimleri
LBS_SORT
List box kontrolüne eleman eklendiğinde eklenen elemanların sıralı gözükmesini sağlar.
LBS_EXTENDEDSEL
Uzatmalı list box kontrolü anlamına gelir. Eğer bu pencere biçimi belirlenirse shift ve ok tuşlarıyla ya da shift ve mouse ile bir elemandan başlayarak n tane elemanın seçilmesi sağlanabilir. Ancak ayrık seçim yapılamaz.
LBS_MULTIPLESEL
Ayrık seçilebilme özelliğini kazandırır. Ctrl ve mouse ile birden fazla seçim yapılabilir.
LBS_MULTICOLUMN
Çok sütunlu list box kontrolü aslında tek sütunlu gibi işlem görür. Sütun sayısı ayarlanamaz.
LBS_NOTIFY
Mouse ile click ya da double-click yapıldığında üst pencereye mesaj gönderilmesini sağlar.
LBS_STANDARD
Bu bir sembolik sabittir. #define LBS_STANDARD (LBS_NOTIFY | LBS_SORT | WS_VSCROLL | WS_BORDER)
List Box Kontrolüne Gönderilen Mesajlar
1. List box kontrolüne eleman eklenmesi
Bunun için LB_ADDSTRING mesajı kullanılır. Mesajın parametreleri:
wParam = 0;
lParam = (LPARAM) str; /*Eklenecek yazının başlangıç adresi*/
Eğer list box LBS_SORT ile yaratılmışsa otomatik olarak sıralı bir biçimde listeye eklenir, değilse sona eklenir.
List box mesalarının geri dönüş değerleri işlemin başarısı hakkında bilgi vermektedir. Geri dönüş değeri 3 biçimde olabilir:
LB_ERRSPACE
(-2)
LB_ERR
(-1)
LB_OK
(0)
Normal olarak bu değerin kontrol edilmesine gerek yoktur.
List box kontrolüne eleman otomatik olarak yerleştirilemez, ancak bu mesajla yerleştirilebilir. Kontrolün içerisinde başlangıçta n tane eleman gözükmesi isteniyorsa üst pencerenin WM_CREATE mesajı içerisinde eleman eklenebilir.
char *names[10] = {"Ali", "Veli", "....", ...};
WM_CREATE:
hListBox = CreateWindow(.................);
for (i = 0; i < n; ++i)
SendMessage(hListBox, LB_ADDSTRING, 0, (LPARAM) names[i])
break;
2. List box kontrolüne eleman insert edilmesi
Bunun için LB_INSERTSTRING mesajı kullanılır. Mesaj parametreleri:
wParam = index no;
lParam = (LPARAM) str;
List box içerisindeki ilk eleman 0 olmak üzere her elemana artan sırada bir sayı karşılık düşürülmüştür. Buradaki index numarası ilki 0 olmak üzere kaç numaralı satır olduğunu anlatır. Örneğin bu sayı 5 olarak girilmişse insert edilecek elemanın index numarası 5 olacak biçimde kaydırma yapılır(yani önceki 5 numara 6 olur). List box sıralı olarak oluşturulursa böyle bir ekleme sırayı bozar.
3. Bir elemanın silinmesi.
Bunun için LB_DELETESTRING mesajı kullanılır. Mesajın parametreleri:
wParam = index no;
lParam = 0;
Kontrolden bütün elemanların silinmesi için LB_RESETCONTENTS mesajı kullanılır. wParam ve lParam parametreleri 0 girilmelidir.
4. Toplam eleman sayısının elde edilmesi.
List box içerisindeki toplam eleman sayısı, eleman eklenirken programcı tarafından da izlenebilir. Ancak LB_GETCOUNT msajı ile bu sayı alınabilir. wParam ve lParam 0 olarak girilir. SendMessage fonksiyonunun geri dönüş değeri toplam eleman sayısını verir.
5. List box içerisindeki bir elemanın yazısının elde edilmesi
Bunun için LB_GETTEXT mesajı kullanılır. Mesajın parametreleri:
wParam = index no;
lParam = (LPARAM) buf;
char buf[50];
SendMessage(hListBox, LB_GETTEXT, indexno, buf);
6. List box'taki bir elemanın karakter uzunluğunun bulunması
Bunun için LB_GETTEXTLEN mesajı kullanılır. Mesajın parametreleri:
wParam = index no;
lParam = 0;
Geri dönüş değeri karakter uzunluğudur.
7. List box kontrolündeki aktif elemanın index numarasının tespit edilmesi
Eğer list box genişletilmiş ve ayrık seçime izin vermeyecek biçimde yaratılmış ise bunun için LB_GETCURSEL kullanılır. Mesajın parametreleri:
wParam = 0;
lParam = 0;
Fonksiyonun geri dönüş değeri o anda seçili olan list box elemanın index numarasını verir.
Çoklu seçime izin verilen list box kontrolü için LB_GETSELITEMS mesajı kullanılır. Mesajın parametreleri:
wParam = maximum eleman sayısı;
lParam = (LPARAM) adr;
Fonksiyonun geri dönüş değeri diziye yerleştirilen index numaraları sayısıdır.
int selItems[100];
int nOfSel;
nOfSel = SendMessage(hListBox, LB_GETSELITEMS, 100, (LPARAM) selItems);
8. Diğer list box mesajları
WM_SETREDRAW
Peşi sıra n tane eleman ekleneceği durumda görüntünün titrememesi için görüntü güncelleme işlemini kapatır ya da açar. wParam = FALSE girilirse kapatır, TRUE girilirse açar(lParam = 0).
LB_SETCURSEL
Aktif eleman klavye ya da mouse ile değil doğrudan programlama yoluyla değiştirilebilir.
LB_SELECTSTRING
bu mesaja bir yazı parametre olarak girilir(lParam), bu yazı ile başlayan list bos elemanı seçilir.
List Box Kontrolünün Üst Pencereye Gönderdiği Mesajlar
Kontrolde bir olay gerçekleştiğinde üst pencere WM_COMMAND mesajıyla çağırılır. HIWORD(wParam) ne sebeple, LOWORD(wParam) alt pencere ID değeri, lParam alt pencere handle değeri olur.
List box kontrolünü gönderdiği en önemli iki mesaj aktif eleman değiştirildiğinde ya da bir elemana double click yapıldığında gönderilen mesajlardır. Eğer HIWORD(wParam) = LBN_SELCHANGE ise o anda aktif eleman değişmiştir, yani eğer LB_GETCURSEL mesajı gönderilirse yeni aktif elemanın index numarası elde edilir. Eğer HIWORD(wParam) LBN_DBLCLK ise bir eleman
üzerine double click yapıldığı anlaşılır. Bunun dışında LBN_ERRSPACE, LBN_SETFOCUS, LBN_KILLFOCUS gibi mesajlar da vardır. Ancak fazla kullanılmazlar.
List Box mesajlarını incelemek için tipik bir yapı:
case WM_COMMAND:
switch(LOWORD(wParam)) {
case IDLIST:
switch(HIWORD(wParam)) {
.........
.........
case LBN_SELCHANGE:
ProcessSelChange();
break;
case LBN_DBLCLK:
ProcessDblClk();
break;
}
break;
........
........
........
}
break;
void ProcessSelChange(void)
{
int index;
index = SendMessage(hListBox, LB_GETCURSEL, 0, 0);
.....
}
Windows sisteminin burada ele alınmayan diğer kontrolleri dialog pencereleri konusunda ele alınacaktır. Ancak diğer kontrollerin yaratılması ve kullanılmasında yukarıda açıklanan aynı sistematik kullanılmaktadır.
Pencere Parametrelerini Değiştirmek
CreateWindow fonksiyonunda verilen parametreler user32.dll modülü içerisinde oluşturulan ve pencere bilgilerini tutan içsel bir veri yapısında tutulurlar. CreateWindow fonksiyonundan elde edilen handle değeri aslında bu içsel veri yapısının adresidir. Ancak bu içsel veri yapısı içerisindeki veri yapısı GetWindowLong fonksiyonuyla elde edilebilir, SetWindowLong fonksiyonuyla değiştirilebilirler. Pencerenin parametrik bilgileri değiştirildiğinde bu pencereyle ilgili ilk handle işlemi yapıldığı zaman değişiklik hissedilir. GetWindowLong ve SetWindowLong fonksiyonlarının kullanımları birbirlerine çok benzer. Prototipleri:
LONG GetWindowLong(
HWND hWnd,
int nIndex
);
LONG SetWindowLong(
HWND hWnd,,
int nIndex,
LONG dwNewLong
);
Fonksiyonların birinci parametresi parametrik bilgileri değiştirilecek pencerenin handle değeridir. İkinci parametre pencerenin hangi parametresinin değiştirileceğini ya da elde edileceğini belirtir.
Örneğin bir pencere WS_BORDER pencere biçimiyle yaratılmamış olsun. WS_BORDER biçimini bu pencere ekleyebilmek için öncelikle pencerenin pencere biçimi parametresini elde etmek gerekir. Bu işlem için GWL_STYLE sembolik sabitini kullanmak gerekir.
dwResult = GetWindowlong(hWnd, GWL_STYLE);
SetWindowLong(hWnd, GWL_STYLE, dwResult | WS_BORDER);
WS_BORDER'ı kaldırmak isteseydik:
SetWindowLong(hWnd, GWL_STYLE, dwResult & ~WS_BORDER);
Örneğin pencere fonksiyonu içerisinde hInstance değeri şöyle elde edilebilir:
dwReseult = GetWindowLong(hWnd, GWL_HINSTANCE);
GWL_WINDOWPROC ile pencere fonksiyonunun başlangıç adresi elde edilebilir. Elde edilen bu başlangıç adresinin bir fonksiyon göstericisine yerleştirilmesi gerekir. Normal olarak pencere fonksiyonunun başlangıç adresi uzun bir biçimde aşağıdaki gibi bir fonksiyon göstericisine atanabilir:
LRESULT(CALLBACK *p)(HWND, UINT, WPARAM, LPARAM);
p = (LRESULT(CALLBACK *p)(HWND, UINT, WPARAM, LPARAM))GetWindowLong(hWnd, GWL_WINDOWPROC);
Ancak WNDPROC typedef ismi aşağıdaki gibi tanımlanmıştır ve işlemleri kolaylaştırır:
typedef LRESULT (CALLBACK *WNDPROC)(HWND, UINT, WPARAM, LPARAM);
Bu typedef sayesinde yukarıda işlem kolaylaşır:
WNDPROC p;
p = (WNDPROC)GetWindowLong(hWnd, GWL_WINDOWPROC);
Pencere Fonksiyonunun Hook Edilmesi(Windows subclassing)
Hooking bir olay sırasında bir fonksiyon çağırılıyorsa başka bir fonksiyonun çağırılması sağlanarak o fonksiyonun da önceki fonksiyonu çağırmasıyla bir çeşit araya girme işleminin oluşturlmasına veriln bir isimdir. Örneğin kontrollerin pencere fonksiyonları user32.dll içerisindedir. Normal olarak kontrollerle ilgili işlem yapıldığında kontrolün pencere fonksiyonu çağırılır. Ancak GetWindowLong API fonksiyonuyla kontrolün pencere fonksiyonunun adresi elde edilebilir. SetWindowLong fonksiyonuyla programcının pencere fonksiyonu kontrolün pencere fonksiyonu yerine yazılabilir. Programcının pencere fonksiyonu içerisinde kontrolün pencere fonksiyonu çağırılabilir. Win32 sistemlerinde prosesler ayrık bir biçimde belleğe yüklenip çalıştırıldığı için bir prosesin başka bir prosesteki pencere fonksiyonunu hook edebilmesi normal yöntemlerle mümkün değildir. SetWindowLong fonksiyonunun geri dönüş değeri ilgili özelliğin değiştirilmesinden önceki değerdir(GetWindowLong fonksiyonuna gerek yok). Adresi bilinen bir pencere fonksiyonu doğrudan çağırılabilir. Ancak UNICODE/ASCII dönüştürülmesinin doğrudan yapılabilmesi için tavsiye edilen yöntem CallWindowProc API fonksiyonun kullanılmasıdır. Bu durumda push button kontrolünün hook edilmesi aşağıdaki gibi yapılabilir:
LRESULT CALLBACK NewProc(HWND, UINT, WPARAM, LPARAM);
WNDPROC pfnOldProc;
hButton = CreateWindow("button", .............);
pfnOldProc = (WNDPROC)SetWindowLong(hButton, GWL_WINDOWPROC, (LONG)NewProc);
..
...
LRESULT CALLBACK NewProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
.......
.......
return CallWindowProc(pfnOldProc, hWnd, message, wParam, lParam);
}
Dialog Pencereleri
İçerisinde kontrollerin bulunduğu işletim sistemi tarafından doğrudan desteklenen kaynak tabanlı çalışan özel pencerelere dialog pencereleri denir. Dialog pencereleri "modal" ve "modeless" olmak üzere iki kısma ayrılır. Modal dialog pencereleri DialogBox API fonksiyonuyla açılır, EndDialog API fonksiyonuyla kapatılır. Modeless dialog pencereleri ise seyrek kullanılır ve biraz daha değişik organize edilir. Diaolg penceresinin türü ne olursa olsun, yaratılması için belirli adımlardan geçmek gerekir. Dialog penceresi yaratmanın ilk adımı rc dosyası içerisinde bir dialog kaynağı yazmaktır. Dialog kaynağının ismi alfabetik ya da sayısal olarak istenilen biçimde verilebilir. Bu isimden sonra dialog anahtar sözcüğü kullanılmalıdır. Daha sonra blok içerisinde dialog penceresi içerisine yerleştirilecek kontrollerin türleri, koordinat parametreleri, alt pencere ID değerleri, pencere biçimleri belirlenen bir syntax biçimine uygun olarak yazılır. Dialog kaynağının oluşturulması manual yöntemnle yapılabileceği gibi kaynak editörleriyle görsel bir biçimde de yapılabilir. Bundan sonra dialog penceresini yaratan API fonksiyonu çağırılmalıdır. Bu API fonksiyonları dialog kaynağının ismini parametre olarak alır. Dialog penceresi içerisine yerleştirilecek kontrolleri tek tek CreateWindow API fonksiyonunu kullanarak yaratır. Modal dialog pencereleri için DialogBox, modeless dialog pencereleri için CreateDialog API fonksiyonları kullanılmaktadır. Modal bir dialog penceresini kapatmak için EndDialog, modeless bir dialog penceresini kapatmak içinse doğrudan DestroyWindow API fonksiyonları kullanılır. Dialog pencereleri bir grup kontrolün kolay bir biçimde yaratılmasını sağlamak için tasarlanmıştır. Bir grup kontrolü doğrudan ana pencerenin içerisinde oluşturmak yerine başka bir pencerenin içerisinde oluşturmak çeşitli kullanım kolaylıkları sağlamaktadır.
Windows İşletim Sisteminin Pencere Yapısı
Windows sisteminin pencere yapısıda kullanılan 5 önemli kavram vardır:
- Ana pencere(top level window, frame window)
- Üst pencere(parent window)
- Alt pencere(child window)
- Owner pencere(owner window)
- Owned pencere(owned window)
Ana pencereler masa üstü penceresinin alt pencereleriymiş gibi işlem görür. CreateWindow ile hWndParent parametresi NULL girilerek, pencere biçiminde WS_CHILD kullanılmayarak yaratılırlar. Ana pencereler doğrudan görev çubuğu(taskbar) üzerinde görünürler. Bir program istenildiği kadar ana pencereye sahip olabilir(Görev çubuğu ile Ctrl+Alt+Del'e basıldığında elde edilen görev listesi farklı kavramları ifade eder. Görev çubuğunda ana pencereler, görev listesinde programlar listelenir). Üst pencere birden fazla alt pencereye sahip olabilir. Alt pencereler CreateWindow fonksiyonunda hWndParent parametresi yerine üst pencerenin handle değeri, pencere biçimi yerine de WS_CHILD kullanılarak yaratılırlar. Alt pencere kavramı ile owned pencere kavramları çok yakındır. Owned pencere yaratabilmek için CreateWindow fonksiyonunun hWndParent parametresi yerine üst pencerenin handle değeri girilir, fakat pencere biçiminde WS_CHILD belirtilmez. Alt pencere üst pencerenin sınırları dışına çıkamaz ama owned pencere çıkabilir. Owned pencerenin üst penceresine owner pencere denir. Owned pencereler her zaman owner pencerelerin üzerinde kalırlar. Owner pencere minimize edildiğinde owned pencere görüntülenmez. Üst pencerenin bir alt pencere listesi vardır. Üst pencerenin alt pencerelerine kardeş pencereler denilir(sibliting windows). Windows pencerelere ilişkin içsel veri yapısını şöyle kurar: Masa üstünün alt pencere listesinde bütün ana pencereler bulunur. Ana pencerelerin her birinin alt pencere listesinde ana pencerelerin kendi alt pencereleri bulunmaktadır. yani masa üstü aslında directory sistemindeki root directory gibi işlem görür.
Alt Pencere Üst Pencere Sistemine İlişkin Faydalı Fonksiyonlar
GetDlgItem Fonksiyonu
Windows sisteminde pencerenin türü ne olursa olsun, handle değerleri birbirlerinden farklıdır. Bu handle değeri pencerenin veri yapısına ilişkin sistem bölgesinin başlabngıç adresidir. Her pencerenin bir kardeş pencere listesi vardır. Masaüstü penceresinin kardeş pencere listesi içerisinde ana pencereler bulunur. Farklı alt pencerelerin ID değerleri aynı olabilir, ancak kardeş pencerelerin ID değerleri birbirlerinden farklı olmak zorundadır. Bunun için yalnızca alt pencerenin ID değerini vererek alt pencerenin handle değerini elde edemeyiz. Alt pencerenin ID değeri dışında ayrıca üst pencerenin handle değerini de verirsekalt pencerenin handle değerini elde edebiliriz. Bu API fonksiyonu alt pencerenin ID ve üst pencerenin handle değeri verilerek alt pencerenin handle değerini elde etmekte kullanılır. Prototipi:
HWND GetDlgItem( HWND hDlg, int nID);
Bu işlem tipik olarak dialog pencerelerinde kullanıldığı için fonksiyonların isimlendirilmesi dialog çağrışımı yapacak biçimde yapılmıştır. Oysa bu fonksiyonlar her türlü alt pencere üst pencere sisteminde çalışır.
GetDlgCtrlId Fonksiyonu
Bu fonksiyon GetDlgItem fonksiyonunun mantıksal olarak tersini yapmaktadır. Yani alt pencerenin handle değeri ile alt pencerenin ID değerinin elde edilmesini sağlar. Prototipi:
int GetDlgCtrlId(HWND hWndChild);
SendDlgItemMessage Fonksiyonu
Bu fonksiyon aslında kendi içerisinde GetDlgItem ve SendMessage fonksiyonlarını çağırmaktadır. Üst pencerenin handle, alt pencerenin ID değerini alarak alt pencereye mesaj gönderir. Prototipi:
LONG SendDlgItemMessage(
HWND hMainWnd,
int nID,
UINT message,
WPARAM wParam,
LPARAM lParam
);
Fonksiyon aslında şöyle yazılmıştır:
LONG SendDlgItemMessage(
HWND hMainWnd, int nID, UINT message,
WPARAM wParam, LPARAM lParam)
{
HWND hChildWnd;
hChildWnd = GetDlgItem(hmainWnd, hID);
return SendMessage(hChildWnd, message, wParam, lParam);
}
Dialog Kaynağının Oluşturulması
Bir dialog kaynağı aşağıdaki genel biçim ile belirtilir:
<Kaynak İsimi> <dialog> [Pencere Biçim]
{
Kontrol bilgileri
}
Buradaki pencere biçimi dialog penceresinin yaratılmasında kullanılan pencere biçimidir. Genellikle WS_POPUP içerir. WS_POPUP owned pencere yaratmak için tercih edilen bir pencere biçimdir. Pencere başlığı içermez. Bunun yanı sıra üst pencere yaratılırken kullanılan diğer pencere biçimleri buraya eklenebilir. Örneğin dialog penceresi WS_CAPTION eklenebilir, bu durumda dialog penceresinin başlığı çıkar, ya da WS_SYSMENU eklenerek sistem menüsüne sahip olması sağlanabilir. Hatta owned pencere yerine alt pencere WS_CHILD eklenerek oluşturulabilir(WS_POPUP ile WS_CHILD birlikte kullanılamaz). Dialog kaynağının içerisine kontrol bilgileri yerleştirilir. Her kontrolün bir ismi vardır. Ayrıca kontrollerin pencere biçimleri, ID numaraları koordinatları belirtilir.
Dialog içerisindeki kontrollerin isimleri şöyledir:
CTEXT
static kontrol
PUSHBUTTON
push button kontrolü
LISTBOX
list box kontrolü
COMBOBOX
combo box kontrolü
EDITTEXT
edit box kontrolü
DEFPUSHBUTTON
default push button kontrolü
ICON
icon kontrolü
Tabii aslında dialog kaynağı da normal olarak kaynak editörü ile oluşturulur. Kaynak editöründe dialog penceresi ve kontroller görsel olarak yerleştirilir. Kaynak editörü bunun kaynak dilindeki karşılığını yazar.
Aslında dialog kaynağında belirtilen herşey dialog penceresinin ve içerisindeki kontrollerin CreateWindow fonksiyonu ile yaratılması için gereksinim duyulan parametre bilgileridir. Dialog penceresi içerisinde programcının oluşturduğu kontroller de kullanılabilir.
Modal Dialog Pencereleri
Modal dialog pencereleri DialogBox API fonksiyonuyla yaratılır, EndDialog API fonksiyonuyla sonlandırılır. Tabii dialog penceresi de bir pencere olduğuna göre onun da bir pencere fonksiyonunun olması gerekir. Dialog penceresi üzerindeki bir push button kontrolüne click yapıldığında, push button kontrolü dialog pencerisine ilişkin pencere fonksiyonunu WM_COMMAND mesajıyla çağırır. Dialog pencerelerinin pencere fonksiyonlarına gerçek pencere fonksiyonu denir. Bu fonksiyonlar user32.dll içerisindedir ve programcıdan gizlenmiştir. DialogBox API fonksiyonu programcının oluşturduğu yapay bir pencere fonksiyonunu parametre olarak alır. Dialog kontrolleri üzerinde bir işlem gerçekleştiğinde gizlenmiş gerçek dialog penceresi fonksiyonu programcının yapay dialog penceresi fonksiyonunu çağırır. Gerçek dialog pencere fonksiyonunun gizlenmesinin nedeni şöyle açıklanabilir: Gerçek dialog pencere fonksiyonu WM_CREATE mesajı içerisinde kontrolleri yaratır, ok tuşlarına ve tab tuşuna duyarlı işlemler yapar, daha pek çok ayrıntılı alt seviye işlemleri gerçekleştirir. Yani yazılması karmaşıktır. Oysa yapay dialog pencere fonksiyonu yalnızca kontrollerle iletişim kurmak için temel işlemleri yapacak biçimde tasarlanır. DialogBox API fonksiyonunun parametrik yapısı şöyledir:
int DialogBox(
HINSTANCE hInstance,
LPCTSTR lpResource,
HWND hParentWnd,
DLGPROC lpDialogFunc
);
Fonksiyonun birinci parametresi WinMain fonksiyonuna geçirilen hInstance değeridir. İkinci parametre dialog kaynağının ismidir. Üçüncü parametre dialog penceresinin açılacağı üst pencerenin handle değeridir. Dördüncü parametre yapay dialog penceresi fonksiyonuna ilişkin fonksiyon göstericisidir. Buradaki DLGPROC windows.h içerisinde aşağıdaki gibi typedef edilmiştir:
typedef BOOL (CALLBACK *DLGPROC)(HWND, UINST, WPARAM, LPARAM);
Yapay Dialog Penceresi Fonksiyonunun Tasarımı
Yapay pencere fonksiyonunun geri dönüş değeri normalinden faklı olarak LRESULT(long) değil BOOL(int) biçimindedir. Normal pencere fonksiyonunda bir mesaj işlenmişse 0 değeri ile işlenmemişse DefWindowProc fonksiyonunun geri dönüş değeriyle geri döner. Yapay dialog pencere foınksiyonunun geri dönüş değeri mesaj işlenmemişse FALSE ile işlenmişse TRUE değeriyle geri döner. Aslında yapay dialog pencere fonksiyonu FALSE ile geri döndüğünde gerçek pencere fonksiyonu bu mesajın işlenmediğini anlaarak DefWindowProc fonksiyonunu çağırır. bu durumda DefWindowProc fonksiyonu yapay dialog penceresi fonksiyonunun içerisinde çağırılmamalıdır.
WM_INITDIALOG Mesajı
Yapay dialog penceresi fonksiyonunda WM_CREATE mesajı işlenmeye çalışılırsa programcı şunu bilmelidir: Bu noktada henüz dialog penceresinin kendi penceresi yaratılmış olmasına karşın içerisindeki kontroller henüz yaratılmamıştır. Dolayısıyla bu mesajın yapay dialog penceresi içerisinde işlenmesi pek faydalı değildir. Oysa kontroller yaratıldıktan sonra gerçek pencere fonksiyonu yapay pencere fonksiyonunu WM_INITDIALOG mesajıyla çağırır. Bu noktada kontroller yaratılmıştır ama görünür halde değildir. Yani gerçek dialog penceresi fonksiyonunun WM_CREATE mesaında şunlar yapılmıştır:
1. Yapay dialog penceresi fonksiyonu WM_CREATE mesajıyla çağırılmıştır.
2. Kontroller WS_VISIBLE pencere biçimi olmadan yaratılmıştır.
3. Yapay dialog penceresi fonksiyonu WM_INITDIALOG mesajıyla çağırılmıştır.
4. Kontroller ShowWindow fonksiyonuyla görüntülenmiştir.
Örnek Dialog Penceresi Programı
Dağıtılan örnek programda menü'den bir seçim yapıldığında bir dialog penceresi oluşturulmuştur. Dialog penceresi içerisinde 4 tane kontrol yaratılmıştır. Edit box içerisine yazılan yazı bir push button'a basıldığında list box'a eklenmiştir. Diğer push button'a basıldığında dialog penceresi sonlandırılmıştır. Pencere açıldığında list box'ın içerisinde iki yazı görünmektedir. Bu yazılar WM_INITDIALOG mesajında eklenmiştir. EndDialog fonksiyonu doğal olarak yapay dialog penceresi fonksiyonu içerisinde çağırılmalıdır. DialogBox API fonksiyonu dialog pencerelerini ve kontrolleri yarattıktan sonra kendi mesaj döngüsüne girer. Tıpkı WinMain'deki mesaj döngüsü gibi kuruktan mesajları alır, dialog penceresine ilişkinse DispatchMessage ile işler, başka bir pencereye ilişkinse kuyruktan alarak görmemezlikten gelir ve siler.Böylece modal dialog penceresi açıldığında kapanana kadar arka planda bir işlem gerçekleşemez. yapay pencere fonksiyonu içerisinde EndDialog API fonksiyonu çağırıldığında gerçek dialog penceresi fonksiyonu dialog penceresini kapatır, DialogBox içerisindeki mesaj döngüsünden çıkılır, böylece akış DialogBox fonksiyonundan devam eder.
Default Push Button
Deafult push button sadece dialog pencerelerinde kullanılır. Dialog penceresi üzerinde hiçbir push button kontrolü aktif kontrol değilse(örneğin o anda edit kontrolü üzerinde yazı yazılıyor olabilir) enter tuşuna basıldığında üzerine click yapılmış varsayılan push button kontroldür. Bir dialog üzerinde tek bir DEFPUSHBUTTON kontrolü olmalıdır. Eğer hiçbir push button DEFPUSHBUTTON değilse enter tuşuna basıldığında LOWORD(wParam) IDOK olacak biçimde WM_COMMAND mesajı çağırılır. Dialog penceresi içerisinde ESC ya da Ctrl+break tuşlarına basıldığında WM_COMMAND mesajı LOWORD(wParam) IDCANCEL olacak biçimde çağırılır.
Sınıf Çalışması: Programın menüsü şu biçimdedir:
Dialog1
Dialog2
Exit
Dialog1: Adı Soyadı ve bu değeri alacak bir edit box, No ve bu değeri alacak bir edit box bir list box, ok ve exit pushbutton'ları
Dialog2: Static kontrol ve ok tuşu
Dialog Tabanlı Uygulamalar
Dialog tabanlı çalışma programda bir ana pencere olmadan yalnızca dialog penceresi açarak çalışma anlamına gelir. Tabii bu durumda DialogBox fonksiyonunda hWndParent parametresi olarak NULL girmek gerekir. Dialog tabanlı çalışma küçük uygulamalarda hızlı kod yazma olanağını sağlar. Dialog tabanlı çalışmada WinMain içerisinde yalnızca DialogBox fonksiyonuyla dialog penceresi açılır. Başka bir koda gereksinim duyulmaz.
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpszCmdParam, int nCmdShow)
{
return DialogBox(
hInstace,
MAKEINTRESOURCE(ID_DIALOG),
NULL,
(DLGPROG)DialogProc
);
}
Böylece yalnızca yapay dialog penceresi fonksiyonunun yazılması ile küçük uygulamalar gerçekleştirilebilir. Herhangi bir WNDCLASS yapısının tanıtılması ve mesaj döngüsünün oluşturulması gerekmez, çünkü DialogBox fonksiyonu zaten dialog penceresinin ana penceresini yaratmak için önceden tanımlanmış bir sınıfı kullanmaktadır. DialogBox fonksiyonu ayrıca kendi mesaj döngüsüne sahip olduğu için WinMain içerisinde bir mesaj döngüsü yaratmaya gerek kalmaz. İçinden çıkılabilen en basit diloag pencere fonksiyonu şöyle yapılabilir:
BOOL CALLBACK DialogProc(HWND hWnd, UINT iMessage,
WPARAM wParam, LPARAM lParam)
{
if (iMessage == WM_COMMAND && LOWORD(wParam) == IDOK) {
EndDialog(hWnd, 0);
return TRUE;
}
return FALSE;
}
Bu minimum kodla yazılmış olan dialog penceresi fonksiyonu dialog mesajlarını işlemez ancak test amacıyla kullanılabilir.
Özel Kontroller
Programcı kendisinin yarattığı bir kontrolü de dialog penceresinin içerisine yerleştirebilir. Gerçek dialog penceresi fonksiyonu ilgili kontrolü CreateWindow fonksiyonuyla yaratabilmek için kontrolün yaratılacağı sınıf ismine gerek duyar. Aslında dialog penceresinin resource'unun içinde kullanılan PUSHBUTTON, EDITTEXT gibi dialog elemanları bu kontrollerin yaratılması esnasında kullanılacak sınıf ismini belirtir. En genel dialog kaynağı elemanı "CONTROL" diye isimlendirilen elemandır. Çünkü bu eleman içerisinde yaratuılacak kontrolün sınıfı belirtilebilir. Böylece PUSHBUTTON, EDITTEXT, STATIC gibi ayrı anahtar sözcükler kullanmak yerine bu kontrollerin hepsi sınıf ismi belirtilerek CONTROL anahtar öszcüğüyle belirtilebilir. Yani dialog elemanı olarak PUSHBUTTON yazmak yerine CONTROL yazarak button biçiminde sınıf ismi belirtmek aynı anlama gelir.
CONTROL Elemanının Syntax'i
CONTROL "başlık yazısı",
alt_pencere_id_değeri,
"sınıf ismi",
pencere biçimi,
x,
y,
genişlik,
yükseklik
Örneğin aşağıdaki iki kontrol elemanı eşdeğer anlamdadır:
PUSHBUTTON "Edit Ok",
IB_PBOK,
75,
32,
50,
14,
WS_CHILD | WS_VISIBLE
CONTROL "Edit Ok",
ID_PBOK,
"button",
WS_CHILD | WS_VISIBLE,
75,
32,
50,
14
Standart Dialog Pencereleri
Her Windows programında gördüğümüz Open, Save, Find-Replace, Color, Font, Print gibi dialog pencereleri tek bir API fonksiyonuyla oluşturulabilen standart dialog pencereleridir.
Open ve Save Dialog Pencereleri
Open ve Save dialog pencereleri hemen hemen birbirlerinin aynısıdır. Open penceresi dosya açmak, Save penceresi saklamak amacıyla kullanılır. Open dialog penceresi GetOpenFileName API fonksiypnuyla, Save dialog penceresi ise GetSaveFileName API fonksiyonuyla yaratılır. Bu dialog pencereleri ilgili API fonksiyonlarıyla yaratıldıktan sonra kullanıcı bir dosya seçerek OK tuşuna basar(Dosya ismi klavyeyle de yazılabilir). Böylece fonksiyon sonlanır. Programcı seçilen dosyanın ismini elde eder. Bu iki API fonksiyonu da OPENFILENAME isimli bir yapı değişkeninin adresini alır. OPENFILENAME yapısının elemanları genel olarak ikisi dışında bu dialog penceresini konfigüre etmek amacıyla kullanılır. Ancak iki eleman bu API fonksiyonları tarafından doldurulur. Seçilmiş dosyanın isim ve path ifadesi bu iki elemana yazılır. Bu API fonksiyonlarının geri dönüş değerleri FALSE ise kullanıcı dialog penceresini cancel ile kapatmıştır, TRUE ise ok ile kapatmıştır. OPENFILENAME yapısının lpstrFilter elamanı Open ve Save pencerelerinde çıkan filtreleme bilgilerini barındırır. Her filtre elemanı iki parçadan oluşur. Birinci parça yazı parçasıdır, ikinci parça filtreleme bilgisini tutar.
Pencere Başlık Yazısının Değiştirilmesi
Pencere başlığı üzerinde işlem yapan iki fonksiyon vardır: GetWindowText ve SetWindowText. SetWindowText fonksiyonu CreateWindow fonksiyonunun ikinci parametresiyle belirtilen pencere başlığı yazısının değiştirilmesini sağlar.
BOOL SetWindowText(HWND hWnd, LPCSTR lpString);
Bu fonksiyon pushbutton penceresine uygulanırsa düğme üzerindeki yazının değiştirilmesine, editbox penceresine uygulanırsa editbox içerisindeki yazının değiştirilmesine, static penceresine uygulanırsa static pencere yazısının değiştirilmesine yol açar.
GetWindowText fonksiyonu bunun tam tersi olarak pencere başlık yazısının elde edilmesinde kullanılır.
BOOL GetWindowText(HWND hWnd, LPSTR lpString, int nMaxCount);
Bu fonksiyonlara ek olarak üst pencerenin handle değeri ve alt pencerenin ID değerleri bilindiğinde alt pencerenin pencere başlığı üzerinde işlem yapan SetDlgItemText ve GetDlgItemText fonksiyonları da vardır. Tabii bu fonksiyonlar kendi içlerinde GetDlgItem ve SetWindowText, GetWindowText fonksiylarını kullanarak işlem yaparlar.
OPENFILENAME Yapısının Önemli Elemanları
Bu yapının ilk elemanı olan lStructSize elemanı yapının byte uzunluğunu tutmalıdır. hwndOwner elemanı standart dialog penceresini açan üst pencerenin handle değerini tutar. hInstance elemanına programın hInstance değeri girilmelidir. Bu yapının en önemli elemanlarından birisi lpstrFilter elemanıdır. Bu eleman birden fazla yazıyı tutan bir gösterici biçimindedir. C'de char türünden bir göstericinin birden fazla yazıyı tutabilmesi şöyle mümkün olabilir: Her yazının sonu NULL karakter ile bitirilir, tüm yazının bittiği ise iki NULL karakter kullanılarak anlatılır. lpstrFilter elemanı filtre yazılarını tutar. Her filtre elemanı iki yazıdan oluşmaktadır. Bu yazının ilki filtreleme sırasında çıkacak yazıyı belirtit ve filtreleme üzerinde bir etkisi yoktur. İkinci yazı filtrelemeye ilişkin yazıdır. Birden fazla filtreleme için ; koymak gerekir. Örnek:
x.lpstrFilter = "Executable Files\0*.exe;*.com\0Text Files\*.txt\0";
Seçim yapıldıktan sonra elde edilen dosya ismi bu API fonksiyonları tarafından lpstrFileTitle elemanının gösterdiği adrese yerleştirilir. Yani fonksiyon çağırılmadan önce bu elemanın tahsis edilmiş bir diziyi göstermesi gerekir. nMaxFileTitle elemanı ise bu dizinin en büyük uzunluğunu belirtmektedir. Bu eleman dosya ismini path ifadesi olmadan yalnızca isim biçiminde tutar. Tabii dosyanın uzantısı da alınmaktadır. Yapının lpstrInitialDir elemanı dialog penceresinin default hangi dizin için açılacağını belirtir. Seçilen dosyanın ismi tüm path bilgisiyle birlikte lpstrFile elemanının gösterdiği adrese yerleştirilir. Bu elemanın gösterdiği yazının maksimum elemanın nMaxFile elemanında saklanmalıdır.
Örnek Programın Açıklaması
Örnek programda bir menü alınmış, menüye Open, Save ve Exit elemanları yerleştirilmiştir. Sembolik sabitleri ID_FILE_OPENX, ID_FILE_SAVEX, ID_FILE_EXITX.
Checkbox ve Radiobutton Kontrolleri
Checkbox küçük bir kare ve yanında yazı bulunan bitr konroldür. Genellikle dialog pencerelerinin içerisinde kullanılır. Üzerine click yapıldığında içi çarpılanır. Genellikle bir özelliği dahil etmek ya da kaldırmak amacıyla kullanılır. Bu kontrolden elde edilecek bilgi kontrolün çarpılanıp çarpılanmadığıdır. Checkbox kontrolü CreateWindow fonksiyonunda "button" sınıf ismi girilerek yaratılır. Ancak pencere biçimi olarak BS_CHECKBOX girilir.
hCheckBox = CreateWindow("button", "Example", WS_CHILD | WS_VISIBLE | BS_CHEKBOX.....);
CreateWindow fonksiyonunun ikinci parametresinde belirtilen pencere başlık yazısı kare görüntüsünün yanında çıkar. Checkbox'lar otomatik, normal ve üç konumlu omlak üzere üçe ayrılırlar. Checkbox kontrolüne çarpı koyabilmek için BM_SETCHECK mesajı gönderilir. Bu mesajda lParam 0 alınır, wParam 1 ya da 0 olabilir. 1 çarpılama oluşacağını, 0 oluşmayacağını belirtir.
SendMessage(hCheckBox, BM_SETCHECKBOX, 1, 0);
Checkbox kontrolünün çarpılanıp çarpılanmadığını BM_GETCHECK mesajı ile elde edebiliriz. Bu mesajda wParam ve lParam 0 girilir. Fonksiyonun geri dönüş değeri 0 ya da 1 olur. 1 çarpılı, 0 çarpısız anlamına gelir.
status = SendMessage(jhCheckBox, BM_GETCHECK, 0, 0);
Checkbox kontrolü üzerine click yapıldığında tıpkı pushbutton kontrolünde olduğu gibi checkbox kontrolü WM_COMMAND mesajı ile BN_CLICKED durum kodu ile çağırılır. Normal checkbox'larda kontrolün üzerine click yapıldığında çarpılama işlemi kontrolün kendisi tarafından değil, gönderilen mesaj ile programcı tarafından yapılır. Normal checkbox'larda çarpılama işlemi aşağıdaki gibi yapılabilir.
case WM_COMMAND:
switch(LOWORD(wPAram)) {
case ID_CHECK:
if (HIWORD(wParam) == BN_CLICKED) {
if (SendMessage(hCheckBox, BM_GETCHECK, 0, 0))
SendMessage(hCheckBox, BM_SETCHECK, 0, 0);
else
SendMessage(hCheckBox, BM_SETCHECK, 1, 0);
}
break;
}
Oysa otomatik checkbox kontrolünde çarpılama işlemi kontrolünden kendisi tarafından zaten yapılmaktadır. Otomatik checkbox yaratabilmek için BS_CHECKBOX yerine BS_AUTOCHECKBOX pencere biçimi kullanılır. Checkbox genellikle dialog pencerelerinde kullanılır. Dialog penceresi ilk görüntülendiğinde çarpılanmış bir görüntü elde edebilmek için WM_INITDIALOG mesajında çarpılama işleminin bilinçli bir şekilde yapılması gerekir.
case WM_INITDIALOG:
SendDlgItemMessage(hWnd, ID_CHECK, BM_SETCHECK, 1, 0);
return TRUE;
Üç konumlu checkbox kontrolünde checkbox'ın 3 durumu vardır: Çarpılı, çarpısız ve etkin omayan durum. Bu chackbox kontrolü pencere biçimine ek olarak BS_3STATE eklenerek yaratılırlar. Otomatik ve üç konumlu chackbox kontrolleri Win32 sistemleriyle eklenmiştir.
Radiobutton kontrolü dairesel bir şeklin içerisine noktasal bir şeklin yerleştirilmesine olanak sağlar. Genellikle bir grup radiobutton kontrolü biçiminde kullanılır. Genellikle radiobutton kontrollerinin bir anda yalnızca bir tanesi seçili olacak biçimde kullanılır. Uygulamada birden fazla seçenekten yalnızca bir tanesinin seçilmesi gerektiği durumlarda kullanılır. Checkbox kontrolleri tek başlarına kullanılabilir, ancak radiobutton kontrolleri grup olarak kullanılmalıdır. Bu kontrolün yanında tıpkı checkbox'ta olduğu gibi betimleyici bir yazı vardır. Bir grup içerisindeki bütün radiobutton kontrolleri tek tek CreateWindow fonksiyonuyla yaratılmalıdır. Radiobutton kontrolleri normal ve otomatik olmak üzere iki kısma ayrılırlar. Otomatik radiobutton kontrolleri Win32 sistemleriyle tanımlanmıştır. Bu kontrolü yaratabilmek için CreateWindow fonksiyonu içerisinde sınıf ismi olarak "button" girmek, pencere biçimi olarak BS_RADIOBUTTON ya da BS_AUTORADIOBUTTON vermek gerekir. Pencere başlık yazısı betimleyici yazı olarak görünür. Bu kontrol ana pencerenin içerisine gömülmek yerine genellikle dialog pencerelerinde kullanılır. Radiobutton kontrolünün üzerine click yapıldığında kontrol üst pencereye BN_CLICKED durum koduyla mesaj gönderir. Radiobutton kontrolünü çarpılamak için checkbox'ta yapıldığı gibi BM_SETCHECK mesajı gönderilir.
SendMessage(hRadioButton, BM_SETCHECK, 1, 0);
Bu kontrolün çarpılanıp çarpılanmadığı BM_GETCHECK mesajı ile sorgulanabilir.
status = SendMessage(hRadioButton, BM_GETCHECK, 0, 0);
Normal radiobutton'larda bir grup kontrolden bir tanesine click yapıldığında click yapılanı çarpılamak, çarpılı olanı silmek programcının görevidir. Autoradiobutton yalnızca dialog pencerelerinde kullanılır. Bunun için bir radiobutton grubu tanımlanması gerekir. Bu grup kaynak içerisinde tanımlanabilmektedir. Autoradiobutton grubunda bir kontrole click yapıldığında click yapılan otomatik olarak çarpılanır. Çarpılanmış olan da otomatik olarak silinir. Normal radiobutton'larda çarpılama işlemi birkaç biçimde yapılabilir:
case WM_COMMAND:
switch(LOWORD(wParam)) {
case ID_RADIO1:
......
......
......
case ID_RADIO2:
......
......
......
case ID_RADIO3:
......
......
......
}
break;
yada daha sistematik bir yöntem kullanılabilir:
struct CBOX {
WORD id;
int status;
} x[4] = {{ID_RADIO1, 0}, {ID_RADIO2, 1}, {ID_RADIO3, 0}, {ID_RADIO4, 0}};
switch (LOWORD(wParam)) {
case ID_RADIO1:
case ID_RADIO2:
case ID_RADIO3:
case ID_RADIO4:
ProcessCheck(LOWORD(wParam));
break;
}
void ProcessCheck(WORD id)
{
int i;
HWND hRadio;
for (i = 0; i < 4; ++i) {
if(x[i].status == 1) {
x[i].status = 0;
hRadio = GetDlgItem(hWnd, x[i].id);
SendMessage(hRadio, BM_SETCHECK, 0, 0);
}
if (x[i].id == id) {
x[i].status = 1;
hRadio = GetDlgItem(hWnd, id);
SendMessage(hRadio, BM_SETCHECK, 1, 0);
}
}
}
Örnek programda iki radiobutton dialogh penceresi üzerine çıkartılmış ve durumları elde edilmiştir. İster auto, ister normal olsun bir grup içerisindeki hangi radiobutton'ın çarpılanmış olduğunu anlamak için bir sorgulama yapılması gerekir. En pratik yöntem hepsine BM_GETCHECK mesajı göndermek ve hangisi çarpılanmışsa onu tespit etmek olabilir.
Autoradiobutton'larda radiobutton grubunun tespit edilmesi WS_GROUP pencere biçimiyle yapılmaktadır. Dialog kaynağında radiobutton kontrolü RADIOBUTTON ismiyle yaratılır. Kaynakta ilk WS_GROUP pencere biçimine sahip radiobutton grubun ilk elemanı kabul edilir. Bundan sonraki radiobutton kontrolleri grup içerisindedir. Bir sonraki WS_GROUP pencere biçimli radiobutton kontrolüne kadarki tüm radiobutton kontrolleri bu grup içinde kabul edilir.
Çizim İşlemleri
Bir pencerenin üzerine doığrudan yazısal ya da grafiksel çizimler yapılabilir. Pencerenin üzerine yazı yazma işlemi de bir çeşit çizim işlemi olarak değerlendirilir. Aslında static kontrolünü kullanarak yazı yazılması durumunda da bir çeşit çizim işlemi yapılmaktadır. Ancak burada çizim işlemini static kontrolünün pencere fonksiyonu yapar. Oysa bir yazı yazmak için bir alt pencerenin tanımlanması gerekmez. Bir pencere içerisinde yapılan çizim Windows tarafından otomatik olarak saklanmaz. Örneğin bir pencere başka bir pencere tarafından kapatıldığında Windows kapatılan pencere içerisindeki çizim görüntüsünü saklamaz. Bu görüntünün, yani bütün pencere çizimlerinin yeniden programcı tarafından yapılması gerekir. Windows bir pencerenin bir bölümü herhangi bir biçiminde kapatıldığında ya da açıldığında o pencere için mesaj kuyruğuna WM_PAINT mesajını bırakır. Bu durumda pencere içerisindeki bütün mesajlar WM_PAINT mesajı içerisinde yapılmalıdır. WM_PAINT mesajı şu durumlarda gönderilir:
1. Pencerenin boyutları değiştirildiğinde,
2. Bir pencereyi kaplamış olan başka bir pencerenin çekilmesi durumunda,
WM_PAINT şu durumlarda gönderilmez:
1. Bir menünün açılmasıyla ve kapatılmasıyla oluşan durumda Windows pencere görüntüsünü otomatik olarak saklar, WM_PAINT mesajını göndermez.
2. Bir pencere taşındığında Windows pencere görüntüsünü otomatik olarak oluşturur.
Pencere Çizimleri Nerede Yapılamalıdır?
Windows pencere çizim görüntülerini otomatik olarak saklamadığı için pencere görüntüsünün her değişiminde yani her WM_PAINT mesajı geldiğinde bütün çizimler yeniden yapılmalıdır. Bu durumda bütün çizimler WM_PAINT mesajı içerisinde yapılmalıdır. Eğer çizimler başka yerde yapılırsa pencere görüntüsünün değişiminde başka yerde yapılan çizimler kaybolur.
Pencere Güncelleme Alanı(Window Update Region)
Bir pencerenin yeniden çizilmesi gereken en küçük dikdörtgensel alanına güncelleme alanı denir. Güncelleme alanı dikdörtgensel olmak zorundadır. Pencere
içerisindeki görüntüyü yeniden oluşturmak için yalnızca güncelleme alanı içerisindeki bölümün yeniden çizilmesi yeterlidir. Üçgensel bir bölge ya da dairesel bir bölge güncelleme alanı olamaz. Windows bu bölgelere ilişkin en küçük dikdörtgeni hesap ederek güncelleme alanı yapar. Windows her pencere için bir günceleme alanının koordinat bilgisini tutmaktadır. Tabii Windows her pencere için tek pencere alanı tutar ve bunu sürekli güncelleştirir. Örneğin bir pencerenin boyutu değiştirildiğinde güncelleme alanı tüm pencere olur. Bir pencerenin güncelleme alanının koordinat bilgisi bir API fonksiyonu yardımıyla sistemden alınabilir. Güncelleme alanı boş küme ise pencerenin hiçbir bölgesinin yeniden çizilmesine gerek yoktur. Güncelleme alanının boş küme yapılması Windows tarafından otomatik bir biçimde yapılmaz. Windows güncelleme alanını gerektiğinde güncelleştirir. Örneğin daraltp genişletebilir. Ancak boş küme yapmaz. Windows bütün pencerelerin güncelleme alanlarına periyodik olarak bakar, güncelleme alanı boş küme olmayan bir pencere alanıyla karşılaştığında ona WM_PAINT mesajını gönderir. Bu mesajı göndermesiyle kendisi güncelleme alanını ototatik olarak boş küme yapmaz. Güncelleme alanı programcı tarafından boş küme yapılmalıdır. Güncelleme alanının boş küme yapılması için en iyi yer WM_PAINT mesajının içidir. Yani sonuç olarak eğer WM_PAINT içerisinde güncelleme alanı boş küme yapılmazsa pencereye sürekli WM_PAINT mesajı gönderilir, program da sürekli ,çiizm yapan bir biçimde çalışır ve performans kaybı olur(Aslında Windows yeni güncelleme alanı eski güncelleme alanının içerisinde kalıyorsa güncelleme alanını güncellemez).
Çizim İşleminin Bileşenler
Çizim kavramı pencere içerisine yerleştirilen tüm görüntüleri içerir ve çok fazla bileşeni vardır. Örneğin bir çizimin şekil çizgileri birkaç bileşene sahip olan kalemle yapılır. Bir kalem çizginin biçimi, rengi, kalınlığı ile karakterize edilir. En basit bir çizim için bile bir kaleme sahip olmak gerekir ve çizimin bu kalemle yapılacağını sisteme açıklamak gerekir. Diğer bir biçim bileşeni fırçadır. Bir bölgenin içi boyanacaksa bir fırçayla boyanır. Bir fırçayı fırçanın rengi ve deseni karakterize eder. Bir yazı yazılacaksa karakterlerin görüntüsünü belirten bir font oluşturmak gerekir. Font bir yazıdaki bütün karakterlerin görüntü biçimlerini tanımlayan bir kavramdır.
Çizim için Handle Alınması(Display Context)
Bir çizim işlemi için öncelikle bir handle elde edilmesi gerekir. Handle özel API fonksiyonlarıyla elde edilebilir. Bir handle'ın elde edilmesi sırasında Windows çizim için gereken bütün bilşenleri bir veri yapısı biçiminde dinamik olarak oluşturur ve çizim bileşenlerine default bazı değerleri yerleştirir. Bu API fonksiyonları handle olarak veri yapısının başlangıç adresini verirler. Çizim için alınan handle değerinin typedef ismi HDC biçimindedir(HDC'nin gerçek türü void *'dır). Alınan bu handle değeri çizim ile ilgili bütün API fonksiyonlarına parametre olarak geçirilir. Böylece bu API fonksiyonları çizim bileşenlerinin neler olduğunu anlayabilir. Çizim için handle değeri WM_PAINT mesajının başında alınmalı sonunda geri bırakılmalıdır. Çünkü bu handle için tahsis edilen alan dinamik bir alandır ve bütün çalışan programlar için ortak bir alan söz konusudur. Bir programda alınmış olan bir handle'ın bırakılmaması diğer bir program için özellikle Win3.x sistemlerinde bir kaynak azalması yaratır. 32 bit Windows sistemlerinde GDI modülünün heap alanı çok geniş olduğu için alınan handle'ın bırakılmaması sebebiyle ciddi bir problem çıkmayabilir. Çizim için handle alan üç tür API fonksiyonu vardır: BeginPaint, GetDC, GetWindowDC. Eğer handle BeginPaint fonksiyonuyla alınırsa EndPaint fonksiyonuyla bırakılmalıdır. GetDC veya GetWindowDC fonksiyonlarıyla alınmış ise ReleaseDC fonksiyonuyla bırakılmalıdır. BeginPaint fonksiyonu çizim handle'ını almasının yanı sıra güncelleme alanını boş küme yapar. Oysa GetDC güncelleme alanını boş küme yapmaz. Bu durumda WM_PAINT içerisinde BeginPaint API fonksiyonula handle almak daha doğrudur ve uygulamada hep böyle yapılır. Örneğin tipik bir WM_PAINT mesajı şöyle işlenmelidir:
case WM_PAINT:
hDC = BeginPaint(..............);
...........
...........
...........
EndPaint(............);
break;
Çizim yapan bir API fonksiyonuna BeginPaint fonksiyonuyla alınan bir handle değeri geçirilirse bu API fonksiyonu hiçbir zaman güncelleme alanının dışına çizim yapamaz. Yani çizim parametreleri tüm pencereye ilişkin olsa bile yalnızca güncelleme alanı içerisindeki bölgeyi çizerler. Aslında bu çalışma biçimi gereksiz çizim yapılmaması için tasarlanmıştır. Ancak tabii güncelleme alanının tamamen dışında kalan çizimler için bile API fonksiyonlarının içine girilir(Halbuki bu fonksiyonlar reel olarak hiçbir çizim yapamayacaklardır), böylece yine gereksiz bir zaman kaybı oluşabilecektir. Bu zaman kaybının da oluşmaması isteniyorsa WM_PAINT içerisinde önce güncelleme alanına bakılmalı, program akışının gereksiz çizimlere girmemesi sağlanmalıdır. Ancak çizim yapan API fonksiyonlarına GetDC ile alınan handle geçirilirse bu API fonksiyonları çalışma alanının her tarafına çizim yapabilirler. BeginPaint ve GetDC fonksiyonlarıyla alınan handle için orijin noktası çalışma alanının sol üst köşesidir. Yani bu handle'lar ile ancak çalışma alanı içerisine çizim yapılabilir. Ancak GetWindowDC ile alınan handle için orijin noktası pencere başlığının sol üst köşesidir. Yani pencerenin her yerine çizim yapılabilir.
BeginPaint ve EndPaint Fonksiyonları
Prototipleri:
HDC BeginPaint(HWND, hWnd, LPPAINTSTRUCT lpPaint);
BeginPaint fonksiyonunun birinci parametresi çizim için alınacak handle'a ilişkin pencerenin handle değeridir. İkinci parametre PAINTSTRUCT türünden yapının adresini alır. BeginPaint bu yapının içerisini faydalı bilgilerle doldurur. Fonksiyonun geri dönüş değeri çizim işlemi için elde edilen handle bilgisidir.
BOOL EndPaint(HWND hWnd, CONST PAINTSTRUCT *lpPaint);
Alınan handle değerinin geri bırakılması BeginPaint fonksiyonundaki parametrelerle yapılır.
Tipik bir WM_PAINT mesajı şöyle işlenir:
HDC hDC;
PAINTSTRUCT pc;
....
....
case WM_PAINT:
hDC = BeginPaint(hWnd, &pc);
....
....
....
EndPaint(hWnd, &pc);
Çizim İşlemlerinde Renk
Windows'daki bütün renkler kırmızı, yeşil ve mavinin bileşiminden elde edilir. Burada bu üç rengin herbiri 0-255 arasında bir ton ile ifade edilir ve bunların karışımından da bir renk elde edilir(RGB bilgileri video kartına bağlı olarak 8 bitten daha az olabilir. Ancak Windows bunları uygun bir biçimde oranlamaktadır). Örneğin R=0, G=0, B=255 değerleri kullanıldığında tam bir mavi renk elde edilir. Toplam elde edilebilecek renk sayısı 8 * 8 * 8 biçimindedir. (24 bit True Color mode RGB tonlarının herbirinin 1'er byte ile ifade edildiğini anlatır).
API fonksiyonları bir renk bilgisini parametre olarak alacağı zaman bunu ayrı ayrı RGB parametreleri biçiminde değil tek bir long sayı biçiminde alırlar. Windows.h içerisinde şöyle bir typedef vardır:
typedef long COLORREF;
Bütün API fonksiyonları RGB tonlarını bir long sayı içerisinde aşağıdaki gibi beklerler:
Ayrı ayrı parametrelerle verilmiş olan RGB bilgilerini long bir sayı biçimine dönüştüren bir makro windows.h içerisinde aşağıdaki gibi tanımlanmıştır:
#define RGB(r, g ,b) ((COLORREF)(((BYTE)(r)|((WORD)(g)<<8))|(((DWORD)(BYTE)(b)<<16)))
Yani sonuç olarak bir API fonksiyonu renk bilgisini COLORREF parametresiyle ister, biz de onu RGB makrosuyla veririz.
SetPixel Fonksiyonu
Bu fonksiyon ekrana bir tek pixel basar. Prototipi:
COLORREF SexPixel(HDC hDC, int X, int Y, COLORREF crColor);
Icon İşlemleri
.ico dosyas formatına sahip içerisinde görüntü tek tek pixel renkleriyle kodlanmış bir biçimde bulunan grafiksel görüntülere icon denir. Icon görüntülenme işlemleri Windows tarafından doğrudan desteklenmektedir. Win95'e karar yalnızca 32x32'lik icon görüntüleri kullanılıyordu. Daha sonra Win sistemleri 16x16'lık icon görüntülerini de destekler hale gelmiştir. Windows içerisinde görev çubuğunda, masa üstünde, pencerenin sol üst köşesinde icon görüntüleri kullanılır. Bunun dışında programcının istediği yerde herhangi bir icon görüntülenebilir.
Programın Ana Icon'ının Oluşturulması
Genel olarak bir icon şu adımlardan geçilerek kullanılır:
1. Icon özel editörlerle çizilir ve ico uzantısıyla diskte saklanır.
2. Icon'ın kaynak dosyasında aşağıdaki gibi belirtilmesi gerekir:
Kaynak_ismi Icon Dosya_ismi
Örnek: MYICON ICON X.ICO
3. LoadIcon API fonksiyonuyla icon kaynağı kullanıma hazır hale getirilir. Prototipi:
HICON LoadIcon(HINSTANCE hInstance, LPCTSTR lpIconName);
Bir programın ana icon'ını belirleme işlemi WNDCLASS yapısının hIcon elemanına LoadIcon fonksiyonundan alınan handle değerinin yerleştirilmesiyle yapılır. Ancak Windows'da çalışmayı kolaylaştırmak amacıyla önceden tanımlanmış özel icon handle değerleri vardır. Bunların kullanılması için kaynak içinde belirtilmesine gerek yoktur.
Bir pencerenin parametrik bilgileri üzerinde işlem yapan GetWindowLong, SetWindowLong fonksiyonlarının yanı sıra bir pencerenin yaratıldığı WNDCLASS yapısının elemanları üzerinde işlem yapan GetClassLong ve SetClassLong fonksiyonları da vardır. SetClassLong fonksiyonunu kullanarak WNDCLASS yapısının hIcon elemanı istenildiği zaman değiştirilebilir. Böylece herhangi bir zaman ana pencerenin icon'ı değiştirilmiş olur. Örneğin mouse'un sol tuşuna basıldığında icon şöyle değiştirilebilir:
case WM_LBUTTONDOWN:
SetClassLong(hWnd,
GCL_HICON,
(LONG)LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON2));
break;
Pencere İçerisine Icon Çizilmesi
Bir pencere içerisine icon çizebilmek için icon'ın kaynakta belirtilip LoadIcon ile yüklenmesi gerekir. LoadIcon ile yükleme işlemi örneğin WM_CREATE mesajı içerisinde yapılabilir. Ancak çizim işleminin WM_PAINT mesajı içerisinde DrawIcon fonksiyonuyla yapılması gerekir. Prototipi:
BOOL DrawIcon(HDC hDC, int X, int Y, HICON hIcon);
Burada x, y koordinatları icon'ın çizileceği bölgenin sol üst köşebnin koordinatlarıdır.
Programın Çalışma Zamanı Sırasında Herhangi Bir Icon'ın Görüntülenmesi
LoadIcon fonksiyonu ile HICON elde edebilmek için icon dosyasının kaynakta belirtilmiş olması gerekir. Halbuki kaynağa yazılma işlemi programın çalışmasından önce yapılmaktadır. Bir ico dosyasının ismini vererek hiç kaynakta belirtmeden HICON elde edebilmek için Loadwindows-api API fonksiyonu kullanılmaktadır. Loadwindows-api şömyle kullanılır:
HIcon = (HICON)Loadwindows-api(NULL,
Filename,
windows-api_ICON,
0,
0,
LR_DEFAULTSIZE | LR_LOADFROMFILE);
UpdateWindow fonksiyonunun yaptığı tek şey SendMessage ile pencereye WM_PAINT mesajını göndermektir.
Güncelleme alanının değiştirilmesi
InvalidateRect fonksiyonuyla bir pencerenin güncelleme alanı istenilen dikdörtgensel bölge haline getirilebilir. Güncelleme alanı boş küme olmaktan çıkartılırsa kısa bir süre sonra Windows pencereye WM_PAINT mesajnı gönderir.
BOOL InvalidateRect(HWND hWnd, CONST RECT *lRect, BOOL hErase);
Fonksiyonun birinci parametresi güncelleme alanının değiştirileceği handle değeridir. İkinci parametre güncelleme alanının koodinatlarını tutan yapının başlangıç adresidir. Eğer bu parametre NULL olarak girilirse güncelleme alanı çalışma alanı olarak alınır. Üçüncü parametre TRUE ya da FALSE olabilir. TRUE ise pencerenin zemini yeniden boyanır, FALSE ise boyanmaz.
Çizim Bileşenlerinin Belirlenmesi ve Kaldırılması
Bir çizimde kullanılacak kalem, fırça gibi çizim bileşenleri için bir seçme işleminin yapılması gerekir. Bütün çizim bileşenleri diğerinden bağımsız ayrı bir çizim nesnesidir. Yani bütün çizim bileşenlerinin önce yaratılması gerekir. Örneğin bir kalem yaratmak için CreatePen, bir fırça yaratmak için CreateBrushIndirect gibi API fonksiyonları vardır. Bu fonksiyonlar çizim nesnelerine ilişkin handle değerini geri dönüş değeri olarak verirler. Örneğin:
HPEN hPen;
HBRUSH hBrush;
hPen = CreatePen(........);
hBrush = CreateBrushIndirect(.........);
Çizim nesnesi yaratan API fonksiyonlarının geri dönüş değerleri HPEN, HBRUSH, HBITMAP gibi olsa da bunların hepsinin gerçek türü void * biçimindedir. Bir çizim nesnesi yaratıldıktan sonra kullanılması için bir çizim handle'ı ile ilişkilendirilmesi gerekir. Bu ilişkilendirme işlemi SelectObject API fonksiyonuyla yapılır.
HGDIOBJ SelectObject(HDC hDC, HGDIOBJ hGdiObj);
Fonksiyonun birinci parametresi çizim nesnesinin seçildiği DC bilgisidir. Fonksiyonun ikinci parametresi seçilecek çizim nesnesinin handle değeridir. İkinci parametre olarak HPEN, HBRUSH herhangi bir çizim nesnesine ilişkin bir handle girilebilir. Fonksiyonun geri dönüş değeri daha önce seçili olan çizim nesnesinin handle değeridir. SelectObject ile yapılan şey DC veri yapısının ilgili elemanının çizim nesnesinin handle değeriyle doldurulmasıdır. Özetle çizim işlemlerinde şunlar olmaktadır:
1. Çizim için bir DC nesnesi yaratılır. Yaratıldığında çizim nesneleri için default değerler kullanılmaktadır.
2. Çizim nesneleri CreateXXX fonksiyonlarıyla yaratılarak handle değerleri ede edilir.
3. SelectObject API fonksiyonu ile DC nesnesiyle çizim nesnesi ilişkilendirilir.
Temel Çizim Fonksiyonları
Bir doğru çizebilmek için iki noktaya gereksinim vardır. Noktalardan ilki MoveToEx fonksiyonuyla tespit edilir, daha sonra LineTo fonkiyonuyla diğer nokta belirlenerek çizim yapılır. Windows'da pek çok çizim işleminde güncel nokta kavramı kullanılır. Güncel nokta MoveToEx fonksiyonuyla değiştirilir. Örneğin LineTo fonksiyonu güncel noktadan belirtilen noktaya kadar çizim yapar. LineTo fonksiyonu güncel noktayı doğrunun son noktası olarak günceller.
MoveToEx(...);
LineTo(...);
LineTo(...);
BOOL MoveToEx(HDC hDC, int x, int y, LPPOINT lppoint);
BOOL LineTo(HDC hDC, int x, int y);
LineTo fonksiyonu çizgiyi DC nesnesiyle belirtilen kalemle çizer. Yani örneğin farklı bir renkte, farklı bir kalınlıkta, farklı bir biçimde çizebilmek için yeni bir kalem yaratmak gerekir.
Çizim Nesneleri Ne Zaman Yaratılmalı?
Çizim nesnelerinin her defasında WM_PAINT mesajı içinde yaratılmasına gerek yoktur. Örneğin programında başında WM_CREATE mesajı içerisinde kalemler ve fırçalar yaratılabilir. Ancak çizim nesnesinin SelectObject ile seçilme işlemi DC nesnesi yaratıldıktan sonra Yani WM_PAINT mesjaı içeirisinde yapılmalıdır. Çizim nesnelerini geri bırakmak için DeleteObject API fonksiyonunu kullanmak gerekir.
BOOL DeleteObject(HGDIOBJ hObject);
Çizim nesneleri boşaltılmazsa program bittiğinde işletim sistemi tarafından boşaltılır(WM_DESTROY mesajı içerisinde boşaltmanın önemli bir anlamı yoktur).
Önceden Tanımlanmış Çizim Nesneleri
Windows'da bazı çizim nesneleri sistem yüklendiğinde otomatik olarak zaten sistem tarafından oluşturulur. Böylelikle klasik bazı çizimler için çizim nesnesi yaratmaya gerek kalmaz. Tabii bu çizim nesnelerinin SelctObject ile seçilmesi gerekir. Önceden tanımlanmış çizim nesnelerinin handle değerlerini alabilmek için GetStockObject API fonksiyonu kullanılır.
HGDIOBJ GetStockObject(int fn);
Fonksiyonun parametresi önceden tanımlanmış çizim nesnesini anlatan bir sayıdır. Bu sayı şunlardan biri olabilir:
BLACK_BRUSH
DKGRAY_BRUSH
GRAY_BRUSH
HOLLOW_BRUSH
LTGRAY_BRUSH
NULL_BRUSH
WHITE_BRUSH
BLACK_PEN
WHITE_PEN
NULL_PEN
Dikdörtgen Çizme
Dikdörtgen çizmek için Rectangle API fonksiyonu kullanılır.
BOOL Rectangle(HDC hDC, int x1, int y1, int x2, inty y2);
Bu fonksiyon sol üst ve sağ alt köşegeniyle belirtilen dikdörtgeni çizer. Dikdörtgenin çizgileri kalemle çizilir, içi fırçayla boyanır.
Fırça yaratabilmek için CreatBrushIndirect API fonksiyonu kullanılır.
HBRUSH CreateBrushIndirect(CONST LOGBRUSH *lplb);
Fonksiyon parametre olarak LOGBRUSH türünden bir yapı değişkeninin adresini alır.
Pencere Zemininin Silinmesi
BeginPaint fonksiyonu çizime ilişkin handle'ı almadan önce WM_ERASEBCGND mesajını SendMessage ile pencere yollar(GetWindowDC ve GetDC fonksiyonları bu mesajı yollamaz). WM_ERASEBKGND mesajı işlenmezse DefWindowProc tarafından işlenir. DefWindowProc WNDCLASS yapısının hbrBackground elemanı ile belirtilen fırça ile tüm pencere zeminini boyar. Bir pencerenin zemin rengini değiştirmek için tek yapılacak şey WNDCLASS yapısının hbrBackground elemanını başka bir fırça ile set etmektir. Bu eleman WNDCLASS register ettirilmeden önce set edilmelidir. Ya da SetClassLong ile GCL_HBRBACKGROUND parametresi kullanılarak herhangi bir yerde örneğin WM_CREATE mesajı içerisinde saet edilebilir.
WM_MOUSEMOVE Mesajı
Mouse pencere içerisinde dolaştırıldığında Window sürekli olarak WM_MOUSEMOVE mesajını kuyruğa bırakır. Böylelikle mouse hareketinin takip edilebilmesi sağlanır. Bu mesajın parametre bilgileri şunlardır:
wParam
Tuş bilgisi
LOWORD(lParam)
x
HIWORD(lParam)
y
Sürükle ve Bırak İşlemlerinin Gerçekleştirilmesi
En kolay yöntem WM_MOUSEMOVE mesajında mouse'un sol tuşuyna nasılı olup olmadığının tespit edilmesidir. Sürükleme işlemi şöyle anlaşılabilir:
case WM_MOUSEMOVE:
if (wParam & MK_LBUTTON) {
....
....
....
}
break;
Ancak sürükle ve bırak hareketi için kullanılan ve tavsiye edilen algoritma şudur:
static int flag = 0;
case WM_LBUTTONDOWN:
flag = 1;
break;
case WM_LBUTTONUP:
flag = 0;
break;
case WM_MOUSEMOVE:
if (flag) {
....
....
....
}
break;
Çizim İşleminin WM_PAINT Mesajının Dışında Başka Bir Yerde Yapılması
Çizim işlemi kalıcı bir biçimde yapılacaksa WM_PANT mesajı içerisinde yapılacaktır. Ancak bazı durumlarda başka mesajlarda da çizimin yapılması zorunlu olabilir. Örneğin sistemin aşırı yüklenmesi sonucunda çizim işleminin InvalidateRect sonrası belirsiz bir zamanda yapılacak olması gecikmeye yol açabilir. Klavyeden basılan tuş karşısında hemen çizim yapılması gerektiği bir durum buna örnek verilebilir. WM_PAINT mesajı dışında çizimin yapıldığı diğer bir örnek ekranda hızlı bir çizim işleminin görüntülenmek istendiği durumdur. Örneğin mouse'un sürüklenmesi ile içi boyalı dikdörtgensel bir alan çizilecek olsa ikide bir WM_PAINT mesajının gelmesi ve bütün pencere çizimlerinin yeniden yapılması görüntüde titremelere yol açabilir. Çizim işlemi başka bir mesajda yapılacaksa GetDC API fonksiyonuyla handle alınır, çizim bu handle ile yapılır. Aynı çizim WM_PAINT mesajı içerisinde kalıcı bir biçimde tekrar yapılmalıdır.
Çizim Görüntülerinin WM_PAINT Mesajı için Saklanması
Çizim işlemleri başka bir mesajla yapılıyorsa aynı işlemlerin görüntünün korunması amacıyla WM_PAINT mesajı içerisinde de yapılması gerekir. Bunun için yapılan çizim işlemlerinin bir veri yapısı içerisinde saklanması gerekebilir. Bu veri yapısı için bir yapı dizisi kullanılabilir. Tabii en iyi eknik bir bağlı liste yapısının kullanılmasıdır.
Koordinat Sistemleri
Koordinat sistemi DC nesnesinde belirtilen "mapping mode"a bağlıdır. Koordinat sistemi genellikle ya çalışılan pencerenin çalışma alanının sol üst köşesi olur ya da desktop'ın sol üst köşesi olur. Bir grup API fonksiyonunda koordinat sistemi eğer ana pencere söz konusuysa dektop'ın sol üst köşesi, alt pencere söz konusu ise çalışma alanının sol üst köşesidir. Örneğin CreateWindow ve MoveWindow API fonksiyonları böyle çalışır. Ancak bazı grup API fonksiyonlarında koordinat sistemi her zaman desktop'ın sol üst köşesidir. Örneğin GetWindowRect fonksiyonu bu biçimde çalışır. Nihayet bir grup API fonksiyonunda da koordinat sistemi her zaman çalışma alanının sol üst köşesidir.
GetWindowRect Fonksiyonu
Bu fonksiyon herhangi bir pencerenin koordinat bilgilerini elde etmekte kullanılır. Her zaman pixel birimiyle çalışır ve koordinat sistemi her zaman desktop'ın sol üst köşesine göredir. Prototipi:
BOOL GetWindowRect(HWND hWnd, LPRECT lpRect);
GetWindowRect fonksiyonunun orijin noktası bazı API fonksiyonlarıyla örtüşmeyebilir. Örneğin en tipik problem şudur: Bir alt pencere belli bir miktar öteleneceği durum.
Desktop orijinli bir noktanın herhangi bir pencerenin çalışma alanına göre koordinat dönüşümünün yapılabilmesi için üst pencerenin desktop'a göre konumunun da bilinmesi gerekir. Bu dönüştürmeyi otomatik olarak yapan iki API fonksiyonu vardır: ScreenToClient, ClientToScreen.Prototipler:
BOOL ScreenToClient(HWN hWnd, LPPOINT lpPoınt);
BOOL ClientToScreen(HWN hWnd, LPPOINT lpPoınt);
ScreenToClient fonksiyonunda fonksiyonun birinci parametresi pozisyonu belirlenecek pencerenin handle değeridir. İkinci parametre dönüştürülen koordinat bilgisidir.
ClientToScreen fonksiyonunun birinci parametresi ilgili pencerenin handle değeri, ikinci parametresidesktop'a göre yeni parametre bilgileridir. Fonksiyonların birinci parametreleri dönüşümü yapılmak istenen pencerenin handle değeridir. Örneğin bir pencere içerisindeki (8,10) koordinatları pencerenin çalışma alanına göre konum belirtsin. Biz bu noktayı desktop'a göre konum belirtecek hale dönüştürmek istiyorsak fonksiyonu şöyle kullanmalıyız:
struct tag POINT {
int x, y;
} POINT;
POINT x = {8, 10};
ClientToScreen(hWnd, &x);
Pencere başlığı söz konusu ise GetClientRect ile aluınan koordinat pencerenin sol üst köşesine ilişkindir.
Popup Menü İşlemleri
Herhangi bir yerde görüntülenebilen menülere popup menü denir. Programın ana menüsü aslında bir menü çubuğuna geçirilmiş bir grup popup menüden oluşmaktadır. Popup menünün bir elemanı başka bir popup menü olabilir. Popup menü elemanları aktif, pasif, gri ışıklı yapılabilir. Ya da herhangi bir menü elemanı "checked" yapılabilir. Popup menüler Windows programlarında genellikle mouse'un sağ tuşuna basıldığında görüntülenirler. Ya da genel olarak menü çubuğuna iliştirilmiş olarak bulunurlar. Popup menülerle çalışabilmek için önce işletim sisteminin popup menüsü için bir veri yapısı tahsis etmesi gerekir. Yani popup menüler normal bir handle mekanizmasındaki gibi çalışırlar. Bir popup menünün açılabilmesi için dört adımdan oluşan şu işlemlerin yapılması gerekir:
1. CreatePopupMenu API fonksiyonu ile popup menü için bir handle alınır.
2. AppendMenu ya da InsertMenu API fonksiyonlarıyla menüye eleman eklenir.
3. TrackPopupMenu API fonksiyonuyla popup menü görünür hale getirilir.
4. Menü için alınan handle(yani işletim sisteminin tahsis ettiği bölge) DestroyMenu API fonksiyonuyla sisteme iade edilebilir.
Genellikle CreatePopupMenu ve menüye eleman ekleme işlemleri WM_CREATE mesajında yalnızca bir kez yapılır. Bu durumda menü WM_DESTROY mesajında bırakılabilir(WM_DESTROY zaten programın bitmesi sırasında gönderildiğine göre bu işleme gerek olmayabilir). TrackPopupMenu fonksiyonu ise program içerisinde herhangi bir yerde, örneğin mouse'un sağ tuşuna basıldığında çağırılabilir.
Menülere Eleman Eklenmesi ve Insert Edilmesi
AppendMenu Fonksiyonunun Parametrik Yapısı
BOOL AppendMenu {
HMENU hMenu,
UINT uFlags,
UINT_PTR uIDNewItem,
LPCSTR lpNewItem
);
Fonksiyonun birinci parametresi popup menünün handle değeridir. İkinci parametre menü elemanı olarak eklenecek bilginin nasıl bir bilgi olduğunu belirtir. Girilecek parametre değerlerinden bazıları şunlardır:
MF_STRING
Menü elemanı olarak bir yazı görüntülenecektir.
MF_BITMAP
Menünün bir elemanı bitmap olabilir. Bunu yapabilmek için bitmap çizilir ve kaynak dosyada belirtilir. Daha sonra LoadBitmap fonksiyonuyla yüklenir ve elde edilen handle değeri fonksiyonun üçüncü parametresinde verilir.
MF_CHECKED
Menü elemanı yanında checked şekliyle yaratılır.
MF_DISABLED
Menü elemanı pasif hale gelir. Pasif duruma gelmiş menü elemanı seçilemez, yani seçilmeye çalışılsa mesaj göndermez. Ancak rengi gri değildir.
MF_GRAYED
Menü elemanı pasif hale getirilir, rengi de gri gözükür. Böyle oluşturulmuş menü elemanı disabled menü elemanını işlevsel olarak kapsar. Her ikisinde de menü elemanı seçilemez, ancak grayed'de aynı zamanda elena gri olarak gözükür.
AppendMenu fonksiyonunun üçüncü parametresi menü elemanı seçildiğinde WM_COMMAND mesajına gönderilecek menü ID değeridir. Eğer ikinci parametre MF_POPUP ise üçüncü parametre olarak popup menünün handle değeri girilmelidir. Dördüncü parametresi menü elemanı olarak görüntülencek yazıdır.
TrackPopupMenu Fonksiyonu
BOOL TrackPopupMenu {
HMENU hMenu,
UINT uFlags,
int x,
int y,
int nReserved,
HWND hWnd,
CONST RECT *prcRect
);
Fonksiyonun birinci parametresi görüntülenecek popup menünün handle değeridir. İkinci parametre popup menünün hizalanma biçimdir. Üçüncü ve dördüncü parametreler menünün görüntüleneceği koordinat bilgisini içerir. Ancak bu koordinat bilgisi desktop orijinlidir. ClientToScreen API fonksiyonuyla koordinat bilgisini dönüştürmek gerekebilir. Beşinci parametre kullanılmamaktadır, sıfır girilmektedir. Altıncı parametre popup menünün üzerinde görüntüleneceği pencereninin handle değeridir. Aslında bu parametre WM_COMMAND mesjaının gönderileceği pencere fonksiyonunu belirlemekte kullanılır. Fonksiyonun son parametresi kullanılmamaktadır, NULL girilmelidir.
Popup Menülerde Seçilme İşlemi
Popup menüde bir seçim yapıldığında tıpkı normal ana menü işlemlerinde olduğu gibi WM_COMMAND mesajı LOWORD(wParam) menü elemanının ID değeri olacak şekilde çağırılır. Menü elemanının "checked" ya da disabled durumlarının kontrolü otomatik olarak yapılmaz, bu mesajlarda yapılmalıdır.
/*-----popup.dsw------*/
InsertMenu Fonksiyonu
BOOL InsertMenu {
HMENU hMenu,
UINT uPosition,
UINT uFlags,
UINT_PTR uIDNewItem,
LPCSTR lpNewItem
);
Bu fonksiyon bir menü elemanını popup menünün herhangi bir yerine ekleyerek yerleştirir. Fonksiyonun birinci parametresi ekleme işleminin yapılacağı popup menü handle değeridir. İkinci parametresi insert edilecek pozisyon numarasıdır. Bu değer sıfırdan başlar. Bu değer -1(0xFFFFFFFF) ise sona ekleme işlemi yapılır. Üçüncü parametre insert işleminin hangi ölçüte göre yapılacağını belirtir. MF_BYPOSITION insert işleminin ilk eleman sıfır olmak üzere pozisyon numarasına göre yapılacağını belirtir. Dördüncü parametre menü ID değeri ya da popup menü handle değeridir. Son parametre menü yazısını belirtir.
Menü Elemanlarının "Checked" Yapılması
Menü elemanlarının checked ya da unchecked yapılması CheckMenuItem API fonksiyonuyla yapılır.
DWORD ChechMenuItem {
HMENU hMenu,
UINT uIDCheckItem,
UINT uCheck
);
Fonksiyonun birinci parametresi check işleminin yapılacağı popup menü elemanıdır. İkinci parametre check edilecek menü elemanının pozisyon numarası ya da ID değeridir. Check edilme işleminin neye göre yapılacağı üçüncü parametrede tespit edilir. Üçüncü parametre MF_CHECKED, MF_UNCHECKED biçiminde girilebilir. Bu değerlere ayrıca MF_BYPOSITION veya MF_BYCOMMAND | operatörüyle kombine edilebilir. Örneğin MF_CHECKED | MF_BYPOSITION. Default olarak bu parametre MF_BYCOMMAND seçilmiş gibi işlem yapılır. Fonksiyonun geri dönüş değeri menü elemanının önceki durumuna ilişkindir.Bir menü elemanının enable ve disable yapılması da benzer biçimde EnableMenuItem fonksiyonuyla yapılmaktadır.
Pulldown Menülerle Özel İşlemlerin Yapılması
Bir programın menüsüne ilişkin popup menü handle'ları elde edilebilir. Örneğin bir programın ana menüsü içerisindeki bir menü elemanının "checked" yapılabilmesi için o menü elemanının ilişkin olduğu popup pencerenin handle değeri gerekir. Bir pulldown menü sisteminin bir popup penceresinin handle değerini elde edebilmek için GetSubMenu API fonksiyonu kullanılır. Prototipi:
HMENU GetSubMenu (
HMENU hMenu,
int nPos
);
Fonksiyonun birinci parametresi pulldown menünün handle değeridir. Bu handle değeri menü LoadMenu ile yüklenirken elde edilen geri dönüş değeridir. İkinci parametre ilk popup menü sıfır olmak üzere menü sisteminin kaçıncı popup menüsünün handle'ının elde edileceğini bildirir. Fonksiyon pulldown menünün istenen popup menüsünün handle'ıyla geri döner.
Bir pulldown menü herhangi bir biçimde yüklenmiş ise o menünün handle değeri GetMenu API fonksiyonuyla alınabilir.
Bir pılldown menünün handle değeri kullanılarak da doğrudan herhangi bir popuptaki menü elemanı "checked" yapılabilir. Bunun için CheckMenuItem ya da EnableMenuItem fonksiyonlarında handle değeri olarak pulldown menünün handle değerini girmek gerekir. Ancak bu yöntemde MF_BYPOSITION belirlemesinin bir anlamı kalmaz. Pulldown menü WNDCLASS yapısının lpszMenuName elemanı set edilerek de açılmış olabilir. Bu durumda handle değeri GetMenu fonksiyonuyla alınabilir.
/*-----menupopup.dsw-----*/
Kaynaktan Hareketle Popup Menü Açılması
Popup menü elemanlarını kaynakta belirterek açma işlemi yapılabilir. Bunun için:
1. Kaynakta bir pulldown menü kaynağı oluşturulur.
2. LoadMenu ile handle elde edilir.
3. GetSubMenu fonksiyonuyla sıfırıncı popup menü elde edilir.
4. TrackPopupMenu ile menü görüntülenir.
Aslında CheckMenuItem, EnableMenuItem, AppendMenu, InsertMenu fonksiyonları hem pulldown menü hem de popup menü sistemleri için çalışabilmektedir.
DLL Kullanımı
DLL'ler Windows işletim sisteminin kullandığı dinamik kütüphane dosyalarıdır. İşletim sistemlerinde dinamki ve statik olmak üzere ikiye ayrılır. Statik kütüphane dosyalarının uzantısı lib, dinamik kütüphane dosyalarının uzantısı dll biçimindedir.
Statik Kütüphane Sistemi Nasıldır?
Lib dosyaları obj modüllerden oluşmuştur. Obj modüllerinin içerisinde derlenmiş modüller vardır. Statik kütüphanelere ekleme fonksiyonlar düzeyinde yapılmaz. Fonksiyonlar bir obj dosya içinde bulunur, obj dosya kütüphaneye eklenir. Bir statik kütüphaneden fonksiyon çağırıldığında derleyici bu fonksiyonu obj modülü içerisine yazar, linker çağırılan fonksiyonu önce proje içerisindeki diğer modüllerde arar, eğer burada bulamazsa proje içerisinde belirtilen lib dosyalarında da arar. Linker bir fonksiyonu lib dosyası içerisinde bulursa fonksiyonun bulunduğu obj modülün hepsini exe dosyasının içerisine yazar. Statik kütüphane sisteminde çağırılan her fonksiyonun exe dosya içerisine yazılması Windows gibi bir sistemde etkin bir çözüm değildir. Örneğin eğer API fonksiyonları lib dosyalar içerisinde olsaydı, her program en azından en temel API fonksiyonlarının kodlarını exe dosya içerisinde içermek zorunda kalacaktı. Böylelikle disk kullanım oranı büyük ölçüde düşecekti. Bunun çözümü dinamik kütüphane sistemlerindedir.
Dinamik Kütüphaneler
Dinamik kütüphanelerde tıpkı statik kütüphanelerde olduğu gibi fonksiyon barındırır. Ancak temel bir farklılıkları vardır. Dinamik bir kütüphaneden bir fonksiyon çağırıldığında linker kodu exe dosyanın içerisine yazmaz. Exe dosya içeirisinde özel bir bölüme fonksiyonun hangi dll içerisinde olduğunu yazar. Exe dosya çalıştırılacağı zaman Windows'un yükleyici programı exe dosya ile birlikte bu exe dosyanın kullandığı dll dosyalarını da belleğe yükler. Programın çalışma zamanı sırasında dll içerisinde bulunan fonksiyonların çalışması sırasında akış dll dosyasının içerisine girerek buradaki fonksiyonu çalıştırır. Windows işletim sisteminde belleğe bağımsız olarak yüklenebilen exe ve dll dosyalarına genel olarak modül denilmektedir.
Statik ve Dinamik Kütüphane Sistemlerinin Karşılaştırılması
1. Dll fonksiyonları exe dosyaları içerisine yazılmadığından exe dosyaların diskte kapladığı toplam alan azalır.
2. Dinamik kütüphane kullanan bir programın başka bir makinada çalıştırılabilmesi için exe dosyasının yanı sıra dll dosyalarının da taşınması gerekir.
3. Dinamik kütüphane sisteminin kullanılabilmesi için sistemin bir sanal bellek mekanizmasına sahip olması, yani genel olarak bellek probleminin olmaması gerekir. Çünkü bir dll'den birtek fonksiyon bile çağırılacak olsa bütün modül belleğe yüklenmek zorundadır.
4. Dinamik kütüphaneler bir programdaki kısmi değişikliklerin kolay yapılabilmesine olanak tanır. Yani exe dosyaya dokunmadan yalnızca yeni bir dll'in eskisiyle değiştirilmesiyle kısmi değişiklikler sağlanabilir.
5. Dinamik kütüphane kullanan bir programın yükleme zamanı göreli olarak daha uzundur.
Windows'un tüm API fonksiyonları dll modülleri içerisindedir. API fonksiyonlarına ilişkin statik bir kütüphane yoktur.
Modüllerin Import ve Export Tabloları
Aslında exe ve dll dosyaları arasında ciddi bir fark yoktur. Yani bir exe dosyası dll dosyası gibi kullanılabilir. Dll dosyaları için söz konusu olan herşey exe dosyalar için de geçerlidir.
Export Tablosu
Bir modüldeki bir fonksiyonun başka bir modül tarafından çağırılabilmesi için o fonksiyonun modülün export tablosuna yazılması gerekir. Örneğin bir dll dosyasının içerisinde 100 tane fonksiyon bulunsun. Eğer bunlardan yalnızca biri export tablosuna yazılmışsa başka bir modül yalnızca bu fonksiyonu çağırabilir. Tabii bu fonksiyon kendi içerisinde kalan 99 fonksiyonu çağırıyor olabilir. Export tablosunun genel yapısı aşağıdaki gibidir:
Export tablosu dışarıdan başka bir modül tarafından çağırılacak fonksiyonlar için fonksiyonların modül içerisindeki başlangıç adreslerini tutar. Örneğin func isimli bir fonksiyon bir dll içerisinde bulunsun ve bir exe dosyasından çağırılmış olsun. İşletim sistemi programın akışının bu fonksiyona geçirilmesi için bu fonksiyonun dll içerisinde nerede olduğunu bilmesi gerekir. işte bu bilgiyi dll dosyasının export tablosundan elde etmektedir.
Fonksiyon isimleri export tablosuna alfabetik olarak sıralı bir biçimde yerleştirilir. Export tablosunda bir fonksiyonu hızlı bir biçimde bulabilmek için ikili arama(binary search) yöntemi kullanılır. Tabloda fonksiyon ismi ve sıra numarası bir yapı biçiminde yani ikili kayıtlar biçiminde bulunur. Ancak fonksiyon adresleri sütunu bağımsız bir sütundur.
Export Tablosundaki Fonksiyonun Adreinin Tespit Edilmesi
İsimden hareketle bir fonksiyonun adresi şöyle bulunur:
1. Fonksiyon ismi ikili arama yöntemi kullanılarak fonksiyon ismi sütununda aranır. Buradan fonksiyon adresinin sıra numarası elde edilir.
2. Bu sıra numarası fonsiyon adresi sütununa index yapılarak fonksiyon adresi çekilir.
Arama işlemi def dosyası(module definition file) kullanılarak doğrudan sıra numarasına göre de yapılabilir. Bu durumda yalnızca fonksiyonun fonksiyon sütunundaki sıra numarası bilinir. Fonksiyon adresi doğrudan bu sütundan çekilir. Yani bir exportable fonksiyon fonksiyon ismi bilenerek ya da sıra numarası bilinerek aranabilir. Sıra numarası bilinerek yapılan arama çok daha hızlı bir sonuca ulaştırır. Export tablosuna yazılacak fonksiyonların isimleri ve sıra numaraları programcı tarafından belirlenebilmektedir.
Export tablosundaki fonksiyonların adresleri modülün ilk byte'ın sıfır olacak biçimde göreli olarak düzenlenmiştir.
Import Tablosu
Bir program içerisinde bir dll çağırılmışsa derleyici bunun makina kodunu CALL [adr] biçiminde yazar. Bu çağırma biçimine sembolik makina dilinde dolaylı çağırma(indirect call) denir. Bu makina komutu şu anlama gelmektedir: "Mikroişlemci adr ile belirtilen adrese git, orada fonksiyonun başlangıç adresi var. O adresi al ve fonksiyona atla".
Import tablosunun genel biçimi aşağıdaki gibidir:
Derleyicinin ürettiği köşeli parantez içindeki adresin değeri import tablosunun dolaylı adres sütununun satırlarını göstermektedir. Yani runtime sırasında dolaylı adres sütununda belirtilen adreslere programın akışı geçirilmektedir. Dolaylı adres sütunu yükleyici tarafından modüllerin export tablolarına bakılarak doldurulmaktadır. Bu durumda bir dll yüklenmesinde şunlar yapılmaktadır.
1. Yükleyici exe dosyayı yükler
2. Yükleyici dll dosyalarını yükler.
3. Yükleyici dll'lerin export tablolarına bakarak, fonksiyonların göreli adreslerini elde eder. Bu değerlere dll dosyalarının yükleme adreslerini ekleyerek gerçek adresleri bulur.
4. Yükleyici bu adresleri import tablosunun dolaylı adres sütunlarına yazar.
Import ve export tablolarının her ikisi de hem dll dosyaları içerisinde hem de exe dosyaları içerisinde vardır. Bir dll dosyası içerisindeki fonksiyon başka bir dll dosyası içerisindeki başka bir fonksiyonu çağırabilir.
dllexport ve dllimport Bildirimleri
Bir fonksiyonu başka bir modülden çağırabilmek için o fonksiyonun export tablosuna yerleştirilmesi gerekir. Bir fonksiyonu export tablosuna yerleştirmek için fonksiyon prototipinin başına __dllspec(dllexport) ifadesinin yerleştirilmesi gerekir. __declspec(dllexport) fonksiyon tanımlaması sırasında da kullanılabilir(Hem prototipte hem de tanımlama sırasında kullanılabilir). Başka bir modülün export tablosunda belirtilmiş bir fonksiyonun çağırılması için özel bir işleme gereksinim yoktur. Çünkü linker çağırılan fonksyonları programın diğer modüllerinde bulamazsa otomatik olarak dll içerisinde de arar. Ancak bir dll içerisindeki bir fonksiyonun çağırılması söz konusuysa bu fonksiyon prototipinin önüne __declspec(dllimport) ifadesini yerleştirmek linker'ın işini kolaylaştırır.
Bir DLL'in Kullanıma Hazır Hale Getirilmesi
Bir dll bir kütüphanedir ve içerisinde fonksiyonlar vardır. Bir dll'e ilişkin kaynak dosya C ya da CPP dosyası biçiminde yazılabilir. Bir dll'in oluşturulabilmesi için en iyi yöntem x.h ve x.c biçiminde iki dosya kullanılmasıdır. x.h dosyası dll içerisindeki fonksiyonların prototiplerini barındırır. Bu dosya hem x.c dosyası içerisinden, hem de dll'i kullanan bir modül içerisinden include edilir. Bu dosya içerisindeki prototiplerin başında dll için derleme yapılıyorsa __declspec(dllexport), başka bir modülden kullanılıyorsa __declspec(dllimport) ifadelerinin yazılması gerekir. Bu durumda ön işlemci komutlarıyla x.h dosyasına hiç dokunmadan fonksiyon prototipinin başındaki ifadeyi dışarıdan değiştirebiliriz. Tipik bir dll başlık dosyası şöyle organize edilebilir:
#ifndef _X_H_
#define _X_H_
#ifndef __MYLIB__
#define DLLCALL __declspec(dllimport)
#else
#define DLLCALL __declspec(dllexport)
#endif
/* Function prototypes */
DLLCALL int Add(int x, int y);
DLLCALL int Multiply(int x, int y);
#endif
Dll dosyası kendisi derlenirken __MYLIB__ define edilir, kullanım kodunda kullanılmaz.
Bir dll oluşturabilmek için project menüsünden proje türü olarak "Win32 Dynamic Linked Library" seçilmelidir. Bir dll kendi başına çalışamaz. Oysa bir exe kendi başına WinMain fonksiyonundan çalışır. Bir dll derlendiğinde ve link edildiğinde geliştirme ortamı dll ve lib uzantılı iki dosya oluşturur. Lib dosyasına "Import Library" denir ve normal bir lib formatında değildir. Import library dosyasından dll kullanılırken faydalanılır.
/*-----dll.dsw-----*/
Bir Dll'in Uygulama İçerisinden Kullanılması
Bir dll'i oluşturduktan sonra dll içerisindeki fonksiyonlar başka bir modülden çağırılabilir. Bir dll sırasıyla şu dizinler içerisinde aranır:
1. Exe dosyasının bulunduğu dizin içerisinde
2. O anda bulunulan dizin içerisinde. O anda bulunulan geçerli dizin eğer prompt içerisindeysek programın çalıştırıldğı dizindir. Ancak prompt içerisinde değilsek yani örneğin masa üstündeki bir simgeyi çift tıklayarak çalıştırmışsak ya da Windows Explorer aracılığıyla programı çalıştırmışsak manuel yöntemle belirlenebilir. Aslında bir exe programını çalıştıran CreateProcess isimli bir API fonksiyonu vardır. Bu API fonksiyonunun bir parametresi de geçerli dizini belirtir. Aslında masa üstünde ya da Windows Explorer içerisinde programı çalıştırmak istesek Windows'un kendisi de programı CreateProcess isimli API fonksiyonuyla çalıştırır. Yani mouse'un sağ tuşuna basılarak geçerli dizin set edildiğinde Windows bu bilgiyi CreateProcess ile programı çalıştırırken kullanır.
3. Windows dizininin altındaki System dizininde
4. Windows dizininde
5. Path ile belirtilen dizinlerde(Bu path autoexec.bat'taki path değildir)
6. Registry içerisinde özel bir yöntemle belirtilerek
Dll uygun dizin içerisine çekildikten sonra dll'i kullanacak programın proje dosyası oluşturulur. Import Library dosyasını projeye dahil etmek gerekir. Bu sayede linker çağırılan bir fonksiyonun hangi dll içerisinde olduğunu tespit eder. Import Library'nin projeye dahil edilmesi "Add files to project" seçeneğiyle ya da "Project=>Settings=>Link=>Object/library" modules içine yazarak yapılabilir. Sonuş olarak şu işlemlerin yapılması gerekir:
1. Dll’in uygun bir dizine kopyalanması
2. Başlık dosyasının kullanılacak dosyadan include edilmesi
3. Import Library'nin projeye eklenmesi
Import Library linker için, başlık dosyası derleyici için dll dosyası ise yükleyici için bulundurulmaktadır. Import Library ile aynı görevde olan bir def dosyası da vardır. Dll dosyasından Import Lıbrary ya da def dosyası elde edilemez.
DLL'lerin Sonradan Yüklenmesi
Normal bir dll import library kullanılarak link edilmişse exe program yüklenirken işletim sistemi tarafından otomatik olarak belleğe yüklenir. Oysa bir dll dosyası programım çalışmasının herhangi bir aşamasında LoadLibrary API fonksiyonuyla yüklenebilir. Bir dll böyle kullanılacaksa projeye import library'i eklenmez.
HINSTANCE LoadLibrary(
LPCTSTR lplibFileName
);
Fonksiyonun parametresi yüklenecek dll dosyasının ismidir. Fonksiyonun geri dönüş değeri dll dosyasının yüklendiği bellek adresidir. Başarısızlık durumunda NULL değeriyle geri döner. Örneğin dll dosyası WM_CREATE mesajı içerisinde dinamik olarak yüklenebilir. Dosya ismi bir path ifadesi içerebilir. Eğer içeriyorsa dosya yalnızca o dizinde aranır, içermiyorsa daha önce belirtilen sırada aranır. LoadLibrary fonksiyonuyla yüklenen dll istenildiği zaman FreeLibrary fonksiyonuyla hafızadan çıkarılabilir.
BOOL FreeLibrary(
HMODULE hLibModule
);
Fonksiyon LoadLibrary fonksiyonundan elde edilen geri dönüş değerini parametre olarak alır ve dll dosyasını hafızadan çıkarır. Örneğin bu API fonksiyonu WM_DESTROY mesajında kullanılabilir.
Bir dll LoadLibrary ile daha sonra yüklenmişse dll içerisindeki bir fonksiyonun çağırılması için o fonksiyonun adresinin runtime sırasında GetProcAddress API fonksiyonuyla elde edilmesi gerekir. Bu biçimde bir fonksiyonun çağırılabilmesi için fonksiyonun isminin ve parametrik yapısının bilinmesi gerekir. Ancak fonksiyon isimleri dll içerisine bazı nedenlerden dolayı değiştirilerek yazılmaktadır. Bu durumda bir fonksiyonun gerçek isminin elde edilmesi gerekir. Bu isim aslında programcı tarafından belirlenebilir, ya da Dumpbin programıyla:
Dumpbin /exports
seçeneğiyle elde edilebilir. Fonksiyon ismi yerine export tablosundaki sıra numarası da kullanılabilir. Sıra numaraları dumpbin programıyla ya da programcı tarafından hazırlanan def dosyası ile elde edilebilir. Dumpbin.exe programı sisteme ilişkin dosya formatlarını anliz etmeye yarayan Visual C++ paketiyle verilen özel bir programdır.
FARPROC GetProcAddress(
HMODULE hModule, /*DLL'in handle'ı*/
LPCSTR lpProcName /*Fonksiyon ismi*/
);
Fonksiyonun birinci parametresi LoadLibrary fonksiyonundan elde edilen handle değeridir. İkinci parametre fonksiyonun dll içerisindeki ismidir. İkinci parametre ile verilen adresin yüksek anlamlı iki byte'ı 0 ise fonksiyon düşük anlamlı iki byte'ında export tablosundaki sıra numarasının bulunduğunu düşünür. Böyle bir durumda bu parametre:
(LPCSTR) MAKELONG (n, 0)
veya
MAKEINTRESOURCE(n)
biçiminde verilebilir. Bu durumda bir fonksiyon çağırılması şöyle yapılabilir:
int (*pAdd) (int, int);
pAdd = (int (*) (int, int)GetProcAddress(hDllInstance, "Add");
result = pAdd(10, 20);
UNICODE Karakter Tablolama Sistemi
ASCII tablosunda her karakter 1 byte yer kaplayacak biçimde tutulur. Bu durum ancak 256 değişik karakterin ifade edileceği anlamına gelir. Son yıllarda her bir karakterin 2 byte ile ifade edildiği UNICODE tablolama sistemi ASCII sistemiyle birlikte kullanılmaya başlanmıştır. UNICODE tablosunun ilk 256 karakteri ASCII tablosundaki karakterlerin aynsıdır. Bu tabloda dünyadaki bütün ulslara ilişkin harf simgeleri vardır.
Bir UNICODE karakter C'de unsigned short int türüyle ifade edilir. Bu tür şimdilik her sistemde 2 byte uzunluğundadır. Windows.h içerisinde typedef unsigned short int wchar_t biçiminde ve typedef wchar_t WCHAR biçiminde iki typedef ifadesi vardır. C++'ta 1996 standardında wchar_t bir anahtar sözcüktür. wchar_t C++'ta 2 byte uzunluğunda işaretsiz bir tam sayı türünü belirtir. UNICODE bir yazı wchar_t türünden bir yazı içerisinde saklanabilir. C'de normal string'ler birer byte uzunluğunda ASCII değerleriyle belleğe yerleştirilirler. Bir string'in başına L getirilirse (L"string" gibi) UNICODE string elde edilir. Bu durumda derleyici string'in herbir karakterini iki byte uzunluğunda UNICODE karakter tablosuna uygun olarak yerleştirir. NULL karakter iki byte uzunluğunda 0 biçimindedir. Derleyici UNICODE string yerine wchar_t türünden bir adres yerleştirir. C'de çalışıyorsak bu adres unsigned short int türünden bir adrestir. C++'ta çalışıyorsak wchar_t türündendir. Örneğin:
char *p = L"string"; /*hata*/
wchar_t *p = L"string"; /*doğru*/
UNICODE karakter sabiti de söz konusudur. UNICODE karaketre sabitleri L'a' biçiminde belirtilir. Tabii wchar_t türünden bir değişkene doğrudan bir ASCII karakter sabiti atanabilir(C'de farklı türden bilgiler birbirlerine atanabilir).
wchar_t x;
x = 'a';
Ancak yine de okunabilirlik bakımından atamanın şu biçimde yapılması tavsiye edilir.
x = L'a';
Bir programın UNICODE ve ASCII version'larının kolaylıkla elde edilebilmesi için şöyle bir sistem düşünülmüştür:
#ifdef UNICODE
typedef wchar_t TCHAR;
else
typedef char TCHAR;
#endif
Eğer program içerisinde her zaman TCHAR ismi kullanılırsa windows.h include ifadesinin yukarısına #define UNICODE yazılıdğında bütün TCHAR'lar wchar_t yazılmadığında ise bütün TCHAR'lar char anlamına gelirler. Yani bütün program TCHAR ismini kullanacak biçimde yazılmalıdır. Tabii bir string'in de otomatik olarak UNICODE ya da ASCII string biçiminde kolay bir dönüştürmeye tabii tutulması gerekmektedir. Bu işlem TEXT makrosuyla yapılmaktadır.
#ifdef UNICODE
#define TEXT(x) L##x
#else
#define TEXT(x) x
#endif
Bu makro windows.h içerisinde tanımlanmıştır. Görüldüğü gibi eğer UNICODE sembolik sabiti tanımlanmış ise TEXT("ali") gibi bir makro ön işlemci tarafından L"ali" biçimine dönüştürülür. Eğer UNICODE sembolik sabiti TEXT("ali") "ali" biçimine dönüştürülür. TEXT makrosu karakter sabitleri için de kullanılabilir. Sonuç olarak bir programı ASCII ve UNICODE uyumlu yazabilmek için:
1. char ya da wchar_t yerine her zaman TCHAR kullanılmalı,
2. UNICODE string ya da ASCII string yerine her zaman TEXT makrosu kullanılmalıdır.
Yazı Parametresi Alan API Fonksiyonları
Bir yazıyı parametre olarak alan API fonksiyonlarından gerçekte iki tane vardır: ASCII ve UNICODE versiyonları. Örneğin, MessageBox fonksiyonundan iki tane vardır: MessageBoxA, MessageBoxW. Aslında 32 bit Wndows sistemlerinde 16 bit Windows sistemlerindeki gibi MessageBox isimli bir fonksiyon yoktur. MessageBox yazıldığı zaman ön işlemci, UNICODE sembolik sabitinin tanımlanıp tanımlanmadığına göre bunu MessageBoxA ya da MessageBoxW şekline döndürür. Tabii, bu fonksiyonların UNICODE versiyonlarında parametreler wchar_t * biçiminde ASCII versiyonları char * biçimindedir. Örneğin, MessageBox API fonksiyonu için windows.h içerisinde şu biçimde bir işleme sokulmuştur.
#ifdef UNICODE
#define MessageBox MessageBoxW
#else
#define MessageBox MessageBoxA
#endif
Örneğin;
wchar_t s[] = {3041, 1515, 1010, 0};
MessageBox(hWnd, s, s, MB_OK);
ASCII ve UNICODE Uyumlu Tür İsimleri
Unıcode sembolik sabitine bağımlı olan iki tür ismi vardır. Yani bu durumda LPTSTR ve LPCSTR duruma göre wchar_t ya da char * olmaktadır. Aslında bu türler gösterimlerde daha çok iki versiyonu olan API fonksiyonlarının parametrelerini belirtmek için kullanılır.Örneğin MessageBox fonksiyonun prototipi şöyledir:
int MessageBox(
HWND hWnd,
LPCTSTR lpText,
LPCTSTR lpCaption,
UINT uType
);
Standart C Fonksiyonları ve UNICODE İşlemleri
C’ nin standart string fonksiyonları (örneğin; strlen(), strcpy() gibi) default olarak ASCII sistemine göre çalışmaktadır. Bütün bu fonksiyonların ayrıcaUNICODE versiyonları da vardır. Ancak en iyisi bu fonksiyonların UNICODE duyarlı isimlerini kullanmaktır. UNICODE duyarlı isimler tchar.h dosyasında tanımlanmıştır. Bu yüzden bu dosyanın da include edilmesi gerekir. Ancak bu duyarlılık, UNICODE değil _UNICODE ismine göre yazılmıştır. Örneğin, tchar.h içerisinde şöyle bir _tcslen ismi strlen fonksiyonunun _UNICODE duyarlı biçimidir.
#ifdef _UNICODE
#define _tcslen wcslen
#else
#define _tcslen strlen
#define
Bunların dışında tchar.h içeriside TEXT makrosu gibi ancak _UNICODE sembolik sabitine duyarlı _T, __T, _TEXT makroları da vardır. Sonuç olarak, durum biraz karışmış olmasına karşın, şu özet notlar alınabilir:
1. UNICODE ve ASCII string elde etmek için TEXT, _T, _TEXT, __T makrolarının hepsi kullanılabilir. ancak TEXT UNICODE sözcüğüne diğerleri ise _UNICODE sözcüğüne duyarlıdır. Tabii TEXT dışındakilerde tchar.h dosyasının include edilmesi gerekir.
2. TCHAR türü UNICODE duyarlı bir türdür. Yani yalnızca windows.h dosyasının include edilmesiyle kullanılabilir.
3. Bir yazıyı parametre olarak alan API fonksiyonlarından ASCII ve UNICODE olmak üzere ikişer tane vardır.
4. Bir yazıyı parametre olarak alan Standart C fonksiyonlarının UNICODE versiyonları da vardır. Bunların _UNICODE duyarlı isimleri kullanılmaktadır.
ALT İŞLEMLİ ÇALIŞMA (THREAD MECHANISMS)
Bir proses en azından bir alt işlem (thread) içerir. Bu alt işleme ana alt işlem (main thread) denir. Her alt işlem, sanki ayrı bir program gibi işletim sistemi tarafından çizelgeleme işlemine sokulur. (süresi 20 milisaniyedir.). Program WinMain fonksiyonunun bulunduğu ana alt işlemden çalışmaya başlar. CreateThread API fonksiyonuyla program içerisindeki bir fonksiyon, bir alt işlem olarak belirlenebilir. Yani artık, bu prosesin iki alt işlemi vardır. Her iki alt işlemde birbirinden bağımsız olarak, sanki farklı programlarmış gibi çalışabilirler. Alt işlemler arasındaki geçiş, ya aynı prosesin iki alt işlemi arasında ya da farklı proseslerin alt işlemleri arasında olabilir. Aynı prosesin alt işlemleri arasında yapılan geçişin performans maliyeti fazla deeğildir.
Alt İşlemler ve Prosesler Arsında Haberleşme
Bir prosesin iki alt işlemi asenkron çalışan iki ayrı fonksiyondur. Bunlar aynı prosese ilişkin oldukları için aynı global değişkenleri kullanabilirler. Ancak farklı proseslere ilişkin alt işlemler arasındaki haberleşmeye kısaca proseslwer arasındaki haberleşme (interproses com)denir.prosesler arsındaki heberleşme aynı makinadaki iki programın ya da Network içindeki herhangi iki makine arasındaki proseslerin haberleşmesi biçiminde iki bölümde incelenmektedir. Her iki yöntem için de farklı yöntemler kullanılmaktadır.
Bir Alt İşlemin Yaratılması
CreateThread API fonksiyonuyla yapılmaktadır.HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
DWORD dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId);
Fonksiyonun 1. Parametresi; alt işlemin güvenlik parametrelerine ilişkin bir yapının adresidir. NULL geçilebilir.
2. Parametre; bir alt işlemin kullanılacağı stack uzunluğudur. 0 girilirse, default bir uzunluğu alır.
3. Parametre; geri dönüş değeri void, parametresi LPVOID türünden bir fonksiyon göstericisidir. Yani bu parametreye alt işlem olarak tanımlanacak fonksiyonun adresi geçirilecektir.
4. Parametre; alt işlem olarak tanımlanan fonksiyona geçirilecek parametredir.
5. Parametre; yaratılan alt işlemin davranışını belirtir. Bu parametreye 0 girilirse, alt işlem yaratılır yaratılmaz çalıştırılır.
6. Parametre; DWORC türünden bir değişkenin adresini alır. Fonksiyon bu değişkene alt işlemin ID değerini yerleştirir.
Fonksiyonun geri dönüş değeri, yaratılan alt işleme ilişkin çeşitli bilgilerin saklandığı bir bölgenin adresi olan handle değerid
Bir thread CreateThread API fonksiyonuyla yaratıldığında alt işlemin yönetilebilmesi için bir handle alınır. Bu alan CloseHandle API fonksiyonula silinebilir.Prototipi:
BOOL CloseHandle(HANDLE handle);
void WINAPI MyThread(..... stdcall) biçiminde çağırılmalıdır.
Bir alt işlemin çalışması ExitThread fonksiyonuyla sonlandırılabilir. Eğer bu fonksiyon çağırılmamışsa alt işlem fonksiyonunun sonunda derleyici tarafından otomatik olarak sonlandırılır. Prototipi:
void ExitThread(DWORD dwExitCode);
Alt İşlemlerin Bekletilmesi
Bir alt işlem Sleep fonksiyonuyla istenilen kadar milisaniye çizelge dışı bırakılabilir. Tabii bu bekleme işlemi çizelge dışına atılarak gerçekleştirilir. Meşgul bir döngü(busy loop) kullanılmaz. Prototipi:
void Sleep(DWORD dwMiliSec);
Bu fonksiyona 0 parametresi geçildiğinde kalan quanta süresi kadar bir bekleme oluşabilir(Yani sanki o quanta süresi bitmiş gibi işlem yapılır). Ancak Sleep fonksiyonu kodun içinde bulunan thread'i bekletebilir(Yani başka bir alt işlem bekletilemez). Başka bir alt işlemin bekletilmesi SuspendThread API fonksiyonuyla yapılmaktadır. Prototipi:
DWORD SuspendThread(HANDLE hThread);
Bu fonksiyon bir alt işlemin çalışmasını ResumeThread API fonksiyonu çağırılana kadar durdurur. Prototipi:
DWORD ResumeThread(HANDLE hThread);
Alt işlem kullanımına ilişkin örnek programın açıklanması:
Program kodu UNICODE uyumlu yazılmıştır. Ancak Windows 95 ve 98 sistemlerinde RegisterClass API fonksiyonu UNICODE sisteminde çalışmaz, sıfırla geri döner. Tabii Windows NT sistemlerinde böyle bir problem yoktur.
Alt İşlemler ve Mesaj Kuyruğu
Bir alt işlem içerisinde CreateWindow ile pencere yaratıldığında o alt işleme ilişkim bir mesaj kuyruğu da yaratılır. Yani her alt işlemin farklı bir mesaj kuyruğu vardır.
Alt İşlemlerin Seri Hale Getirilmesi
İki alt işlem asenkron bir biçimde çalışır. Ancak pek çok uygulamada belli bir eş zamanlılığın sağlanması gerekebilir. Örneğin paralel porta takılan bir aygıt beş satırlık bir kod ile iki alt işlem tarafından programlanıyor olabilir. Bir alt işlemde programlama işlemi başladığında alt işlemler arasında geçiş olur, akış diğer alt işleme gelirse o alt işlem de aynı programlayan kodu çalıştırırsa problemli bir durum ortaya çıkar. Çünkü tekrar bir alt işlemler arası geçiş ile program diğer alt işlemden devam ettiğinde alt işlem programlama adımlarının bazılarının yapıldığını sanarak kaldığı yerden devam eder. Oysa bu işlemler diğer alt işlemler tarafından bozulmuştur. Eğer bir kod bir bölgedeyse o kod o bölgeden çıkana kadar başka bir kodun o işlemleri yapan bölgeye girmesi istenmiyorsa o bölgeye kritik bölge denir(critical section). Kritik kod(bölge) işletim sistemlerinin sağladığı özel fonksiyonlarla ele alınmalıdır. Kritik kodların global değişkenlerle yaratılması mümkün değildir.
int flag = 0;
DWORD WINAPI Thread1(LPVOID lpvParam){
{
while(flag == 1)
;
flag = 1;
..... /*kritik kod*/
..... /*kritik kod*/
..... /*kritik kod*/
..... /*kritik kod*/
..... /*kritik kod*/
flag = 0;
}
DWORD WINAPI Thread2(LPVOID lpvParam){
{
while(flag == 1)
;
flag = 1;
..... /*kritik kod*/
..... /*kritik kod*/
..... /*kritik kod*/
..... /*kritik kod*/
..... /*kritik kod*/
flag = 0;
}
Bu işlem gerçekleştirilemez.
Kritik kodların üstesinden gelebilmek için Windows işletim sisteminde çeşitli API fonksiyonları vardır. Örneğin:
CreateSemaphore
CreateEvent
CreateMutex
Bu API fonksiyonlarıyla bir handle alınır. Bu handle
ReleaseSemaphore
ReleaseEvent
ReleaseMutex
gibi API fonksiyonlarıyla bırakılır. WaitForSingleObject API fonksiyonuyla bekleme alt işlem çizelge dışı bırakılarak yapılır.
HANDLE hSemaphore;
hSemaphore = CreateSemaphore(... 1 ...);
DWORD WINAPI Thread1(LPVOID lpvParam){
{
WaitForSingleObject(hSemaphore);
..... /*kritik kod*/
..... /*kritik kod*/
..... /*kritik kod*/
..... /*kritik kod*/
..... /*kritik kod*/
ReleaseSemaphore(hSemaphore);
}
DWORD WINAPI Thread2(LPVOID lpvParam){
{
WaitForSingleObject(hSemaphore);
..... /*kritik kod*/
..... /*kritik kod*/
..... /*kritik kod*/
..... /*kritik kod*/
..... /*kritik kod*/
ReleaseSemaphore(hSemaphore);
}
Örneğin Semaphore nesnesi şöyle kullanılır. CreateSemaphore fonksiyonu ile semaphore yaratılır ve bişr handle elde edilir. Handle içerisinde bir sayaç vardır. Bu sayacın ilk durumu CreateSemaphore fonksiyonuyla belirlenir. WaitForSingleObject semaphore nesnesinin sayaç değeri 0 ise bekler, 0 dışı ise beklemez ama 1 düşürür. ReleaseSemaphore ise sayacı bir arttırır. Yani kritik kod WaitForSingleObject ile ReleaseSemaphore arasındaki koddur. Semaphore nesnesi prosesler arasında bir isim verilerek de kullanılabilir.
Grafik İşlemlerinde Görüntü Modu ve Koordinat Sistemleri
Windows'da kullanılan orijin noktası 3 biçimde olabilir:
1. Ekranın sol üst köşesi orijin olabilir(Popup menu çıkartırken kullandığımız fonksiyonlar bu orijine ilişkindir).
2. Orijin pencere başlığının sol üst köşesi olabilir(GetWindowDC ile alınan handle orijin noktasının böyle olduğunu varsayarak çizim yapar).
3. Pencerenin çalışma alanının sol üst köşesi orijin noktası olabilir(Bütün GDI fonksiyonları bu noktayı orijin kabul eder).
Windows'da GDI fonksiyonları için orijin nokası çalışma alanının istenilen bir noktasına çekilebilir. Çalışma alanının orijin belirten noktasına "View port origin" denilmektedir. View port origin noktası her zaman pixel cinsinden belirlenir. Orijin noktası SetViewportOrgEx fonksiyonuyla değiştirilebilir. Prototipi:
BOOL SetViewportOrgEx(
HDC hdc, // handle of device context
int x, // new x-coordinate of viewport origin
int y, // new y-coordinate of viewport origin
LPPOINT lpPoint // address of structure receiving original origin (NULL)
);
View port orijini dışında yine pencere görüntüsünü kaydırmak amacıyla pencere orijin noktası değiştirilebilir. Yani pencere içerisindeki görüntü kaydırılarak istenilen bir DC bölgesinin pencere sınırları içerisinde kalması sağlanabilir. SetViewportOrgEx yalnızca çizim fonksiyonları için orijin noktasını değiştirir. Halbuki pencere orijininin değiştirilmesi yalnızca görüntünün kaydırlması anlamına gelir. Pencere orijinini değiştiren SetWindowOrgEx fonksiyonu vardır. Prototipi:
BOOL SetWindowOrgEx(
HDC hdc, // handle of device context
int x, // new x-coordinate of window origin
int y, // new y-coordinate of window origin
LPPOINT lpPoint // address of structure receiving original origin
);
Görüntü Modu(Mapping mode)
DC nesnesinin çizime ilişkin en önemli elemanı görüntü modudur. Default görüntü modeu MM_TEXT'tir. Aslında çizim yapılırken kullanılan x, y değerleri pixellere karşılık gelmek zorunda değildir. Bu koordinata mantıksal koordinat denir. GDI fonksiyonları bu mantıksal koordinatları gerçek pixel koordinatlarına dönüştürerek çizim yaparlar. Görüntü modu şunlar üzerinde etkili olmaktadır:
1. Mantıksal koordinatların pixel koordinatlarına dönüştürülme biçiminde etkili olur.
2. x ve y artımlarının hangi yöne doğru olacağı üzerinde etkili olur.
3. Orijin üzerinde de etkisi vardır.
MM_TEXT Modu
Bir DC elde edildiğinde bu default moddur. Mantıksal koordinatlarla pixel koordinatları birebir örten biçimindedir. Yani (10, 10) noktası çalışma alanının sol üst köşesinden itibaren 10 pixel sağ ve 10 pixel aşağıda bulunan pixel'dir. Bu mod doğrudan pixel tabanlı olduğu için çözünürlüğün değiştirilmesinden çok fazla etkilenir. x, y arttırımı x sağa y aşağıya doğru yapılmaktadır.
MM_LOENGLISH ve MM_HIENGLISH Modları
Bu iki mod da x arttırımları sağa doğru y arttırımları yukarı doğrudur. MM_LOENGLISH modunda mantıksal bir birim 0.01 inch'e, MM_HIENGLISH'te ise her bir birim 0.001 inch'e karşılık gelmektedir. Örneğin MM_HIENGLISH modunda (0,0)'dan (1000, 1000)'e bir doğru çizildiğinde (0,0)'dan 1 inch sağa 1 inch yukarı olacak şekilde bir doğru çizilmektedir. Tabii GDI sistemi gerçek bir inch hesabı yapamaz. Çünkü çizginin kaç inch olacağı çözünürlüğe ve ekranın büyüklüğüne bağlıdır(Bir çizimin kaç inch oduğunun belirlenebilmesi için kullanılan monitörün büyüklüğünün ve çalışılan çözünürlüğün bilinmesi gerekir. Çünkü bunlar bilinirse bir pixel'in genişliği ve yüksekliği bulunabilir. Buradan hareketle tam bir ölçü hesaplanabilir). Ancak Windows sisteminin monitör uzunluğunu otomatik olarak bilmesi tam olarak mümkün değildir. Bu yüzden buradaki inch gerçek bir inch büyüklüğünü ifade edemez. Aslında Control Panel içerisinde sistem fontunun ayarlarıyla oynanarak dolaylı bir biçimde monitör uzunluğu girilebilir.
MM_LOMETRIC ve MM_HIMETRIC Modları
Bu iki modda mantıksal birim milimetre cinsinden belirlenmiştir. Arttırım x için sağa, y için yukarıya doğrudur. Bir mantıksal birim MM_LOMETRIC'te 0.1 milimettre, MM_HIMETRIC'te ise 0.01 milimetredir.
Kartezyen Koordinat Sisteminde Doğru Çizen Program
Pencerenin herhangi bir bölgesine birbirini kesen iki doğru çizilebilir. Bunların kesişim noktsı view port origin olarak belirlenebilir. Görüntü modu için MM_TEXT uygun değildir. MM_LOMETRIC kullanılmıştır. Program bir doğru çizmektedir. menüden seçim yapıldığında bir dialog penceresi çıkar. (x1, y1) ve (x2, y2) noktalarını ister ve doğru MoveToEx ve LineTo fonksiyonlarıyla çizilir. Programın menü yapısı: File=>Exit, Graph=>Line. Görüntü modunu değiştirmek için SetMapMode fonksiyonu kullanılır. Prototipi:
int SetMapMode(
HDC hdc, // handle of device context
int fnMapMode // new mapping mode
);
Scroll Bar'ların Kontrolü
Scroll bar'lar yatay ve düşey olmak üzere iki kısıma ayrılmaktadır. Bir scroll bar ya otomatik olarak pencerenin sağ ve alt köşelerine yerleştirilir ya da bir alt pencere gibi yaratılır. Pencerenin sağına ve altına otomatik yerleştirilen scroll bar'lar birer alt pencere değildir. Scroll bar üzerinde şu hareketler yapılabilir:
1. Scroll bar oklarına tek click yapılabilir.
2. Scroll bar düğmesinin herhangi iki tarafına tek click yapılabilir.
3. Scroll bar düğmesi üzerinde sürükle ve bırak hareketi yapılır.
Scroll bar kontrolü bir alt pencere olarak "scrollbar" sınıfıyla yaratılabilir. Scroll bar üzerinde yukarıda tanılanan işlemlerden birisi tanımlandığında scroll bar üst pencereye mesaj gönderir(Kontrol olarak yaratılmışsa üst pencereye, pencereye gömülü olarak yaratılmışsa gömülü olarak yaratılmış olan pencereye mesaj gönderir).
Scroll bar üzerinde işlem yapıldığında scroll bar düğmesi otomatik olarak ilerletilmez. Programcı SetScrollPos API fonksiyonuyla düğmeyi ilerletir. Scroll bar üzerinde işlem uygulandığında üst pencere diğer kontrollerde olduğu gibi WM_COMMAND mesajıyla çağırılmaz, WM_HSCROLL ve WM_VSCROLL mesajlarıyla çağırılır.
WM_HSCROLL ve WM_VSCROLL Mesajları
Mesajın parametreleri şunlardır: Eğer mesaj alt pencere biçimindeki bir scroll bar'dan gönderilmişse lParam içerisinde alt pencerenin handle değeri bulunur. Pencere içerisine otomatik iliştirilmiş olan scroll bar'lardan gönderilmişse lParam içerisinde 0 bulunur. LOWORD(wParam) scroll bar üzerinde hangi işlemin uygulandığını anlatır ve şunlardan bir tanesi olabilir:
SB_LINEUP Scroll bar yukarı(sol) ok tuşuna basma sonucu
SB_LINEDOWN Scroll bar aşağı(sağ) ok tuşuna basma sonucu
SB_PAGEUP Scroll bar düğmesinin yukarısına(soluna) basma sonucu
SB_PAGEDOWN Scroll bar düğmesinin aşağısına(sağına) basma sonucu
SM_THUMBPOSITION Scroll bar düğmesi sürüklenip bırakıldıktan sonra
SB_THUMBTRACK Scroll bar düğmesi sürüklenirken
SB_TOP Klavye kullanımı için ayrılmıştır
SB_BOTTOM Klavye kullanımı için ayrılmıştır
SM_TOP ve SB_BOTTOM yalnızca klavye kullanımı için düşünülmüştür. Eğer scroll bar alt pencere biçiminde yaratıldıysa girdi odağı scroll bar üzerine verildiğinde kalvyenin tuşları otomatik olarak şu mesajları gönderir:
Home SB_TOP
End SB_BOTTOM
Page Up SB_PAGEUP
Page Down SB_PAGEDOWN
Left/Up SB_LINEUP
Right/Down SB_LINEDOWN
HIWORD(wParam) bazı mesajlarda scroll bar düğmesinin pozisyonunu almakta kullanılır.
Scroll Bar Kontrolünin Kullanımı
Yapılacak ilk iş SetScrollRange fonksiyonuyla scroll bar için mantıksal bir skala belirlemektir. Bu işlem örneğin WM_CREATE mesajı içerisinde yapılabilir. Prototip:
BOOL SetScrollRange(
HWND hWnd, // handle of window with scroll bar
int nBar, // scroll bar flag
int nMinPos, // minimum scrolling position
int nMaxPos, // maximum scrolling position
BOOL bRedraw // redraw flag
);
Fonksiyonun birinci paremetresi hangi pencereye ilişkin scroll bar işlemi uygulanacağıdır. Eğer gömülü scroll bar söz konusuysa ana pencerenin handle'ı geçirilmelidir. İkinci parametre üç değer alabilir: Eğer kontrole ilişkin ayarlama yapılıyorsa SB_CTL, pencereye gömülü scroll bar ayarlamaları için SB_VERT ya da SB_HORZ kullanılmalıdır. Üçünü ve dördüncü parametreleri skala aralığını belirtir. Beşinci parametre kontrol üzerindeki görüntünün güncellenmesiyle ilgilidir. Skala belirlendikten sonra WM_HSCROLL ya da WM_VSCROLL mesajları işlenir. Mesaj içerisinde mesajın geliş sebebi bir switch ile soruşturulmalıdır.
switch (LOWORD(wParam)) {
case SB_LINEUP:
....
....
....
break;
case SB_LINEDOWN:
....
....
....
break;
....
....
....
}
Bu switch içerisinde şu belirlemeler yapılmalıdır:
- Scroll bar ok'larına click yapıldığında ne kadar bir öteleme yapılacak,
- Ara bölgelere click yapıldığında ne kadar öteleme yapılacak,
Bu belirlemelerden sonra switch deyiminin dışında SetScrollPos API fonksiyonuyla düğme yeni yerine yerleştirilmelidir. Prototipi:
int SetScrollPos(
HWND hWnd,
int nBar,
int nPos,
BOOL bRedraw
};
Klavye Mesajları
Klavyede bir tuşa basıldığında WM_KEYDOWN çekildiğinde de WM_KEYUP mesajları Windows tarafından gönderilir. Klavye mesajları girdi odağına(input focus) sahip olan pencereye gönderilmektedir. Bir t anında üzerinde mavi bant olan pencereye aktif pencere denir. Yalnızca hWndParent olarak NULL girilmiş olan pencereler aktif olabilir. Yani yalnızca top level pencereler aktif olabilir. Mouse ile bir üst pencerenin içindeki alt pencereye click yapıldığında alt pencerenin en üst penceresi otomatik olarak aktif pencere yapılır. Girdi odağı(input focus) klavye mesajlarının gönderileceği penceredir. Girdi odağı bir alt pencereye verilebilir. Özetle bir üst pencerenin içerisinde bir alt pencere varsa ve alt pencerenin üzerine click yapılmışsa girdi odağını alan pencere alt pencere olur ama o pencerenin en üst penceresi aktif pencere yapılır. Girdi odağını herhangi bir pencereye verebilmek için SetFocus API fonksiyonu kullanılır. Prototipi:
HWND SetFocus(
HWND hWnd
);
Fonksiyonun parametresi girdi odağının verileceği pencerenin handle'ıdır. Fonksiyonun geri dönüş değeri girdi odağına sahip olan eski pencerenin handle'ıdır.
Girdi odağı bir alt pencereye verilirse o pencerenin en üs penceresi otomatik olarak aktif pencere olur. Girdi odağının hangi pencerede olduğu GetFocus API fonksiyonuyla alınabilir. Prototipi:
HWND GetFocus(void);
Fonksiyon girdi odağına sahip olan pencerenin handle'ıyla geri döner.
Sanal Karakter Kodları(Virtual Key Codes)
Klavyedeki özel tuşların ASCII kodları 0'dır. Bu tuşlar birbirlerinden scan code'larıyla ayrılabilir. Scan code'ları ise genel olarak donanıma bağlı bir biçimde belirlenmektedir. Windows sistemlerinde değişik donanıma sahip klavyeler bağlanabilir. Bu klavyeler üzerindeki özel tuşların scan code'ları birbirlerinden farklı olabilir. Örneğin 101/102 tuşlu IBM klavyelerinde F1 tuşunun extended scan code'u 0x3B'dir. Windows sisteminin pek çok değişik donanımlarda çalışabileceği varsayılmıştır. Eğer özel tuşların scan code'ları doğrudan program içerisinde kullanılsa önemli bir taşınabilirlik problemi çıkar. Örneğin:
if (ch == 0x3B) {
....
....
}
Burada ch ile F1 tuşu karşılaştırılmaya çalışılmıştır. Çünkü F1 tuşunun extended scan code'u 0x3B'dir. Ancak bu program farklı bir donanımda çalıştırılmak istendiğinde o donanımdaki F1 tuşunun scan code'u 0x3B olmayabilir. Bunun çözümü için sanal karakter kodları tasarlanmıştır. Basılan tuşun bilgisi klavye sürücüsü tarafından standart sanal karakter kodlarına dönüştürülmektedir. Program içerisinde programcı donanım ne olursa olsun hep sanal karakter kodlarını kullanır. Çünkü scan code'dan sanal karakter koduna dönüşüm yapılması aşağı seviyede çalışan klavye sürücüsü tarafından yapılmaktadır. Windows.h içerisinde VK_XXX biçiminde bütün tuşların sanal karakter kodları sembolik sabitler biçiminde tanımlanmıştır. Sonuç olarak özel tuşlar kullanılacaksa onların sanal karakter kodları kullanılmalıdır. Alfabetik ve sayısal karakterlerin sanal karakter kodları ASCII kodlarıyla aynıdır. Bunlar için özel bir VK_ tanımlamasına gerek duyulmamıştır.
Klavye Mesajları
Klavyede bir tyuşa basıldığında WM_KEYDOWN, çekildiğinde ise WM_KEYUP mesajları gönderilir. Sisteme ilişkin özel tuşlar için ayrıca WM_SYSKEYDOWN ve WM_SYSKEYUP mesajları gönderilir. Bu mesajalrın parametreleri şöyledir: wParam parametresine basılan tuşun sanal karakter kodu yerleştirilir. lParam parametresi aşağıdaki gibi düzenlenmiştir:
Yineleme sayısı normal olarak 1 olur. Sistem çok yavaşladığında bir tuşa sürekli basılma durumunda iki tuşa basılma arasında anı tuşun bilgisini farklı bir mesaj biçimin değil, aynı mesajın yinelenme sayısı biçiminde kodlayabiliyoruz. OEM scan code klavyenin orijinal scan code'udur.
WM_CHAR Mesajı
WM_CHAR mesajı Windows tarafından kuyruğa gönderilmez. TranslateMessage API fonksiyonu tarafından çağırılır. Prototip:
BOOL TranslateMessage(
CONST MSG *lpMsg // address of structure with message
);
Bu fonksiyon isteğe bağlı olarak mesaj döngüsünün içerisine yerleştirilir. Yani bu fonksiyon mesaj dögüsünden kaldırılsa WM_CHAR mesajı oluşmaz. Bu fonksiyon kuyruktan alınan mesajı inceler, bu mesaj WM_KEYDOWN ise basılmış olan tuşun bir ASCII tuşu olup olmadığına bakar. Eğer basılan tuş bir ASCII tuşu ise bu tuş için WM_CHAR mesajını kuyruğa yerleştirir. Bu mesajın wParam parametresi basılan tuşun UNICODE karakter karşılığıdır(ASCII tablosu UNICODE tablosunun ilk 256 karakteridir). lParam WM_KEYDOWN mesajında olduğu gibi ilave bilgileri içerir.
Program içerisinde görüntülenmek istenen bir yazı söz konusuysa WM_KEYDOWN mesajı yerine WM_CHAR mesajının işlenmesi daha uygundur.
Örnek:
hListBox = CreateWindow("listbox", ...);
pOldWnddProc = (WNDPROC) GetClassLong(....);
SetWindowLong(...NewWndProc...);
LRESULT CALLBACK NewWndProc(
HWND hWnd,
UINT message,
WPARAm wParam,
LPARAM lParam)
{
if (message == WM_KEYDOWN && wParam == VK_ENTER) {
SendMessage(GetParent(hWnd), WM_COMMAND, MAKELONG(getDlgID(hWnd), LBN_DBLCLK), (LPARAM)hWnd);
}
return CallWndProc(...pOldWndProc...);
}
Listbox kontrolünde enter tuşuna basıldığında sanki doubleclick yapılmış gibi seçme işleminin sağlanması yukarıdaki gibi bir işlemle sağlanabilir.
Modeless Dialog Pencereleri
Modal bir dialog penceresi DialogBox API fonksiyonuyla açılır, EndDialog API fonksiyonuyla kapatılır. Modal bir dialog penceresine girildiğinde dialog penceresinin gerçek pencere fonksiyonu mesaj kuyruğunu kontrol altına alarak yalnızca dialog işlemlerinin yapılmasını sağlar. Oysa modeless dialog pencereleri mesaj döngüsünü kontrol altına almaz. Ayrı bir üst pencere gibi çalışmaktadır. Modeless dialog pencerelerinin mesajları ana mesaj döngüsü yoluyla işlenir. Modeless bir dialog penceresi CreateDialog API fonksiyonuyla yaratılır. Bu fonksiyon kaynaktan(resource) hareketle bütün kontrolleri yaratır ve çalışmasını bitirir. Tabii pencere görünür durumda kalır. Modeless dialog pencerelerinin mesajları normal bir pencere gibi ana mesaj döngüsünden hareketle ele alınmaktadır. Kullanıcı açısından bakıldığında modal ve modeless dialog pencereleri arasında önemli farklar vardır. Modal dialog penceresi açıldığında dialog penceresi dışında diğer pencereler için hiçbir işlem yapılamaz(Kapatılana kadar). Oysa modeless dialog pencereleri açıldığında başka pencerelerle ilgili işlemler gerçekleştirilebilir. Modeless dialog pencereleri çok nadir kullanılır(Tipik uygulaması Find & Replace). Modeless dialog pencereleri "Owned Window" olarak yaratılır. Yani asıl pencerenin dışına sürüklenebilir. Ana pencere minimize edildiğinde o da minimize edilir. Modeless dialog pencereleri normal bir pencere gibi DestroyWindow API fonksiyonuyla kapatılır.CreateDialog API fonksiyonunun prototipi:
HWND CreateDialog(
HINSTANCE hInstance, // handle to application instance
LPCTSTR lpTemplate, // identifies dialog box template name
HWND hWndParent, // handle to owner window
DLGPROC lpDialogFunc // pointer to dialog box procedure
);
Tıpkı modal dialog pencerelrinde olduğu gibi modeless dialog pencerelerin de bir pencere fonksiyonları vardır. Kontroller yine gerçek pencere fonksiyonunun WM_CREATE mesajında oluşturulur. Programcının son parametre olarak belirttiği pencere fonksiyonu gerçek pencere fonksiyonu değildir, onun tarafından çağırılacak yapay pencere fonksiyonudur.
Modeless Dialog Penceresine İlişkin UygulamaFırtça yaratmak için kullanılan CreateBrushIndirect fırçanın pek çok özelliğini belirlememize olanak sağlanmaktadır. Bu fonksiyonun alternatifi olarak CreateSolidBrush fonksiyonu kullanılabilir. Bu fonksiyon yalnızca fırçanın rengini belirlememize olanak sağlar. Prototipi:
HBRUSH CreateSolidBrush(
COLORREF crColor // brush color value
);
Uygulamanın menü yapısı:
Bir dialog penceresinin yeniden açıldığında en son görüntüyü koruması bazen istenebilir. Bunun için kontrol bilgileri çıkarken global değişkenlere atanır. WM_INITDIALOG mesajında da o değerlerle tekrar kontroller konumlandırılır.
IsDialogMessage API FonksiyonuModeless dialog pencereleri için ayrıca mesaj döngüsünün içerisinde IsDialogMessage API fonksiyonunun çağırılması gerekir. Eğer bu fonksiyon çağırılmazsa modeless dialog penceresi pek çok işlemini yapar ancak özellikle klavye ile ilgili işlemlerde başarısız olur. Bu fonksiyon mesajın modeless dialog penceresine ilişkin olup olmadığını tespit eder, eğer ilişkinse bazı ek işlemlerden sonra kendi içerisinde TranslateMessage ve DispatchMessage fonksiyonlarına sokar. Eğer kuyruktan alınan mesaj bir modeless dialog penceresine ilişkinse ayrıca TranslateMessage ve DispatchMessage fonksiyonlarına sokulmamalıdır. Fonksiyonun geri dönüş değeri 0 ise mesaj modeless dialog penceresine ilişkin değildir, o halde işlenmelidir. 0 dışı bir değer ise mesaj modeless dialog penceresine ilişkindir işlenmemelidir. Genellikle modeless dialog pencerelerinin handle değerleri global değişkenlerde saklanır. Başlangıçta bu global değişkenin içerisine NULL değeri koymak kontrol açısından uygundur. özetle bir modeless dialog penceresi açılacaksa programın mesaj döngüsü şöyle olmalıdır:
while (GetMessage(&message, 0, 0, 0)) {
if (hModelessDlg == 0 || !IsDialogMessage(hModelessDlg, &message)) {
TranslateMessage(&message);
DispatchMessage(&message);
}
}
SetDlgItemInt ve GetDlgItemInt FonksiyonlarıBu fonksiyonlar aslında SetDlgItemText ve GetDlgItemText fonksiyonlarının başka biçimleridir. SetDlgItemInt fonksiyonu bir int sayıyı parametre olarak alır. Bunu yazısal biçime dönüştürerek SetDlgItemText ile pencere yazısını değiştirir. GetDlgItemInt fonksiyonu da pencere yazısını alarak bunu atoi fonksiyonundan geçirir ve bir int sayı biçiminde programcıya verir. Bu fonksiyonlar özellikle edit ve static kontrollerinde kullanılmaktadır.
Timer İşlemleriEğer programcı isterse belirlenen bir periyodla kuyruğa WM_TIMER isimli mesaj Windows tarafından gönderilebilir. Bu sayede periyodik işlem gerektiren durumların üstesinden gelinebilir. Örneğin belirli aralıklarla bir işlemi yürüten programlar ya da saat programları bu mesajdan faydalanılarak yazılırlar. Mesajın yollanacağı periyod milisaniye mertebesinde belirlenebilir. Buradaki periyod uzunluğu sistemin çalışmasına bağlı olarak ileri ve geri yönde sapmalar gösterebilir. Zamana dayalı kritik işlemler bu mesaj kullanılarak gerçekleştirilemez. Sistemin WM_TIMER mesajını kuyruğa bırakabilmesi için SetTimer API fonksiyonuyla bir timer yaratmak gerekir. Prototipi:
UINT SetTimer(
HWND hWnd, // handle of window for timer messages
UINT nIDEvent, // timer identifier
UINT uElapse, // time-out value
TIMERPROC lpTimerFunc // address of timer procedure
);
Fonksiyonun birinci parametresi mesajın gönderileceği pencerenin handle değeridir. Fonksiyonun ikinci parametresi yaratılacak timer'ın ID değerdir. Eğer daha önce bu ID değeriyle bir timer yaratılmışsa eskisi silinerek yenisi geçerli olur. Bu değer genellikle sembolik sabit olarak tanımlanır. Bir programın birden fazla timer yaratması durumunda bu ID değeri timer'ların tespit edilmesi amacıyla kullanılır. Windows bir timer'ın zamanı geldiğinde kuyruğa WM_TIMER mesajını bırakır ve mesajın wParam parametresine ID değerini yerleştirir. Programcı da bir switch içerisinde mesajın hangi timer dolayısıyla gönderildiğini anlar. Bu değer aynı zamanda bir handle değeri gibi kullanılmaktadır. Örneğin timer yok edilirken bu ID değeri kullanılmaktadır. Fonksiyonun üçünü parametresi milisaniye cinsinden gönderilme periyodunu belirtir. Fonksiyonun son parametresi timer fonksiyonunun baişlangıç adresini alır. Bu değer NULL girilebilir. Bu fonksiyonun aşağıdaki gibi bir parametrik yapıya sahip olması gerekir:
VOID CALLBACK TimerProc(HWND, UINT, WPARAM, LPARAM);
Fonksiyonun geri dönüş değeri başarı durumunda ikinci parametresiyle girilen ID değerinin aynısıdır. Başarısızlık durumunda 0'dır. Fonksiyonun başarısının kontrol edilmesinde fayda vardır.
WM_TIMER MesajıBu mesajın wParam parametresine timer'ın ID değeri, lParam parametresine ise SetTimer fonksiyonunun son parametresi olarak geçirilen fonksiyonun adresi yerleştirilir.
Yaratılan timer çalışma bitirilince KillTimer fonksiyonuyla yok edilir. Prototipi:
BOOL KillTimer(HWND hWnd, UINT ID);
Örnek programda timer WM_CREATE mesajında yaratılmış, WM_DESTROY mesajında da yok edilmiştir.
SetTimer fonksiyonun son parametresi NULL yerine belirtilen parametre yapısına sahip bir fonksiyonun başlangıç adresi olarak girilirse periyod dolduğunda doğrudan o fonksiyon çağırılır.Bu durumda WM_TIMER mesajı etkisiz kalır. Aslında bu durumda mesaj yine kuyruğa bırakılır, ancak DispatchMessage API fonksiyonu pencere fonksiyonunu çağırmak yerine bu fonksiyonu çağırır.
SetTimer fonksiyonunun birinci parametresi de NULL girilebilir. Bu durumda son parametre mutlaka girilmelidir. DispatchMmessage API fonksiyonu belirtilen fonksiyonu çağırır.
Bir BMP Çizimi Yapmanın Adımları1. Bitmap resource'ta çizilir, ya da FindResource fonksiyonuyla doğrudan dosyadan yüklenir. LoadBitmap fonksiyonuyla bitmap'e ilişkin handle elde edilir.
2. WM_PAINT içerisinde CreateCompatibleDC ile bellek için DC elde edilir ve SelectObject fonksiyonuyla bu DC için bitmap seçilir.
3. BitBlt fonksiyonuyla bellek tabalı DC'den normal DC'ye kopyalama yapılır.