Standart C Programlama Dili


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 Bölü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 -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.