Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Problems with OnClick, OnMouseEnter & OnMouseLeave

119 views
Skip to first unread message

Ramy

unread,
Jun 29, 2004, 7:34:07 AM6/29/04
to

Hi, i think it's better to open new tread becouse the last one
already has too many subjects. My new code is the code bellow,
my button base on a Panel, on this panel i have a small image
which is part of the button's class ( later i'll add also
2 labels under this image ), when i move the mouse curser over
the button i show the panel Inner Bevel, when the mouse is out
of the button i don't show this bevel ( BevelInner = bvNone ).

As i said in the other tread i also need to know if the user
has press inside the button but released the mouse button only
when it was out of my button's borders, then i don't want to
call my ButtonClick operation.

I see 2 problems with this code -

1. if i enter to the button slow and inside the image, and then
go out of the button slowly then everything is fine. but if i am
inside the image, and then i move the mouse fast ( not even too
fast ) out of the button's borders, then the WndProc get only
'CM_MOUSELEAVE' message for the Image, but is doesn't get the
'CM_MOUSELEAVE' and the 'CM_MOUSEENTER' messages for the panel,
so the application still think that it is inside the button
( and i still see panel's bevel... ). Why? and how can i solve
such situations?

2. If i click inside the button, on the image for example, and
then i move the mouse cursor out of the button, and only then
release it, the application still think that it is inside the
button and it calls the MouseUpInsideButton(); function,
this is not good, if the user released the mouse button out
of the button i don't want to do the operation.

Is there any good solution for this situation?

Thanks very much,
Ramy

Here is the code -

// - - - - - - - - - - - WndProc - - - - - - - - - - - - -

void __fastcall TBaseButton::WndProc( TMessage &Message )
{
static bool MouseAlreadyInButton = false;

if ( Message.Msg == CM_MOUSEENTER || Message.Msg == CM_MOUSELEAVE )
{
TImage *ImagePtr = dynamic_cast<TImage*>( reinterpret_cast<TControl*>( Message.LParam ) );

if ( Message.Msg == CM_MOUSEENTER )
{
if ( ImagePtr )
MouseAlreadyInButton = true;
else
if( !MouseAlreadyInButton )
OnMouseEnter();
}
else if( !ImagePtr )
{
RECT Rect;
POINT Point;
::GetWindowRect( Handle, &Rect ); // ( Win 32 Call )
::GetCursorPos( &Point ); // ( Win 32 Call )

if ( !::PtInRect( &Rect, Point ) )
{
MouseAlreadyInButton = false;
OnMouseLeave();
}
}
}

if ( MouseInImage )
{
if ( Message.Msg == WM_LBUTTONDOWN )
{
MouseDownInsideButton();
}

if ( Message.Msg == WM_LBUTTONUP )
{
MouseUpInsideButton(); // = "Button Click"
}
}

inherited::WndProc( Message );
}

// Mouse Enter Event

// - - - - - - - - - - Mouse Enter - - - - - - - - - - - - -

void __fastcall TBaseButton::OnMouseEnter( void )
{
BevelOuter = bvNone;
BevelInner = bvRaised;
MouseInImage = true;
}

// - - - - - - - - - - Mouse Leave - - - - - - - - - - - - -

// Mouse Leave Event

void __fastcall TBaseButton::OnMouseLeave( void )
{
BevelOuter = bvNone;
BevelInner = bvNone;
MouseInImage = false;
}

Ramy

unread,
Jun 29, 2004, 7:55:16 AM6/29/04
to

OK forget for now about problem 2, i solved it like this -

if ( MouseInImage )
{
if ( Message.Msg == WM_LBUTTONDOWN )
{
MouseDownInsideButton();
}

if ( Message.Msg == WM_LBUTTONUP )
{
RECT Rect;
POINT Point;
::GetWindowRect( Handle, &Rect ); // ( Win 32 Call )
::GetCursorPos( &Point ); // ( Win 32 Call )

if ( !::PtInRect( &Rect, Point ) )
{
MouseAlreadyInButton = false;
OnMouseLeave();
}

else


MouseUpInsideButton(); // = "Button Click"
}
}

Although that it's not a perfect solution becouse i want the
panel's bevel to disappear at the moment i leave the button's
borders, but in my solution if the user press inside the button
and release the mouse left button only when it's out of the
button, then the panel's bevel will be visible untill the user
has release the mouse button...

Thanks,
Ramy


JD

unread,
Jun 29, 2004, 4:08:38 PM6/29/04
to

"Ramy" <Ra...@NoMail.com> wrote:
> [...] I see 2 problems with this code -

You'll need to search and replace 'ButtonImage' with the name
that you gave the image on the panel and then insert your
OnClick code.

//-------------------------------------------------------------
class TBaseButton : public TPanel
{
protected:
DYNAMIC void __fastcall MouseDown(TMouseButton Button, TShiftState Shift, int X, int Y);
DYNAMIC void __fastcall MouseMove(TShiftState Shift, int X, int Y);
DYNAMIC void __fastcall MouseUp(TMouseButton Button, TShiftState Shift, int X, int Y);
private:
typedef TPanel inherited;
HDC hdc;
TCanvas* ButtonCanvas;
bool ButtonDown;
void __fastcall ButtonImageMouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y);
void __fastcall ButtonImageMouseMove(TObject *Sender, TShiftState Shift, int X, int Y);
void __fastcall ButtonImageMouseUp(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y);
void __fastcall ButtonMouseDown();
void __fastcall ButtonMouseMove( TShiftState Shift );
void __fastcall ButtonMouseUp();
bool __fastcall MouseInButton();
void __fastcall ReleaseButton();
public:
__fastcall TBaseButton( TComponent* Owner );
__fastcall TBaseButton::~TBaseButton();
};
//-------------------------------------------------------------
__fastcall TBaseButton::TBaseButton(TComponent* Owner) : TPanel( Owner )
{
hdc = ::GetDC( Handle );
ButtonCanvas = new TCanvas();
ButtonCanvas->Handle = hdc;
ButtonDown = false;

ButtonImage->OnMouseDown = ButtonImageMouseDown;
ButtonImage->OnMouseMove = ButtonImageMouseMove;
ButtonImage->OnMouseUp = ButtonImageMouseUp;
}
//-------------------------------------------------------------
__fastcall TBaseButton::~TBaseButton()
{
delete ButtonCanvas;
::ReleaseDC( Handle, hdc );
}
//-------------------------------------------------------------


void __fastcall TBaseButton::WndProc( TMessage &Message )
{

if( Message.Msg == CM_MOUSEENTER || Message.Msg == CM_MOUSELEAVE )
{
TRect r = Rect( 0, 0, Width, Height );
if( MouseInButton() ) Frame3D( ButtonCanvas, r, clBtnHighlight, clBtnShadow, 2 );
else Frame3D( ButtonCanvas, r, Color, Color, 2 );
}
inherited::WndProc( Message );
}
//-------------------------------------------------------------
void __fastcall TBaseButton::MouseDown(TMouseButton Button, TShiftState Shift, int X, int Y)
{
ButtonMouseDown();
inherited::MouseDown(Button, Shift, X, Y);
}
//-------------------------------------------------------------
void __fastcall TBaseButton::MouseMove(TShiftState Shift, int X, int Y)
{
ButtonMouseMove( Shift );
inherited::MouseMove(Shift, X, Y);
}
//-------------------------------------------------------------
void __fastcall TBaseButton::MouseUp(TMouseButton Button, TShiftState Shift, int X, int Y)
{
ButtonMouseUp();
inherited::MouseUp(Button, Shift, X, Y);
}
//-------------------------------------------------------------
void __fastcall TBaseButton::ButtonImageMouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y)
{
ButtonMouseDown();
}
//-------------------------------------------------------------
void __fastcall TBaseButton::ButtonImageMouseMove(TObject *Sender, TShiftState Shift, int X, int Y)
{
ButtonMouseMove( Shift );
}
//-------------------------------------------------------------
void __fastcall TBaseButton::ButtonImageMouseUp(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y)
{
ButtonMouseUp();
}
//-------------------------------------------------------------
void __fastcall TBaseButton::ButtonMouseDown()
{
TRect r = Rect( 0, 0, Width, Height);
Frame3D( ButtonCanvas, r, clBtnShadow, clBtnHighlight, 2 );
ButtonImage->Left = ButtonImage->Left + 1;
ButtonImage->Top = ButtonImage->Top + 1;
ButtonDown = true;
}
//-------------------------------------------------------------
void __fastcall TBaseButton::ButtonMouseMove( TShiftState Shift )
{
if( Shift.Contains(ssLeft) && ButtonDown )
{
if( !MouseInButton() ) ReleaseButton();
}
}
//-------------------------------------------------------------
void __fastcall TBaseButton::ButtonMouseUp()
{
if( ButtonDown )
{
ReleaseButton();
ShowMessage("Execute Button Click");
}
}
//-------------------------------------------------------------
bool __fastcall TBaseButton::MouseInButton()


{
RECT Rect;
POINT Point;
::GetWindowRect( Handle, &Rect );

::GetCursorPos( &Point );
if( ! ::PtInRect(&Rect, Point) ) return false;
else return true;
}
//-------------------------------------------------------------
void __fastcall TBaseButton::ReleaseButton()
{
TRect r = Rect( 0, 0, Width, Height);
Frame3D( ButtonCanvas, r, Color, Color, 2 );
ButtonImage->Left = ButtonImage->Left - 1;
ButtonImage->Top = ButtonImage->Top - 1;
ButtonDown = false;
}
//-------------------------------------------------------------

~ JD

Ramy

unread,
Jun 30, 2004, 2:13:31 AM6/30/04
to
Hmm... maybe i was a little confused... i think that you already
gave me the answer, i'll check it and let you know if i'll have
problem with this code, now after that i removed the Bevel.

"If the only reason that you're keeping track of the mouse
entering and leaving the panel/image is to know when it was
clicked, you can delete all of that code and use the mouse
events directly.

Assign OnMouseDown/Move/Up events to the Image and check for
clicking only on the Image:

class TBaseButton : public TPanel
{
private:
bool Clicked;
void __fastcall ImageMouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y);
void __fastcall ImageMouseMove(TObject *Sender, TShiftState Shift, int X, int Y);
void __fastcall ImageMouseUp(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y);
};

//-------------------------------------------------------------
__fastcall TBaseButton::TBaseButton(TComponent* Owner) : TPanel( Owner )
{

Clicked = false;
// allocate the image
Image->OnMouseDown = ImageMouseDown;
Image->OnMouseMove = ImageMouseMove;
Image->OnMouseUp = ImageMouseUp;
}
//-------------------------------------------------------------
void __fastcall TBaseButton::ImageMouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y)
{
Clicked = true;
// call the function to animate a button down click
}
//-------------------------------------------------------------
void __fastcall TBaseButton::ImageMouseMove(TObject *Sender, TShiftState Shift, int X, int Y)
{
if( Shift.Contains(ssLeft) )
{
RECT Rect;
POINT Point;
::GetWindowRect( Image->Handle, &Rect );


::GetCursorPos( &Point );
if( ! ::PtInRect(&Rect, Point) )

{
// animate the button up
Clicked = false;
}
}
}
//-------------------------------------------------------------
void __fastcall TBaseButton::ImageMouseUp(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y)
{
if( Clicked )
{
Clicked = false;
// animate the button up
// perform OnClick
}
}
//-------------------------------------------------------------

~ JD"


JD

unread,
Jun 30, 2004, 2:34:45 AM6/30/04
to

"Ramy" <Ra...@NoMail.com> wrote:
> [...] i'll check it and let you know if i'll have problem
> with this code [...]

Don't use this code. Use the code in my last post. I tested it
by subclass the panel's WndProc because I wasn't inclined to
create a seperate class so there might be a type-o in the
coversion but in my test it worked perfectly.

~ JD

Ramy

unread,
Jun 30, 2004, 6:00:32 AM6/30/04
to

Hi JD, if you still have that test on your PC, can you please
copy - paste it's code into your next respond in this thread?

Thanks!

JD

unread,
Jun 30, 2004, 8:54:23 AM6/30/04
to

"Ramy" <Ra...@NoMail.com> wrote:
> [...] paste it's code into your next respond in this thread?

It's been here all the long !!

http://newsgroups.borland.com/cgi-bin/dnewsweb?cmd=article&group=borland.public.cppbuilder.vcl.components.using&item=82530&utag=

~ JD

Ramy

unread,
Jun 30, 2004, 9:20:57 AM6/30/04
to

Sorry JD i did not see this post, don't have to be so angry,
i'm reading the newsgroup directly from Borland's pages and it's
very hard to follow messages here becouse you don't see them as
one cluster of messages, but messages from one thread are
scattered all around over several pages, so i missed this post.

I'll test your code, and i'll let you know how it goes, thanks.

Ramy

Ramy

unread,
Jun 30, 2004, 12:04:03 PM6/30/04
to

__fastcall TBaseButton::~TBaseButton()
{
delete ButtonImage;
}

Of Course....

Ramy

JD

unread,
Jun 30, 2004, 3:30:51 PM6/30/04
to
"Ramy" <Ra...@NoMail.com> wrote:
> [...] i kept getting an arror of "control has no parent
> window" on the line -
>
>hdc = ::GetDC( Handle );

I didn't think of that one because I just dropped a panel on
the form and tested with it. The error is because until it's
been constructed, the TBaseButton doesn't have a Parent. That
can be fixed by moving that line after the Parent has been
assigned.

>which was solved after i added this line before it -
>
>Parent = dynamic_cast<TWinControl *>( Owner );

There you go.

> I'm just interesting, what did you change between this code
> and the code before, that solved the problem of not detecting
> "MouseLeave" when i moved the mouse fast from the image and
> out of the button?

The first sample relied on the messages arriving in a specific
order. I was able to see that the messages were sent to each
control correctly but by moving the mouse quickly, the
MouseLeave for the Panel would arrive before the MouseLeave
for the Image which screwed up the static bool and that code
depended on the static bool being correct.

The big difference in this code is that it doesn't try to
determine if the message was from the panel or the image and
that when it gets either of the messages, it checks where the
mouse is independent of the VCL.

> ( My code after the changings is bellow )
>
>void __fastcall TForm1::Timer1Timer( TObject *Sender )
>{
> Timer1->Enabled = false;
>
> TBaseButton *Button1 = new TBaseButton( this );

Why are you using a Timer to allocate the button? Timers are a
limited system resource (16 of them if I remember correctly)
that should be avoided when ever possible because if other
applications have used up the resource, your program won't run
and you'll get an Out Of Resource error.

~ JD

JD

unread,
Jun 30, 2004, 3:35:06 PM6/30/04
to

"Ramy" <Ra...@NoMail.com> wrote:
>
>__fastcall TBaseButton::~TBaseButton()
>{
> delete ButtonImage;
>}
>

In reality, you don't need that there. You assigned the button's
owner as the form when you allocated it and since you want the
button to be there as long as the form is there, you *could*
just let the native behavior handle it's destruction.

To be clear, when an object is destroyed, all of it's owned
objects are also destroyed in a nice orderly fashion.

~ JD

JD

unread,
Jun 30, 2004, 3:43:16 PM6/30/04
to

"Ramy" <Ra...@NoMail.com> wrote:
> [...] don't have to be so angry,

That wasn't anger. It was more like when you do something
stupid and your friends just won't let you forget it. <vbg>

> i'm reading the newsgroup directly from Borland's pages [...]

Me too. I hate readers. They just don't give me enough room.
What you can do is click on any link in the thread and then
click on the Related button. That's what I do.

~ JD

Ramy

unread,
Jul 1, 2004, 2:16:54 AM7/1/04
to

>That wasn't anger. It was more like when you do something
>stupid and your friends just won't let you forget it. <vbg>

:-D

>What you can do is click on any link in the thread and then
>click on the Related button. That's what I do.

Thanks that's was a good tip!

Ramy

Ramy

unread,
Jul 1, 2004, 2:24:40 AM7/1/04
to
Thanks, basicaly i know this rule, i don't know why i did that,
but tell me, why in your example did you wrote -

[...]
ButtonCanvas = new TCanvas();
[...]

and than -

__fastcall TBaseButton::~TBaseButton()
{
delete ButtonCanvas;

[...]
}

?

Why didn't you gave the ButtonCanvas an owner so that when the
button is destroyed so is the ButtonCanvas? why did you have to
destroyed it manualy in your Destructor?

Ramy

Ramy

unread,
Jul 1, 2004, 2:44:18 AM7/1/04
to

>>which was solved after i added this line before it -
>>
>>Parent = dynamic_cast<TWinControl *>( Owner );
>
>There you go.

Yes :-)

>The first sample relied on the messages arriving in a specific
>order. I was able to see that the messages were sent to each
>control correctly but by moving the mouse quickly, the
>MouseLeave for the Panel would arrive before the MouseLeave
>for the Image which screwed up the static bool and that code
>depended on the static bool being correct.
>
>The big difference in this code is that it doesn't try to
>determine if the message was from the panel or the image and
>that when it gets either of the messages, it checks where the
>mouse is independent of the VCL.

Thanks for the explenation, the new code works great!

>> ( My code after the changings is bellow )
>>
>>void __fastcall TForm1::Timer1Timer( TObject *Sender )
>>{
>> Timer1->Enabled = false;
>>
>> TBaseButton *Button1 = new TBaseButton( this );
>
>Why are you using a Timer to allocate the button? Timers are a
>limited system resource (16 of them if I remember correctly)
>that should be avoided when ever possible because if other
>applications have used up the resource, your program won't run
>and you'll get an Out Of Resource error.

I did that just as a seperated test, not as part of my project,
i just wanted to test the new code and the class, i did that
becouse several times in the past it happened to me that i got
errors when there was a code that use components that where not
yes created, when i start the code 250 ms after the application
starts that's insures me that all the components that i'm going
to use or set their values are already there.

But i know that normally i have to do that in the first
'OnActive' event or in the 'FormCreate'.

Ramy

JD

unread,
Jul 1, 2004, 4:30:49 AM7/1/04
to

"Ramy" <Ra...@NoMail.com> wrote:
> [...] i don't know why i did that,

I still do it all of the time myself. It's always been that
every allocation has a corresponding deallocation and it's a
hard habit to break - especially when others have to work with
your code.

>Why didn't you gave the ButtonCanvas an owner so that when the
>button is destroyed so is the ButtonCanvas? why did you have to
>destroyed it manualy in your Destructor?

A TCanvas is one of the VCL objects that doesn't have an Owner
property just like all non-visual object don't have a Parent
property.

~ JD

JD

unread,
Jul 1, 2004, 4:34:35 AM7/1/04
to

"Ramy" <Ra...@NoMail.com> wrote:
> [...] normally i have to do that in the first 'OnActive'

> event or in the 'FormCreate'.

Hasn't anyone told you not to use OnCreate?:

http://www.bcbdev.com/articles/suggest.htm#oncreate

~ JD

Ramy

unread,
Jul 1, 2004, 6:59:21 AM7/1/04
to

No, but thanks for the Tip! also this page have some realy
good tips which i did not know about.

Just a remark, it is written there -

"Don't link with LIB files or OBJ files created by other
compilers -

Visual C++ uses a derivative of the COFF file format format
for LIB and OBJ files. Borland compilers use the OMF format.
The two are not compatible. You cannot link with a LIB or OBJ
file that was created with Visual C++ (or any other compiler
for that matter)."


Well,

I had to work once with a Frame Grabber that uses Visual C++ LIB
file of type COFF, as it is i could not use it in the builder,
to solve this problem i used the application "Wintools.exe"
which is on the directory -

...\Borland\CBuilder4\Examples\WinTools

This application makes a converting of COFF Libs format into
OMF Lib format, after i did that i could work with the Frame
Grabber without any problem.

Ramy

Ramy

unread,
Jul 1, 2004, 10:14:03 AM7/1/04
to
Thanks.

Ramy

0 new messages