TSpeedButtons

Hello,
  Here is my problem : i wrote an component based on tPanel, and using 8 TSpeedbuttons and when the mouse leave quickly any of the SpeedButtons, this one stay with the border drawn, it doesn't returns to "flat state".
As it is well known that a good experience is better than an long discussion : Full source code (8 Ko) available for who wants to help me.


Thanks for al ideas.
dsk@pt.lu
          HelpMe
helpme020897Asked:
Who is Participating?
 
LischkeCommented:
HelpMe,

To 1) In particular when the mouse leaves the form then the problem appears. As long as you move the mouse over any VCL window the mouse enter and leave detection works.

To 2) I assume drawing the glyphs changes the overall timing a bit so that it seems to work without glyphs. But this has nothing to do with the leave detection.

My 3) It seems you don't believe what I explain all the time. Perhaps a short summary convinces you:

a) Mouse enter and leave detection in Delphi programs is entirely (!) done by the VCL.
b) This detection is called after a message has been process, so if there are no messages (e.g. because the mouse is not over a VCL window) then this mechanism cannot work.
c) The up/down state of flat speed buttons is based on this enter/leave detection. This does not work reliably, so the buttons might not reflect correctly the mouse position.


Point c) is not very important, though, because you need some special action to create the bad effect. If it is still an issue then use then read ID Q183107 of MSDN of which I have cited the summary below:

SUMMARY
Microsoft Windows NT 4.0 and Microsoft Windows 98 provide the TrackMouseEvent API to notify a window that the mouse has left the window. Windows 95 does not provide any such notification. An application can detect when the mouse exits a window by starting a timer when the mouse enters the window, and using that timer to monitor the mouse position and find when it is no longer within the window.
---- end quote

Ciao, Mike
0
 
LischkeCommented:
Hi helpme,

this is a well known problem and has to do with the limited mouse leave detection support by Windows. For NT there's an API to register a window to check it for a leaving mouse cursor, but this is not available for Win9x. The only reliable solution I found is to use a timer. Set it up to check, say, every 300ms the window over which the mouse is hovering and if it is no longer the one from the last test then invalidate the last one to get it updated.

Ciao, Mike
0
 
helpme020897Author Commented:
Hi Lischke,
Thanks for your intervantion,
   But why If I put a TPanel on a form and then i put several TSpeedButons on th eTPanel, I do not have the problem ?
  If i understand what you said, the problem sould occurs.

HelpME
0
Keep up with what's happening at Experts Exchange!

Sign up to receive Decoded, a new monthly digest with product updates, feature release info, continuing education opportunities, and more.

 
helpme020897Author Commented:
re-hi Lischke,
  I tryed what you tell me, but it does'nt works. It seems that TSpeedButtons "repaint", "invalidate" or update methods doesn't the test if mouse is over or not, so the control is effectively re-drawn but keep its state.
So it is drawn as if the mouse where still over it.

   HelpMe
0
 
LischkeCommented:
Mmmh, what can we do then?

It's a fact that the leave detection is not reliable because it is based on mouse moves over a VCL window. If you move the mouse fast enough away to a place not covered by a your Delphi application then the VCL will not get the opportunity to determine that the mouse left. You can see that in the VCL code (see also Forms.pas line 6899 ff.). The VCL internal messages CM_MOUSEENTER and CM_MOUSELEAVE are exclusive created in this code and only really used by TSpeedButton (and only if it is flat style).

In order to execute the DoMouseIdle code the application needs to get any message which triggers TApplication.Idle method, which in turn triggers the leave detection. If the app does not get messages (usually by moving the mouse, but also a paint or timer event would be okay) then the leave detection is never executed.

To precise what I said about resetting the left button in an timer event: Just send the button a dummy mouse leave message (Button1.Perform(CM_MOUSELEAVE, 0, 0);) and it should reset its state correctly.

Ciao, Mike
0
 
shenqwCommented:
hi. helpme

  It's not bug for delphi. ok,see the delphi source code for reason.

The speedbutton isn't a Twincontrol, he is TGraphicControl,No Message
he can receive.so his parent control must is TWincontorl. The Parent
receive the message and find the current pos control,if current pos
control is not nil, then the parent contorl send the message to the
TGraphicContorl. Your can see Source code at Contols.pas

function TWinControl.IsControlMouseMsg(var Message: TWMMouse): Boolean;

The speedbutton state will change only receive CM_MOUSELEAVE & CM_MOUSEENTER
self-defined messages.Trigger the two message source code as

function TApplication.DoMouseIdle: TControl;
var
  CaptureControl: TControl;
  P: TPoint;
begin
  GetCursorPos(P);
  Result := FindDragTarget(P, True);
  if (Result <> nil) and (csDesigning in Result.ComponentState) then
    Result := nil;
  CaptureControl := GetCaptureControl;
  if FMouseControl <> Result then
  begin
    if ((FMouseControl <> nil) and (CaptureControl = nil)) or
      ((CaptureControl <> nil) and (FMouseControl = CaptureControl)) then
      FMouseControl.Perform(CM_MOUSELEAVE, 0, 0);
    FMouseControl := Result;
    if ((FMouseControl <> nil) and (CaptureControl = nil)) or
      ((CaptureControl <> nil) and (FMouseControl = CaptureControl)) then
      FMouseControl.Perform(CM_MOUSEENTER, 0, 0);
  end;
end;


so if you move mouse quickly and mouse leave the application's any window, the DoMouseIdle
will not execute. then the cm_mouseleve or cm_mouseenter will not send to the contorl.
but when you mouse enter any window of the application, the speedbutton will become ok.
we can make test on that;

drag a speedbutton into your form.and make the speedbutton left=-1,then run your pragram.
you will find that how slowly and how quickly you move your mouse leave the contorl and
also leave the form, the contorl's state is still up. but when your mouse enter in the
application, from anywhere,the speedbutton's state is ok. another test : when your mouse
leave you form.

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  if not PtInRect(GetClientRect,Mouse.CursorPos) then
  PostMessage(handle,wm_mousemove,0,0);
end;


a second later(Timer.Internal:=1000),the speedbutton's state is ok.   That's why,I think.

If you want avoid that , use a timer to check the mouse currpos, if the mouse not in the
application, the update you speedbutton state(by send a message , like wm_mosuemove,
cm_mosueexit etc.)

Good Luck
shenqw
0
 
EpsylonCommented:
Hi, why don't you design your component using a frame? Once you have created a frame with the SpeedButtons you can add the frame to the palette as a template. Now you can drop it on a form like any other component.
And the problem apears to be gone...


Regards,

Epsylon.
0
 
k6__Commented:
The only Solution to your problem is
to buy (very very cheap! only $20) and
use the TToolbarButton97!!! it's the
best without any mouseover problems =)

the address:
http://www.jordanr.dhs.org/

This problem is history for me =)
0
 
k6__Commented:
Oh i forgot to mention that TToolbarbutton97
is included in TTOOLBAR97! so you must
download TToolbar97 and install the
package in order to get TToolbarbutton97.
Now inside your component replace
all the "TSpeedButton" with "TToolbarButton97" and include in
your unit "USES" the "TBCTRLS" unit in
order your component to get support
of the TToolbarButton97 component.

That's all



0
 
LischkeCommented:
shenqw, not a very nice plagiat :-(

Epsilon, even with frames the basic problem still persists as the mouse leave detection is still the same.

k6__, most of the people here know me as a very friendly and peaceful man, but sometimes I become really annoyed. Not only that your suggestion is totally senseless as we are talking here about fixing a problem in a control of the questioneer, not to replace the own work with something other which is likely totally different. No, you also proposed an answer, which is VERY UNFRIENDLY because you are the last one here and other people already discussed here.

Please, propose comments only! If it is the solution you will still earn well deserved points. Otherwise it is like everybody stands politely in a line while you jump the queue.

Ciao, Mike
0
 
EpsylonCommented:
Mike, I can't reproduce the problem so I must be missing something...
0
 
LischkeCommented:
Have you also tried to move the mouse quickly so that it leaves your application entirely?
0
 
k6__Commented:
I'm sorry Lischke =( .. i'll post
comments next time...

Anyway.. because from Delphi 3 Speedbutton
has problems the only solution is to
gain a third party component... i have
tried many ways to pass this problem
in the past but none worked.. espesially
try to execute a loop with
application.processmessage inside and
pass the mouse over a TSpeedbutton..
The button will remain "UP" until the
loop finish... This doesn't happend with
Jordan's TToolbarButton97 =)
0
 
LischkeCommented:
k6__, I'm sorry if I sounded to harsh, but what you did happens regularly and other circumstances made me crazy for a moment.

I hope we still can make up a good team here...

Ciao, Mike
0
 
MadshiCommented:
Hi friends, Eps, did you test it under win9x or winNT? Because this problem only occurs under win9x...

I've solved this problem in my own speed button component with a timer, too. That works perfectly. BTW, Jordan Russell's Toolbar97 components are shareware, but (if I remember correctly) you can download the sources without registering - and if you only want to look at how he solved this problem (by using a timer again), you are allowed to do so I think.

Hi, k6__, how about withdrawing your answer?   :-)

Regards, Madshi.
0
 
EpsylonCommented:
Hi everyone, I can't remember it for sure anymore but there's a good chance I tried it on NT   :o)
0
 
helpme020897Author Commented:
Sorry K6, but i would like to be abble to resolve this probleme with standards components. I will never use any solution like this because if i give my component to a friend, he will have to purchase the TtoolBarButton97 and this is not my own goal.


   Thanks anyway


                  HelpMe
0
 
helpme020897Author Commented:
Hi all, and thanks everybody.
Here more informations :
1) even when the mouse leaves the Form, the button(s) doesn't refresh and stay in "up" state.
2) I draw myself the différents glyphs, and if there are no glyphs loaded, there is no problems. I use canvas.draw in order to draw those glyph, isn't it correct  ?

Remember : just ask me i you want the source code to have a look on this, and do what you want with it, I just wants this TDBnav works correctly in my app.

HelpME
dsk@pt.lu

0
 
LischkeCommented:
HelpMe, have you then tried to combine the timer approach with the Perform call I suggested above?

Ciao, Mike
0
 
mullet_attackCommented:
Hi everyone !

I've never noticed this problem before, so thanks all for making me aware. I read all before this, and agree fully with why it occurs. Using a timer to solve the problem seemed a simple solution, I can't figure why helpme doesn't do that. Anyway, for my own fun I did it. Here's my results. It seems to work. (uses standard components :-))

unit SpeedyButton;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  Buttons, Extctrls;

type
  TSpeedyButton = class(TSpeedButton)
  private
    BtnTimer : TTimer;
    { Private declarations }
  protected
    procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;
    procedure OnBtnTimer(Sender : TObject);
    { Protected declarations }
  public
    constructor Create(AOwner : TComponent) ; override;
    destructor Destroy ; override;
    { Public declarations }
  published
    { Published declarations }
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('Planetech', [TSpeedyButton]);
end;

{ TSpeedyButton }

constructor TSpeedyButton.Create(AOwner: TComponent);
begin
  inherited;
  btnTimer := TTimer.create(owner);
  with btnTimer do
    begin
      interval := 100;
      OnTimer := OnBtnTimer;
    end;
end;

destructor TSpeedyButton.Destroy;
begin
  btnTimer.enabled := false;
  btnTimer.free;
  inherited;

end;

procedure TSpeedyButton.MouseMove(Shift: TShiftState; X, Y: Integer);
begin
  btnTimer.enabled := true;
  inherited;
end;

procedure TSpeedyButton.OnBtnTimer(Sender: TObject);
begin
  if Not PtInRect(ClientRect,ScreenToClient(Mouse.CursorPos)) then
    begin
      btnTimer.enabled := false;
      Perform(CM_MOUSELEAVE, 0, 0);
    end;
end;

end.
0
 
MadshiCommented:
Hi mullett_attack, that looks alright. Just one little addition: When creating a TTimer object, Enabled is set to true by default. So you should do this:

  with btnTimer do
    begin
      Enabled := false;
      interval := 100;
      OnTimer := OnBtnTimer;
    end;

Regards, Madshi.

P.S: In my components I've improved mouse-out-detection with this idea: In the MouseMove event I'm looking through all other speedButtons on the form. If one of them is still in MouseOver-state (though the mouse has already left it), I'm performing MOUSELEAVE, too. This way the react time is even better, and the timer is only the very last resort.
0
 
mullet_attackCommented:
Madshi,
good comment, I meant to write enabled := false, but you know what happens when in a hurry :-)

I like your idea. If you have already done this, why not answer the Q and get the points? 500 is a lot !
0
 
LischkeCommented:
Because I have already answered the question only HelpMe is not convinced yet.

Ciao, Mike
0
 
mullet_attackCommented:
Couldn't agree more. Reading it all again, this question has been answered 3 or 4 times already.
0
 
MadshiCommented:
:-)  And I can't post my components sources here, because they need some units which are owned by my firm. Besides, the sources are much too long (the speedButtons have a lot of extra functionality).
0
 
mullet_attackCommented:
Madshi, I know the problem. I am a contract programmer, so when I write code for clients, I have in my contract that components that don't have their secrets in them I can keep also. Usually little stuff, but handy...
0
 
helpme020897Author Commented:
Mullet_Attack : all right !  Now i understand what Lischke means with timers ! I have to create a component from TSpeedButon class...  I didn't understood this.

So , now my problem is solved... but i hazve anither problem : Who earned the points ? Lischke with the earliest correct answerd i didn' understood or Mullet_Attack with later answer but with code source ?

Listening.

Thanks all of you.


       HelpME
0
 
LischkeCommented:
Hi helpme,

usually the first one with the correct answer gets the points, but this depends of course on the answer. If you expected to get source code and prefer mullet_attacks contribution then you can of course accept one comment from him/her as answer. But think about it and decide what's more important: the fish or to learn fishing. I gave you the latter...

Btw: Next time when you have problems to understand then you should ask more, so I know my words are not good enough. There's no need for shame if one does not know a particular fact. It happens to me all the time :-)

Ciao, Mike
0
 
mullet_attackCommented:
Mike, I agree with your "fish or learn fishing" comment. I too usually like to give answers that cause the asker to learn something. However in this case your answer was first and correct because the original question asked 'why?'. If the Q was 'can someone give me code to do blah' then my answer was better (although you would have given code in that case I'm sure). The problem here is do we 'experts' just answer the question, or do we solve the problem? For some-one with good Delphi skill your answer would be sufficient, as they would understand to sub-class the existing button. (no offense, helpme :-)) I am happy if helpme reads my code, _fully_ understands it and realises your answer was right. Mike, points I don't care too much about, you have them. One day maybe the opposite will occur. :-)
0
 
LischkeCommented:
Well spoken...
0
 
mullet_attackCommented:
BTW, mullet_attack is 'him' :-)

Mark
0
 
helpme020897Author Commented:
Okay, points are for Lischke, and i do applause Mullet_Attack's fair play.  :-)
0
 
shenqwCommented:
hi Lischke:

>shenqw, not a very nice plagiat :-(

Your Time:Saturday, April 29 2000 - 02:18AM CST
My Time:Saturday, April 29 2000 - 02:21AM CST

Do you think i can write so long in only 3 minutes???I had used half an hour to wirte that because My english is very poor.
0
 
LischkeCommented:
Thank you helpme.

shenqw, I'm really sorry if I offended you and claimed something untrue. The posting times here at E-E are not reliable at all. I even had situations where I posted two comments related to another one and both still apeared before the comment they commented :-)

Please accept my apologies.

Ciao, Mike
0
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

All Courses

From novice to tech pro — start learning today.