I have a form with a control (myControl) and a child control (myChildControl).
I want to manage the mouse exit on myControl so that I can take some actions.
The problem is that the On Mouse Leave event is fired even though the mouse is still physically inside myControls but getting over myChildControl, while in this case I wouldn't need the event to be fired.
I cannot set the HitTest property in the child control as I need to take some actions on mouse events on it too.
Conceptually the Mouse Leave event fires properly, but what's the cleanest way to manage this case?
Set HitTest = False for the child control. Then in the OnMouseDown of the parent control, use PtInRect() to check whether the mouse down event occured over the child.
If needed you can do the same for other mouse events too.
Edit after comment regarding several child controls.
I don't really agree with you about messyness. Following example for any number of child controls that might need to respond to mouse down events, on the parent control (TPanel in this example). Adding the last two lines for eventual other events is not a big deal, IMO.
procedure TForm20.Panel1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Single);
var
ctrl: TControl;
begin
for ctrl in (Sender as TControl).Controls do
if PtInRect(ctrl.ParentedRect, PointF(X, Y)) then
if assigned(ctrl.OnMouseDown) then
ctrl.OnMouseDown(ctrl, Button, Shift, x-ctrl.Position.X, y-ctrl.Position.Y);
end;
A completely different approach, would be to use the OnMouseEnter event of the control your parent control is placed on (or if placed on the form, which doesn't have a OnMouseEnter, use the OnMouseMove), to trigger the OnExit substitute. As this could trigger very often, you could avoid unneeded actions, by declareing a boolean for ActionRequired
Related
This question already has answers here:
How to suppress mouse wheel in TcxComboBox
(1 answer)
Suppress mouse scroll events on specific controls but NOT their parent?
(1 answer)
Closed last year.
I've found numerous things in this regard, but they're either for third-party controls, different specific situations, or for a completely different language. What I need should be fairly simple.
I have a TDBGrid with a TComboBox placed over a specific cell. As the user scrolls through this grid, the combo box moves along to the corresponding cell. User is further able to change the value of this combo box to update the database.
However, when using the mouse wheel to scroll, if the mouse pointer just happens to be over this combo box, it ends up changing the value of the combo box, rather than scrolling the grid.
How do I suppress the scrolling in the combo box?
I am answering this QA style, because I found such a simple solution on my own which I couldn't find on various forums.
The simplest method is to add an OnKeyDown event handler to the TComboBox, and add the following:
procedure TfrmMain.cboStatusKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if Key in [VK_UP, VK_DOWN] then
Key:= 0;
end;
Keep in mind that this also blocks keyboard up/down events. If you also wish to block keyboard left/right events, then you can also do this:
procedure TfrmMain.cboStatusKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if Key in [VK_UP, VK_DOWN, VK_LEFT, VK_RIGHT] then
Key:= 0;
end;
How to select all text of a TEdit1 whenever user click on it or click to select some text of it
It can be quite dangerous to do anything beyond the default behaviour of the TEdit control. Your users know how the standard Windows controls behave and any deviation from this is likely to cause confusion.
By default the AutoSelect property is set to True.
Determines whether all the text in the edit control is automatically selected when the control gets focus.
Set AutoSelect to select all the text when the edit control gets focus. AutoSelect only applies to single-line edit controls.
Use AutoSelect when the user is more likely to replace the text in the edit control than to append to it.
When this property is True, the entire contents of the edit control are selected when it gets the focus by means of keyboard action. If the control gets the focus by a mouse click then the contents will not all be selected. In that case you simply press CTRL+A to select all. A double click will select the word underneath the mouse. This is all standard behaviour implemented by the underlying Windows control.
If you change the select in response to the OnClick event, as per the currently selected answer, then you will find that it is impossible to move the caret with a mouse click. This is exceedingly counter-intuitive behaviour.
This is a classic example of why you need to be very careful about changing the behaviour of a control from its default. It's simply very easy not to miss a particular use case when testing but when your users get hold of the program, they are sure to find all such wrinkles.
What you could safely do is to call SelectAll from OnDblClick. This would, I believe have no annoying side-effects.
Another option would be to call SelectAll when the focus switched to the edit control, but not every time you click in the control. This might feel a little odd to the user, but I personally think it would be reasonable to take this course of action. If you want to do this you need to handle the OnEnter event of your edit control:
procedure TForm1.Edit1Enter(Sender: TObject);
begin
PostMessage(Edit1.Handle, EM_SETSEL, 0, -1);
end;
How to select all text of a TEdit1 whenever user click on it
Select Edit1 in the VCL editor and double-click on the OnClick event:
procedure TForm13.Edit1Click(Sender: TObject);
begin
Edit1.SelectAll;
end;
You can also link this event to another control like a button.
Select the button, choose and click on the V arrow to select an event you want to link.
Now both Edit1.OnClick and Button1.OnClick link to the same event.
How to select some text of a TEdit1 whenever user click on it:
procedure TForm1.Edit1Click(Sender: TObject);
begin
Edit1.SelStart:= 1;
Edit1.SelLength:= 2;
end;
You must use OnMouseUp;
procedure cxMRUEdit1MouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if Button=mbLeft then cxMRUEdit1.SelectAll;
end;
Delphi 7 on Windows 7
I want to scroll down a list (TElTree) using the mouse wheel... so I start writing some code in the ElTree's OnMouseWheelDown event. Then I notice that it's scrolling 2 rows at once. The mouse wheel settings in Control Panel are set to scroll only 1 line at a time. If I place a breakpoint inside the event handler, I discover that the event handler is itself being executed twice in quick succession.... Why? How to ensure it only executes once (code please)?
When the wheel scrolls, the OS sends WM_MOUSEWHEEL messages. The high-order word of the wParam parameter indicates how far the wheel has turned. If it has turned one "click," then its value will be 120, or WHEEL_DELTA. But it might be less than that if your scroll wheel recognizes scrolling less than a click's worth.
Correct WM_MOUSEWHEEL message handlers need to consider that parameter and either keep a "scrolling accumulator" to keep track of how far the wheel has scrolled or else have the ability to scroll less than a full line. Message handlers that assume that each message signifies a full click of the wheel will be sorry.
Solution is quite simple: After calling the function you want to perform OnMouseWheel you must set Handled:=True so that the routine is not called multiple times. e.g.
procedure TMainForm.FormMouseWheelUp(Sender: TObject; Shift: TShiftState;
MousePos: TPoint; var Handled: Boolean);
begin
YourFunctionToExecute(Sender, Shift, MousePos, Handled);
Handled:=True;
end;
I have a weird behavior with TStringGrid in Delphi 7.
Delphi does not call the OnMouseUp event if a pop-up menu is associated to the grid. Basically, when the RMB is pressed, the pop of the menu somehow cancels/delays the OnMouseUp. Actually, to be 100% accurate, next time you push a mouse button the OnMouseUp is called twice – once for the current event, and once for the lost/delayed event.
This will screwup the entire logic of the program as unwanted code will be called next time when the user presses a mouse button.
The automatic popping up of a context menu is a response to a right click of the mouse. The same click also fires the OnMouseUp event. The VCL developers could either choose to fire the 'OnMouseUp' event before the popup is shown, or after. Apparently the latter is in effect, that is, the event is fired when the popup is closed (either by mouse or by the keyboard like pressing 'Esc').
There's no doubling of the event, when you press the left button to close the popup, you're firing the 'OnMouseUp' event again by releasing the left button.
You have several alternatives. One is to derive a new class and override the MouseDown method to fire your own event. An example;
type
TMyStringGrid = class(TStringGrid)
private
FOnRButtonUp: TMouseEvent;
protected
procedure MouseDown(Button: TMouseButton; Shift: TShiftState;
X, Y: Integer); override;
published
property OnRButtonUp: TMouseEvent read FOnRButtonUp write FOnRButtonUp;
end;
[...]
procedure TStringGrid.MouseDown(Button: TMouseButton; Shift: TShiftState; X,
Y: Integer);
begin
if (Button = mbRight) and Assigned(FOnRButtonUp) then
FOnRButtonUp(Self, Button, Shift, X, Y);
inherited;
end;
Another alternative can be to handle VM_RBUTTONUP message. This can either be done by deriving a new class as above, or replacing the WindowProc of the grid. There's an example of replacing the WindowProc here in this question.
Another alternative can be to leave the mouse-up event alone and do your processing in the OnPopup event of the popup menu. This event is fired before the popup is shown. You can get the mouse coordinates with Mouse.CursorPos.
Still, another alternative can be to set the AutoPopup property of the popup menu to False, and in the OnMouseUp event (or better yet in the OnContextMenu event) first do some processing and then show the popup. An example;
procedure TForm1.StringGrid1MouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
var
Pt: TPoint;
begin
// Do processing
if Button = mbRight then begin
Pt := (Sender as TStringGrid).ClientToScreen(Point(X, Y));
PopupMenu1.Popup(Pt.X, Pt.Y);
end;
end;
I already took an approach somehow similar with the one described by Sertac: I just don't use the PopupMenu property anymore to assign a pop-up menu to the grid. Instead, inside my grid (my grid is a heavily modified string grid derived from TStringGrid) I handle the mouse down event and display the pop-up the way I want AND do the extra processing I wanted to do BEFORE the menu pops.
I was using two TButton components on a form that functioned as Plus and Minus. When clicked they would add or subtract from an integer which would then be displayed on a TLabel.
This functioned as desired where the speed at which I would click would fire the OnClick event without fault.
I have replaced the buttons with PNGButton components so that I could make them look more pretty. Everything still works as before except that the OnClick event doesn't seem to be firing every time I click the components. I tested this with a simple TImage component also and the result is the same.
If I click very slowly it will fire every time, but if I click at a regular pace it only seems to fire every second click.
What can I do to make sure that the OnClick event is fired every time?
Instead of using the OnClick event, use the OnMouseUp event:
procedure TForm.btnMinusMouseUp(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
if Self.NumSelected > 0 then begin
Self.NumSelected := Self.NumSelected - 1;
Self.UpdateLabel;
end;
end;