Board index » cppbuilder » TPopupMenu Images and Checkboxes

TPopupMenu Images and Checkboxes


2006-09-07 05:32:27 PM
cppbuilder102
Hi,
Can anyone help me. I have made a Popup menu for a CAD program using
OpenGL. This popup menu shows the items that can be drawn in this OpenGL
component. Every item has an individual icon next to it, but I also want
to have a check box next to it so I can change the visibility of that
item. Currently I enclose the caption of invisible items between (), but
that is not a nice solution.
Can anyone explain to me how to show icons and check boxes within popup
menu items?
Thanks
Wiljo.
 
 

Re:TPopupMenu Images and Checkboxes

Wiljo < XXXX@XXXXX.COM >wrote:
Quote

[...] Can anyone explain to me how to show icons and check
boxes within popup menu items?
OwnerDraw them. Set the OwnerDraw property to true and add an
OnDrawItem event to do it.
~ JD
 

Re:TPopupMenu Images and Checkboxes

JD wrote:
Quote
Wiljo < XXXX@XXXXX.COM >wrote:

>[...] Can anyone explain to me how to show icons and check
>boxes within popup menu items?


OwnerDraw them. Set the OwnerDraw property to true and add an
OnDrawItem event to do it.

~ JD

Thanks.
This was a little more tricky than thought at first glance.
I don't use the TPopupMenu dropped on a form, but I allocate a global
one without the need for a form, so every global function can use this
dynamic popup menu. The popup menu will reflect the tree hierarchy
supplied by a single pointer. The tree hierarchy consists of items of
all kind of types. Every type must be indicated with a unique item on
the menu. The check mark icons indicate if items of the hierarchy are
visible or not.
To make owner drawing of items possible, I have created a class that
just contains that OnOwnerDraw function. Then the address of the
function can be assigned using a static instance of that class.
Although I have got this working, the chevron ">" for the submenu is not
shown for the selected item. All other submenu items do show a chevron.
I draw this chevron at the correct position, but nothing is shown. If I
move the chevron image a couple of pixels to the left, something is
shown, but the original chevron seems to be obscuring it in reverse colors.
Does anyone know how I can draw a visible chevron correctly.
Wiljo.
 

{smallsort}

Re:TPopupMenu Images and Checkboxes

Wiljo < XXXX@XXXXX.COM >wrote:
Quote

[...] The check mark icons indicate if items of the
hierarchy are visible or not.
That seems to me to be counter-intuitive because a check mark
is used to indicated selection. If you're using it to indicate
that items are not present in the menu, thats one thing but if
they are indeed there but in a submenu, that's what the menu-
arrow is for.
Quote
To make owner drawing of items possible, I have created a
class that just contains that OnOwnerDraw function. Then the
address of the function can be assigned using a static
instance of that class.
It is correct that events need to be members of a class but
why a seperate class when this will do:
void __fastcall TForm1::DrawMenuItem(TObject *Sender, TCanvas *ACanvas, const TRect &ARect, bool Selected )
{
//
}
Quote
[...] the chevron ">" for the submenu is not shown for the
selected item. All other submenu items do show a chevron.
I don't see this behavior (XP). In fact, I don't even need to
draw the menu arrow because the OS draws it after I'm finished.
Quote
I draw this chevron at the correct position, but nothing is
shown. If I move the chevron image a couple of pixels to the
left, something is shown, but the original chevron seems to
be obscuring it in reverse colors.
You're going to have to post your code.
~ JD
 

Re:TPopupMenu Images and Checkboxes

JD wrote:
Quote
Wiljo < XXXX@XXXXX.COM >wrote:

>[...] The check mark icons indicate if items of the
>hierarchy are visible or not.


That seems to me to be counter-intuitive because a check mark
is used to indicated selection. If you're using it to indicate
that items are not present in the menu, thats one thing but if
they are indeed there but in a submenu, that's what the menu-
arrow is for.

I guess I didn't make myself clear enough. We are developing a CAD
program, where all objects are stored in a hierarchical structure. The
item check mark in the menu indicates whether the associated object in
the OpenGL window is visible or not. To me that doesn't seem
counter-intuitive, all OpenGL drawable items will be visible in the
popup menu, and the check mark shows whether or not they are actually
visible in the window. So one can change the visibility state of a
particual item.
Quote

>To make owner drawing of items possible, I have created a
>class that just contains that OnOwnerDraw function. Then the
>address of the function can be assigned using a static
>instance of that class.


It is correct that events need to be members of a class but
why a seperate class when this will do:

void __fastcall TForm1::DrawMenuItem(TObject *Sender, TCanvas *ACanvas, const TRect &ARect, bool Selected )
{
//
}

As I wrote in my post, we don't create/call the PopupMenu from inside a
form. We create a global Popup menu and add items dynamically,
independantly of any form or class. So we can't just assign
TForm1::DrawMenuItem, because we don't have a meaningful Form object to
use for it.
The check mark was there when I dynamically add simple items withou an
ImageList. But when I create a popup menu to show the current OpenGL
visibility states of the object tree hierarchy, I added an ImageList
along with it, and the check mark icon was lost. We draw all kinds of
different things in the OpenGL window, and we want their type indicated
with a little icon from that ImageList.
Only by using a seperate CMenuOwnerDraw class and correct member
function, it is possible to owner draw every menu item with check mark,
individual icon and caption.
Quote

>[...] the chevron ">" for the submenu is not shown for the
>selected item. All other submenu items do show a chevron.


I don't see this behavior (XP). In fact, I don't even need to
draw the menu arrow because the OS draws it after I'm finished.

I also see that it is drawn, but white instead of black. And on a white
background, one doesn't see the chevron at all. I must mention that this
only occurs for the item that has focus, all other visible items are
drawn correctly. It is the focused item, that has a submenu, that
doesn't draw the chevron.
Quote

>I draw this chevron at the correct position, but nothing is
>shown. If I move the chevron image a couple of pixels to the
>left, something is shown, but the original chevron seems to
>be obscuring it in reverse colors.


You're going to have to post your code.

Okay then, here it is:
static TPopupMenu *sapPopupMenu;
static TImageList *sapStateImages;
void FC COwnerDrawMenuItem
::mOnDrawItem ( System::TObject *Sender,
Graphics::TCanvas *ApCanvas,
const Types::TRect &ArcRect,
Windows::TOwnerDrawState State
)
{
TMenuItem *pMenuItem = dynamic_cast<TMenuItem *>( Sender );
if( pMenuItem != NULL )
{
//* Now take care of drawing the item itself
if( pMenuItem->Caption == "-" )
{
int cy = ArcRect.Top + ArcRect.Height() / 2;
//* Draw the seperator
ApCanvas->Pen->Color = clDkGray;
ApCanvas->MoveTo( ArcRect.Left, cy - 2 );
ApCanvas->LineTo( ArcRect.Right, cy - 2 );
}
else
{
TRect itemRect = ArcRect;
mDrawCheckMark( &itemRect, ApCanvas, pMenuItem );
mDrawImage( &itemRect, ApCanvas, pMenuItem->ImageIndex );
//* Create a small space between the icons and the text
itemRect.Left = itemRect.Left + 4;
ApCanvas->TextOut( itemRect.Left, itemRect.Top,
pMenuItem->Caption );
//* Draw the submenu chevron.
if( State.Contains( odSelected )
&& pMenuItem->Count>0 )
{
itemRect.Left = ArcRect.Right - 16;
sapStateImages->Draw( ApCanvas, itemRect.Left,
itemRect.Top, STATE_MENU_CHEVRON, true );
///////////
//* WJ20060912: Somehow it is not drawn, or obscured
///////////
}
}
}
}
//---------------------------------------------------------------------------
void COwnerDrawMenuItem
::mDrawCheckMark ( TRect *ApRect,
TCanvas *ApCanvas,
TMenuItem *ApMenuItem
)
{
if( ApMenuItem->Count == 0 )
{
sapStateImages->Draw( ApCanvas, ApRect->Left,
ApRect->Top, ApMenuItem->Checked
? STATE_CHECKMARK_ON : STATE_CHECKMARK_OFF, true );
}
ApRect->Left = ApRect->Left + 16;
}
//---------------------------------------------------------------------------
void COwnerDrawMenuItem
::mDrawImage ( TRect *ApRect,
TCanvas *ApCanvas,
int AImageIndex
)
{
if( NULL != sapPopupMenu->Images
&& AImageIndex>= 0 )
{
sapPopupMenu->Images->Draw( ApCanvas, ApRect->Left, ApRect->Top,
AImageIndex, true );
//* ApRect->Left is updated with the width of the image
ApRect->Left = ApRect->Left + sapPopupMenu->Images->Width;
}
}
//---------------------------------------------------------------------------
void FC COwnerDrawMenuItem
::mOnMeasureItem ( System::TObject *Sender,
Graphics::TCanvas *ApCanvas,
int &Width,
int &Height
)
{
TMenuItem *pMenuItem = dynamic_cast<TMenuItem *>( Sender );
if( pMenuItem != NULL )
{
SIZE size = ApCanvas->TextExtent( pMenuItem->Caption );
Height = 19;
Width = 3 * 16 + 4 + size.cx;
//* Three icons: checkmark, custom and chevron,
//* the caption and a small space
}
}
// The sapPopupMenu is created and initialized:
bool gbCreatePopupMenu( TImageList *ApImageList )
{
sapPopupMenu = new( HERE ) TPopupMenu( NULL );
sapPopupMenu->AutoHotkeys = maManual;
if( ApImageList != NULL )
{
sapPopupMenu->Images = ApImageList;
sapPopupMenu->OwnerDraw = true;
}
return sapPopupMenu != NULL;
}
And every item is also correctly initialized.
The problem is in the mOnDrawItem function where the chevron is drawn,
but doesn't become visible for the item that has focus.
I hope someone can help me solve this problem!
thx,
Wiljo.
 

Re:TPopupMenu Images and Checkboxes

Wiljo < XXXX@XXXXX.COM >wrote:
Quote

[...]
TMenuItem *pMenuItem = dynamic_cast<TMenuItem *>( Sender );

if( pMenuItem != NULL )
It is correct to check the result of dynamic_cast because
it can return NULL. However, it will only return NULL if
Sender *is not* a TMenuItem and in this case you know that
this event has been assigned only to TMenuItem(s) so
dynamic_cast isn't needed. A static_cast is perfectly
acceptable here and it does not have the additional runtime
overhead that dynamic_cast does.
Quote
ApCanvas->Pen->Color = clDkGray;
You need to account for the user changing the colors.
Quote
mDrawCheckMark( &itemRect, ApCanvas, pMenuItem );

mDrawImage( &itemRect, ApCanvas, pMenuItem->ImageIndex );
I strongly disagree with creating function calls to functions
that are only a few lines of code and the functions are never
reused. In terms of legacy code, it's just more {*word*99}py little
calls that someone will have to track down and in terms of
performance, for such a small function, it increases the
overhead by double digit percentage (didn't want to actually
profile it but I'm confident with that).
Quote
ApCanvas->TextOut( itemRect.Left, itemRect.Top,
pMenuItem->Caption );
This will not draw menu accelerators.
Quote
//* Draw the submenu chevron.
if( State.Contains( odSelected )
&& pMenuItem->Count>0 )
You do not need to manually draw the menu arrow.
Quote
sapStateImages->Draw( ApCanvas, itemRect.Left,
itemRect.Top, STATE_MENU_CHEVRON, true );
I also disagree with directly referencing sapStateImages within
this event when it can be accessed via the PopupMenu's Images
property.
Quote
[...] but doesn't become visible for the item that has focus.
You forgot (or were never told) that a Canvas (which is just
an object that encapsulates an HDC) is not static (well most
anyway) and you never initialized the properties that you're
responsible for.
When I tried your code, I got the same results that you were
getting. When I rewrote it, the problem disappeared and the
difference was the Canvas code that you didn't have. This
worked for me:
TRect R = ARect;
TMenuItem *pItem = static_cast<TMenuItem*>( Sender );
ACanvas->Brush->Color = clMenu;
ACanvas->Font->Color = clMenuText;
ACanvas->FillRect( R );
if( pItem->Caption == "-" )
{
int Y = R.Top + R.Height() / 2;
ACanvas->Pen->Color = clMenuText;
ACanvas->MoveTo( R.Left, Y - 2 );
ACanvas->LineTo( R.Right, Y - 2 );
}
else
{
if( State.Contains( odSelected )
{
ACanvas->Brush->Color = clHighlight;
ACanvas->Font->Color = clHighlightText;
ACanvas->FillRect( R );
}
// draw the check box
if( pItem->Count == 0 )
{
R.Top += 2;
R.Bottom -= 2;
R.Right = R.Left + (R.Bottom - R.Top);
unsigned int BoxState = DFCS_BUTTONCHECK;
if( pItem->Checked )
{
BoxState |= DFCS_CHECKED;
}
::DrawFrameControl( ACanvas->Handle, &R, DFC_BUTTON, BoxState );
R.Top = ARect.Top;
R.Bottom = ARect.Bottom;
R.Right = ARect.Right;
}
R.Left += 16;
// draw the image
if( sapPopupMenu->Images )
{
if( pItem->ImageIndex>-1 )
{
sapPopupMenu->Images->Draw( ACanvas, R.Left, R.Top, pItem->ImageIndex, true );
}
R.Left += sapPopupMenu->Images->Width + 2;
}
::DrawText( ACanvas->Handle, pItem->Caption.c_str(), -1, &R, DT_LEFT | DT_SINGLELINE | DT_VCENTER );
}
~ JD
 

Re:TPopupMenu Images and Checkboxes

JD wrote:
Quote
Wiljo < XXXX@XXXXX.COM >wrote:

>[...]
>TMenuItem *pMenuItem = dynamic_cast<TMenuItem *>( Sender );
>
>if( pMenuItem != NULL )


It is correct to ..... A static_cast is perfectly
acceptable here and it does not have the additional runtime
overhead that dynamic_cast does.

I'll take this into account.

>mDrawCheckMark( &itemRect, ApCanvas, pMenuItem );
>
>mDrawImage( &itemRect, ApCanvas, pMenuItem->ImageIndex );


I strongly disagree with creating function calls to functions
that are only a few lines of code and the functions are never
reused....

Thanks, and you are right.

>ApCanvas->TextOut( itemRect.Left, itemRect.Top,
>pMenuItem->Caption );

This will not draw menu accelerators.

The text of the items in this example do not contain accelerators, but
other dynamically generated Popup menu's might. So TextOut is indeed
incorrect.
Quote

>//* Draw the submenu chevron.
>if( State.Contains( odSelected )
>&& pMenuItem->Count>0 )


You do not need to manually draw the menu arrow.

Well I added that code, because I didn't see the arrow!

>sapStateImages->Draw( ApCanvas, itemRect.Left,
>itemRect.Top, STATE_MENU_CHEVRON, true );


I also disagree with directly referencing sapStateImages within
this event when it can be accessed via the PopupMenu's Images
property.

Well it is actually another ImageList, not the one from PopupMenu. The
Images in the PopupMenu contains the icons of the items. The
sapStateImages contains the check mark and menu arrow. But is no longer
necessary when I used your code.
Quote

>[...] but doesn't become visible for the item that has focus.


You forgot (or were never told) that a Canvas (which is just
an object that encapsulates an HDC) is not static (well most
anyway) and you never initialized the properties that you're
responsible for.
I figured that too. The Canvas is not correctly initialized and I didn't
know how to change it correctly.
Quote

When I tried your code, I got the same results that you were
getting. When I rewrote it, the problem disappeared and the
difference was the Canvas code that you didn't have. This
worked for me: .....
~ JD

Thanks alot JD,
The code worked for me too. I only changed the position where the second
rectangle is drawn. This rectangle shows which item has focus, and now
excludes the icons, just the text.
I do have a note though. I saw you wrote:
R.Left += 16;
Be aware though that Left is a property of Rectangle R, and not an
attribute. Although it works in this example, because it is directly
replaced by the correct attribute (left), not all properties do. Most
properties would have read and write functions, where an increment like
this will not work (Although the compiler will not complain). I have
allways learned that a property must be written out to:
R.Left = R.Left + 16;
Best regards,
Wiljo.