I'm subclassing the combo box control to put checkmarks next to items in a
drop-down list. My code is based on an MFC C++ example from CodeProject, and
I'm using the vbAccelerator owner-draw combo box as a skeleton which I have
modified to hook the necessary events on the correct windows.
As the combo box is set to be owner-drawn, the WM_DRAWITEM notification is
received for each of the items. I extract the DRAWITEMSTRUCT and find all
sensible values -- the HDC passed is valid, it has a valid font set, the
item's clipping rectangle is reasonable. I set the text foreground and
background appropriately and update the RECT for the text to make room for a
checkbox, then pass the item's text into the DrawText API. DrawText returns
16, indicating that the text came out 16 pixels tall, and sure enough,
everything is working properly, except on a couple of computers.
I prepared the control on my personal computer in the office and it works
perfectly here. I sent it off to our outsourcing team in India for them to
use, and they sent back an odd screenshot with all the text missing. I went
from computer to computer in the office and found 2 other computers on which
it worked perfectly before finally finding a computer that reproduces the
problem. The computers upon which it works are 2 x Windows XP SP2 and 1 x
Windows Vista Business. The computers upon which it fails are both Windows XP
SP2.
No error code is returned by any of the API calls during the WM_DRAWITEM
handler, the text simply doesn't appear. All the other drawing works just
fine --ExtTextOut in opaque mode used to draw the background rectangle, for
instance, and DrawFrameControl to draw the checkbox.
I tried, for debugging, selecting the stock system font into the HDC, and
that also has no effect.
Here is the actual DrawText call:
DrawText hDC, strText, Len(strText), udtTextRect, DT_SINGLELINE Or
DT_VCENTER Or DT_END_ELLIPSIS
I doubt there's anything wrong with it, as it works fine on the majority of
the systems it has been tried on. However, on at least two computers, the
text is not drawn but it returns a successful response value.
Does this sound familiar to anyone? :-)
Thanks,
Jonathan Gilbert
Work Software Systems
Please disregard this post and use the other one which was cross-posted to
the microsoft.public.vb.winapi.graphics newsgroup as well as this one.
Jonathan Gilbert
Work Software Systems
"Jonathan Gilbert" wrote:
> Hello,
>
[snipped]
Randy
"Jonathan Gilbert" <logi...@hotmail.com> wrote in message
news:565EEA8B-DD18-4524...@microsoft.com...
The machine on which the control was developed (and on which it continues to
work perfectly) is a very similar computer except with an Intel 82865G
graphics adapter. It has the same driver installed as the other two machines.
I found another machine on which the problem can be reproduced. It is
completely different, with an NVIDIA Vanta onboard adapter that also appears
to have the latest drivers installed. (Confusingly, NVIDIA's web site
indicates that there might be a newer driver, but the installer it offers for
download informs me that the computer does not have a compatible adapter...)
I tried both with the standard Windows XP theme and with the Windows Classic
theme. The other computers involved all have Fast User Switching enabled, but
this one has Fast User Switching disabled, so that doesn't seem to be related
either.
Any further ideas? This is an odd problem, to be sure.
Thanks,
Jonathan Gilbert
Work Software Systems
-------------
Extracting parameters...
Replacing font...
Original font: AB0A09B1
Drawing item #8 to hDC 1B010E2B
Drawing list box item => Text: Eric Vernon, Checked? 1
Text metrics:
tmHeight: 16
tmExternalLeading: 0
Adjusted text rectangle: left changed from 0 to 22
Drawing regular: 0 on FFFFFF
About to draw text, text rect: (22, 120) -> (488, 135) 466x15
-> DrawText returned: 16
Unreplacing font...
System font was: 18A0021
Returning 1
-------------
This includes the code which selects the stock SYSTEM_FONT into the DC; it
did not originally do that, and that change did not make a difference.
The code itself is translated from C++ code from the following CodeProject
page:
http://www.codeproject.com/combobox/checkcombo.asp
In VB6, it looks like this (the same code except without the debug trace
output that generated the above log):
Private Function plDrawItem(ByVal wParam As Long, ByVal lParam As Long) As
Long
Dim udtDrawItem As DRAWITEMSTRUCT
Dim hdc As Long
Dim udtBitmapRect As RECT
Dim udtTextRect As RECT
Dim udtMetrics As TEXTMETRIC
Dim strText As String
Dim lngCheck As Long ' 0 = no check, 1 = unchecked, 2 = checked
Dim lngState As Long
Dim hOriginalFont As Long
CopyMemory udtDrawItem, ByVal lParam, Len(udtDrawItem)
hdc = udtDrawItem.hdc
'hOriginalFont = SelectObject(hdc, GetStockObject(SYSTEM_FONT))
udtBitmapRect = udtDrawItem.rcItem
udtTextRect = udtDrawItem.rcItem
' Check which part of the combo box we're drawing
If udtDrawItem.ItemId < 0 Then
' Drawing the static portion
strText = GetText
' Don't draw a checkbox
lngCheck = 0
Else
' Drawing one of the items
strText = List(udtDrawItem.ItemId)
lngCheck = 1 + IIf(Checked(udtDrawItem.ItemId), 1, 0)
' Allocate space for the checkbox
GetTextMetrics hdc, udtMetrics
udtBitmapRect.left = 0
udtBitmapRect.Right = udtBitmapRect.left + udtMetrics.tmHeight +
udtMetrics.tmExternalLeading + 6
udtBitmapRect.tOp = udtBitmapRect.tOp + 1
udtBitmapRect.Bottom = udtBitmapRect.Bottom - 1
udtTextRect.left = udtBitmapRect.Right
End If
' Draw the checkbox, if required
If lngCheck > 0 Then
SetBkColor hdc, GetSysColor(COLOR_WINDOW)
SetTextColor hdc, GetSysColor(COLOR_WINDOWTEXT)
lngState = DFCS_BUTTONCHECK
If lngCheck = 2 Then
lngState = lngState Or DFCS_CHECKED
End If
DrawFrameControl hdc, udtBitmapRect, DFC_BUTTON, lngState
End If
' Draw the rest of the row
If (udtDrawItem.ItemState And ODS_SELECTED) <> 0& Then
SetBkColor hdc, GetSysColor(COLOR_HIGHLIGHT)
SetTextColor hdc, GetSysColor(COLOR_HIGHLIGHTTEXT)
Else
SetBkColor hdc, GetSysColor(COLOR_WINDOW)
SetTextColor hdc, GetSysColor(COLOR_WINDOWTEXT)
End If
' Erase and draw
strText = " " & strText
ExtTextOut hdc, 0, 0, ETO_OPAQUE, udtTextRect, 0, 0, 0
If (udtDrawItem.ItemState And (ODS_FOCUS Or ODS_SELECTED)) = (ODS_FOCUS Or
ODS_SELECTED) Then
DrawFocusRect hdc, udtTextRect
End If
'hOriginalFont = SelectObject(hdc, hOriginalFont)
plDrawItem = 1
End Function
On the systems where it fails, every API call, including the DrawText,
returns a success code. All but the DrawText actually do what they are
supposed to do. DrawText returns 16, which indicates the number of pixels
tall the rendered text was, but no text appears at all.
Does this help?
Thanks,
Jonathan Gilbert
Work Software Systems
"Thorsten Albers" wrote:
> Have you ensured that on the machines in question
> - hDC is not 0 or isn't an invalid/the wrong DC handle
> - strText isn't an empty string
> - udtTextRect doesn't hold an invalid or empty rectangle
>
> We haven't seen much code yet, so its difficult to tell if the reason may
> be faulty code.
> have an Intel 82845G onboard video adapter, and there does not appear to
be a
> newer driver available than the one they have installed. Both are running
> Windows XP SP2 and both have fast user switching enabled, but the
DrawText
> call fails to display anything on one of the two computers.
> DrawText hDC, strText, Len(strText), udtTextRect, DT_SINGLELINE Or
> DT_VCENTER Or DT_END_ELLIPSIS
Have you ensured that on the machines in question
- hDC is not 0 or isn't an invalid/the wrong DC handle
- strText isn't an empty string
- udtTextRect doesn't hold an invalid or empty rectangle
We haven't seen much code yet, so its difficult to tell if the reason may
be faulty code.
--
----------------------------------------------------------------------
THORSTEN ALBERS Universität Freiburg
albers@
uni-freiburg.de
----------------------------------------------------------------------
If I figure out what the difference is, I'll post here for any interested
readers. Also, if anyone has any idea off the top of their heads what the
problem might be, please let me know. :-)
Thanks,
Jonathan Gilbert
Work Software Systems
"Jonathan Gilbert" wrote:
> Yes, as mentioned all of the parameters are sensible. I verified this early
> on by adding a facility to log a brief trace of the WM_DRAWITEM handler each
> time it is called to a file. Here is a sample:
>
[snip]
> Private Function plDrawItem(ByVal wParam As Long, ByVal lParam As Long)
As
> Long
> ...
> strText = " " & strText
> ExtTextOut hdc, 0, 0, ETO_OPAQUE, udtTextRect, 0, 0, 0
> ...
> End Function
As far as I can see the code given doesn't make any call to
DrawText()/DrawTextEx(). And the call to ExtTextOut() is just a dummy, it
doesn't draw any text.
> I just grabbed the original C++ version's demo application from
> codeproject.com and compiled it (took me a while to do statically -- not
sure
> where _AFXDLL was being declared, but with it, it wouldn't build against
the
> static version of the runtime libraries). When I run it on the systems
where
> the VB6 version fails, the C++ version succeeds. Thus, there must be some
> difference between the two. I doubt that the difference is within the
draw
> method itself, so I'll have to closely inspect the window creation.
'_AFXDLL' has to be defined in the project options of the C/C++ compiler.
If '_AFXEXT' is defined, '_AFXDLL' will be defined automatically. This
preprocessor definition is needed since the C++ project depends on the MS
foundation class (MFC). It is not easy to port a MFC project to VB since
the MFC can't be used in VB. The MFC is a very powerful library and it
isn't unlikely that the C++ project needs this library to work properly.
--
Randy Birch
MS MVP, Visual Basic
http://vbnet.mvps.org/
Please respond to the newsgroups so all can benefit.
"Jonathan Gilbert" <logi...@hotmail.com> wrote in message
news:07D0E60D-FD86-41F8...@microsoft.com...
Shoot. That was just a copy/paste error of some sort, but I don't understand
how it happened. There *is* a DrawText line -- the code does work on most of
the machines I've tested on. Somehow it got excluded when I copy/pasted the
entire function into the message. As pasted into a previous message, the
DrawText line, which occurs immediately after the ExtTextOut line (whose
purpose, as you noticed, is not to draw text but just to fill the background
area -- not my idea, but functional even on the systems where the DrawText
fails), is as follows:
DrawText hDC, strText, Len(strText), udtTextRect, DT_SINGLELINE Or
DT_VCENTER Or DT_END_ELLIPSIS
I don't understand how that got excluded when I pasted the routine, but
there you have it. In the log output I sent in a previous message, the
"DrawText returned ..." line was output by:
Print #1, "-> DrawText returned: " & DrawText(hDC, strText, Len(strText),
udtTextRect, DT_SINGLELINE Or DT_VCENTER Or DT_END_ELLIPSIS)
> > I just grabbed the original C++ version's demo application from
> > codeproject.com and compiled it (took me a while to do statically -- not
> > sure where _AFXDLL was being declared, but with it, it wouldn't build against
> > the static version of the runtime libraries). When I run it on the systems
> > where the VB6 version fails, the C++ version succeeds. Thus, there must be
> > some difference between the two. I doubt that the difference is within the
> > draw method itself, so I'll have to closely inspect the window creation.
>
> '_AFXDLL' has to be defined in the project options of the C/C++ compiler.
> If '_AFXEXT' is defined, '_AFXDLL' will be defined automatically.
That was what confused me, though -- those macros are not defined in the
project configuration's preprocessor macros section. In the original VC++ 6.0
.DSP file, the macro is being defined by "# ADD" lines specifying that the
command-line argument '/D "_AFXDLL"' should be added to the command-line
string. However, after converting the project to VC++ 8.0 (as I have only
Visual Studio 2005), the option disappeared completely behind the scenes. I
still do not know where it was being set, but adding "#undef _AFXDLL" before
"#include <afxwin.h>" in the project's StdAfx.h permitted me to compile
against static run-time libraries. For any big, long-running project I
imagine this would probably cause problems somewhere, but for this test
application it doesn't seem to have caused any serious side-effects.
In any event, I succeeded in generating a static build of the original
project and was able to test it on the other machines without locating the
necessary run-time redistributable installers. As noted, it worked in that
configuration.
> This preprocessor definition is needed since the C++ project depends on
> the MS foundation class (MFC). It is not easy to port a MFC project to VB
> since the MFC can't be used in VB. The MFC is a very powerful library
> and it isn't unlikely that the C++ project needs this library to work properly.
Yep, certainly the C++ project is based on it. Of course, the *concept* of
the C++ project doesn't require MFC, and while it wasn't trivial to port the
overall structure (vbAccelerator's owner-draw combo box, which uses
SSubTmr6.dll, helped immensely), porting the WM_DRAWITEM handler was
relatively easy.
I compared the two checkboxes, went through them with a fine-toothed comb,
and I couldn't spot any significant differences. One of the combo boxes
includes two other styles that the other one does not: WS_TABSTOP and
WS_SORT. I can't see how either of these would have an effect on the drawing.
The one which works also includes WS_EX_NOPARENTNOTIFY, but according to
MSDN, this only comes into play when the entire window is being destroyed.
WM_MEASUREITEM isn't implemented identically, but it does return sensible
values. WM_DRAWITEM is identical as far as I can tell, and on the systems
where the VB6 port works, it looks absolutely identical, to the pixel, to the
same control hosted in the MFC C++ demo project.
This is frustrating. =/
Thanks for the suggestion, but this isn't the case either. In the message
including the diagnostic log output, this line:
Drawing regular: 0 on FFFFFF
..is showing the return values of GetSysColor for COLOR_WINDOWTEXT and
COLOR_WINDOW. The first value is passed to SetTextColor and the second to
SetBkColor.
Recall also that this WM_DRAWITEM code is identical (as far as I can tell,
at least) to the original C++ version. The C++ version sets its foreground
and background colours in exactly the same way:
if (lpDrawItemStruct->itemState & ODS_SELECTED) {
SetBkColor(dc, GetSysColor(COLOR_HIGHLIGHT));
SetTextColor(dc, GetSysColor(COLOR_HIGHLIGHTTEXT));
}
else {
SetBkColor(dc, GetSysColor(COLOR_WINDOW));
SetTextColor(dc, GetSysColor(COLOR_WINDOWTEXT));
}
..and on all systems tested so far, the C++ version works regardless of
whether the VB6 version works. If it were a system configuration issue where
COLOR_WINDOWTEXT were identical to COLOR_WINDOW, that would affect the C++
version too (and would render the entire machine close to useless, as no text
would appear in any standard textboxes anywhere :-). If it were an error in
the declarations in the VB6 version so that one of COLOR_WINDOW or
COLOR_WINDOWTEXT were not the correct constant value and they ended up
returning the same colour, it would behave in that way on all workstations,
not just some.
This is a frustratingly elusive problem. =/
I welcome any other suggestions.
-------------
Extracting parameters...
Replacing font...
Original font: AB0A09B1
Drawing item #8 to hDC 1B010E2B
Drawing list box item => Text: Eric Vernon, Checked? 1
Text metrics:
tmHeight: 16
tmExternalLeading: 0
Adjusted text rectangle: left changed from 0 to 22
Drawing regular: 0 on FFFFFF
About to draw text, text rect: (22, 120) -> (488, 135) 466x15
-> DrawText returned: 16
Unreplacing font...
System font was: 18A0021
Returning 1
-------------
Looking at this log a discrepancy between the font (= text) height and the
height of the formatting rectangle is to be noted. The font height is 16
pixels, the height of the drawing surface passed to DrawText() is 15
pixels, i.e. the drawing surface is not sufficient for the font's height. I
don't know how DrawText() in general behaves in this case, if it draws
anything or not. It might be that different display drivers behave
different in such a case (cmp. Randy's posting).
To ensure that this is not causing the problem for testing pruposes you
should set the rectangle to the actual font height (udtTextRect.Bottom =
udtTextRect.Top + udtMetrics.tmHeight) .
In addition: As far as I remember over several years (and versions of
Windows) people have reported problems when using DrawText() with one of
the *_ELLIPSIS flag constants, both in this and other newsgroups. E.g. a
google search like "+drawtext +ellipsis groups:microsoft.public.vb.winapi"
will show you at least some of the postings.
To ensure that this is not causing the problem for testing purposes remove
the DT_END_ELLIPSIS.
And to check whether DrawText() is the problem or not make the DrawText()
line a comment and replace it for testing purposes by a ExtTextOut() line
(which is sufficient for your text drawing except that it doesn't offer the
ellipsis option).
See also the thread
Vista and DrawText API
from
08/16/2007 23:53
in this newsgroup where a bug of DrawText() on Windows Vista has been
reported and verified by several people.
Thank you for this suggestion. It led to the solution of the problem! :-)
It turns out the DT_END_ELLIPSIS declaration was "&H8000". As this value
fits into an "Integer", VB6 naturally stored it as a 16-bit value. When this
was ORed against the other flags, which were 32-bit values, it got
sign-extended and effectively included flags &HFFFF8000. Flags in this range
include DT_HIDEPREFIX and DT_PREFIXONLY -- obviously not a good combination!
It is odd that it worked on some computers -- I would expect that combination
to always do nothing, but I suppose it would be best described as having
"undefined behaviour". :-)
The correct declaration of "&H8000&" solves the problem with no other changes.
Thank you very much for helping me solve this issue. :-)