Link to home
Create AccountLog in
Avatar of JDJVR
JDJVR

asked on

How to get CM_MOUSEENTER and CM_MOUSELEAVE to work with any control without modifying the components

Hello Experts,

I want to control the display of certain elements when the mouse cursor moves on to, and off,  a control such as a TBitBtn, TImage, etc. I have found very useful examples in the EE solutions repository. The code I use for accomplishing what I need is as follows:

In the form's unit interface:

.
private
    procedure CMMouseEnter(var Msg: TMessage); message CM_MOUSEENTER;
    procedure CMMouseLeave(var Msg: TMessage); message CM_MOUSELEAVE;
.
.  
and in the form's implementation section:
.
procedure TForm1.CMMouseEnter(var Msg: TMessage);
begin
  if (Msg.LParam = Integer(BitBtn1)) then
  begin
    Image1.Visible := True;
  end;
end;

procedure TForm1.CMMouseLeave(var Msg: TMessage);
begin
  if (Msg.LParam = Integer(BitBtn1)) then
  begin
    Image1.Visible := False;
  end;
end;

This works quite well BUT ONLY IF BITBTN1 IS DIRECTLY PLACED ON FORM1. If I place BitBtn1 on a Panel1, which in turn is placed on Form1, the above code I use does not work. I have a strong feeling this has something to do with the parent-child relationship and handling of CM_MOUSEENTER and CM_MOUSELEAVE at the parent.
How can I get this to work with controls not directly placed on the Form1, but on panels and sub-panels of panels on a form? I DO NOT WANT TO MODIFY ANY COMPONENTS.

Can anyone help? - I need some example code urgently

Thanks,
JDJVR
Avatar of calinutz
calinutz
Flag of Romania image

I think that this is what you need....


...
  private
    FFocusControl: TControl;
    procedure ApplicationIdle(Sender: TObject; var Done: Boolean);
    { Private declarations }
  public
    procedure OnEnter(Sender: TObject);
    procedure OnExit(Sender: TObject);
    { Public declarations }


procedure TForm1.ApplicationIdle(Sender: TObject; var Done: Boolean);
var
  CurControl: TControl;
  P: TPoint;
begin
  GetCursorPos(P);
  CurControl := FindDragTarget(P, True);
  if FFocusControl <> CurControl then
  begin
    if FFocusControl <> nil then
      OnExit(FFocusControl);
    FFocusControl := CurControl;
    if FFocusControl <> nil then
      OnEnter(FFocusControl);
  end;
end;

procedure TForm1.OnEnter(Sender: TObject);
begin
  //OnEnter code
  if (sender = Image2)or(sender=Label14) then
  begin
  Image2.Left:=Image2.Left+1;
  Image2.Picture.LoadFromFile('C:\save1.bmp');
  Label14.Font.Style:=[fsBold];
  end;
end;

procedure TForm1.OnExit(Sender: TObject);
begin
  //OnExit code
  if (sender = Image2)or(sender=Label14) then
  begin
  Image2.Left:=Image2.Left-1;
  Image2.Picture.LoadFromFile('C:\save2.bmp');
  Label14.Font.Style:=[];
  end;
end;
SOLUTION
Avatar of Mike Littlewood
Mike Littlewood
Flag of United Kingdom of Great Britain and Northern Ireland image

Link to home
membership
Create a free account to see this answer
Signing up is free and takes 30 seconds. No credit card required.
See answer
also you should put in inherited in the idle event too I think.

procedure TForm1.ApplicationIdle(Sender: TObject; var Done: Boolean);
var
  CurControl: TControl;
  P: TPoint;
begin
  GetCursorPos(P);
  CurControl := FindDragTarget(P, True);
  if FFocusControl <> CurControl then
  begin
    if FFocusControl <> nil then
      OnExit(FFocusControl);
    FFocusControl := CurControl;
    if FFocusControl <> nil then
      OnEnter(FFocusControl);
  end;
  inherited;
end;
In fact you could even make it more generic if you know for example you want the same action to happen to all TBitBtn's pictures on the form for example.
From reading your original code you don't want an image on the button when the mouse is off the control and you do when you hover the mouse over it.
You could use the tag property of the TBitBtn to hold to visible image index reference of the image you want to show and clear the image after it leaves the control.

procedure TForm1.OnEnter(Sender: TObject);
begin
  //OnEnter code
  if (sender is TBitBtn) then
  begin
    ImageList1.GetBitmap((Sender as TBitBtn).Tag, (Sender as TBitBtn).Glyph);
    (Sender as TBitBtn).Invalidate;
  end;
end;

procedure TForm1.OnExit(Sender: TObject);
begin
  //OnExit code
  if (sender is TBitBtn) then
  begin
    (Sender as TBitBtn).Glyph := nil
  end;
end;
Avatar of JDJVR
JDJVR

ASKER

Hi guys,
Tried your suggestions but could not get the code to work. I think we are getting lost in the details of what happens in the OnExit and OnEnter procedures. That is not my problem at this stage and therefore not important. If you take a look at my original code which worked well for any control placed directly on Form1 (Form1 is therefore the parent) there is no problem executing. But if a control (say BitBtn2) is located on a child of Form1 such as a Panel1, the code would work for Panel1, but not for any children of  Panel1. (If I moved the mouse cursor over Panel1 and had code in place to be executed, it would work - but not for BitBtn2 or any other child components of Panel1.) My original code therefore only works for ANY IMMEDIATE CHILD COMPONENTS OF FORM1.

To give more background, I need help text to appear when moving the mouse cursor on ANY control in the application (Buttons, ListBoxes, etc) and on ALL forms in the application. The help text is just a simple background image with a label which appears when the mouse is over the control, and disappears when the mouse moves off the control. I have made peace with the fact that I would in all probability have to duplicate whatever solution is appropriate, for every form in the application. The ideal is to have just one block of code to control this for the entire application, but I somehow feel this will in all probability not be possible. Maybe I'm wrong. You can now see why I do not want to modify components because it would mean I would have to modify every single component in the application only for displaying a pop-up help text graphic associated with it - which is ludicrous. Borland could have saved us a lot of hassles by publishing OnMouseEnter and OnMouseLeave events for most of their components.

Is there any way that MY ORIGINAL CODE can be altered to overcome the problem? We just need to implement some code that takes care of the parent-child issue - it should not be too difficult.

Thanks,
JDJVR
Avatar of JDJVR

ASKER

I am increasing the points to 300 - The difficulty grade may not be that high (I don't know) but I need a solution urgently.
ASKER CERTIFIED SOLUTION
Avatar of Eddie Shipman
Eddie Shipman
Flag of United States of America image

Link to home
membership
Create a free account to see this answer
Signing up is free and takes 30 seconds. No credit card required.
Avatar of JDJVR

ASKER

Hello EddieShipman!
I knew if I tried long enough I would somehow coax you to give some input. The code you supplied works fine if the same action is required for all BitBtns on the form. This means that the same graphic is displayed if the mouse cursor moves over ANY BitBtn on the form. But the problem is that each BitBtn has its own specific associated graphic, so I'm afraid the solution you gave does not work for me. In some way or another each BitBtn (or any other control, for that matter) must be identified by a code section such as:
if (Msg.LParam = Integer(BitBtn1)) then
  begin
    Image1.Visible := True;
  end;
in order to get the correct image to appear. As I have explained before, the above code works for all controls directly parented by Form1 as per my original question, but not for subsequent child controls lower in the object hierarchy. Any ideas?

JDJVR
Avatar of JDJVR

ASKER

I have tried incorporating the above code into procedure CMMMouseEnter/Leave but it does not work - forgot to mention this.
Then just check the sender's name in the CMMouseEnter/Leave, duh!!!

 if TBitBtn(Sender).Name = 'BitBtn1' then
 begin
  // do whatever for BitBtn1
 end;

 if TBitBtn(Sender).Name = 'BitBtn2' then
 begin
  // do whatever for BitBtn2
 end;

Or,use the tag property and use a case statement:

  case  TBitBtn(Sender).Tag of
  0:
    // do BitBtn1's stuff here
  1:
    // do BitBtn2's stuff here
  2:
    // do BitBtn3's stuff here
  end;
Avatar of JDJVR

ASKER

Yeah, I realized it a while after typing the last comment - but its late over here and I'm not thinking straight anymore. I also got the code supplied by mikelittlewood to work, so the problem's solved. I'll  be splitting the points between EddieShipman and mikelittlewood. Going to catch some shut-eye now.

Thanks,
JDJVR
Your question was:
" I want to control the display of certain elements when the mouse cursor moves on to, and off,  a control such as a TBitBtn, TImage, etc. I have found very useful examples in the EE solutions repository."

This was your question... and my answer... was first and it did what you asked for in your question... right?
 Wasn't I supposed to get at least a part of the points?
Curious...
:(
No offense << mikelittlewood >> but your answer was taken from mine and modified.
I do not need those points... It just seemed unfair, that's all.
I guess I will not be answering << JDJVR >> any more questions then. I am quite busy most of the time.
I do not like WASTING my time.
Hi Calinutz,

Yes I did just modify your answer, but I was just trying to give some other alternatives for storing his images more than anything.
I didn't see any need to modify much of your original code as I believe your answer was the method I would have used.

No offense taken and I hope the above message vindicates your answer.

Cheers
Mike
Avatar of JDJVR

ASKER

Hello calinutz and mikelittlewood,

I am sorry if I offended anyone through the points awarding, but let me explain my motivation:

I am also in the middle of a hectic schedule, trying to fix a hundred things simultaneously, mostly late at night. When calinutz offered a solution, I was at that stage already overworked and could not get his example code to work. calinutz did not point out the ApplicationIdle procedure link with the Application.OnIdle event, which caused me an entire late night trying to get it working, WASTING MY TIME, which I also do not enjoy.

I am not a Delphi guru, this is why I depend on the experts to provide solutions as clearly as possible. The experts unfortunately assume us nitwits to know more than we actually do. So calinutz offered a partial solution but the "little extra" offered by mikelittlewood got me to get the thing working very quickly and I therefore decided to award him half the points. EddieShipman was the only expert to actually address my original question, but the other solution proved equally suitable.

In retrospect, I probably should have given calinutz his share of the points but it was late at night, I was tired, frustrated and not thinking straight (you can see that from my last communication with EddieShipman). So, calinutz, please accept my apologies - next time I will leave points awarding for the next morning when my brain works properly. Hope you understand.

Regards
JDJVR

You can re-open the question and split the points diferently if you want.
Go to Community Support and ask to reopen for split.
Avatar of JDJVR

ASKER

Nah, what's done is done. Spent too much time on this issue already. Hope to do better next time.
JDJVR
As I said before : I do not need those points... It just seemed unfair at that time, that's all.
I think I know what it is like to work late ( I usually wake up at 7 in the morning , go to work ,and go to bed at 4-5 in the morning... and meanwhile I work... so I know how it is like to be tired).
Appologies accepted