Dr. Erhard Henkes (e.henkes@gmx.net) - C++ und MFC - Stand: 27.06.2006

zurück zum Inhaltsverzeichnis

zurück zum vorherigen Kapitel
 

Weitere Steuerelemente

Die klassischen Standard-Steuerelemente bis Windows 3.0 umfassen Button (verschiedene Typen), Edit Box, List Box, Combo Box und Scrollbar. Mit Windows 95 wurden die sogenannten common controls auf Basis commctrl.h und comctl32.dll eingeführt. Diese Elemente benötigen wegen der Ausrichtung auf die 32-bit-Version des Betriebssystems als Minimum Windows 3.1x mit Win32s.

Zu den Steuerelementen (alte Standardelemente in blauer Farbe) gehören nun insgesamt folgende Typen:


AnimationButtonCombo BoxComboBoxExDate and Time PickerDrag List BoxEditFlat Scroll BarHeaderHot KeyImage ListsIP Address ControlsList BoxList-ViewMonth CalendarPagerProgress BarProperty SheetsReBarRich EditScroll BarsStaticStatus BarsSysLinkTabToolbarToolTipTrackbarTree-ViewUp-Down


Die neueren Win32 common controls senden ihre Nachrichten an das Elternfenster typischerweise als WM_NOTIFY.
Die bereits aus 16-bit Windows bekannten Windows Standard-Steuerelemente senden Nachrichten als WM_COMMAND.


Listenelement (List View)

Zum Einstieg in ein Steuerelement (control), das seine Nachrichten mit WM_NOTIFY versendet und komplexe Möglichkeiten bietet, ist das Listenelement (ListView) gut geeignet.

Dies ist ein Listenelement, das in den MFC durch CListCtrl gesteuert wird. Erstellen Sie eine Dialog-Anwendung und platzieren Sie ein Listenelement in die Dialog-Ressource, wie oben abgebildet. Nach dem Kompilieren und Ausführen der Anwendung ergibt sich folgendes Bild:


In der Ressourcen-Datei stellt sich dieses Listenelement wie folgt dar:

BEGIN
    DEFPUSHBUTTON   "OK",IDOK,260,7,50,14
    PUSHBUTTON      "Abbrechen",IDCANCEL,260,23,50,14
    CONTROL         "List1", IDC_LIST1, "SysListView32", WS_BORDER | WS_TABSTOP, 16, 16, 229, 168
END

Zunächst noch eine Klärung, damit die klassische List Box nicht verwechselt wird mit dem hier besprochenen List View Control:

Platform SDK
List Boxes ListView Control
Window class
ListBox
SysListView32 (in Sourcecode: WC_LISTVIEW, ist definiert als "SysListView32")
MFC class
CListBox CListCtrl
MFC view class
keine CListView


Unser IDC_LIST1 hat also bisher die beiden Eigenschaften WS_BORDER (Rand wie oben sichtbar) und WS_TABSTOP (kann den Tastatur-Focus erhalten, wenn die TAB Taste gedrückt wird. Mit jedem TAB wechselt der Tastatur-Focus zum nächten Steuerelement mit dem WS_TABSTOP Style). Will man weitere Eigenschaften, so muss man dies unter Eigenschaften einstellen. Dort sind bisher folgende Eigenschaften "angeklickt": Sichtbar, Rand und Tabstopp.

Wir beschaffen uns nun mittels Assistent (Strg+W) eine Membervariable m_List1 und fügen in OnInitDialog(...) zunächst Spalten in das Listenelement ein:

m_List1.InsertColumn(0, "Spalte 1", LVCFMT_LEFT, -1, 0);
m_List1.InsertColumn(1, "Spalte 2", LVCFMT_LEFT, -1, 1);

Danach folgen zur Demonstration vier Elemente in unserem Listenelement:

int i;
i = m_List1.InsertItem(LVIF_TEXT, 0, "Eins Eins", 0, 0, 0, NULL);
m_List1.SetItem(i, 1, LVIF_TEXT, "Eins Zwei", 0, 0, 0, NULL);
i = m_List1.InsertItem(LVIF_TEXT, 1, "Zwei Eins", 0, 0, 0, NULL);
m_List1.SetItem(i, 1, LVIF_TEXT, "Zwei Zwei", 0, 0, 0, NULL);

Damit unsere Spalten nach dem Einfügen automatisch auf die richtige Breite gesetzt werden, folgt ein "Autosize":

m_List1.SetColumnWidth(0, LVSCW_AUTOSIZE);
m_List1.SetColumnWidth(1, LVSCW_AUTOSIZE);


Damit wir unsere in InsertColumn(...) festgelegten Spaltenüberschriften sehen, müssen wir vorher unser Listenelement unter Eigenschaften - Formate von Symbol auf Bericht (LVS_REPORT) umstellen. Es gibt als weitere Auswahl noch Minisymbol und Liste.

In der Dialog-Ressource sieht das wie folgt aus:

CONTROL "List1", IDC_LIST1, "SysListView32", LVS_REPORT | WS_BORDER | WS_TABSTOP, 16, 16, 229, 168

Nun haben wir den ersten Einstieg in eine einfache Report-Liste geschafft:



Schauen Sie sich bitte in der MSDN selbst ausgiebig die verwendeten Member-Funktionen

CListCtrl::InsertColumn(...)
CListCtrl::SetColumnWidth(...)
CListCtrl::InsertItem(...)
CListCtrl::SetItem(...)

bezüglich der verwendeten Parameter und bezüglich der Überladungen an.
Als Beispiel sehen Sie hier die Erklärungen für InsertColumn(...):

int CListCtrl::InsertColumn( int nCol, const LVCOLUMN* pColumn );
int CListCtrl::InsertColumn( int nCol, LPCTSTR lpszColumnHeading, int nFormat = LVCFMT_LEFT, int nWidth = -1, int nSubItem = -1 );

Return Value:
The index of the new column if successful or -1 otherwise.

Parameters:

nCol:                The index of the new column.
pColumn:             Address of an LVCOLUMN structure that contains the attributes of the new column.
lpszColumnHeading:   Address of a string containing the column’s heading.
nFormat:             Alignment of the column. It can be one of these values:
                     LVCFMT_LEFT, LVCFMT_RIGHT, or LVCFMT_CENTER.
nWidth:              Width of the column, in pixels.
                     If this parameter is -1, the column width is not set.

nSubItem:            Index of the subitem associated with the column.
                     If this parameter is -1, no subitem is associatied with the column.


typedef struct _LVCOLUMN
{

  UINT   mask;
  int    fmt;
  int    cx;
  LPTSTR pszText;
  int    cchTextMax;
  int    iSubItem;
  int    iImage;
  int    iOrder;
} LVCOLUMN, FAR *LPLVCOLUMN;

Nachrichten sind das Salz in der Suppe. Welche gibt es? Wie fängt und behandelt man diese?
Schauen wir zunächst die Nachrichtentypen an. Da gibt es drei Gruppen: NM_..., LVN_... und HDN_...


Nehmen wir doch gleich die erste Nachricht NM_CLICK und fügen eine Behandlungsroutine ein:

BEGIN_MESSAGE_MAP(CTest001Dlg, CDialog)
    //{{AFX_MSG_MAP(CTest001Dlg)
    ON_WM_SYSCOMMAND()
    ON_WM_PAINT()
    ON_NOTIFY(NM_CLICK, IDC_LIST1, OnClickList1)
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

void CTest001Dlg::OnClickList1(NMHDR* pNMHDR, LRESULT* pResult)
{
    MessageBox("Auf Listenelement geklickt.");    
    *pResult = 0;
}

Linke Mausklicks innerhalb des Listenelementes (nicht auf die Spaltenüberschriften!) werden damit abgefangen. Das sieht zunächst einfach aus, ist es aber genau genommen nicht, denn diese Nachricht wird vom Kindfenster als WM_NOTIFY an das Elternfenster gesendet:

aus MSDN:

NM_CLICK


pNMHDR = (LPNMHDR) lParam;

Notifies a control's parent window that the user has clicked the left mouse button within the control. NM_CLICK is sent in the form of a WM_NOTIFY message.

pNMHDR
Address of an NMHDR structure that contains additional information about this notification message.
typedef struct tagNMHDR 
{
HWND hwndFrom;
UINT idFrom;
UINT code;
} NMHDR;
hwndFrom
Window handle to the control sending a message.
idFrom
Identifier of the control sending a message.
code
Notification code. This member can be a control-specific notification code or it can be one of the common notification codes.



MFC liefert uns freundlicherweise in unserer Funktion bereits den Parameter pNMHDR, ein Zeiger auf diese Struktur NMHDR. Wir können damit weitere Informationen aus unserer Nachricht erhalten:

void CTest001Dlg::OnClickList1(NMHDR* pNMHDR, LRESULT* pResult)
{
    CString str;
    str.Format("Code: %i  IdFrom: %i  FensterFrom: %i",
        pNMHDR->code, pNMHDR->idFrom, pNMHDR->hwndFrom);
   
    MessageBox(str);
   
    *pResult = 0;
}

Damit erhalten wir beim Linksklick z.B.:



In resource.h finden wir die Zahl 1000 als Windows-interne ID für unser IDC_LIST1:

#define IDM_ABOUTBOX                    0x0010
#define IDD_ABOUTBOX                    100
#define IDS_ABOUTBOX                    101
#define IDD_TEST001_DIALOG              102
#define IDR_MAINFRAME                   128
#define IDC_LIST1                       1000

Das Windows-Handle für unser Listenelement ist die Zahl 8847746. Bei jedem Start ändert sich diese Zahl.
Jetzt bleibt noch der Code -2. Was hat dies zu bedeuten? Wo ist das genau definiert?

Die Antwort findet sich hier.
In  ...\Microsoft Visual Studio\VC98\Include\commctrl.h  findet man folgende Informationen:


typedef struct tagNMMOUSE
{

    NMHDR   hdr;
    DWORD   dwItemSpec;
    DWORD   dwItemData;
    POINT   pt;
    DWORD   dwHitInfo; // any specifics about where on the item or control the mouse is
} NMMOUSE, FAR* LPNMMOUSE;


typedef NMMOUSE NMCLICK;

#define NM_FIRST                (0U-  0U)       // generic to all controls

#define NM_LAST                 (0U- 99U)

#define NM_OUTOFMEMORY          (NM_FIRST-1)
#define NM_CLICK                (NM_FIRST-2)    // uses NMCLICK struct
#define NM_DBLCLK               (NM_FIRST-3)
#define NM_RETURN               (NM_FIRST-4)
#define NM_RCLICK               (NM_FIRST-5)    // uses NMCLICK struct
#define NM_RDBLCLK              (NM_FIRST-6)
#define NM_SETFOCUS             (NM_FIRST-7)
#define NM_KILLFOCUS            (NM_FIRST-8)
#define NM_CUSTOMDRAW           (NM_FIRST-12)
#define NM_HOVER                (NM_FIRST-13)
#define NM_NCHITTEST            (NM_FIRST-14)   // uses NMMOUSE struct
#define NM_KEYDOWN              (NM_FIRST-15)   // uses NMKEY struct
#define NM_RELEASEDCAPTURE      (NM_FIRST-16)
#define NM_SETCURSOR            (NM_FIRST-17)   // uses NMMOUSE struct
#define NM_CHAR                 (NM_FIRST-18)   // uses NMCHAR struct


Diese Nachrichten vom Typ NM_... gelten übrigens für sämtliche common controls.

Was wir noch nicht verstehen, ist, warum das mit dem NM_CLICK erst ab dem zweiten Linksklick funktioniert. Das schauen wir uns später genau an.

Also, wenn dieses NM_CLICK mittels WM_NOTIFY vom Listenelement an das Elternfenster gesendet wird, dann können wir das zusätzlich auch anders abfangen. Bitte fügen Sie noch mittels Assistent OnNotify(...) und folgenden Code ein:

BOOL CTest001Dlg::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
    if ( ((NMHDR*)lParam)->code == NM_CLICK)
        MessageBox("NMCLICK");
   
    return CDialog::OnNotify(wParam, lParam, pResult);
}

Nun erhalten wir zwei MessageBoxes, die eine aus OnNotify(...) und die andere aus
OnClickList1.
Was steht denn nun bei WM_NOTIFY zusätzlich in wParam?

WM_NOTIFY 

idCtrl = (int) wParam;
pNMHDR = (NMHDR*) lParam;

idCtrl
Identifier of the common control sending the message. This identifier is not guaranteed to be unique. An application should use the hwndFrom or idFrom member of the NMHDR structure (passed as the lParam parameter) to identify the control.
Neugierig, wie wir hoffentlich noch sind, testen wir das sofort:

BOOL CTest001Dlg::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
if ( ((NMHDR*)lParam)->code == NM_CLICK)
{
CString str;
str.Format( "idCtrl: %i", (int) wParam );
MessageBox("NMCLICK\n" + str);
}

return CDialog::OnNotify(wParam, lParam, pResult);
}




Wir sehen nun, dass uns bei der Struktur NMHDR, auf die lParam zeigt, nichts entgeht.
wParam enthält beim Listenelement nur das bereits bekannte 
pNMHDR->idFrom

Jetzt probieren wir das Nachrichtenabfangen auf andere Weise, denn wir schauen uns nun die Nachrichten an, die mittels WM_NOTIFY von unserem Listenelement an das Elternfenster gesendet werden. Wir bauen OnNotify so um, dass wir alle Nachrichten WM_NOTIFY von IDC_LIST1 vom Typ NM_ (0 bis -99) erhalten:

BOOL CTest001Dlg::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
    if ( wParam == IDC_LIST1 && ((NMHDR*)lParam)->code > -100 )
    {
        CString str;
        str.Format( "idCode: %i", ((NMHDR*)lParam)->code );
        MessageBox(str);
    }
   
    return CDialog::OnNotify(wParam, lParam, pResult);
}

Jetzt erhalten wir eine ganze Menge Nachrichten, die wir mittels commctrl.h "entziffern":

Beim Starten:
-12
NM_CUSTOMDRAW

Beim ersten Linksklick:
-16
NM_RELEASEDCAPTURE
 -8 NM_KILLFOCUS
 -7 NM_SETFOCUS

Beim zweiten Linksklick:
-16 NM_RELEASEDCAPTURE
 -2 NM_CLICK

Da die
MessageBoxes stören können, kommentieren wir diese aus und verwenden unsere Liste selbst als "Logger":

void CTest001Dlg::OnClickList1(NMHDR* pNMHDR, LRESULT* pResult)
{
    CString str;
    str.Format("Code: %i  IdFrom: %i  FensterFrom: %i",
        pNMHDR->code, pNMHDR->idFrom, pNMHDR->hwndFrom);
   
    //MessageBox(str);
   
    *pResult = 0;
}



BOOL CTest001Dlg::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
    if ( wParam == IDC_LIST1 && ((NMHDR*)lParam)->code > -12)
    {
        static int pos = 1;
        pos++;
        CString str;
        str.Format( "idCode: %i", ((NMHDR*)lParam)->code );
        m_List1.InsertItem(LVIF_TEXT, pos, str, 0, 0, 0, NULL);
    }
   
    return CDialog::OnNotify(wParam, lParam, pResult);
}

Wir müssen
-12 NM_CUSTOMDRAW (wird beim Neuzeichnen gesendet) aussparen, sonst wird es zu wild.
Die Ausgabe ist interessant:



Jetzt erkennen wir auch ganz klar, warum erst ab dem zweiten Klick die Nachricht NM_CLICK (-2) erscheint, denn beim ersten Klick wird lediglich der Focus (
NM_SETFOCUS) gesetzt. Die -3 (NM_DBLCLK) rührt von einem Doppelklick (-2 und -3) her.

Warum erfolgt
NM_CUSTOMDRAW so oft, dass wir es ausparen müssen? Ja, das liegt daran, dass Zeichenoperationen in unserem Control IDC_LIST1 stattfinden, logischerweise durch unser InsertItem(...). Sie sehen, das ist alles gar nicht so einfach. 

Das haben wir gleich! Wir machen unser erstes Listenelement weniger breit und setzen ein zweites Listenelement (IDC_LIST2 und m_List2 verwenden) rechts daneben, in das wir die Nachrichten des ersten insertieren.  Dann können wir uns den Nachrichten komplett öffnen und zunächst alle Nachrichten vom Typ NM_... (0 bis -99) abfangen.

    m_List1.InsertColumn(0, "Spalte 1", LVCFMT_LEFT, -1, 0);
    m_List1.InsertColumn(1, "Spalte 2", LVCFMT_LEFT, -1, 1);
       
    m_List2.InsertColumn(1, "Nachrichten", LVCFMT_LEFT, -1, 1);
   
    int i;
    i = m_List1.InsertItem(LVIF_TEXT, 0, "Eins Eins", 0, 0, 0, NULL);
    m_List1.SetItem(i, 1, LVIF_TEXT, "Eins Zwei", 0, 0, 0, NULL);
    i = m_List1.InsertItem(LVIF_TEXT, 1, "Zwei Eins", 0, 0, 0, NULL);
    m_List1.SetItem(i, 1, LVIF_TEXT, "Zwei Zwei", 0, 0, 0, NULL);

    m_List1.SetColumnWidth(0, LVSCW_AUTOSIZE);
    m_List1.SetColumnWidth(1, LVSCW_AUTOSIZE);


und

BOOL CTest001Dlg::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
    if ( wParam == IDC_LIST1 &&  ((NMHDR*)lParam)->code > -100  )
    {
        static int pos = 0;
        pos++;
        CString str;
        str.Format( "idCode: %i", ((NMHDR*)lParam)->code );
        m_List2.InsertItem(LVIF_TEXT, pos, str, 0, 0, 0, NULL);
        m_List2.SetColumnWidth(0, LVSCW_AUTOSIZE);
    }
   
    return CDialog::OnNotify(wParam, lParam, pResult);
}



Jetzt haben wir endlich die Wahrheit bezüglich NM_...! Wir sehen nun, dass beim Zeichnen von IDC_LIST1 beim Start der Anwendung  
NM_CUSTOMDRAW stattfindet und auch NM_RELEASEDCAPTURE nach jedem Mausklick statt findet.

NM_RELEASEDCAPTURE:
Informiert das Elternfenster eines Steuerelementes, dass das Steuerelement-Kindfenster die Maus ("mouse capture") wieder frei gibt.


Ändert man die erste Zeile wie folgt ab

if ( wParam == IDC_LIST1 )

dann erhält man alle Nachrichten und daher ganz zu Beginn noch zwei Mal -102. Da hilft wieder nur der Blick in commctrl.h:

#define NM_FIRST                (0U-  0U)       // generic to all controls
#define NM_LAST                 (0U- 99U)

#define LVN_FIRST               (0U-100U)       // listview
#define LVN_LAST                (0U-199U)

#define HDN_FIRST               (0U-300U)       // header
#define HDN_LAST                (0U-399U)

Es handelt sich bei -102 also um LVN_FIRST - 2.

#define LVN_INSERTITEM          (LVN_FIRST - 2)

Klar, diese Nachricht rührt von den beiden InsertItem(...) in OnInitDialog(...) her.

Obiges System ist gut geeignet zum Austesten weiterer Nachrichten.  Toben Sie sich aus und checken Sie commctrl.h.

Klickt man z.B. in den Spaltenkopf, erhält man  -108:
#define LVN_COLUMNCLICK         (LVN_FIRST - 8)

Betätigt man eine Taste, so erhält man -155:
#define LVN_KEYDOWN             (LVN_FIRST - 55)

Klickt man auf die Einträge in den Spalten, dann erscheint  - 100 und -101:
#define LVN_ITEMCHANGING        (LVN_FIRST - 0)
#define LVN_ITEMCHANGED         (LVN_FIRST - 1)

Nachfolgend einige LVN_... in der Übersicht:

--------------------------------------------------------------------------------

Nachricht                Bedeutung
--------------------------------------------------------------------------------
 
LVN_BEGINDRAG            A drag-and-drop operation involving the left mouse button is beginning.
LVN_BEGINLABELEDIT       A label-editing operation is beginning.
LVN_BEGINRDRAG           A drag-and-drop operation involving the right mouse button is beginning.
LVN_COLUMNCLICK          A column was clicked.
LVN_DELETEALLITEMS       A user has deleted all items from the control.
LVN_DELETEITEM           A user has deleted a single item from the control.
LVN_ENDLABELEDIT         A label-editing operation is ending.
LVN_GETDISPINFO          A request for the parent window to provide
                         information needed to display or sort a list view item.
LVN_INSERTITEM           A new item was inserted.
LVN_ITEMCHANGED          An item was changed.
LVN_ITEMCHANGING         An item is changing.
LVN_KEYDOWN              A key was pressed.
LVN_PEN                  Used for pen Windows (for systems with a pen and digitizer tablet).
LVN_SETDISPINFO          Forces the parent to update display information for an item.


Diese LVN_ sind im Gegensatz zu NM_ spezifisch für das Listenelement.


lParam zeigt übrigens auf etwas mehr als nur auf die für alle common controls einheitliche Struktur NMHDR, nämlich folgende umfassendere Struktur NMLISTVIEW:
typedef struct tagNMLISTVIEW 
{
NMHDR hdr;
int iItem;
int iSubItem;
UINT uNewState;
UINT uOldState;
UINT uChanged;
POINT ptAction;
LPARAM lParam;
} NMLISTVIEW, *LPNMLISTVIEW;

hdr
NMHDR structure that contains information about this notification message.
iItem
Identifies the list-view item, or -1 if not used.
iSubItem
Identifies the subitem, or zero if none.
uNewState
New item state. This member is zero for notification messages that do not use it.
uOldState
Old item state. This member is zero for notification messages that do not use it.
uChanged
Set of flags that indicate the item attributes that have changed. This member is zero for notifications that do not use it.
Otherwise, it can have the same values as the mask member of the LVITEM structure.
ptAction
POINT structure that indicates the location at which the event occurred.
This member is undefined for notification messages that do not use it.
lParam
Application-defined value of the item. This member is undefined for notification messages that do not use it.

Wir können lParam also sowohl nach NMHDR* als auch nach NMLISTVIEW* umwandeln. Wenn wir z.B. mit der Maus klicken, enthält NMHDR in code die Nachricht NM_CLICK und in NMLISTVIEW::POINT die Mausposition. Hier ein konkretes Beispiel:

BOOL CTest001Dlg::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
    if ( wParam == IDC_LIST1 && ((NMHDR*)lParam)->code == NM_CLICK )
    {
        static int pos = 0;
        pos++;
        CString str;
        str.Format( "x: %i  y: %i",
                  ((NMLISTVIEW*)lParam)->ptAction.x ,
                  ((NMLISTVIEW*)lParam)->ptAction.y );

        m_List2.InsertItem(LVIF_TEXT, pos, str, 0, 0, 0, NULL);
        m_List2.SetColumnWidth(0, LVSCW_AUTOSIZE);
    }
   
    return CDialog::OnNotify(wParam, lParam, pResult);
}

Damit haben wir nun den Überblick über die Nachricht WM_NOTIFY und deren Parameter in Zusammenhang mit dem Listenelement:
In wParam findet sich die ID des Listenelements, und lParam zeigt auf eine Struktur vom Typ NMLISTVIEW, die wir entsprechend in den Datentyp NMHDR oder
NMLISTVIEW umwandeln können.

Mit diesem Wissen über die Nachricht WM_NOTIFY und die Struktur NMLISTVIEW bewaffnet, können wir z.B. in einer Funktion OnClickList1(...), die gezielt NM_CLICK von IDC_LIST1 abfängt, folgenden einfachen Code verwenden, um an die Mausposition zu gelangen:

void CTest001Dlg::OnClickList1(NMHDR* pNMHDR, LRESULT* pResult)
{
    CString str;
    str.Format("x: %i  y: %i",
        ((NMLISTVIEW*)pNMHDR)->ptAction.x,
        ((NMLISTVIEW*)pNMHDR)->ptAction.y  );
   
    MessageBox(str);
   
    *pResult = 0;
}

Nachdem wir uns intensiv mit dem newsflow vom Listenelement zum Elternfenster auseinander gesetzt haben und wir in der Lage sind, die gesendeten Informationen auszuwerten, wenden wir uns nun verstärkt dem Element selbst zu.

Beginnen wir mit den möglichen Styles unseres Listenelementes:

Style
Wirkung
LVS_ICON Zeigt große Symbole (Icons)
LVS_SMALLICON Zeigt kleine Symbole (Icons)
LVS_LIST Zeigt die Items als Liste mit kleinen Symbolen (Icons)
LVS_REPORT Zeigt alle "Details" der Items, wenn möglich

Dies kann man bei der Erstellung in der Dialog-Ressource einstellen bzw. während der Laufzeit ändern mit

BOOL CWnd::ModifyStyle( DWORD dwRemove, DWORD dwAdd, UINT nFlags = 0)

Die Spaltenüberschriften eines Listenelementes sind nur sichtbar, wenn der Style LVS_REPORT ("Details") eingeschaltet ist. Sie kennen diese Möglichkeiten sicher gut aus dem Windows "Explorer", hier ein Beispiel für diese Detailansicht mit Spaltenüberschriften:


Hier die Alternative LVS_ICON mit großen Symbolen (Icons), die keine Spaltenüberschriften und keine weiteren Informationen bereit stellt:


Weitere Möglichkeiten bestehen mittels:

DWORD CListCtrl::SetExtendedStyle( DWORD dwNewStyle )

Bezüglich dieser Styles vom Typ LVS_EX_... siehe:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/commctls/listview/ex_styles.asp


Nun zu den "Items" und "SubItems". Hier gibt es eine zentrale Struktur, die man kennen muss, um den Inhalt des Listenelementes zu handhaben:

LVITEM
typedef struct _LVITEM 
{
UINT mask;
int iItem;
int iSubItem;
UINT state;
UINT stateMask;
LPTSTR pszText;
int cchTextMax;
int iImage;
LPARAM lParam;
#if (_WIN32_IE >= 0x0300)
int iIndent;
#endif
#if (_WIN32_WINNT >= 0x560)
int iGroupId;
UINT cColumns; // tile view columns
PUINT puColumns;
#endif
#if (_WIN32_WINNT >= 0x0600)
int* piColFmt
int iGroup
#endif
} LVITEM, *LPLVITEM;

Hier sieht man auch sehr schön die Fortentwicklung der Windowsversionen in den letzten Jahren. Die Datei Comctl32.dll in der Version 6 steht ab Windows XP "serienmäßig" zur Verfügung.

Beginnen wir mit den ersten neun Datenelementen dieser Struktur (teilweise im Original aus MSDN):
mask
Hier wird festgelegt, welche Datenelemente wirklich verwendet werden:

LVIF_COLUMNS cColumns
LVIF_DI_SETITEM
The operating system should store the requested list item information and not ask for it again. This flag is used only with the LVN_GETDISPINFO notification message.
LVIF_GROUPID  
iGroupId
LVIF_IMAGE  
iImage
LVIF_INDENT
iIndent
LVIF_NORECOMPUTE The control will not generate LVN_GETDISPINFO to retrieve text information if it receives an LVM_GETITEM message. Instead, the pszText member will contain LPSTR_TEXTCALLBACK.
LVIF_PARAM
lParam
LVIF_STATE
state
LVIF_TEXT   
pszText

     
iItem
Mit null beginnender Index des Items
iSubItem
Mit 1 beginnder Index des SubItems,  0: kein SubItem 
state
Indicates the item's state, state image, and overlay image. The stateMask member indicates the valid bits of this member. Bits 0 through 7 of this member contain the item state flags. This can be one or more of the item state values.Bits 8 through 11 of this member specify the one-based overlay image index. Both the full-sized icon image list and the small icon image list can have overlay images. The overlay image is superimposed over the item's icon image. If these bits are zero, the item has no overlay image. To isolate these bits, use the LVIS_OVERLAYMASK mask. To set the overlay image index in this member, you should use the INDEXTOOVERLAYMASK macro. The image list's overlay images are set with the ImageList_SetOverlayImage function.Bits 12 through 15 of this member specify the state image index. The state image is displayed next to an item's icon to indicate an application-defined state. If these bits are zero, the item has no state image. To isolate these bits, use the LVIS_STATEIMAGEMASK mask. To set the state image index, use the INDEXTOSTATEIMAGEMASK macro. The state image index specifies the index of the image in the state image list that should be drawn. The state image list is specified with the LVM_SETIMAGELIST message.
stateMask
Value specifying which bits of the state member will be retrieved or modified. For example, setting this member to LVIS_SELECTED will cause only the item's selection state to be retrieved. This member allows you to modify one or more item states without having to retrieve all of the item states first. For example, setting this member to LVIS_SELECTED and state to zero will cause the item's selection state to be cleared, but none of the other states will be affected. To retrieve or modify all of the states, set this member to (UINT)-1.You can use the macro ListView_SetItemState both to set and to clear bits.
pszText
If the structure specifies item attributes, pszText is a pointer to a null-terminated string containing the item text. If the structure receives item attributes, pszText is a pointer to a buffer that receives the item text. Note that although the list-view control allows any length string to be stored as item text, only the first 260 TCHARs are displayed.If the value of pszText is LPSTR_TEXTCALLBACK, the item is a callback item. If the callback text changes, you must explicitly set pszText to LPSTR_TEXTCALLBACK and notify the list-view control of the change by sending an LVM_SETITEM or LVM_SETITEMTEXT message.Do not set pszText to LPSTR_TEXTCALLBACK if the list-view control has the LVS_SORTASCENDING or LVS_SORTDESCENDING style.
cchTextMax
Number of TCHARs in the buffer pointed to by pszText, including the terminating NULL. This member is only used when the structure receives item attributes. It is ignored when the structure specifies item attributes. For example, cchTextMax is ignored during LVM_SETITEM and LVM_INSERTITEM. It is read-only during LVN_GETDISPINFO and other LVN_ notifications.
Note  Never copy more than cchTextMaxTCHARs where cchTextMax includes the terminating NULL into pszText during an LVN_ notification, otherwise your program may fail.
iImage
Index of the item's icon in the control's image list. This applies to both the large and small image list. If this member is the I_IMAGECALLBACK value, the parent window is responsible for storing the index. In this case, the list-view control sends the parent an LVN_GETDISPINFO notification message to retrieve the index when it needs to display the image.
lParam
Value specific to the item. If you use the LVM_SORTITEMS message, the list-view control passes this value to the application-defined comparison function. You can also use the LVM_FINDITEM message to search a list-view control for an item with a specified lParam value.

Wir erleichtern uns nun das Leben, indem wir von codeproject - eine reichhaltige Seite für MFC-Programmierer, die viel Zeit und Kraft sparen kann - ein hervorragend gestaltetes Demo-Projekt für das Listenelement holen und dieses an wesentlichen Stellen besprechen. Wir wählen ein Beispiel, das als Demonstration für die Möglichkeiten des Listenelementes gedacht ist. Bitte downloaden:
http://www.codeproject.com/listctrl/listctrldemo.asp   (Autor: Matt Weagle, USA)

Bitte spielen Sie nach dem Kompilieren ein wenig mit den Möglichkeiten des Programms, die hier völlig auf Demonstration des Listenelementes ausgelegt sind. Ich hoffe, es gelingt Ihnen problemlos, dieses interessante Programm zu erstellen.

Wir erkennen die vier Darstellungsformen Icon, Small Icon, List und Details, die wir hier mittels "Registerkarte" auswählen können. LVS_ und LVS_EX_ Styles können wir mittels zweier weiterer Listenelemente auswählen, die sogar über Checkbuttons verfügen.



Setzen Sie bitte den Style LVS_EX_CHECKBOXES, dann sehen Sie, dass wir damit auch vor die Hauptliste Checkboxes zaubern können. Den bereits vorausgewählten Style LVS_EX_FULLROWSELECT erkennen Sie daran, dass die ganze Reihe incl. Subitems ausgewählt wird.

Nun schauen wir uns das Ganze von innen an.  Beginnen wir mit den für uns hier wichtigen Elementen der Hauptdialogklasse und der Funktion DoDataExchange(...):

CHistoryEdit              m_Log;
CListCtrl           m_cListCtrl;
CTabCtrl         m_cTabListMode;
 
int              m_nItems;

int              m_nSelectItem;
BOOL             m_bImage;

BOOL    m_bHotCursor;
HCURSOR m_hHotCursor;
HCURSOR m_hCustomHotCursor;
 
CImageList    m_cImageListNormal,
              m_cImageListSmall,
              m_cImageListState;
    DDX_Control(pDX,    IDC_LOG,                   m_Log);
    DDX_Control(pDX,    IDC_LIST_CTRL,             m_cListCtrl);
    DDX_Control(pDX,    IDC_LIST_CTRL_MODE,        m_cTabListMode);

    DDV_MinMaxInt(pDX,  m_nItems, 0, 32257);
    DDV_MinMaxInt(pDX,  m_nSelectItem, 0, 32567);
    DDX_Check(pDX,      IDC_BK_IMAGE,              m_bImage);


m_cListCtrl ist die Membervariable für das Listenelement. 
m_Log ist das History-Edit-Feld (siehe HistroyEdit.h und
HistroyEdit.cpp) und m_cTabListMode die Registerkarte zur Auswahl der Darstellung.

Sie sehen auch, dass wir hier "nur" 32258 Elemente unterbringen können.

Der nächste Blick wandert in den Konstruktor:

m_nItems      =   100;
m_nSelectItem =     0;
m_bImage      = FALSE;

Dort werden Werte für obige Elemente gesetzt, also 100 Elemente, Item 0 ausgewählt und kein Hintergrundbild.
Diese Werte können Sie über die beiden Edit-Felder während der Laufzeit verändern. Probieren Sie das Verhalten mit 32000 Items aus. Da benötigt das Programm schon Zeit im Sekundenbereich für den Aufbau, wenn Sie die Registerkarte durchklappern.

Nun geht es weiter mit OnInitDialog(...) :

    // Initial extended style for the list control on this dialog
    DWORD dwStyle = m_cListCtrl.GetExtendedStyle();
    dwStyle |= LVS_EX_FULLROWSELECT;
    m_cListCtrl.SetExtendedStyle(dwStyle);

    // initialize the standard and custom hot cursors
    m_hCustomHotCursor = AfxGetApp()->LoadCursor(IDC_HOTCURSOR);
    m_hHotCursor       = m_cListCtrl.GetHotCursor();
    m_bHotCursor       = FALSE;

    // Setup the tab header
    InitTabCtrl();
    // Setup the column headings
    InitListCtrlCols();
    // Create the image list that is attached to the list control
    InitImageList();
    // Insert the default dummy items
    InsertItems();


Man holt zunächst den vorhandenen Style LVS_EX_ mit GetExtendedStyle(...) und führt eine bit-OR-Verknüpfung mit LVS_EX_FULLROWSELECT durch. Daher ist dies auch bereits zu Beginn ausgewählt. Dann wird der Style mit
SetExtendedStyle(...) zurück geschrieben.

Anschließend wird die Ressource IDC_HOTCURSOR geladen.

Die restlichen Aufgaben sind in eigene Init-Funktionen ausgelagert, ein übersichtliches Vorgehen.

void CListCtrlDemoDlg::InitListCtrlCols()
{
    // Insert some columns
    CRect rect;
    m_cListCtrl.GetClientRect(&rect);
    int nColInterval = rect.Width()/5;

    m_cListCtrl.InsertColumn(0, _T("Item Name"), LVCFMT_LEFT, nColInterval*3);
    m_cListCtrl.InsertColumn(1, _T("Value"),     LVCFMT_LEFT, nColInterval);
    m_cListCtrl.InsertColumn(2, _T("Time"),      LVCFMT_LEFT, rect.Width()-4*nColInterval);
}

Die Funktion CListCtrl::InsertColumn(...) kennen wir bereits. Hier werden also die Spaltenüberschriften und die Breite für die Detaildarstellung vorgegeben.

Jetzt wird es spannend. Die Symbole für das Listenelement werden vorbereitet:

BOOL CListCtrlDemoDlg::InitImageList()
{
    // Create 256 color image lists
    HIMAGELIST hList = ImageList_Create(32,32, ILC_COLOR8 |ILC_MASK , 8, 1);
    m_cImageListNormal.Attach(hList);

    hList = ImageList_Create(16, 16, ILC_COLOR8 | ILC_MASK, 8, 1);
    m_cImageListSmall.Attach(hList);

    // Load the large icons
    CBitmap cBmp;
    cBmp.LoadBitmap(IDB_IMAGES_NORMAL);
    m_cImageListNormal.Add(&cBmp, RGB(255,0, 255));
    cBmp.DeleteObject();

    // Load the small icons
    cBmp.LoadBitmap(IDB_IMAGES_SMALL);
    m_cImageListSmall.Add(&cBmp, RGB(255,0, 255));

    // Attach them
    m_cListCtrl.SetImageList(&m_cImageListNormal, LVSIL_NORMAL);
    m_cListCtrl.SetImageList(&m_cImageListSmall,  LVSIL_SMALL);

    return TRUE;
}


Zur Erinnerung, man verfügt über diese drei Membervariablen vom Typ CImageList:


CImageList    m_cImageListNormal, m_cImageListSmall, m_cImageListState;

Mittels Attach(...) ordnet man diesen Objekten ein Handle auf eine ImageList zu. Anschließend werden diese CImageList mittels Add(...) mit Bitmaps aus den Ressourcen versorgt. Zum Schluss verknüpft man mittels CListCtrl::SetImageList(...) die Bilder / Imagelisten mit dem Listenelement.

Das müssen wir uns genau anschauen, weil dies neu ist. Auf jeden Fall haben Sie hier ein gutes, wohl geordnetes und vor allem komplettes Beispiel für diese Aufgabe.
Nun sollte der Blick nach MSDN schweifen, um die neuen Typen genau zu analysieren:

HIMAGELIST ImageList_Create
(      
    int  cx,
    int  cy,
    UINT flags,
    int  cInitial,
    int  cGrow
);

cx
Width, in pixels, of each image.
cy
Height, in pixels, of each image.
flags
Set of bit flags that specify the type of image list to create.
This parameter can be a combination of the following values, but it can include only one of the ILC_COLOR values.
ILC_COLOR
Use the default behavior if none of the other ILC_COLOR* flags is specified. Typically, the default is ILC_COLOR4, but for older display drivers, the default is ILC_COLORDDB.
ILC_COLOR4
Use a 4-bit (16-color) device-independent bitmap (DIB) section as the bitmap for the image list.
ILC_COLOR8
Use an 8-bit DIB section. The colors used for the color table are the same colors as the halftone palette.
ILC_COLOR16
Use a 16-bit (32/64k-color) DIB section.
ILC_COLOR24
Use a 24-bit DIB section.
ILC_COLOR32
Use a 32-bit DIB section.
ILC_COLORDDB
Use a device-dependent bitmap.
ILC_MASK
Use a mask. The image list contains two bitmaps, one of which is a monochrome bitmap used as a mask. If this value is not included, the image list contains only one bitmap.
ILC_MIRROR
Version 6.00. Microsoft Windows can be mirrored to display languages such as Hebrew or Arabic that read right-to-left. If the image list is created on a mirrored version of Windows, then the images in the lists are mirrored, that is, they are flipped so they display from right to left. Use this flag on a mirrored version of Windows to instruct the image list not to automatically mirror images.
ILC_PERITEMMIRROR
Version 6.00. Specify this flag if ILC_MIRROR is used on an image list that contains a strip of images. ILC_MIRROR must be specified for this flag to have any effect.
cInitial
Number of images that the image list initially contains.
cGrow
Number of images by which the image list can grow when the system needs to make room for new images. This parameter represents the number of new images that the resized image list can contain.

Returns the handle to the image list if successful, or NULL otherwise.


HIMAGELIST hList = ImageList_Create(32,32, ILC_COLOR8 | ILC_MASK , 8, 1);
Mit dieser Anweisung wird also konkret eine Liste für acht 32*32 Bilder mit 8-bit DeviceIndependantBitmap (DIB) geschaffen.
Was bedeutet dieses ILC_MASK genau? Das schauen wir uns einfach "live" an, nämlich einmal ohne und einmal mit ILC_MASK:



Mit ILC_MASK sieht das doch deutlich besser aus, oder? (links ohne, rechts mit ILC_MASK)

Jetzt betritt CImageList die Bühne. Zuerst wird HIMAGELIST und CImageList mit Attach(...) verbunden und dann die passende Bitmap aus den Ressourcen geladen. Da gibt es keine Feinheiten.

Diese kommen erst wieder im Umgang mit CImageList ins Spiel:


m_cImageListNormal.Add( &cBmp, RGB(255, 0, 255) );


Hier findet der Trick mit der Maske statt:
Was sagt MSDN hierzu? Es gibt drei Überladungen:
int CImageList::Add( CBitmap* pbmImage, CBitmap* pbmMask );
int CImageList::Add( CBitmap* pbmImage, COLORREF crMask );
int CImageList::Add( HICON hIcon );
Hier wird die zweite Variante verwendet mit crMask:
Color used to generate the mask. Each pixel of this color in the given bitmap is changed to black and the corresponding bit in the mask is set to one.


Wichtig ist also, dass man die Farbe für die "Transparenz" selbst wählen kann.

Wie läuft das mit dem Zeiger auf CBitmap?
Pointer to the bitmap containing the image or images. The number of images is inferred from the width of the bitmap.

Das "Bildband" wird also auf die Zahl der Bilder in der Liste aufgeteilt. Dies ergibt die Abgrenzung.

Was ist, wenn ich z.B. acht Plätze habe, aber nur sechs Bilder in der Bitmap? Da gibt es nur eines: ausprobieren!

Wir schneiden bei IDB_IMAGES_SMALL einfach die zwei rechten Bilder weg.



Was passiert jetzt genau? Werden die sechs Bilder jetzt in der Breite auf acht aufgeteilt?



Nein das passiert nicht, sondern es werden jeweils 16 Pixel breite Bilder übernommen. Für zwei Items ist eben kein Bild mehr übrig.
Hier übrigens ohne ILC_MASK, daher der schwarze Hintergrund.

Diesbezüglich hilft nur üben, experimentieren, fremde Programme diesbezüglich genau analysieren.

Noch eine Feinheit:
CListCtrl unterstützt zwei verschiedene Bildgrößen: LVSIL_NORMAL und LVSIL_SMALL     m_cListCtrl.SetImageList( &m_cImageListNormal, LVSIL_NORMAL );
    m_cListCtrl.SetImageList( &m_cImageListSmall,  LVSIL_SMALL  );


Wenn man dem Benutzer die Darstellung der großen Symbole nicht bietet, kann man ein CImageList sparen. Sie können übungshalber alles auskommentieren, was für die Darstellungsform LVS_ICON benötigt wird. Viele sind aber inzwischen an diese vier Möglichkeiten bei Listen gewöhnt, und jede Darstellung findet sicher ihre Anhänger. Das hängt auch von der gewohnten Monitorauflösung ab.

Nun wird es wieder einfach:

void CListCtrlDemoDlg::InsertItems()
{
    // Delete the current contents
    m_cListCtrl.DeleteAllItems();

    // Use the LV_ITEM structure to insert the items
    LVITEM lvi;
    CString strItem;
    for (int i = 0; i < m_nItems; i++)
    {
        // Insert the first item
        lvi.mask =  LVIF_IMAGE | LVIF_TEXT;
        strItem.Format(_T("Item %i"), i);
   
        lvi.iItem = i;
        lvi.iSubItem = 0;
        lvi.pszText = (LPTSTR)(LPCTSTR)(strItem);
        lvi.iImage = i%8;        // There are 8 images in the image list
        m_cListCtrl.InsertItem(&lvi);

        // Set subitem 1
        strItem.Format(_T("%d"), 10*i);
        lvi.iSubItem =1;
        lvi.pszText = (LPTSTR)(LPCTSTR)(strItem);
        m_cListCtrl.SetItem(&lvi);

        // Set subitem 2
        strItem.Format(_T("%s"), COleDateTime::GetCurrentTime().Format(_T("Created: %I:%M:%S %p, %m/%d/%Y")));
        lvi.iSubItem =2;
        lvi.pszText = (LPTSTR)(LPCTSTR)(strItem);
        m_cListCtrl.SetItem(&lvi);
    }
}

Mit
lvi.mask =  LVIF_IMAGE | LVIF_TEXT; legt man fest, welche Datenelemente der Struktur LVITEM (siehe oben) berücksichtigt werden sollen, hier wurden pszText und iImage (Text und Bild) aktiviert. Der Rest sollte klar sein.



wird fortgesetzt