Display Text Owner Draw Listbox

I can't figure out how to display the text in an owner draw listbox. I've Googled and looked at the examples in MSDN, but I'm still stuck. If someone could provide me with the MINIMUM code necessary to display the text in the listbox, I would be grateful. This must be owner draw because I need to display different fonts in the same index item, plus I may also display an icon or bitmap. Below is the code and problem area...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	RECT rc;
	int nItem;
	static HWND hwndList;
	LPDRAWITEMSTRUCT lpdis;
	LPMEASUREITEMSTRUCT lpmis;
	
	switch(message)
	{
	case WM_CREATE:

		GetClientRect(hwnd, &rc);

		hwndList = CreateWindowEx(NULL, "listbox", NULL,
			WS_CHILD | WS_VISIBLE | WS_BORDER |
			LBS_MULTIPLESEL | LBS_NOINTEGRALHEIGHT |
			LBS_HASSTRINGS | LBS_OWNERDRAWFIXED,
			rc.left+100, rc.top+100, 200, 400,
			hwnd, (HMENU)IDL_LISTBOX, hAppInstance, NULL);

		nItem = SendMessage(hwndList,LB_ADDSTRING,0,(LPARAM)(LPSTR)"Test");
		SendMessage(hwndList,LB_SETITEMDATA,(WPARAM)nItem,(LPARAM)"001");
		
		return 0;

	case WM_MEASUREITEM:
		lpmis = (LPMEASUREITEMSTRUCT) lParam;
		lpmis->itemHeight=20;
		return TRUE;

	case WM_DRAWITEM:

		lpdis = (LPDRAWITEMSTRUCT) lParam;

		switch( lpdis->itemAction )
		{
		case ODA_SELECT:
		case ODA_DRAWENTIRE:
			// This is the problem area
			// I don't how to code this to make
			// the above strings appear in the list box
			break;
		}
		return TRUE;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}

	return DefWindowProc(hwnd, message, wParam, lParam);
I have never done owner-drawn controls before, so I will be guessing here.

lpdis->itemID has the index of the item to draw. I suppose, however, that if ODA_DRAWENTIRE is requested, then the itemID is of no consequence and the entire control needs to be refreshed. In this case take a look at the scrollbar value to determine the first visible item. This plus the internal bounds of the control should ultimately determine the elements to draw.

If you get ODA_SELECT, then it means that you need to redraw the specified item in lpdis->itemID because its selection state changed. You need to calculate its coordinates and then draw it there. I recommend double buffering and no automatic background redraw to prevent flickering.

If you get ODA_FOCUS, you need to redraw or erase the dotted focus rectangle around the specified item.

lpdis->hDC is the device context that you need to use to perform the drawing.
Thanks for trying, but that won't get it. Why? I don't know exactly, but the lpdis->itemID is part of an example I got from MSDN on owner draw controls, here --

http://msdn.microsoft.com/en-us/library/bb775148(VS.85).aspx

Scroll down to owner draw. What I am trying to do is replace the bitmap in that example with text, because in my program I have to use a different font, i.e., from the code above, "test" would be one font and "001" would be another font.

The code you see is from the link above, but I've just started it. What I'm looking for is the MINIMAL code needed to just display my two strings above in an owner draw list box. I know how to display text in a regular list box, but I need the owner draw because of the two fonts. But I'm not sure how to proceed because I don't really understand what the code in the above link does exactly.
Have you tried the example? It looks OK to me. Why do you say it doesn't work? It seems to be even easier. Apparently, you don't have to calculate the rectangle occupied by the item. Apparently, lpdis->rcItem is the rectangle. All you have to use is DrawText() in it. Just make sure you use the appropriate font for the particular item, which is what you want to accomplish.

Also ODA_DRAWENTIRE still carries an Item ID, so I guess it will repeat the WM_DRAWITEM message for each of the visible items.
I haven't coded the example in full because I want use text instead of a bitmap and I also don't know how I would apply a specific font to each of the items in the same listbox index.

For example, I want my listbox to look like this on line one...

"test 0001"

but I want "test" and "0001" to be different fonts. How would I set a different font for those?
Ok, the example also draws text. I think it uses TextOut() instead of DrawText(). Still, the example draws text. Just ignore the bitmap part. The text part starts with
// Display the text associated with the item
.

The code from MSDN uses a variable "y" to obtain the middle point of the item's rectangle. I would not do it like that if I were to combine multiple fonts in a single element. Instead, I would just go with the baseline. Read further to understand.

First of all, the basics: All you need to do is use TextOut() to write the passed string. The string is obtained in the example by means of SendMessage() and LB_GETTEXT. Feel free to do the same, or read it from elsewhere, like an array of yours, or whatever.

TextOUt() also needs the x, y coordinates where to start writing. MSDN uses the middle point of the height for the y coordinate. I would instead call SetTextAlign() (http://msdn.microsoft.com/en-us/library/dd145091%28VS.85%29.aspx) to set the alignment to TA_BASELINE so text written in different fonts with different sizes are still properly aligned.

To write with a specific font, you must first select it. To select a font, you must first "create" it. Actually, get a handle of it. Use CreateFontIndirect() or CreateFontIndirectEx() to get an HFONT, and if I remember correctly, you use SelectObject() to select the font as the current font. Once you have the font that you want, you write the text that goes with that font. Then you change fonts one more time and write the other part of the text.

Note that the size of the font is important and needs to be taken into account by the time you process WM_MEASUREITEM.

If you haven't used fonts before, all this is kind of chinese to you. I recommend that you read the topic "Fonts and Text" in MSDN (http://msdn.microsoft.com/en-us/library/dd144819%28VS.85%29.aspx).
Thanks for the detailed reply. Actually, I am quite comfortable with fonts in and of themselves. I've created multiple fonts for multiple uses, but I've always assigned a font to the entire list box, or combo box, or what-have-you.

So my question now is, how do I assign it to only that specific part of the text? Do I do it using SelectObject() before writing to TextOut() or DrawText()?
You are probably used to selecting fonts on RAD environments, which believe me, is a thousand times easier than what you actually need to do here. Go ahead and read the topic. You most likely won't be able to pull this together if you don't read it.

To answer your question: Yes, you select the font, draw the first part of the text in the selected font, then create and select the second font, and then draw the rest of the text.
Ok, thanks for the help. And no, I am pretty new to programming, so I've never done fonts on RAD, just Win32 API.
See Adv. Win32 ng http://tinyurl.com/cmhb5g where hundreds of C code samples for od lb have been posted since 90's..
(and from Windows source code itself...)
Last edited on
Thanks for that link. I find that the search button there doesn't really find what you want. It is a very good site, but as of now the only way I can really see how to find all the entries I want is to scroll through the list page by page. It's a shame their search engine isn't much better.

Anyway, thanks again, because there appears to be a lot of good stuff there.
This was an owner-draw Listbox I did some time ago - The listbox is an MFC class but it uses
plain WIN32 API functions so you should be able to figure out what is going on.
It didn't do anything about the font when I wrote it - but I've added that so you can see how
straightforward it is.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
// CMyListBox message handlers

void CMyListBox::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
    // TODO: Add your code to draw the specified item
    LPDRAWITEMSTRUCT lpDStruct = lpDrawItemStruct; //use a shorter name

    switch (lpDStruct->CtlID)
    {
    case IDC_MYLISTBOX:
    {
        switch (lpDStruct->itemAction)
        {
        case ODA_DRAWENTIRE:
            if (lpDStruct->itemState & ODS_SELECTED)
            {
                DrawSelected(lpDStruct);
            }
            else
            {
                DrawEntire(lpDStruct);
            }
            break;
        case ODA_FOCUS:
            DrawFocused(lpDStruct);
            break;
        case ODA_SELECT:
            //DrawSelected(lpDStruct);
            if (lpDStruct->itemState & ODS_SELECTED)
            {
                DrawSelected(lpDStruct);
            }
            else
            {
                DrawEntire(lpDStruct);
            }

            break;
        default:
            break;
        }
    }
    }

}



void CMyListBox::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
    // TODO: Add your code to determine the size of specified item
    //This function appears only to be called for owner draw variable
    //which is what we want most of the time.
    //With owener draw fixed - the item height appears to be fixed by the system
    //and seemed to be the determined by the font height which can screws up
    //things like butmaps - hence use ODV
    LPMEASUREITEMSTRUCT lpMStruct=lpMeasureItemStruct;
    lpMStruct->itemWidth=250;
    lpMStruct->itemHeight=24;//as required
}

void CMyListBox::OnLButtonDblClk(UINT nFlags, CPoint point)
{
    // TODO: Add your message handler code here and/or call default


    //Pass this message onto the parent (in this case dialogbox) window
    CListBox::OnLButtonDblClk(nFlags, point);
}

void CMyListBox::DrawEntire(LPDRAWITEMSTRUCT lpDStruct)
{

    CRect rect(lpDStruct->rcItem);
    HDC dc =lpDStruct->hDC;
    MYLISTITEM *a = (MYLISTITEM*)lpDStruct->itemData;


    HBRUSH brush, oldbrush;
    HPEN pen,oldpen;
    HFONT hFont, hOldFont;
    LOGFONT logFont;

    pen=CreatePen(PS_SOLID,1,RGB(255,255,255));
    brush=CreateSolidBrush(RGB(255,255,255));

    oldpen=(HPEN)SelectObject(dc,pen);
    oldbrush=(HBRUSH)SelectObject(dc,brush);

    //erase the background - draw rectangle using pen and brush
    Rectangle (dc,rect.left,rect.top,rect.right+1,rect.bottom);

    //draw the bitmap
    DrawBitmaps(dc, rect,a->checked);

    //Create the font
    memset(&logFont,0,sizeof(logFont));

    logFont.lfHeight = 16;
    logFont.lfWeight = FW_BOLD;

    strcpy(logFont.lfFaceName,"courier");
    hFont = CreateFontIndirect(&logFont);
    hOldFont = (HFONT)SelectObject(dc,hFont);

    //Write Text
    SetTextColor(dc,RGB(0,0,0));
    SetBkMode(dc,TRANSPARENT);
    TextOut(dc,rect.left+20,rect.top+2,a->title,strlen(a->title));

    if (lpDStruct->itemState & ODS_FOCUS)
    {
        DrawFocusRect(dc,rect);
    }

    //clean up
    SelectObject(dc,hOldFont);
    SelectObject(dc,oldpen);
    SelectObject(dc,oldbrush);


}



void CMyListBox::DrawFocused(LPDRAWITEMSTRUCT lpDStruct)
{
    HDC dc =lpDStruct->hDC;
    CRect rect(lpDStruct->rcItem);
    DrawFocusRect(dc,&rect);
}

void CMyListBox::DrawSelected(LPDRAWITEMSTRUCT lpDStruct)
{

    CRect rect(lpDStruct->rcItem);
    HDC dc =lpDStruct->hDC;
    MYLISTITEM *a = (MYLISTITEM *)lpDStruct->itemData;


    //Create brushes, pens, bitmaps and font
    HBRUSH brush, oldbrush;
    HPEN pen,oldpen;
    HFONT hFont, hOldFont;
    LOGFONT logFont;

    pen=CreatePen(PS_SOLID,1,RGB(0,0,255));//
    brush=CreateSolidBrush(RGB(0,0,255));

    oldpen=(HPEN)SelectObject(dc,pen);
    oldbrush=(HBRUSH)SelectObject(dc,brush);

    //draw background
    Rectangle (dc,rect.left,rect.top,rect.right,rect.bottom);


    //draw bitmap
    DrawBitmaps(dc,rect,a->checked);

    //Create the font
    memset(&logFont,0,sizeof(logFont));

    logFont.lfHeight = 24;
    logFont.lfWeight = FW_BOLD;

    strcpy(logFont.lfFaceName,"courier");
    hFont = CreateFontIndirect(&logFont);
    hOldFont = (HFONT)SelectObject(dc,hFont);

    //Write the text
    SetTextColor(dc,RGB(255,255,255));
    SetBkMode(dc,TRANSPARENT);
    TextOut(dc,rect.left+20,rect.top+2,a->title,strlen(a->title));

    if (lpDStruct->itemState & ODS_FOCUS)
    {
        DrawFocusRect(dc,rect);
    }
    //clean up
    SelectObject(dc,hOldFont);
    SelectObject(dc,oldpen);
    SelectObject(dc,oldbrush);

}

int CMyListBox::CompareItem(LPCOMPAREITEMSTRUCT lpCompareItemStruct)
{
    // TODO: Add your code to determine the sorting order of the specified items
    // return -1 = item 1 sorts before item 2
    // return 0 = item 1 and item 2 sort the same
    // return 1 = item 1 sorts after item 2

    /*
    Only called if the OD listbox is created with 'sort' option
    */

    return 0;
}

void CMyListBox::DrawBitmaps(HDC hDC, RECT rect,int checked)
{

    HDC memdc, memdc1;
    memdc=CreateCompatibleDC(hDC);
    memdc1=CreateCompatibleDC(hDC);

    HBITMAP bitmap,bitmap1, oldbitmap, oldbitmap1;

    if (checked==1)
    {
        bitmap=LoadBitmap(AfxGetInstanceHandle(),MAKEINTRESOURCE(IDB_GREENTICK));
        bitmap1=LoadBitmap(AfxGetInstanceHandle(),MAKEINTRESOURCE(IDB_GREENTICKMASK));
    }
    else
    {
        bitmap=LoadBitmap(AfxGetInstanceHandle(),MAKEINTRESOURCE(IDB_REDX));
        bitmap1=LoadBitmap(AfxGetInstanceHandle(),MAKEINTRESOURCE(IDB_REDXMASK));

    }

    oldbitmap=(HBITMAP)SelectObject(memdc,bitmap);
    oldbitmap1=(HBITMAP)SelectObject(memdc1,bitmap1);

    //draw the bitmap
    BitBlt(hDC,rect.left+2,rect.top+2,16,16,memdc,0,0,SRCINVERT);
    BitBlt(hDC,rect.left+2,rect.top+2,16,16,memdc1,0,0,SRCAND);
    BitBlt(hDC,rect.left+2,rect.top+2,16,16,memdc,0,0,SRCINVERT);

    //cleanup
    SelectObject(memdc,oldbitmap);
    SelectObject(memdc1,oldbitmap1);
}
Man O man, guestgulkan, that is REALLY, REALLY, REALLY appreciated! Absolutely outstanding. Thank you VERY much.
> It is a very good site, but as of now the only way I can really see how to find all the entries

It's not a site !!
It's a newsgroup (forum) from Usenet where you must ask questions,
with a newsreader, like Windows Mail for example :
http://support.cox.com/sdccommon/asp/contentredirect.asp?sprt_cid=285b1ded-6ed2-42d9-ac7b-9ad40513af96
Google Groups is just a (small) part of archives and the search engine is indeed very bad.
It's the oldest Win32 group with the best Win32 gurus in the world and ancient MS guys... with Windows source code (NT/2K/XP/Vista)
Then it's unbeatable for undocumented apis and the best source codes in plain C/win32...
Last edited on
I love the groupt, but I don't have Vista, and I can't to this day figure out how to use Outlook News Reader, at least not effectively.

But I do appreciate the links very much.
Topic archived. No new replies allowed.