D.3.2. Örnek Windows GUI Uygulaması
Bu kısımda çok basit bir Windows masaüstü (GUI) program örneği verilmektedir. Program temelde boş bir pencere oluşturup kullanıcının fare ile pencere içinde çizimler yapmasını sağlamaktadır; ayrıca çok basit bir menü ve bazı fonksiyon tuşlarına karşılık işlem yapabilme özelliği de eklenmiştir.
Bir Windows uygulamasının birden fazla kaynak dosyadan oluşabileceğine
dikkat edin. Burada verilen örnek en küçük bir Windows uygulamasına denk
geldiği için, aşağıda verilen kaynak dosya isim örneklerinin, en az sayıdaki
kaynak dosya adedi olduğunu düşünebilirsiniz. Bizim örneğimizde, kolaylık
olsun diye, tüm dosyaların isimleri aynı (wingui
) verilmiştir.
Bu örnek için, dosya tipleri (yani dosya isim uzantıları) dosyanın türünü
belirtmek için yeterlidir. Özetle:
wingui.h | Hem wingui.c C kodu dosyası, hem de
wingui.rc kaynak tanımları komut dosyası tarafından
kullanılan ortak tanımların bulunduğu başlık dosyası. |
wingui.ico | Program için kullanılacak ikon dosyası. Adı wingui.rc kaynak
dosyasında belirtilmiştir. Bunun için herhangi bir grafik dosyasını
ICO
tipine dönüştüren programlar kullanabileceğiniz gibi, örnek olarak
bu web sayfası için kullanılan ikon dosyasını
da indirip kullanabilirsiniz. |
wingui.rc | Derlenen programla birlikte bağlanacak olan wingui.res kaynak
dosyası için gerekli olan kaynak tanımları komut dosyası. Cygwin tarafından
sağlanan windres derleyicisi ile wingui.rc dosyası
derlenerek, bağlayıcının anlayabileceği wingui.res dosyasına
dönüştürülür. Kaynaklar, program dışında
tanımlanmasına rağmen, program tarafından kullanılabilen, menüler, ikonlar,
iletişim kutuları ve bit eşlemli grafikler gibi nesnelerdir. Bunlar
programdan bağımsız olarak oluşturulurlar, ancak sonuçta programın
yürütülebilir dosyasının içine bağlanırlar. |
wingui.c | Program mantığını oluşturan C kaynak kod dosyası. Şimdiye kadar
gördüğümüz programların aksine, bu dosyada main isminde
bir fonksiyon olmaz. Onun yerine Windows program mantığına uygun
yazılmış bir WinMain fonksiyonu olur. |
C kodu ve kaynak tanımları komut dosyalarının ayrı ayrı derlenmesi
ve daha sonra bağlanması gerekeceğinden, yukarıda bahsi geçen dosyaların
değişmesi durumunda, tekrar uygun derleme ve bağlama işlemlerini
otomatik olarak yapacak olan Makefile
adında bir
make dosyası da kullanılmıştır. Make dosyaları hakkında daha geniş bilgi
için Kısım B.4’e bakınız. Dosyanın içeriği şu
şekildedir:
# Windows GUI program hazırlama make dosyası. PR = wingui DERC = gcc DERW = x86_64-w64-mingw32-gcc BAGOP = -mwindows DEROP = $(BAGOP) -std=c17 -pedantic -Wall -s hepsi : $(PR)C $(PR)W $(PR)C $(PR)W : $(PR).c $(PR).res $($(subst $(PR),DER,$@)) $(DEROP) $^ -o $@ $(PR).res : $(PR).rc $(PR).h $(PR).ico windres --codepage=65001 -O coff -i $< -o $@ temiz : rm -f $(PR).o $(PR).res $(PR)C $(PR)W
Yukarıdaki make dosyasını, aşağıda verilen diğer kaynak dosyaları ile
birlikte, boş bir dizine indirip make
komutunu
girdiğinizde iki yürütülebilir dosya oluşacaktır:
(1) winguiW.exe
dosyası cygwin1.dll
’e
ihtiyaç duymayan x86_64-w64-mingw32-gcc
derleyicisi ile
derlenen programdır. (2) winguiC.exe
dosyası ise normal
gcc
derleyicisi ile derlenip, yürütme sırasında
cygwin1.dll
dosyasına ihtiyaç duyan programdır.
Daha detaylı bilgiler sonraki altkısımda verilmektedir. Normal
koşullarda make
komutunun çalıştıracağı komutlar şöyledir:
windres -O coff -i wingui.rc -o wingui.res gcc -mwindows -std=c17 -pedantic -Wall -s wingui.c wingui.res -o winguiC x86_64-w64-mingw32-gcc -mwindows -std=c17 -pedantic -Wall -s wingui.c wingui.res -o winguiW
Sisteminizde komutları çalıştırmadan, yukarıdaki komut
listesini almak için make -Bn
komutunu kullanabilirsiniz.
Hem wingui.c
C kodu dosyası, hem de wingui.rc
kaynak tanımları komut dosyası tarafından kullanılan ve
bu programa özgü oluşacak özel durumlar için tanımlanmış kodları barındıran
wingui.h
başlık dosyasının içeriği aşağıda verilmiştir:
#define IDM_YENICIZ 101 #define IDM_CIKIS 102 #define IDM_BILGI 201
Kaynak tanımları komut dosyası olan wingui.rc
dosyasının
içeriği aşağıda verilmiştir:
#include "wingui.h" /* uygulamaya özgü tanımlar */ #include <windows.h> /* yaygın Windows fonksiyonları */ WINGUI ICON "wingui.ico" WINGUI MENU { POPUP "&Dosya" { MENUITEM "&Yeni Ciz\tF5", IDM_YENICIZ MENUITEM SEPARATOR MENUITEM "&Cikis", IDM_CIKIS } POPUP "&Yardim" { MENUITEM "&Bilgi...\tF1", IDM_BILGI } } WINGUI ACCELERATORS { VK_F1, IDM_BILGI, VIRTKEY VK_F5, IDM_YENICIZ, VIRTKEY }
Bu dosyada ICON dosyasının adı (Satır 4); program tarafından kullanılacak olan MENU’nün yapısı ve karşılığında oluşacak durumlar (Satır 6-18); en son olarak da, klavyeden kullanılabilecek iki kısayol tuşu F1 ve F5 (Satır 20-24) tanımlanmıştır.
Programın C kodu ise aşağıda verilmiştir:
#include "wingui.h" /* uygulamaya özgü tanımlar */ #include <windows.h> /* yaygın Windows fonksiyonları */ const char szUygAdi [] = "WINGUI", /* uygulama adı */ szBaslik [] = "Basit Bir Cizim Programi", /* başlık çubuğu */ szBilgi [] = "Farenin sol tusuna basip" " surukleyerek cizim yapabilirsiniz..."; void pencere_temizle (HWND hWnd) { HDC hDC = GetDC(hWnd); PatBlt(hDC, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN), PATCOPY); ReleaseDC(hWnd, hDC); } /* pencere_temizle */ void cizgi_ciz (HWND hWnd, int fwKeys, int x, int y) /* Önceki ve mevcut fare konumu arasında çizgi çiz. */ { static int eskix = -1, eskiy = -1; if (fwKeys & MK_LBUTTON) { /* sol tuş basılmış */ HDC hDC = GetDC(hWnd); if (eskix!=-1 && eskiy !=-1) MoveToEx(hDC, eskix, eskiy, NULL); else MoveToEx(hDC, x, y, NULL); LineTo(hDC, eskix = x, eskiy = y); ReleaseDC(hWnd, hDC); } else eskix = eskiy = -1; } /* cizgi_ciz */ LRESULT CALLBACK pencere_fonk (HWND hWnd, UINT mesaj, WPARAM wParam, LPARAM lParam) /* ana pencereye gelen mesajları işleyen pencere fonksiyonu */ { switch (mesaj) { case WM_COMMAND: switch (LOWORD(wParam)) { case IDM_CIKIS: DestroyWindow(hWnd); break; case IDM_YENICIZ: pencere_temizle(hWnd); break; case IDM_BILGI: MessageBox(hWnd, szBilgi, szBaslik, MB_OK|MB_ICONINFORMATION); break; default: /* Diğer mesaj tiplerini Windows’a bırak. */ return DefWindowProc(hWnd,mesaj,wParam,lParam); } break; case WM_CREATE: MessageBox(NULL,szBilgi,szBaslik,MB_OK|MB_ICONINFORMATION); break; case WM_MOUSEMOVE: cizgi_ciz(hWnd, wParam, LOWORD(lParam), HIWORD(lParam)); break; case WM_DESTROY: PostQuitMessage(0); break; default: /* Diğer mesaj tiplerini Windows’a bırak. */ return DefWindowProc(hWnd, mesaj, wParam, lParam); } return 0; } /* pencere_fonk */ int WINAPI WinMain (HINSTANCE hThisInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nCmdShow) /* uygulamanın ana giriş noktası */ { HWND hWnd; MSG msg; HANDLE hAccelTable; WNDCLASS wcl; hWnd = FindWindow(szUygAdi, szBaslik); if (hWnd) { /* Bu uygulamanın sistemde tek bir kopyası çalışsın istiyoruz. */ /* Zaten çalışan başka bir kopya varsa, onu öne getirip, çık. */ if (IsIconic(hWnd)) ShowWindow(hWnd, SW_RESTORE); SetForegroundWindow(hWnd); return 0; } /* pencere sınıfı ilkleme işlemleri */ wcl.style = CS_HREDRAW | CS_VREDRAW; wcl.lpfnWndProc = (WNDPROC)pencere_fonk; wcl.cbClsExtra = 0; wcl.cbWndExtra = 0; wcl.hInstance = hThisInst; wcl.hIcon = LoadIcon (hThisInst, szUygAdi); wcl.hCursor = LoadCursor (NULL, IDC_ARROW); wcl.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wcl.lpszMenuName = szUygAdi; /* kaynak dosyasında tanımlı */ wcl.lpszClassName = szUygAdi; /* pencere sınıfı adı */ if (!RegisterClass(&wcl)) /* Pencere sınıfını kaydet. */ return 0; hWnd = CreateWindow( /* Ana program penceresini yarat. */ szUygAdi, /* pencere sınıfı adı */ szBaslik, /* pencere başlığı */ WS_OVERLAPPEDWINDOW,/* başlığı ve çerçevesi olan pencere */ CW_USEDEFAULT, /* yatay (x) başlangıç noktası */ CW_USEDEFAULT, /* dikey (y) başlangıç noktası */ CW_USEDEFAULT, /* pencere genişliği */ CW_USEDEFAULT, /* pencere yüksekliği */ NULL, /* ebeveyn veya sahip pencere tutamağı */ NULL, /* menü veya alt pencere kimlik tutamağı */ hThisInst, /* uygulama kopya tutamağı */ NULL /* pencere yaratım alanına gösterge */ ); if (hWnd==NULL) return 0; ShowWindow(hWnd, nCmdShow); /* Pencereyi görüntüle. */ UpdateWindow(hWnd); /* Pencereyi yenile. */ hAccelTable = LoadAccelerators(hThisInst, szUygAdi); /* Kısayolları yükle. */ /* WM_QUIT gelene kadar ana mesaj döngüsünü çalıştır. */ while (GetMessage(&msg, NULL, 0, 0)) if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) { TranslateMessage(&msg); /* klavye kullanımı için */ DispatchMessage(&msg); /* Windows’a kontrolü ver. */ } return msg.wParam; } /* WinMain */
Gördüğünüz gibi, Windows GUI programları kitabın diğer bölümlerinde verilen
örnek C programlarından oldukça farklıdır. Program içinde, Windows grafik
ortamına özel bir takım API fonksiyon kullanımları dışında,
main
fonksiyonu da bulunmaz. Onun yerine WinMain
isimli fonksiyon programın giriş noktasıdır. Bu fonksiyon başladığında,
uygulama penceresinin davranışı hakkında bazı bilgiler belirlenir.
En önemlisi de, bu örnekte pencere_fonk
adını verdiğimiz
bir fonksiyonun adresidir; bu fonksiyon, pencerenin davranışını,
görünüşünü, kullanıcıyla nasıl etkileşim kurduğunu vs. tanımlar.
Ardından, uygulama penceresi oluşturulur ve pencereyi tanımlayan bir tutamak
(hWnd
) alınır. Pencere başarıyla oluşturulup görüntülendikten
sonra, bir while
döngüsüne girilir. Program,
kullanıcı tarafından pencere kapatılıp uygulamadan çıkılıncaya kadar
bu döngüde kalır.
Uygulama mantığının çoğunun tanımlandığı yer olmasına rağmen, programın
pencere_fonk
fonksiyonunu doğrudan çağırmadığına dikkat edin.
Windows işletim sistemi, programınıza bir dizi mesaj
ileterek onunla
iletişim kurar. while
döngüsünün içindeki kod bu işlemi
yürütür. Program DispatchMessage
fonksiyonunu her çağırdığında,
dolaylı olarak Windows’un her mesaj için bir kez
pencere_fonk
’u çağırmasına neden olur.
Programı çalıştırdığınızda, farenin sol tuşuna basıp sürükleyerek
pencerede bazı çizgiler çizebilir, menü öğelerinin beklendiği gibi çalışıp
çalışmadığını test edebilir, pencereyi taşıyabilir, simge durumuna
küçültebilir, büyütebilir veya yeniden boyutlandırabilirsiniz. Bazı
durumlarda pencerede çizdiğiniz çizgilerin kaybolduğunu fark edeceksiniz.
Bunun nedeni, örneğin uygulama penceresinin boyutu değiştiğinde,
pencere içeriklerinin yeniden çizilmesi gerektiği, ancak bunun sorumluluğunun
mevcut uygulamada olmasıdır. Böyle bir ihtiyaç olduğunda, Windows uygulamaya
bir WM_PAINT
mesajı gönderir; uygulama da pencere içeriğini
yeniden görüntüleyerek bu mesaja yanıt verir. Ancak, örnek programımızda biz
bu mesajı işlemeyip DefWindowProc
’a bırakıyoruz (Satır 65).
DefWindowProc
fonksiyonu bir uygulamanın işlemediği herhangi bir
pencere mesajı için varsayılan işlemi sağlamak için varsayılan pencere
yöntemini çağırır. Böylece her mesajın işlenmesi sağlanmış olur. Dikkat
ederseniz, DefWindowProc
fonksiyonunun,
pencere_fonk
’un aldığı aynı parametrelerle çağrıldığını
göreceksiniz.
Silinen pencere içeriklerini yeniden görüntülemek için kullanılacak
değişik yöntemler olabilir. Bunlardan biri program tarafından alınan fare
komutlarının bir yerde saklanması ve tekrardan oynatılması
olabilir.
Diğer bir yöntem pencere görüntüsünün sürekli olarak ikinci bir kopyasının
bellekte tutulması ve ihtiyaç olduğunda ekrandaki pencereye kopyalanması
olabilir. Bu yöntemlerin tartışılması bu ekin kapsamı dışındadır.