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:
Animation | Button | Combo Box | ComboBoxEx | Date and Time Picker | Drag List Box | Edit | Flat Scroll Bar | Header | Hot Key | Image Lists | IP Address Controls | List Box | List-View | Month Calendar | Pager | Progress Bar | Property Sheets | ReBar | Rich Edit | Scroll Bars | Static | Status Bars | SysLink | Tab | Toolbar | ToolTip | Trackbar | Tree-View | Up-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.
LVSIL_NORMAL
: nur für LVS_ICON
(große Symbole, 32*32)
LVSIL_SMALL
: für LVS_SMALLICON
, LVS_LIST
und LVS_REPORT
(kleine Symbole, 16*16)
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