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;
}
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
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
"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"
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
Thanks!
It's been here all the long !!
~ JD
I'll test your code, and i'll let you know how it goes, thanks.
Ramy
Of Course....
Ramy
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
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
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
:-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
[...]
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
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
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
Hasn't anyone told you not to use OnCreate?:
http://www.bcbdev.com/articles/suggest.htm#oncreate
~ JD
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