View Issue Details

IDProjectCategoryView StatusLast Update
0001832JEDI VCL00 JVCL Componentspublic2004-06-10 00:26
ReporteranonymousAssigned Touser72 
PrioritynormalSeverityminorReproducibilityalways
Status resolvedResolutionfixed 
Product Version 
Target VersionFixed in Version 
Summary0001832: TJvPanel - OnMouseLeave and Enter events get fired in strange orders
DescriptionPlace a TJvPanel (pnl1) on a form, and then place another TJvPanel (pnl2) inside pnl1.

i.e pnl1 is on form1, pnl2 is on pnl1

Add a memo to the form, and set the MouseEnter/Leave handlers on the panels to track the events as they fire, e.g:

procedure TForm3.pnl1MouseEnter(Sender: TObject);
begin
  JvMemo1.Lines.Add((Sender as TJvPanel).Name + '.Enter');
end;

With handlers on pnl2 only:

Mouse enters pnl2, get events:
pnl2.Leave
pnl2.Enter

Mouse leaves pnl2
<no events>


With handlers on both pnl1 and pnl2:

Mouse enters pnl1
pnl1.Leave
pnl1.Enter

Mouse enters pnl2
pnl1.Leave
pnl2.Leave
pnl1.Enter
pnl2.Enter

Mouse leaves pnl2
pnl1.Leave
pnl1.Enter

Mouse leaves pnl1
<no events>


Additional Informationunit Unit3;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, JvExStdCtrls, JvMemo, ExtCtrls, JvExExtCtrls, JvComponent,
  JvPanel;

type
  TForm1 = class(TForm)
    JvMemo1: TJvMemo;
    pnl1: TJvPanel;
    pnl2: TJvPanel;
    procedure pnl2MouseEnter(Sender: TObject);
    procedure pnl2MouseLeave(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.pnl2MouseEnter(Sender: TObject);
begin
    JvMemo1.Lines.Add((Sender as TJvPanel).Name + '.Enter');
end;

procedure TForm1.pnl2MouseLeave(Sender: TObject);
begin
    JvMemo1.Lines.Add((Sender as TJvPanel).Name + '.Leave');
end;

end.
TagsNo tags attached.

Activities

user72

2004-06-04 01:07

  ~0004460

YOu could try modifying MouseEnter/MouseLeave and move the "inherited MouseXXXX" calls outisde the if statement and see if that changes anything but I seem to remember that doing that might cause other problems...

hasse42g

2004-06-07 06:24

reporter   ~0004488

The mysterious behaviour you experience is probably caused by the fact that the MouseDown-property of the derived class TJvExCustomPanel is not properly initialized. Most computers initializes boolean variables to false (that's probably the reason the error wasn't discovered before), but that is not true for all systems. Some behave in a random way.

Try to add the following line to the TJvExCustomPanel.Create constructor (in JvExExtCtrls.pas):

  FMouseOver := False;

Regards

Hans-Eric Grönlund

hasse42g

2004-06-07 06:34

reporter   ~0004489

I spoke too soon. The solution does not solve the issue described. But, still - the FMouseOver field variable is uninitialized, which is not recommendable.

hasse42g

2004-06-08 01:11

reporter   ~0004490

Last edited: 2004-06-08 02:22

Ah, finally I found the problem. The messages CM_MOUSEENTER and CM_MOUSELEAVE are invoked for the Control's parent(s), but the help procedures in JvExtControls.pas doesn't take that into concideration. So, to avoid the faulty setting of FMouseOver, do the following:

In Control_MouseEnter:

Change the following:

[...snip...]
  if (not FMouseOver) and not (csDesigning in Instance.ComponentState) then
  begin
    FMouseOver := True;
[...snip...]

into:

[...snip...]
  // Control is nil iff Instance is the control that the mouse has entered.
  // Otherwise this is just a notification that one of it's child controls
  // where entered.
  if (Control = nil) and (not FMouseOver) and not (csDesigning in Instance.ComponentState) then
  begin
    FMouseOver := True;
[...snip...]

In Control_MouseLeave, change the following:

[...snip...]
  if FMouseOver then
  begin
    FMouseOver := False;
[...snip...]

into:

[...snip...]
  // Control is nil iff Instance is the control that the mouse has left.
  // Otherwise this is just a notification that one of it's child controls
  // where left.
  if (Control = nil) and FMouseOver and not (csDesigning in Instance.ComponentState) then
  begin
    FMouseOver := False;
[...snip...]

(Please note that the condition 'not (csDesigning in Instance.ComponentState)' should also be added to conform with Control_MouseEnter.

Hope this helps, at least it works great for me.

Best regards

Hans-Eric Grönlund

edited on: 06-08-04 02:22

AHUser

2004-06-08 02:26

developer   ~0004492

> But, still - the FMouseOver field variable is
> uninitialized, which is not recommendable.

Have you ever looked into TObject.NewInstance -> TObject.InitInstance ? I seems you have not because then you would have seen the "FillChar(Instance^, InstanceSize, 0);". And So all fields of a class are set to 0/False/nil/''.


> Ah, finally I found the problem. The messages CM_MOUSEENTER
> and CM_MOUSELEAVE are invoked for the Control's parent(s),
> but the help procedures in JvExtControls.pas doesn't take
> that into concideration.

What must I do to fix this bug in JvExControls (sorry, I have not the time at the moment to find the fix myself).

user72

2004-06-08 02:29

  ~0004493

Last edited: 2004-06-08 02:34

HEG, I modified your entry slightly (there was a logical) mix up in Control_MouseLeave.

Additionally, IMO the "if Assigned(Event)" should be moved inside the "if" (and maybe the InheritMsgEx should as well?), otherwise it will be triggered even if the mouse didn't enter/leave Instance:

JvExControls.pas:

{$IFDEF VCL}
procedure Control_MouseEnter(Instance, Control: TControl; var FMouseOver: Boolean;
  var FSavedHintColor: TColor; FHintColor: TColor; var Event: TNotifyEvent);
{$ENDIF VCL}
{$IFDEF VisualCLX}
procedure Control_MouseEnter(Instance: TControl; var FMouseOver: Boolean;
  var FSavedHintColor: TColor; FHintColor: TColor);
{$ENDIF VisualCLX}
begin
  // (HEG) Control is nil iff Instance is the control that the mouse has left.
  // Otherwise this is just a notification that the mouse entered
  // one of it's child controls
  if (Control = nil) and not FMouseOver and not (csDesigning in Instance.ComponentState) then
  begin
    FMouseOver := True;
    FSavedHintColor := Application.HintColor;
    if FHintColor <> clNone then
      Application.HintColor := FHintColor;
    {$IFDEF VCL}
    if Assigned(Event) then
      Event(Instance);
    {$ENDIF VCL}
  end;
  {$IFDEF VCL}
  InheritMsgEx(Instance, CM_MOUSEENTER, 0, Integer(Control));
  {$ENDIF VCL}
end;

{$IFDEF VCL}
procedure Control_MouseLeave(Instance, Control: TControl; var FMouseOver: Boolean;
  var FSavedHintColor: TColor; var Event: TNotifyEvent);
{$ENDIF VCL}
{$IFDEF VisualCLX}
procedure Control_MouseLeave(var FMouseOver: Boolean; FSavedHintColor: TColor);
{$ENDIF VisualCLX}
begin
  // (HEG) Control is nil iff Instance is the control that the mouse has left.
  // Otherwise this is just a notification that the mouse left
  // one of it's child controls
  if (Control = nil) and FMouseOver and not (csDesigning in Instance.ComponentState) then
  begin
    FMouseOver := False;
    Application.HintColor := FSavedHintColor;
    {$IFDEF VCL}
    if Assigned(Event) then
      Event(Instance);
    {$ENDIF VCL}
  end;
  {$IFDEF VCL}
  InheritMsgEx(Instance, CM_MOUSELEAVE, 0, Integer(Control));
  {$ENDIF VCL}
end;

Another one: TJvPanel uses a Timer to make sure the CM_MOUSELEAVE message is sent even if there are hickups, and this is causing MouseLeave to be called sometimes even if not really necessary. The overriden MouseEnter/MouseLeave methods in TJvPanel should be modified like so:

procedure TJvPanel.MouseEnter(Control: TControl);
begin
  if csDesigning in ComponentState then
    Exit;
  if not MouseOver and ((Control = nil) or (Control = Self)) then
  begin
    FOldColor := Color;
    if not Transparent then
    begin
      Color := HotColor;
      MouseTimer.Attach(Self);
    end;
  end;
  inherited MouseEnter(Control);
end;

procedure TJvPanel.MouseLeave(Control: TControl);
begin
  if csDesigning in ComponentState then
    Exit;

  if MouseOver and ((Control = nil) or (Control = Self)) then
  begin
    if not Transparent then
    begin
      Color := FOldColor;
      MouseTimer.Detach(Self);
    end;
  end;
  inherited MouseLeave(Control);
end;

With all these changes, it seems it now has the correct behavior (I tested with TJvPanel's and TJvShape's) but I would like some feedback on this before comitting.

edited on: 06-08-04 02:34

hasse42g

2004-06-08 02:50

reporter   ~0004495

AHUser:

>Have you ever looked into TObject.NewInstance -> TObject.InitInstance ? I seems >you have not because then you would have seen the "FillChar(Instance^, >InstanceSize, 0);". And So all fields of a class are set to 0/False/nil/''.

You're right of course, but I'm pretty sure this wasn't always the case (I've been around since TP 3.0). Anyways, I stated that I spoke too soon in my next posting. I was wrong about that one.

>What must I do to fix this bug in JvExControls (sorry, I have not the time at >the moment to find the fix myself).

I thought I did explain that. Please read the whole bugnote (but Peter is already on to it)

Regards

hasse42g

2004-06-08 03:03

reporter   ~0004496

Peter:

>Additionally, IMO the "if Assigned(Event)" should be moved inside the "if" (and >maybe the InheritMsgEx should as well?), otherwise it will be triggered even if >the mouse didn't enter/leave Instance:

I agree with Event, but not with InheritMsgEx.

>Another one: TJvPanel uses a Timer to make sure the CM_MOUSELEAVE message is >sent even if there are hickups, and this is causing MouseLeave to be called >sometimes even if not really necessary. The overriden MouseEnter/MouseLeave >methods in TJvPanel should be modified like so:

These changes look good, although I don't think the (Control = Self) conditions are neccessary. It's always nil for the same control I believe.

user72

2004-06-08 04:14

  ~0004497

> These changes look good,
> although I don't think the (Control = Self) conditions are neccessary
I had it as Control = nil at first and it worked fine but then I thought I might have missed something, so I added the Control = Self as well but I can't see any difference in the tests I've performed.

So Andreas, supposing this is the way to solve it, will you commit or should I?

AHUser

2004-06-09 07:58

developer   ~0004509

You have the changed files. I would have to insert the changes. So you can commit it.


> You're right of course, but I'm pretty sure this wasn't always
> the case (I've been around since TP 3.0).

The instances are initilized to 0 starting with Delphi 1. The TurboVision TObject had done the FillChar, if I remember correctly, but then you were forced to call the inherited constructor as the first command in the new constructor. But that is history.

user72

2004-06-10 00:26

  ~0004518

Fixed in CVS. Also updated template in devtools\JvExVCL

Issue History

Date Modified Username Field Change
2004-06-03 19:09 anonymous New Issue
2004-06-04 01:07 user72 Note Added: 0004460
2004-06-07 06:24 hasse42g Note Added: 0004488
2004-06-07 06:34 hasse42g Note Added: 0004489
2004-06-08 01:11 hasse42g Note Added: 0004490
2004-06-08 02:21 user72 Status new => assigned
2004-06-08 02:21 user72 Assigned To => user72
2004-06-08 02:22 user72 Note Edited: 0004490
2004-06-08 02:26 AHUser Note Added: 0004492
2004-06-08 02:29 user72 Note Added: 0004493
2004-06-08 02:34 user72 Note Edited: 0004493
2004-06-08 02:50 hasse42g Note Added: 0004495
2004-06-08 03:03 hasse42g Note Added: 0004496
2004-06-08 04:14 user72 Note Added: 0004497
2004-06-09 07:58 AHUser Note Added: 0004509
2004-06-10 00:26 user72 Status assigned => resolved
2004-06-10 00:26 user72 Resolution open => fixed
2004-06-10 00:26 user72 Note Added: 0004518