Windows API

List View Kontrolü

► Kontroller

List View kontrolü bir seri öğeyi (item) görüntülemeye yarayan bir kontrol penceresidir.

List View kontrolü, CreateWindowEx() fonksiyonunun lpClassName parametresi içinde WC_LISTVIEW değeri ile tanımlanan bir kontroldür.

WC_LISTVIEW değerini kullanmak için commctrl.h başlık dosyasını programınıza dahil etmeniz gerekir.

List View kontrolü oluşturma

Şimdi, programımıza rapor görünümünde bir List View kontrolü eklemeye çalışalım.

Aşağıdaki satırlar programın başına global değerler olarak eklenir:

#define IDC_LISTVIEW01 101 /* List View için tanımlayıcı makro bildirimi */

HWND hwndListView01;/* List View kontrolü için pencere Handle değeri bildirimi */

WindowProcedure() fonksiyonu içinde renkli olarak gösterilen satırlar ile bir List View kontrolü oluşturulur.

LRESULT CALLBACK WindowProcedure (HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)
{
  switch (message)
  { 
    case WM_CREATE:
         CreateListView(hwnd);						 
         break;

    case WM_DESTROY:
         PostQuitMessage (0);
         break;

    default:
         return DefWindowProc (hwnd, message, wParam, lParam);
  }

  return 0;
}

void CreateListView(HWND hwndParent)
{
  hwndListView01 = CreateWindowEx(WS_EX_CLIENTEDGE , WC_LISTVIEW, "",
                                  WS_CHILD | LVS_REPORT | WS_VISIBLE,
                                  20, 20, 325, 200, hwndParent,
                                  (HMENU)IDC_LISTVIEW01, ghInst, NULL);
}

List View kontrol penceresi oluşturulurken, CreateWindowEx() fonksiyonunun lpClassName parametresi için sistemdeki ön tanımlı değerlerden biri olan WC_LISTVIEW değeri, dwStyle parametresinde ise LVS_REPORT değeri kullanılır.

Program çalıştığında:

  • Program penceresi oluşturulur oluşturulmaz WM_CREATE mesajı mesaj işlem fonksiyonuna gönderilir. switch yapısının bu seçeneği altında CreateWindowEx() fonksiyonu ile bir adet List View kontrolü oluşturulur.

Programın çalışan en son hali aşağıdadır:

#include <windows.h>
#include <commctrl.h>

#define IDC_LISTVIEW01 101

LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM);
void CreateListView(HWND hwndParent);

char szClassName[ ] = "WinAPIWindowsApp";
HINSTANCE ghInst;

HWND hwndListView01;

int WINAPI WinMain (HINSTANCE hThisInstance, 
                    HINSTANCE hPrevInstance,
                    LPSTR lpszArgument,
                    int nCmdShow)
{
  HWND hwndMain;
  MSG messages;
  WNDCLASSEX wincl;
  
  ghInst = hThisInstance;

  wincl.hInstance = hThisInstance;
  wincl.lpszClassName = szClassName;
  wincl.lpfnWndProc = WindowProcedure;
  wincl.style = CS_DBLCLKS;
  wincl.cbSize = sizeof (WNDCLASSEX);

  wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
  wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
  wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
  wincl.lpszMenuName = NULL; 
  wincl.cbClsExtra = 0; 
  wincl.cbWndExtra = 0; 
  wincl.hbrBackground = GetSysColorBrush(COLOR_3DFACE);

  if (!RegisterClassEx (&wincl)) return 0;

  hwndMain = CreateWindowEx (0, szClassName, "WinAPI ListView Uygulaması", WS_OVERLAPPEDWINDOW,
                             CW_USEDEFAULT, CW_USEDEFAULT, 500, 300, HWND_DESKTOP, NULL,
                             hThisInstance, NULL);

  if (!hwndMain) return 0;

  ShowWindow (hwndMain, nCmdShow);
  UpdateWindow(hwndMain);

  while (GetMessage (&messages, NULL, 0, 0) > 0) {
    TranslateMessage(&messages);
    DispatchMessage(&messages);
  }	

  return messages.wParam;
}

LRESULT CALLBACK WindowProcedure (HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)
{
  switch (message)
  { 
    case WM_CREATE:
         CreateListView(hwnd);
         break;

    case WM_DESTROY:
         PostQuitMessage (0);
         break;

    default:
         return DefWindowProc (hwnd, message, wParam, lParam);
  }

  return 0;
}

void CreateListView(HWND hwndParent)
{
  hwndListView01 = CreateWindowEx(WS_EX_CLIENTEDGE , WC_LISTVIEW, "",
                                  WS_CHILD | LVS_REPORT | WS_VISIBLE,
                                  20, 20, 325, 200, hwndParent,
                                 (HMENU)IDC_LISTVIEW01, ghInst, NULL);
}

Program çalıştığında karşınıza gelecek ekran görüntüsü aşağıdadır:

List View kontrolüne sütun ekleme

Artık, List View kontrolüne sütun (başlık) ekleme işlemlerine başlayabiliriz.

Sütunlar, List View kontrolü rapor görünümünde tanımlandığında, öğeleri ve alt öğeleri görüntülemek için kullanılır.

List View kontrolüne bir sütun eklemek için, öncelikle bir LVCOLUMN yapısı tanımlamanız, daha sonra, LVM_INSERTCOLUMN mesajını veya ListView_InsertColumn makrosunu, bir sütun silmek için ise, LVM_DELETECOLUMN mesajını kullanabilirsiniz.

List View kontrolüne 80 piksel genişliğinde 4 adet sütun ekleyen CreateListView() fonksiyonunun yeni içeriği aşağıdadır:

void CreateListView(HWND hwndParent)
{
  char cdizi[12];
  LVCOLUMN lvc;
  int idCol=4;

  hwndListView01 = CreateWindowEx(WS_EX_CLIENTEDGE , WC_LISTVIEW, "",
                                  WS_CHILD | LVS_REPORT | WS_VISIBLE,
                                  20, 20, 325, 200, hwndParent,
                                 (HMENU)IDC_LISTVIEW01, ghInst, NULL);
								 
  /* LVCOLUMN structure için ilk değer atama işlemlerini yapar. */
  lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
  lvc.cx = 80;           /* Sütun genişliği değeri */
  lvc.fmt = LVCFMT_LEFT; /* Sütunları sol tarafa ayarlar */

  /* Sütun ekleme işlemleri */
  for (id1=0; id1<idCol; id1++) {
       lvc.iSubItem = id1;
       sprintf(cdizi, "%s%d", "Column", id1+1);
       lvc.pszText = cdizi;
       /* Sütun ekleme işlemi */
       ListView_InsertColumn(hwndListView01, id1, &lvc);
  }
}

Farklı veri türündeki değişken ve sabitleri bir karakter dizisi içinde birleştirmeye yarayan sprintf() fonksiyonu için programınızın başına aşağıdaki başlık dosyasını ekleyin:

#include <stdio.h>

List View kontrolü için sütun tanımlamalarında kullanılan LVCOLUMN yapısı aşağıdadır:

typedef struct _LVCOLUMN {
  UINT   mask;
  int    fmt;
  int    cx;                   /* Sütun genişliği (piksel olarak) */
  LPTSTR pszText;              /* Sütun adı */ 
  int    cchTextMax;           /* pszText boyutu  */ 
  int    iSubItem;             /* Sütunun alt öğe indeksi  */ 
#if (_WIN32_IE >= 0x0300)
  int    iImage;
  int    iOrder;
#endif 
#if (_WIN32_WINNT >= 0x0600)
  int    cxMin;                /* Sütunların minimum genişliği (piksel olarak) */
  int    cxDefault;
  int    cxIdeal;
#endif 
} LVCOLUMN, *LPLVCOLUMN;

LVCOLUMN yapısı ile ilgili detaylı bilgilere buradan ulaşabilirsiniz.

Yeniden düzenlediğiniz kodlarla program çalıştırdığınızda, karşınıza gelecek ekran görüntüsü aşağıdadır. Yeni eklenen kodlar lacivert renk ile gösterilmiştir.

List View kontrolüne satır ekleme

List View kontrolüne öğe eklemek için, öncelikle bir LVITEM yapısı tanımlamanız, daha sonra, LVM_INSERTITEM mesajı veya ListView_InsertItem makrosu kullanmanız gerekir. Rapor görünümünde, subitem için bir metin değeri mutlaka tanımlanmalıdır.

List View kontrolüne 5 adet öğe ekleyen CreateListView() fonksiyonunun yeni içeriği aşağıdadır:

void CreateListView(HWND hwndParent)
{
  char cdizi[12];
  LVCOLUMN lvc;
  LVITEM lvI;
  int idCol=4, idItem=5;
  int id1, id2;

  hwndListView01 = CreateWindowEx(WS_EX_CLIENTEDGE , WC_LISTVIEW, "",
                                  WS_CHILD | LVS_REPORT | WS_VISIBLE,
                                  20, 20, 325, 200, hwndParent,
                                 (HMENU)IDC_LISTVIEW01, ghInst, NULL);
								 
  /* LVCOLUMN structure için ilk değer atama işlemlerini yapar. */
  lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
  lvc.cx = 80;           /* Sütun genişliği değeri */
  lvc.fmt = LVCFMT_LEFT; /* Sütunları sol tarafa ayarlar */

  /* Sütun ekleme işlemleri */
  for (id1=0; id1<idCol; id1++) {
       lvc.iSubItem = id1;
       sprintf(cdizi, "%s%d", "Column", id1+1);
       lvc.pszText = cdizi;
       /* Sütun ekleme işlemi */
       ListView_InsertColumn(hwndListView01, id1, &lvc);
  }

  
  /* LVITEM structure için ilk değer atama işlemlerini yapar. */
  lvI.mask      = LVIF_TEXT | LVIF_STATE;
  lvI.stateMask = 0;
  lvI.iSubItem  = 0;
  lvI.state     = 0;

  /* Satır ekleme işlemleri */
  for (id1=0; id1<idItem; id1++) {
       lvI.iItem  = id1;
       sprintf(cdizi, "%s%d", "Item", id1+1);
       lvI.pszText   = cdizi;
       /* İlk sütun için öğe ekleme işlemleri */
       ListView_InsertItem(hwndListView01, &lvI);

       /* Diğer sütunlar için öğe ekleme işlemleri */
       for (id2=1; id2<idCol; id2++) {
            sprintf(cdizi, "%s%d-%d", "SubItem", id1+1, id2+1);
            ListView_SetItemText(hwndListView01, id1, id2, cdizi);
       }
  }
  
}

LVITEM yapısı, List View kontrolü içindeki öğerlerin özelliklerini tanımlamak için kullanılır. LVITEM yapısının içeriği aşağıdadır:

typedef struct {
  UINT   mask;           
  int    iItem;         /* Öğenin List View kontrolü içindeki indeksi */ 
  int    iSubItem;      /* Alt öğe indeksi */ 
  UINT   state;
  UINT   stateMask;
  LPTSTR pszText;       /* Öğe adı */
  int    cchTextMax;    /* pszText boyutu  */ 
  int    iImage;
  LPARAM lParam;
#if (_WIN32_IE >= 0x0300)
  int    iIndent;
#endif 
#if (_WIN32_WINNT >= 0x0501)
  int    iGroupId;
  UINT   cColumns;
  UINT   puColumns;
#endif 
#if (_WIN32_WINNT >= 0x0600)
  int    piColFmt;
  int    iGroup;
#endif 
} LVITEM, *LPLVITEM;

LVCOLUMN yapısı ile ilgili detaylı bilgilere buradan ulaşabilirsiniz.

Yeniden düzenlediğiniz kodlarla program çalıştırdığınızda, karşınıza gelecek ekran görüntüsü aşağıdadır:

List View kontrolüne görsel stil ekleme

Öncelikle, CreateWindowEx() fonksiyonu ile List View kontrolünü oluşturduğumuz satırın hemen altına, aşağıdaki işlem satırını ekleyerek, List View kontrolüne tüm satırı seçebilme ve grid çizgilerini gösterme özelliğini ekleyelim:

ListView_SetExtendedListViewStyle(hwndListView01, LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);

ListView_SetExtendedListViewStyle makrosunu veya LVM_SETEXTENDEDLISTVIEWSTYLE kullanarak List View kontrolü için ekstra özellikler tanımlayabilirsiniz. Ancak, bu yöntemlerden birini kullanmak için aşağıdaki satırları programınızın başına eklemeniz gerekir.

#define _WIN32_IE 0x0600    
#define _WIN32_WINNT 0x0501 
#define WINVER 0x0501       

#include <windows.h>

Yukarıdaki değerler programınızın derlenirken kullanılacak olan Windows başlık dosyalarının sürümünü tanımlar. Bu bildirimler aşağıdaki satırdan önce yapılmalıdır:

#include <windows.h>

List View kontrolüne stil uygulamak için, burada gösterilen manifest dosyasını Application.manifest adıyla projenizde kaynak kodların bulunduğu dizine kaydedip, aynı dizinde bulunan resource.rc dosyasının içine aşağıdaki satırı eklemeniz yeterlidir:

resource.rc

1 24 "Application.manifest"

Yukarıda gösterilen kodları ekledikten sonra, programı çalıştırdığınızda, karşınıza gelecek ekran görüntüsü aşağıdadır:

List View kontrolü Custom Draw işlemleri

Custom draw yöntemini kullanarak, List View kontrolünün başlık bilgileri ile öğe bilgilerinin görünümlerini kendi isteklerinize göre değiştirebilirsiniz.

List View kontrolü için custom draw işlemleri gerçekleştirmek istediğinizde, kontrolün içinde bulunduğu ana pencereye WM_NOTIFY mesajı yoluyla, kontrol tarafından gönderilen NM_CUSTOMDRAW bildirim mesajına işlem yapılır.

1. List View kontrolünün içinde bulunduğu ana kontrol fonksiyonunun WM_NOTIFY mesaj bildirimi altında öncelikle; lParam değişkeninde yer alan NMHDR yapısı içindeki ((LPNMHDR)lParam)->code bildirim kodunun, NM_CUSTOMDRAW bildirim mesajı içerip içermediği kontrol edilir.

typedef struct tagNMHDR {
  HWND hwndFrom;     /* Mesajı gönderen kontrolün pencere değeri */
  UINT_PTR idFrom;   /* Mesajı gönderen kontrolün ID değeri */
  UINT code;         /* Bildirim kodu */
} NMHDR;

NMHDR yapısı ile ilgili detaylı bilgilere buradan ulaşabilirsiniz.

2. ((LPNMHDR)lParam)->hwndFrom değeri kontrol edilir.

3. Eğer ((LPNMHDR)lParam)->hwndFrom değeri ListView_GetHeader(hwndListView01) fonksiyonu ile elde edilen List View başlık değerine eşit ise, NMLVCUSTOMDRAW yapısında bulunan nmcd değişkeni yoluyla erişim sağlanan NMCUSTOMDRAW yapısında bulunan değişken değerleri kullanılarak, List View kontrol başlıkları yazılır.

4. Eğer ((LPNMHDR)lParam)->hwndFrom değeri hwndListView01 değerine eşit ise, NMLVCUSTOMDRAW yapısında bulunan değişken değerleri kullanılarak, List View öğe ve alt öğeleri yazılır.

/* List View kontrolü öğe ve alt öğelerinin oluşturulmasında kullanılır. */

typedef struct tagNMLVCUSTOMDRAW {
  NMCUSTOMDRAW nmcd;      /* NMCUSTOMDRAW erişim değişkeni */
  COLORREF clrText;       /* Öğe metin rengi */
  COLORREF clrTextBk;     /* Öğe arka plan rengi */
#if (_WIN32_IE >= 0x0400)
  int iSubItem;           /* Alt öğe indeks değeri */
#endif 
#if (_WIN32_IE >= 0x0560)
  DWORD        dwItemType;
  COLORREF     clrFace;
  int          iIconEffect;
  int          iIconPhase;
  int          iPartId;
  int          iStateId;
  RECT         rcText;
  UINT         uAlign;
#endif 
} NMLVCUSTOMDRAW, *LPNMLVCUSTOMDRAW;

/* List View kontrolü başlık kısmının oluşturulmasında kullanılır. */

typedef struct tagNMCUSTOMDRAWINFO {
  NMHDR     hdr;             /* NMHDR erişim değişkeni */        
  DWORD     dwDrawStage;     /* Çizim safhası değişkeni */ 
  HDC       hdc;             /* Çizimin yapılacağı aygıt değeri */  
  RECT      rc;              /* Çizimin yapılacağı çerçeve değerleri */   
  DWORD_PTR dwItemSpec;      /* Sütun indeks değeri */  
  UINT      uItemState;
  LPARAM    lItemlParam;
} NMCUSTOMDRAW, *LPNMCUSTOMDRAW;

NMLVCUSTOMDRAW yapısı ile ilgili detaylı bilgilere buradan ve NMCUSTOMDRAW yapısı ile ilgili detaylı bilgilere buradan ulaşabilirsiniz.

List View kontrolünün başlık ile öğe ve alt öğelerini Custom Draw yöntemiyle çizmek için kullanılan kodlar aşağıdadır:

case WM_NOTIFY:
{
   switch (((LPNMHDR)lParam)->code) {
      case NM_CUSTOMDRAW:
      {
        LPNMLVCUSTOMDRAW lplvcd = (LPNMLVCUSTOMDRAW)lParam;
        HBRUSH hBrush;
        /* Listview sütun (başlık) çizimi */
        if (((LPNMHDR)lParam)->hwndFrom == ListView_GetHeader(hwndListView01)) {
            char cdizi[12];
            COLORREF bgColor, textColor;
			
            switch (lplvcd->nmcd.dwDrawStage) {
               case CDDS_PREPAINT :
                    return CDRF_NOTIFYITEMDRAW;

               case CDDS_ITEMPREPAINT:
                    SetBkMode(lplvcd->nmcd.hdc, TRANSPARENT);
                    /* lplvcd->nmcd.dwItemSpec değeri sütun indeks değerini göstermektedir. */
                    switch (lplvcd->nmcd.dwItemSpec) {
                       case 0:
                            bgColor = RGB(218, 218, 255);
                            textColor = RGB(180, 150, 50);
                            break;
                       case 1:
                            bgColor = RGB(240, 160, 210);
                            textColor = RGB(220, 80, 130);
                            break;
                       case 2:
                            bgColor = RGB(190, 240, 60);
                            textColor = RGB(240, 190, 20);
                            break;
                       case 3:
                            bgColor = RGB(240, 200, 70);
                            textColor = RGB(150, 150, 255);
                            break;

                       default:
                            return CDRF_DODEFAULT;
                    }

                    hBrush = CreateSolidBrush(bgColor);
                    SetTextColor (lplvcd->nmcd.hdc, textColor);
                    SelectObject(lplvcd->nmcd.hdc, hBrush);
                    FillRect(lplvcd->nmcd.hdc, &lplvcd->nmcd.rc, NULL);
                    sprintf(cdizi, "%s%d", "Column", (int)lplvcd->nmcd.dwItemSpec+1);
                    TextOut(lplvcd->nmcd.hdc, lplvcd->nmcd.rc.left+5, lplvcd->nmcd.rc.top+5, cdizi, strlen(cdizi));
                    DeleteObject(hBrush);

                    return CDRF_SKIPDEFAULT;

               default:
                    return CDRF_DODEFAULT;
            }
        }

        else if (((LPNMHDR)lParam)->hwndFrom == hwndListView01) {
            switch (lplvcd->nmcd.dwDrawStage) {
               case CDDS_PREPAINT :
                    return CDRF_NOTIFYITEMDRAW;

               /* Tüm item satırına (subitem'lar dahil) aynı işlemi yapmak için */
               case CDDS_ITEMPREPAINT:
                    
                    return CDRF_NOTIFYSUBITEMDRAW; /* Her bir subitem'a farklı işlem yapmak için */			       

               case CDDS_SUBITEM | CDDS_ITEMPREPAINT:
               {
                    /* Sadece report mod için yukarıda CDRF_NOTIFYSUBITEMDRAW geri döndürülünce
                    subitem işlemleri yapılır. */ 
                    COLORREF colText;
                    COLORREF colTextBk;

                    switch (lplvcd->iSubItem) {
                       case 0:
                            colTextBk = RGB(150, 150, 255);
                            colText = RGB(190, 240, 60);
                            break;
                       case 1:
                            colTextBk = RGB(220, 80, 130);
                            colText = RGB(240, 160, 160);
                            break;
                       case 2:
                            colTextBk = RGB(160, 210, 50);
                            colText = RGB(190, 240, 60);
                            break;
                       case 3:
                            colTextBk = RGB(180, 150, 50);
                            colText = RGB(240, 200, 70);
                            break;
                    }

                    lplvcd->clrTextBk = colTextBk;
                    lplvcd->clrText = colText;

                    return CDRF_DODEFAULT;
               }
               default:
                    return CDRF_DODEFAULT;
            }
        }
      }
      break;
   }
}
break; 

NMCUSTOMDRAW yapısı içindeki dwDrawStage değişkeni, NM_CUSTOMDRAW mesajı alındığında, hem başlık hem de öğe çiziminde, aşağıda gösterilen çizim safhalarından hangisinde bulunduğumuzu göstermektedir.

Global değerler:

  • CDDS_POSTERASE Silme işlemi tamamlandıktan sonra
  • CDDS_POSTPAINT Boyama işlemi tamamlandıktan sonra
  • CDDS_PREERASE Silme işlemi başlamadan önce
  • CDDS_PREPAINT Boyama işlemi başlamadan önce

Öğelere özgü değerler:

  • CDDS_ITEM Silme işlemi tamamlandıktan sonra
  • CDDS_ITEMPOSTERASE Boyama işlemi tamamlandıktan sonra
  • CDDS_ITEMPOSTPAINT Silme işlemi başlamadan önce
  • CDDS_ITEMPREERASE Boyama işlemi başlamadan önce
  • CDDS_ITEMPREPAINT Boyama işlemi başlamadan önce
  • CDDS_SUBITEM Boyama işlemi başlamadan önce

Bu boyama safhalarında istediğimiz değişiklikleri yapabiliriz. Yaptığımız işlemlerden sonra, işletim sistemi tarafından yapılması gereken işleri gösterebilmek amacıyla aşağıdaki bilgilendirme mesajlarından bir tanesi döndürülür:

  • CDRF_DODEFAULT Kontrola çizimi tamamen kendisinin yapacağını bildirir.
  • CDRF_DOERASE Kontrol sadece arka planı çizer.
  • CDRF_NEWFONT Kontrol uygulamanızın tanımladığı yeni fontu kullanır (CDDS_ITEMPREPAINT safhasında).
  • CDRF_NOTIFYITEMDRAW Kontrol öğe ile ilgili çizim işlemlerinde ana kontrole bildirim gönderir (CDDS_PREPAINT safhasında).
  • CDRF_NOTIFYPOSTERASE Kontrol öğeyi sildikten sonra ana kontrole bildirim gönderir (CDDS_PREPAINT safhasında).
  • CDRF_NOTIFYPOSTPAINT Kontrol, kontrolün tüm boyama işlemleri tamamlandığında, NM_CUSTOMDRAW bildirim kodu gönderir (CDDS_PREPAINT safhasında).
  • CDRF_NOTIFYSUBITEMDRAW Uygulamanız, her bir List View alt öğesi çizilmeden önce, dwDrawStage değişkeninin CDDS_ITEMPREPAINT | CDDS_SUBITEM değeri aldığı bir NM_CUSTOMDRAW bildirim kodu alır. Bu durumda, her bir alt öğe için, font ve arka plan rengi tanılayabilir veya ön tanımlı işlemlerin yapılması için CDRF_DODEFAULT değeri döndürebilirsiniz (CDDS_ITEMPREPAINT safhasında).
  • CDRF_SKIPDEFAULT Uygulamanız kontrolun tamamını çizer (CDDS_ITEMPREPAINT safhasında).
  • CDRF_SKIPPOSTPAINT Kontrol öğe etrafındaki çerçeveyi çizmez.

Yeniden düzenlediğiniz kodlarla program çalıştırdığınızda, karşınıza gelecek ekran görüntüsü aşağıdadır:

Başlık bilgileri çizimi

List View kontrolü için sütun (başlık) değerlerinin çizimi yapılırken, dwDrawStage değişkeni, CDDS_PREPAINT değerini aldığında, CDRF_NOTIFYITEMDRAW değerini geri döndürerek, öğeler (sütun başlıkları) ile ilgili yapılacak çizimler hakkında bildirim almak istediğimizi bildiriyoruz.

dwDrawStage değişkeni, CDDS_ITEMPREPAINT değerini aldığında, çizim işlemini tamamen program yapar ve CDRF_SKIPDEFAULT değerini döndürerek, kontrol tarafından herhangi bir boyama işlemi yapılmaması gerektiğini kontrola bildirir. Diğer bütün safhalar için CDRF_DODEFAULT değerini döndürerek, kontrola tüm boyama işlemlerini kendisinin yapması gerektiğini bildirir.

Çizim işlemini yaparken, önce, SetBkMode() fonksiyonu ile arka plan TRANSPARENT hale getirilerek, yazılacak metin tarafından etkilenmemesi sağlanır. lplvcd->nmcd.dwItemSpec değeri ile sırası belirlenen her sütun için farklı bir arka plan ve metin rengi atanır. CreateSolidBrush() fonksiyonu ile bir fırça değeri oluşturulur. SetTextColor() fonksiyonu ile metin rengi belirlenir. SelectObject() fonksiyonu ile kontrol DC (Device Context) için oluşturulan fırça değeri seçilir. FillRect() fonksiyonu, seçilen fırçayı kullanarak içi dolu bir çerçeve çizer. Daha sonra, TextOut() fonksiyonu ile sütun değerleri ekrana yazılır.

Öğe ve alt öğe bilgileri çizimi

List View kontrolü için öğe değerlerinin çizimi yapılırken, dwDrawStage değişkeni, CDDS_PREPAINT değerini aldığında, CDRF_NOTIFYITEMDRAW değerini geri döndürerek, öğeler ile ilgili yapılacak çizimler hakkında bildirim almak istediğimizi bildiriyoruz.

dwDrawStage değişkeni, CDDS_ITEMPREPAINT değerini aldığında, program herhangi bir işlem yapmadan CDRF_NOTIFYSUBITEMDRAW değerini döndürerek, CDDS_SUBITEM | CDDS_ITEMPREPAINT safhasında her bir sütun için öğelere farklı işlem yapmak istediğini bildirir. CDDS_SUBITEM | CDDS_ITEMPREPAINT safhasında ise, lplvcd->iSubItem değeri ile sırası belirlenen her sütun için farklı bir arka plan ve metin rengi atanır. Daha sonra, öğe ve alt öğe değerleri ekrana yazılır. Diğer bütün safhalar için CDRF_DODEFAULT değerini döndürerek, kontrola tüm boyama işlemlerini kendisinin yapması gerektiğini bildirir.

Başlık bilgileri çiziminde NMCUSTOMDRAW yapısında bulunan değişken değerlerinin, öğe ve alt öğe bilgileri çiziminde ise NMLVCUSTOMDRAW yapısında bulunan değişken değerlerinin kullanıldığına dikkat ediniz.

List View kontrolünde sütun genişletmeyi engelleme

List View kontrolü üzerinde kullanıcıların sütunları genişletmesini engellemek için, öncelikle hem List View kontrolü hem de List View kontrolü başlığı için Subclassing işlemi uygulamanız gerekir.

Aşağıdaki kodları programınıza ekleyerek, List View kontrolü sütunlarını genişletmeyi engelleyebilirsiniz:

/* Programın başına eklenecek fonksiyon bildirimleri */ 
void InitSubClassProcs (void);
LRESULT CALLBACK ListView01Proc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData);
LRESULT CALLBACK ListView01HeaderProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData);

/* Ana fonksiyon içine eklenecek fonksiyon çağrısı */ 

    case WM_CREATE:
         CreateListView(hwnd);						 
         InitSubClassProcs();
         break;

/* Fonksiyon içerikleri */ 
void InitSubClassProcs (void)
{
  SetWindowSubclass(hwndListView01, ListView01Proc, 0, 0);
  SetWindowSubclass(ListView_GetHeader(hwndListView01), ListView01HeaderProc, 0, 0);
}

/* List View kontrolü Subclassing fonksiyonu */
LRESULT CALLBACK ListView01Proc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
  switch (message) {
     case WM_NOTIFY:
     {
          switch (((LPNMHDR)lParam)->code) {
             /* Listview başlık yeniden boyutlandırma engelleme */ 
             case HDN_BEGINTRACKW:
             case HDN_BEGINTRACKA:
                  return TRUE;
                  break;
             }
     }
     break;
 }

  return DefSubclassProc(hwnd, message, wParam, lParam);
}

/* List View başlık kontrolü Subclassing fonksiyonu */
LRESULT CALLBACK ListView01HeaderProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
  switch (message) {
     /* Cursor'ın boyutlandırma işaretine dönüşmesini engelleme */
     case WM_SETCURSOR:
     {
        return TRUE;
     }

     /* Kullanıcının başlığı çift tıklama ile boyutlandırmasını engelleme */
     case WM_LBUTTONDBLCLK:
     {
        return 0;
     }
  }

  return DefSubclassProc(hwnd, message, wParam, lParam);
}

List View kontrolü için düzenlediğiniz fonksiyon içinde, WM_NOTIFY mesajı yoluyla, List View kontrolü başlık kontrolü tarafından, kullanıcı fareyi sol tuşuna basılı olarak başlık sütunları ara bölümleri üzerinde tuttuğunda, gönderilen HDN_BEGINTRACKW (Unicode) ve HDN_BEGINTRACKA (ANSI) bildirim kodlarına işlem yapılır. Bu safhada TRUE değeri döndürüldüğünde, yeniden boyutlandırma engellenmiş olur.

List View başlık kontrolü için düzenlediğiniz fonksiyon içinde ise, WM_SETCURSOR ve WM_LBUTTONDBLCLK mesajlarına işlem yapılır. WM_SETCURSOR mesajı için TRUE değeri döndürüldüğünde, Cursor'ın boyutlandırma işaretine dönüşmesi ve WM_LBUTTONDBLCLK mesajı için 0 değeri döndürüldüğünde, kullanıcının başlığı çift tıklama ile boyutlandırması engellenmiş olur.

Programı çalıştırdığınızda, fareyi başlık sütun ayırma bölgelerine getirseniz bile, fare işaretçisi değişmeyecek ve yeniden boyutlandırma yapılamayacaktır:

List View kontrolünde sütunlara göre sıralama

List View kontrolü üzerinde kullanıcılar başlıklara tıkladığında, List View içinde yer alan öğelerin, tıklanan sütunda yer alan verilere göre sıralanması için, List View kontrolünü içeren ana kontrol fonksiyonu içinde, WM_NOTIFY mesajı altında, LVN_COLUMNCLICK bildirim koduna işlem yapmanız gerekir.

Yeni kodları eklemeden önce, sıralama işlemlerini farklı veri türleri üzerinde gösterebilmeniz için, List View kontrolü içeriğini karakter dizisi, integer ve float değerlerle dolduracak CreateListView() fonksiyon içeriğini aşağıdaki şekilde değiştirmeniz gerekir:

void CreateListView(HWND hwndParent)
{
  char cdizi[12];
  LVCOLUMN lvc;
  LVITEM lvI;
  int idCol=4, idItem=5;
  int id1;
  char col1[5][10] = { "Zeki", "Ümit", "Çetin", "Buğra", "Şakir" };
  int col2[5] = { 21, 17, 38, 15, 42 };
  int col3[5] = { 74, 24, 12, 35, 9 };
  float col4[5] = { 17.84, 8.75, 25.45, 36.62, 10.25 };
  
  hwndListView01 = CreateWindowEx(WS_EX_CLIENTEDGE , WC_LISTVIEW, "",
                                  WS_CHILD | LVS_REPORT | WS_VISIBLE,
                                  20, 20, 325, 200, hwndParent,
                                 (HMENU)IDC_LISTVIEW01, ghInst, NULL);
								 
  ListView_SetExtendedListViewStyle(hwndListView01, LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);
								 
  /* LVCOLUMN structure için ilk değer atama işlemlerini yapar. */
  lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
  lvc.cx = 80;           /* Sütun genişliği değeri */
  lvc.fmt = LVCFMT_LEFT; /* Sütunları sol tarafa ayarlar */

  /* Sütun ekleme işlemleri */
  for (id1=0; id1<idCol; id1++) {
       lvc.iSubItem = id1;
       sprintf(cdizi, "%s%d", "Column", id1+1);
       lvc.pszText = cdizi;
       /* Sütun ekleme işlemi */
       ListView_InsertColumn(hwndListView01, id1, &lvc);
  }

  /* LVITEM structure için ilk değer atama işlemlerini yapar. */
  lvI.mask = LVIF_TEXT | LVIF_STATE  | LVIF_PARAM;
  lvI.stateMask = 0;
  lvI.iSubItem = 0;
  lvI.state = 0;
  
  /* Satır ekleme işlemleri */
  for (id1=0; id1<idItem; id1++) {
       lvI.iItem = id1;
       lvI.pszText = col1[id1];
       lvI.lParam = id1;
       /* İlk sütun için öğe ekleme işlemleri */
       ListView_InsertItem(hwndListView01, &lvI);

       /* Diğer sütunlar için öğe ekleme işlemleri */
       sprintf(cdizi, "%d", col2[id1]);
       ListView_SetItemText(hwndListView01, id1, 1, cdizi); /* 2. sütun int değer */
       sprintf(cdizi, "%d", col3[id1]);
       ListView_SetItemText(hwndListView01, id1, 2, cdizi); /* 3. sütun int değer */
       sprintf(cdizi, "%.2f", col4[id1]);
       ListView_SetItemText(hwndListView01, id1, 3, cdizi); /* 4. sütun float değer */
  }
}

1. Program, CreateListView() fonksiyonu içinde öncelikle, ilki karakter dizisi, 2 ve 3 üncüsü integer ve 4 üncüsü float değerler içeren beşer elemanlı 4 dizi tanımlar.

2. Daha önceden tanımlamış olduğumuz lvI.mask değişkenine LVIF_PARAM değerini ekler. Bu değer sıralama işleminin yapılabilmesi için gereklidir.

3. Sütunları aynı şekilde oluşturduktan sonra, lvI.lParam değişkenine öğe indeks değerini vererek, dizilerde yer alan değerleri kullanarak, öğe ve alt öğeleri oluşturur.

Aşağıdaki kodları programınıza ekleyerek, yukarıda belirtilen sıralama işlemlerini gerçekleştirebilirsiniz:

/* Programın başına eklenecek başlık dosyası */ 
#include <locale.h>

/* Programın başına eklenecek fonksiyon bildirimleri */ 
void OnColumnClick(LPNMLISTVIEW pLVInfo);
int CALLBACK CompareListViewItems(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort);

/* Programın ana fonksiyonuna eklenecek kodlar */ 
case WM_CREATE:
     setlocale(LC_COLLATE, "Turkish"); 
     CreateListView(hwnd);
     InitSubClassProcs();

	 break;

case WM_NOTIFY:
{
   switch (((LPNMHDR)lParam)->code) {      
      case LVN_COLUMNCLICK:
      {
           if (((LPNMHDR)lParam)->hwndFrom == hwndListView01) {
               OnColumnClick((LPNMLISTVIEW)lParam);
           }
           break;
      }
   }
}
break;

/* Sıralama fonksiyonu */ 
void OnColumnClick(LPNMLISTVIEW pLVInfo)
{
  /* Sıralamada kullanılan sütun ile sıralama şekli (azalan veya artan) bilgilerini takip etme */
  static int nSortColumn = 0;
  static BOOL bSortAscending = FALSE;
  LPARAM lParamSort;

  /* Eğer zaten sıralanmış bir sütuna tıklanmışsa, sıralama şeklini değiştirme
  Eğer sütuna ilk defa tıklanıyorsa, sıralama sütununu nSortColumn değişkenine ve
  sıralama şeklini bSortAscending değişkenine atama */
  if (pLVInfo->iSubItem == nSortColumn) bSortAscending = !bSortAscending;
  else {
     nSortColumn = pLVInfo->iSubItem;
     bSortAscending = FALSE;
  }

  /* Sıralama sütun sıra değerini lParamSort değişkenine atama
  Eğer azalan sıralama durumu varsa, lParamSort değişken değeri (-) hale getirilir. */
  lParamSort = 1 + nSortColumn;
  if (!bSortAscending) lParamSort = -lParamSort;

  /* ListView_SortItems() fonksiyonu yoluyla karşılaştırma fonksiyonu çağrılır. */
  ListView_SortItems(pLVInfo->hdr.hwndFrom, CompareListViewItems, lParamSort);
}

/* Karşılaştırma fonksiyonu */ 
int CALLBACK CompareListViewItems(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
  /* lParamSort değişkeninin (-) işaretini kontrol ederek sıralama şeklini belirleme */ 
  BOOL bSortAscending = (lParamSort > 0);
  int nColumn = abs(lParamSort);
  static LVFINDINFO lvf;
  static int nItem1, nItem2;
  static char cdizi1[MAX_PATH], cdizi2[MAX_PATH];

  lvf.flags = LVFI_PARAM;
  lvf.lParam = lParam1;
  nItem1 = ListView_FindItem(hwndListView01, -1, &lvf);

  lvf.lParam = lParam2;
  nItem2 = ListView_FindItem(hwndListView01, -1, &lvf);

  ListView_GetItemText(hwndListView01, nItem1, nColumn-1, cdizi1, sizeof(cdizi1));
  ListView_GetItemText(hwndListView01, nItem2, nColumn-1, cdizi2, sizeof(cdizi2));

  switch(nColumn-1) {
     /* İlk sütundaki karakter dizisi için */
     case 0: 
          if(bSortAscending) return(strcoll(cdizi1, cdizi2));
          else return(strcoll(cdizi1, cdizi2) * -1);
          break;
     /* Dördüncü sütundaki float değerler için */
     case 3:
          if(bSortAscending){
             if(atof(cdizi1) > atof(cdizi2)) return 1;
             else if(atof(cdizi1) == atof(cdizi2)) return 0;
             else return -1;
          }
          else {
             if(atof(cdizi1) > atof(cdizi2)) return -1;
             else if(atof(cdizi1) == atof(cdizi2)) return 0;
             else return 1;
          }
          break;
     /* İki ve üçüncü sütundaki int değerler için */
     default:	 
          if(bSortAscending){
             if(atoi(cdizi1) > atoi(cdizi2)) return 1;
             else if(atoi(cdizi1) == atoi(cdizi2)) return 0;
             else return -1;
          }
          else {
             if(atoi(cdizi1) > atoi(cdizi2)) return -1;
             else if(atoi(cdizi1) == atoi(cdizi2)) return 0;
             else return 1;
          }
          break;
  }
}

1. Programının başına eklenen locale.h başlık dosyasının amacı, WM_CREATE mesajı içindeki aşağıdaki komutu kullanabilmektir:

setlocale(LC_COLLATE, "Turkish");

Bu komutun amacı ise, karşılaştırma fonksiyonu içinde yer alan ve bilgisayardaki LC_COLLATE ayarlarına bağlı olarak karakter dizilerini karşılaştırmaya yarayan strcoll() fonksiyonunun, karşılaştırma işlemini Türkçe karakterleri dikkate alarak yapmasını sağlamaktır.

2. List View kontrolü başlıklarından herhangi birine tıkladığınızda, List View kontrolünün, içinde bulunduğu ana pencereye gönderdiği LVN_COLUMNCLICK bildirim mesajına, ana pencere WM_NOTIFY mesajı içinde işlem yapılır. Burada, lParam değeri parametre olarak geçirilerek OnColumnClick() fonksiyonu çağrılır.

3. OnColumnClick() fonksiyonu içinde;

  • Sıralamada kullanılan sütunu takip etmek için nSortColumn değişkeni, sıralama şekli (azalan veya artan) bilgilerini takip etmek için bSortAscending değişkeni ve sıralamada kullanılan sütun ile sıralama şeklini karşılaştırma fonksiyonuna aktarmak için lParamSort değişkeni tanımlanır.
  • Eğer zaten sıralanmış bir sütuna tıklanmışsa, sıralama şeklini değiştirilir (artan ise azalan, azalna ise artan olarak).
  • Eğer sütuna ilk defa tıklanıyorsa, sıralama sütun indeksi nSortColumn değişkenine ve sıralama şekli bSortAscending değişkenine atanır.
  • Sıralama sütun sıra değeri lParamSort değişkenine atanır.
  • Eğer azalan sıralama durumu varsa, lParamSort değişken değeri, karşılaştırma fonksiyonunda kullanmak üzere, (-) hale getirilir.
  • ListView_SortItems() fonksiyonu yoluyla karşılaştırma fonksiyonu çağrılır.

Karşılaştırma fonksiyonu içindeki, lParam1 parametresi karşılaştırma yapılacak ilk öğe değerini, lParam2 parametresi ise ikinci öğe değerini gösterir. Bu değerler, öğeler List View içine yerleştirilirken, LVITEM yapısının lParam değişkenine atanan değerlerdir. Bu nedenle, sıralama işleminin yapılabilmesi için, LVITEM yapısının lvI.mask değerine LVIF_PARAM değeri eklenmektedir.

Karşılaştırma fonksiyonu, sırlamaya göre, ilk değer ikincisinden önce gelmesi gerekiyorsa negatif, sonra gelmesi gerekiyorsa pozitif bir değer ve iki değer eşit ise sıfır değeri geri döndürmelidir.

4. CompareListViewItems() fonksiyonu içinde;

  • lParamSort değişkeninin (-) işareti kontrol edilerek sıralama şekli belirlenir.
  • ListView_FindItem() fonksiyonu ile, lParam1 ve lParam2 değişkenlerine geçirilen öğeler sırasıyla lvf.lParam değişkenine atanarak, List View kontrolü içinde aranır ve elde edilen öğe indeks değeri sırasıyla nItem1 ve nItem2 değişkenlerine atanır.
  • ListView_GetItemText() fonksiyonu ile her iki öğe indeksine karşılık gelen karakter dizisi elde edilerek, sırasıyla cdizi1 ve cdizi2 dizilerine atanır.
  • Sütunların içerdiği değişken türüne bağlı olarak;

a. Karakter dizisi içeriği olan ilk sütun ise, artan sıralama durumunda, karakter dizilerini karşılaştıran strcoll() fonksiyonunun, ilk karakter dizisi ikincisinden küçük ise negatif, büyük ise pozitif ve eşit ise sıfır değeri olarak döndürdüğü değeri aynı şekilde, azalan sıralama durumunda ise, (-1) ile çarparak döndürür.

b. Float değişken içeriği olan dördüncü sütun ise, atof() fonksiyonu ile karakter dizisinden float değişkene çevrilen değerleri karşılaştırır. Artan sıralama durumunda, ilk değer ikincisinden büyük ise 1 değerini, eşit ise sıfır değerini ve aksi takdirde (-1) değerini döndürür. Azalan sıralama durumunda ise, ilk değer ikincisinden büyük ise (-1) değerini, eşit ise sıfır değerini ve aksi takdirde 1 değerini döndürür.

c. Integer değişken içeriği olan iki ve üçüncü sütunlar ise, atoi() fonksiyonu ile karakter dizisinden integer değişkene çevrilen değerleri karşılaştırır. Artan sıralama durumunda, ilk değer ikincisinden büyük ise 1 değerini, eşit ise sıfır değerini ve aksi takdirde (-1) değerini döndürür. Azalan sıralama durumunda ise, ilk değer ikincisinden büyük ise (-1) değerini, eşit ise sıfır değerini ve aksi takdirde 1 değerini döndürür.

Program çalıştırdığınızda, hangi sütun başlığına tıklarsanız, program o sütunda yer alan değerleri artan şekilde ve diğer sütunlardaki öğeleri de buna uygun olarak sıralayacaktır. Aynı sütuna ikinci kez tıkladığınızda ise, sıralama şekli değişecektir.

List View kontrolünde Owner Draw işlemleri

List View kontrolündeki öğelerle ilgili yani satırlardaki tüm çizimleri program ile yapmak istediğinizde, List View kontrolünü, LVS_OWNERDRAWFIXED değeri ile oluşturmanız gerekir.

Ancak, List View kontrolünün tüm öğe çizimlerini program ile yapmak istediğinizde, WM_NOTIFY mesajı NM_CUSTOMDRAW bildirim kodu altındaki tüm işlemler, satır ve tüm satır seçimi tamamen devre dışı kalır.

List View kontrolünün tüm öğe çizimlerini program ile yapmak için kodları aşağıdaki şekilde değiştirmeniz gerekir:

hwndListView01 = CreateWindowEx(WS_EX_CLIENTEDGE , WC_LISTVIEW, "",
                                WS_CHILD | LVS_REPORT | WS_VISIBLE | LVS_OWNERDRAWFIXED,
                                20, 20, 325, 200, hwndParent,
                                (HMENU)IDC_LISTVIEW01, ghInst, NULL);

Owner Draw işlemi devreye girdiğinde, List View kontrolü, her bir öğe (item) için bir WM_DRAWITEM mesajı gönderir. Ancak, alt öğeler için ayrıca bir mesaj göndermez. Yani, bir satır için sadece tek bir mesaj gönderilir. DRAWITEMSTRUCT yapısının iItemData değişkeni tanımlanan öğe ile ilgili veriler içerir.

WM_DRAWITEM mesajı List View kontrolünün içinde bulunduğu ana kontrolün fonksiyonuna gönderilir. wParam değişkeni mesajı gönderen kontrolün, burada List View, ID değerini içerir. lParam değişkeni ise, çizim yapılacak öğe hakkında bilgiler içeren DRAWITEMSTRUCT yapısının adresini içerir.

typedef struct tagDRAWITEMSTRUCT {
  UINT      CtlType;     /* Kontrolün tipi */
  UINT      CtlID;       /* Kontrolün ID değeri */
  UINT      itemID;      /* Öğe indeks (sıra) değeri */
  UINT      itemAction;  /* Gerekli çizim işlemi */
  UINT      itemState;   /* Çizim işleminden sonra öğenin görüntü durumu */
  HWND      hwndItem;    /* Öğenin pencere değişkeni */
  HDC       hDC;         /* Öğe DC Handle değeri */
  RECT      rcItem;      /* Öğe çizim alanı */
  ULONG_PTR itemData;    /* Program tarafından tanımlanmış öğe değeri */
} DRAWITEMSTRUCT;

DRAWITEMSTRUCT yapısı ile ilgili detaylı bilgilere buradan ulaşabilirsiniz.

Aşağıdaki kodları programınızın ana fonksiyon içine ekleyerek, List View kontrolünün öğelerini Owner Draw yöntemiyle program tarafından çizebilirsiniz:

/* Programın ana fonksiyonuna eklenecek kodlar */ 
case WM_DRAWITEM:
{
   /* Eğer wParam değişkeni List View kontrolü ID'sine eşit ise */
   if (wParam == IDC_LISTVIEW01) {
       LPDRAWITEMSTRUCT lDraw = (LPDRAWITEMSTRUCT)lParam;
       char cdizi[10];
       int id1, id2;
       /* Kontrolün tipi List View ise */
       if (lDraw->CtlType==ODT_LISTVIEW) {
           /* Eğer satır seçilmiş ise tüm satır seçim çizimi yapar. */
           if(lDraw->itemState && ODS_SELECTED) {
              FillRect(lDraw->hDC, &lDraw->rcItem, CreateSolidBrush((COLORREF)RGB(190, 240, 60)));
              SetTextColor (lDraw->hDC, RGB(255, 255, 255));
           }
           else SetTextColor (lDraw->hDC, RGB(0, 180, 0));
           /* Bir satırda yer alan öğe ve alt öğeleri sırayla çizme */
           for (id1=0, id2=5; id1<4; id1++, id2+=80) {
                ListView_GetItemText(hwndListView01, lDraw->itemID, id1, cdizi, sizeof(cdizi));
                TextOut(lDraw->hDC, lDraw->rcItem.left+id2, lDraw->rcItem.top+2, cdizi, strlen(cdizi));
           }
       }
   }
}
break;

WM_DRAWITEM mesajının her satır için sadece bir kez gönderildiğine, alt öğeler için ayrıca gönderilmediğine dikkat ediniz.

1. WM_DRAWITEM mesajı içinde, program önce, wParam değişkeni List View kontrolü ID'sinin eşit olup olmadığını kontrol eder.

2. Eşitlik sağlanırsa, DRAWITEMSTRUCT yapısının değerlerini, yapılacak çizimlerde kullanmak üzere, lDraw değişkenine atar.

3. CtlType değişkeni ODT_LISTVIEW değerine eşit ise, eğer bir satır seçilmiş ise, tüm satır seçim çizimi yapar.

4. Sonra, bir satırda yer alan öğe ve alt öğeleri sırayla çizer.

Programı çalıştırdığınızda, ilk satırı seçtiğinizde karşınıza gelecek ekran görüntüsü aşağıdadır:

List View kontrolüne çalışma zamanında eklediğimiz satırı seçme ve ekranda gösterme

1. Öncelikle Burada gösterildiği gibi bir Windows API projesi oluşturalım. Projeyle birlikte otomatik olarak oluşturulan main.c dosyası içine aşağıda gösterilen kodları ekleyelim:

#define IDC_BUTTON 101

/* LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES parametrelerinin kullanımı için gerekli */
#define _WIN32_IE 0x0600
#define _WIN32_WINNT 0x0501
#define WINVER 0x0501

#include <commctrl.h> /* ListView işlemleri için */
#include <stdio.h>    /* sprintf() işlemleri için */

HWND hwndListView, hwndButton;
void CreateListView(HWND hwndParent);
void AddLineToListView(void);

case WM_CREATE:

     CreateListView(hwnd); /* Listview oluşturma */
     hwndButton = CreateWindowEx(0, "BUTTON", "Satır ekle", /* Buton penceresi oluşturma */
                               WS_CHILD | WS_VISIBLE, 540, 20, 120, 24,
                               hwnd, (HMENU) IDC_BUTTON, NULL, NULL);
     break;

case WM_COMMAND:

     if (HIWORD(wParam) == BN_CLICKED) {
         if (LOWORD(wParam) == IDC_BUTTON) {
             AddLineToListView(); /* ListView kontrolüne satır ekleme */
         }
     }
     break;

Yukarıdaki kodlarla bir ListView ve Button kontrolü oluşturulur. ListView kontrolüne program başlangıcında 25 satır eklenir. Butona tıkladığımızda ise ListView kontrolünün sonuna yeni bir satır eklenir.

2. Şimdi, ListView kontrolünü oluşturmak için kullanacağımız CreateListView() fonksiyonunu programa ekleyelim.

void CreateListView(HWND hwndParent)
{
  char cdizi[20];
  LVCOLUMN lvc;
  LVITEM lvI;
  int idCol=4, idItem=25;
  int id1, id2;

  hwndListView = CreateWindowEx(WS_EX_CLIENTEDGE, WC_LISTVIEW, "",
                                  WS_CHILD | WS_VISIBLE | LVS_REPORT | LVS_SHOWSELALWAYS,
                                  20, 20, 502, 200, hwndParent,
                                  0, 0, NULL);

  ListView_SetExtendedListViewStyle(hwndListView, LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);

  /* LVCOLUMN structure için ilk değer atama işlemlerini yapar. */
  lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
  lvc.cx = 120;           /* Sütun genişliği değeri */
  lvc.fmt = LVCFMT_LEFT;  /* Sütunları sol tarafa ayarlar */

  /* Sütun ekleme işlemleri */
  for (id1=0; id1<idCol; id1++) {
       lvc.iSubItem = id1;
       sprintf(cdizi, "%s%d", "Column", id1+1);
       lvc.pszText = cdizi;
       /* Sütun ekleme işlemi */
       ListView_InsertColumn(hwndListView, id1, &lvc);
  }

  /* LVITEM structure için ilk değer atama işlemlerini yapar. */
  lvI.mask = LVIF_TEXT | LVIF_STATE;
  lvI.stateMask = 0;
  lvI.iSubItem  = 0;
  lvI.state = 0;

  /* Satır ekleme işlemleri */
  for (id1=0; id1<idItem; id1++) {
       lvI.iItem  = id1;
       sprintf(cdizi, "%s%d", "Item", id1+1);
       lvI.pszText   = cdizi;
       /* İlk sütun için öğe ekleme işlemleri */
       ListView_InsertItem(hwndListView, &lvI);

       /* Diğer sütunlar için öğe ekleme işlemleri */
       for (id2=1; id2<idCol; id2++) {
            sprintf(cdizi, "%s%d-%d", "SubItem", id1+1, id2+1);
            ListView_SetItemText(hwndListView, id1, id2, cdizi);
       }
  }
}

CreateListView() fonksiyonu içinde LVS_SHOWSELALWAYS parametresini seçilen satırı sürekli gösterebilmek için kullanıyoruz.

Seçili satırın tamamının seçilebilmesi için ListView_SetExtendedListViewStyle() fonksiyonu ile birlikte LVS_EX_FULLROWSELECT parametresini kullanıyoruz.

Oluşturduğumuz ListView kontrolüne 4 sütun ve 25 satır ekliyoruz.

3. ListView kontrolüne satır eklemek için kullanacağımız AddLineToListView() fonksiyonunu programa ekleyelim.

void AddLineToListView(void)
{
  LVITEM lvI;
  SYSTEMTIME lt;
  char cdizi[20];
  int LVIndex, id1;

  lvI.mask = LVIF_TEXT | LVIF_STATE;
  lvI.stateMask = 0;
  lvI.iSubItem = 0;
  lvI.state = 0;

  SetFocus(hwndListView); /* Focus değerini ListView kontrolüne aktarma */

  GetLocalTime(&lt); /* Lokal zaman değerlerini alma */
  sprintf(cdizi, "%02d-%02d-%04d %02d:%02d:%02d\n", lt.wDay, lt.wMonth, lt.wYear, lt.wHour, lt.wMinute, lt.wSecond);

  lvI.iItem = ListView_GetItemCount(hwndListView); /* ListView satır sayısını alma */
  lvI.pszText = cdizi;
  LVIndex = ListView_InsertItem(hwndListView, &lvI); /* ListView kontrolüne satır ekleme */

  for (id1=1; id1<4; id1++) {
       sprintf(cdizi, "%s%d", "SubItem0", id1+1);
       ListView_SetItemText(hwndListView, LVIndex, id1, cdizi); /* ListView satır iç değerlerini ekleme */
  }

  ListView_SetItemState(hwndListView, -1, 0, LVIS_SELECTED); /* Tüm seçimleri silme */
  
  /* Eklenen satırı seçme */
  ListView_SetItemState (hwndListView, LVIndex, LVIS_FOCUSED | LVIS_SELECTED, LVIS_FOCUSED | LVIS_SELECTED);
  /* Eklenen satırın görünmesini sağlama */
  ListView_EnsureVisible(hwndListView, LVIndex, FALSE);
}

AddLineToListView() fonksiyonu içinde, SetFocus(hwndListView) fonksiyonu ile focus değeri hwndListView kontrolüne geçirilir.

GetLocalTime() fonksiyonu ile bilgisayar zaman değeri alınır.

ListView_GetItemCount() fonksiyonu ile elde edilen satır sayısı kullanılarak ListView_InsertItem() fonksiyonu ile yeni bir satır eklenir.

ListView_SetItemText() fonksiyonu ile zaman değerleri kullanılarak satır iç değerleri belirlenir.

ListView_SetItemState() fonksiyonu ile eklenen satırın seçilmesi sağlanır.

ListView_EnsureVisible() fonksiyonu ile eklenen satırın ekranda gösterilmesi sağlanır.

Yukarıdaki kodların tamamını eklediğimizde, main.c dosyasının en son hali aşağıdaki şekilde olacaktır.

main.c

#if defined(UNICODE) && !defined(_UNICODE)
    #define _UNICODE
#elif defined(_UNICODE) && !defined(UNICODE)
    #define UNICODE
#endif

#define IDC_BUTTON 101

/* LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES parametrelerinin kullanımı için gerekli */
#define _WIN32_IE 0x0600
#define _WIN32_WINNT 0x0501
#define WINVER 0x0501

#include <tchar.h>
#include <windows.h>
#include <commctrl.h> /* ListView işlemleri için */
#include <stdio.h>    /* sprintf() işlemleri için */

/*  Declare Windows procedure  */
LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM);

/*  Make the class name into a global variable  */
TCHAR szClassName[ ] = _T("DenemeApp");

HWND hwndListView, hwndButton;
void CreateListView(HWND hwndParent);
void AddLineToListView(void);

int WINAPI WinMain (HINSTANCE hThisInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR lpszArgument,
                     int nCmdShow)
{
    HWND hwnd;               /* This is the handle for our window */
    MSG messages;            /* Here messages to the application are saved */
    WNDCLASSEX wincl;        /* Data structure for the windowclass */

    /* The Window structure */
    wincl.hInstance = hThisInstance;
    wincl.lpszClassName = szClassName;
    wincl.lpfnWndProc = WindowProcedure;      /* This function is called by windows */
    wincl.style = CS_DBLCLKS;                 /* Catch double-clicks */
    wincl.cbSize = sizeof (WNDCLASSEX);

    /* Use default icon and mouse-pointer */
    wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
    wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
    wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
    wincl.lpszMenuName = NULL;                 /* No menu */
    wincl.cbClsExtra = 0;                      /* No extra bytes after the window class */
    wincl.cbWndExtra = 0;                      /* structure or the window instance */
    /* Use Windows's default colour as the background of the window */
    wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND;

    /* Register the window class, and if it fails quit the program */
    if (!RegisterClassEx (&wincl))
        return 0;

    /* The class is registered, let's create the program*/
    hwnd = CreateWindowEx (
           0,                       /* Extended possibilites for variation */
           szClassName,             /* Classname */
           _T("Deneme Uygulaması"), /* Title Text */
           WS_OVERLAPPEDWINDOW,     /* default window */
           CW_USEDEFAULT,           /* Windows decides the position */
           CW_USEDEFAULT,           /* where the window ends up on the screen */
           700,                     /* The programs width */
           375,                     /* and height in pixels */
           HWND_DESKTOP,            /* The window is a child-window to desktop */
           NULL,                    /* No menu */
           hThisInstance,           /* Program Instance handler */
           NULL                     /* No Window Creation data */
           );

    /* Make the window visible on the screen */
    ShowWindow (hwnd, nCmdShow);

    /* Run the message loop. It will run until GetMessage() returns 0 */
    while (GetMessage (&messages, NULL, 0, 0))
    {
        /* Translate virtual-key messages into character messages */
        TranslateMessage(&messages);
        /* Send message to WindowProcedure */
        DispatchMessage(&messages);
    }

    /* The program return-value is 0 - The value that PostQuitMessage() gave */
    return messages.wParam;
}

/*  This function is called by the Windows function DispatchMessage()  */
LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)                  /* handle the messages */
    {
        case WM_CREATE:

             CreateListView(hwnd); /* Listview oluşturma */
             hwndButton = CreateWindowEx(0, "BUTTON", "Satır ekle", /* Buton penceresi oluşturma */
                                       WS_CHILD | WS_VISIBLE, 540, 20, 120, 24,
                                       hwnd, (HMENU) IDC_BUTTON, NULL, NULL);
             break;

        case WM_COMMAND:

             if (HIWORD(wParam) == BN_CLICKED) {
                 if (LOWORD(wParam) == IDC_BUTTON) {
                     AddLineToListView(); /* ListView kontrolüne satır ekleme */
                 }
             }
             break;

        case WM_DESTROY:
             PostQuitMessage (0);       /* send a WM_QUIT to the message queue */
             break;
        default:                        /* for messages that we don't deal with */
             return DefWindowProc (hwnd, message, wParam, lParam);
    }

    return 0;
}

void CreateListView(HWND hwndParent)
{
  char cdizi[20];
  LVCOLUMN lvc;
  LVITEM lvI;
  int idCol=4, idItem=25;
  int id1, id2;

  hwndListView = CreateWindowEx(WS_EX_CLIENTEDGE, WC_LISTVIEW, "",
                                  WS_CHILD | WS_VISIBLE | LVS_REPORT | LVS_SHOWSELALWAYS,
                                  20, 20, 502, 200, hwndParent,
                                  0, 0, NULL);

  ListView_SetExtendedListViewStyle(hwndListView, LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);

  /* LVCOLUMN structure için ilk değer atama işlemlerini yapar. */
  lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
  lvc.cx = 120;           /* Sütun genişliği değeri */
  lvc.fmt = LVCFMT_LEFT;  /* Sütunları sol tarafa ayarlar */

  /* Sütun ekleme işlemleri */
  for (id1=0; id1<idCol; id1++) {
       lvc.iSubItem = id1;
       sprintf(cdizi, "%s%d", "Column", id1+1);
       lvc.pszText = cdizi;
       /* Sütun ekleme işlemi */
       ListView_InsertColumn(hwndListView, id1, &lvc);
  }

  /* LVITEM structure için ilk değer atama işlemlerini yapar. */
  lvI.mask = LVIF_TEXT | LVIF_STATE;
  lvI.stateMask = 0;
  lvI.iSubItem  = 0;
  lvI.state = 0;

  /* Satır ekleme işlemleri */
  for (id1=0; id1<idItem; id1++) {
       lvI.iItem  = id1;
       sprintf(cdizi, "%s%d", "Item", id1+1);
       lvI.pszText   = cdizi;
       /* İlk sütun için öğe ekleme işlemleri */
       ListView_InsertItem(hwndListView, &lvI);

       /* Diğer sütunlar için öğe ekleme işlemleri */
       for (id2=1; id2<idCol; id2++) {
            sprintf(cdizi, "%s%d-%d", "SubItem", id1+1, id2+1);
            ListView_SetItemText(hwndListView, id1, id2, cdizi);
       }
  }
}

void AddLineToListView(void)
{
  LVITEM lvI;
  SYSTEMTIME lt;
  char cdizi[20];
  int LVIndex, id1;

  lvI.mask = LVIF_TEXT | LVIF_STATE;
  lvI.stateMask = 0;
  lvI.iSubItem = 0;
  lvI.state = 0;

  SetFocus(hwndListView); /* Focus değerini ListView kontrolüne aktarma */

  GetLocalTime(&lt); /* Lokal zaman değerlerini alma */
  sprintf(cdizi, "%02d-%02d-%04d %02d:%02d:%02d\n", lt.wDay, lt.wMonth, lt.wYear, lt.wHour, lt.wMinute, lt.wSecond);

  lvI.iItem = ListView_GetItemCount(hwndListView); /* ListView satır sayısıını alma */
  lvI.pszText = cdizi;
  LVIndex = ListView_InsertItem(hwndListView, &lvI); /* ListView kontrolüne satır ekleme */

  for (id1=1; id1<4; id1++) {
       sprintf(cdizi, "%s%d", "SubItem0", id1+1);
       ListView_SetItemText(hwndListView, LVIndex, id1, cdizi); /* ListView satır iç değerlerini ekleme */
  }
  
  ListView_SetItemState(hwndListView, -1, 0, LVIS_SELECTED); /* Tüm seçimleri silme */

  /* Eklenen satırı seçme */
  ListView_SetItemState (hwndListView, LVIndex, LVIS_FOCUSED | LVIS_SELECTED, LVIS_FOCUSED | LVIS_SELECTED);
  /* Eklenen satırın görünmesini sağlama */
  ListView_EnsureVisible(hwndListView, LVIndex, FALSE);
}

Programı derleyip çalıştırdığımızda 4 sütunlu ve 25 satırlı bir ListView kontrolü oluşturulur. Butona tıkladığımızda ListView kontrolüne bir satır eklenir ve eklenen satırın seçilerek görünmesi sağlanır. Bu durumda, program aşağıdakine benzer bir görüntü oluşturacaktır: