Mouse-Wheel sending message to the wrong control - delphi

I am using Windows XE2, the TVirtualStringTree, and a TComboBox as an in-placed editor.
When I place a TComboBox directly on the form, at run-time I can drop down the list and the mouse wheel scrolls the items in the list up and down (as desired). However, when the TComboBox is created at run-time by TVirtualStringTree as an in-place editor, even though the newly created combo box has focus, the mouse wheel's WM_MOUSEWHEEL message is sent to the tree control and not the combo box.
This is evident because the items in the combo box drop-down list do not scroll. Instead, the tree control behind the combo box scrolls. The fixed portion of the combo box moves with the tree, but the drop-down list becomes disconnected from the fixed portion of the combo box and does not move (as depicted).
In both cases the TComboBox.Style is set to csDropDownList. When the combo box is created as the tree's in-place editor, it is done this way:
FCBox := TComboBox.Create(TreeControl);
FCBox.Visible := False;
FCBox.Parent := TreeControl;
// ... add items to combo box ...
FCBox.Visible := True;
FCBox.SetFocus;
FCBox.DroppedDown := True;
It doesn't matter where the mouse is hovering. It can be directly over the items in the combo box drop-down list and the tree control in the background is still the one that scrolls. The only way to scroll the items in the combo box is to use its scroll bar.
What would cause the parent of the focused control to receive the mouse wheel messages instead of the control itself (in this case, the TComboBox)?

VirtualTrees.pas includes the following declaration in the TBaseVirtualTree class:
private
procedure CMMouseWheel(var Message: TCMMouseWheel); message CM_MOUSEWHEEL;
The component author captured the mouse wheel messages so he could first scroll vertically and then horizontally. The custom code is the reason that the mouse wheel messages are being sent to the TVirtualStringTree instead of the TComboBox. I commented out his code and the TComboBox drop-down list scrolled as expected.
Since I really don't want to remove the TBaseVirtualTree code, I created my own TMyComboBox with the following code to use as the in-place editor. Now scrolling works correctly in both the drop-down list and in the tree control.
interface
type
TMyCombBox = class(TComboBox)
private
procedure CMMouseWheel(var Message: TCMMouseWheel); message CM_MOUSEWHEEL;
end;
implementation
procedure TMyComboBox.CMMouseWheel(var Message: TCMMouseWheel);
begin
if DoMouseWheel([], Message.WheelDelta, SmallPointToPoint(Message.Pos)) then
Message.Result := 1;
end;
This captures the CM_MOUSEWHEEL message before it is passed to the tree control and hands it to the TControl.DoMouseWheel() method to process.

Related

How do I assure that mouse pointer appears when mouse moves over form after commanding the drop down for a combobox to show?

I have a problem where:
I have a form with just a combobox.
The combobox has focus and mouse is not hovering over the form when item 3 happens.
I trigger the combobox's drop-down list to show on a key-press event.
When the drop-down list is visible and then I move my mouse pointer over the form, the pointer is either invisible, shows that it is busy, or shows the resizing icon but does not turn back to a normal pointer when over the form.
Is there something that can be done to assure that, when the drop-down of the combobox shows, that the mouse pointer is visible when I move the pointer over the form?
I have tried:
Applicaiton.ProcessMessages after showing the drop-down.
Changing focus to the form the combobox is on after showing the drop-down.
Adding Key := #0; after calling the drop-down to show.
procedure TForm1.ComboBox1KeyPress(Sender: TObject; var Key: Char);
begin
SendMessage(ComboBox1.Handle, CB_SHOWDROPDOWN, Integer(True), 0);
Key := #0;
end;
Tried using a timer to trigger the drop-down within the key-press event.
Tried using "SetCursor" after commanding the drop-down to appear.
Tried using ".DroppedDown", but did not see any difference in result from that of "SendMessage".
I would hope to be able to show the mouse pointer after the drop-down is displayed, but it is hidden instead. Thanks for any suggestions.
(NOTE: This problem I have run into is not exlusive to Delphi. I was able to duplicate the issue using Visual C# 2017. Either way, if there is a way to correct this, it would be good to know).
As already commented to the question, the issue is not Delphi related. You can observe the same behavior in dialog boxes which contains a similar combo that the OS presents. One example is the one on the "run" dialog.
Involving a single environment, re-setting the cursor in an OnDropDown event handler fixes the problem.
procedure TForm1.ComboBox1DropDown(Sender: TObject);
begin
winapi.windows.SetCursor(Screen.Cursors[Cursor]);
end;
Originally I tested the above because no one calls SetCursor after the drop down. Though it seems that no one calls it before either. So I have no idea about the cause or why the above fix works.

How do I make a TVirtualStringTree process key presses with a higher precedence?

We've got a certain search form at work that was revamped recently. Its functionality is that of a standard search form: enter a few criteria in some boxes at the top, hit the Search button, and display results in a grid down below. But it was ugly and very limited in functionality, so one of my coworkers rebuilt it... right before leaving for a new job. Now I'm trying to complete the last few details.
One of the changes was replacing the old TListBox grid with a much more powerful TVirtualStringTree. But in the process, it appears to have broken something: before, if you clicked on a row in the grid (giving the grid in put focus) and hit Enter, the appropriate event handler would fire and deal with your input, opening the detail view for the selected item. In this grid, however, pressing Enter causes the TButton on the form with the Default = true property to fire its OnClick instead.
How can I make the TVirtualStringTree take precedence when it has input focus, so that it will respond to the user pressing Enter itself before (and preferably instead of) dispatching it to the form?
Handle the WM_GETDLGCODE message and include DLGC_WANTALLKEYS in the returned value. For example:
procedure WMGetDlgCode(var Message: TWMGetDlgCode); message WM_GETDLGCODE;
....
procedure TMyControl.WMGetDlgCode(var Message: TWMGetDlgCode);
begin
inherited;
Message.Result := DLGC_WANTALLKEYS;
end;
Depending on whether or not your control already handles this message and returns something other than 0 you might need to use:
Message.Result := Message.Result or DLGC_WANTALLKEYS;
If you don't want to modify the code for this class then use an interposer or set the WindowProc property of the control to intercept its window procedure.

Combo box - typing selection then clicking out of focus - doesn't select the typed item

I'm having an issue with the combo box. I have an event handler for OnClick which refreshes data based on what item was selected. The problem is when this scenario occurs:
Drop-down the combo box to list the various options
Type on the keyboard to find a matching item
Combo box changes this selection and calls the OnClick event
My screen refreshes due to this selection / event
Click somewhere outside of the combo box to take the focus away from it
Combo box goes back to the previous selection, even though OnClick was already called
Even though Combo box changed back to prior selection, OnClick isn't called again
After this, Combo Box shows different value than what my data actually represents
So when you open a combo box, and type a few letters on the keyboard to find the item in the drop-down list, OnClick is fired which refreshes my screen. But when you click somewhere outside the combo box (thus taking the focus away from it), the combo box changes back to whatever value was previously selected, instead of what I had typed. And at the same time, the OnClick event isn't fired, so the combo box shows the incorrect value compared to what I had loaded on the screen.
How do I make the combo box stay on the selected item in this scenario of typing the item on the keyboard?
In my code, I deal with this using the OnCloseUp event. Well, in fact I'm using a sub-classed combo for my drop-down lists and they override both the Change and CloseUp methods:
procedure TMyDropDownList.Change;
begin
RespondToChange;
inherited;
end;
procedure TMyDropDownList.CloseUp;
begin
RespondToChange;
inherited;
end;
The RespondToChange method reacts to the new ItemIndex value. If it is expensive to react to every single change whilst the combo is dropped down, then you might consider omitting the call to RespondToChange from the Change method.
You could use OnExit to make the entry with the keyboard jive with the Index on the ComboBox; where VarS is assigned OnChange and is the answer you would like to keep:
procedure TForm1.ComboBox1Exit(Sender: TObject);
begin
{ Windows keyboard select bug, force use of selected }
ComboBox1.ItemIndex := ComboBox1.Items.IndexOf(VarS);
end;
I would call this a bug in the ComboBox design.

RichEdit needs two clicks to change cursor (caret) position when changing the active form (Delphi)

Here's the setting (I'm using Delphi 7, not sure if this will happen in later/earlier versions):
Create a new project with two forms.
Put a TMemo, a TRichEdit and a TButton on the first form.
Set the Lines properties of both the TMemo and the TRichEdit to 123456.
In the TButton's OnClick event handler put Form2.Show;
Run the application, click the button and the empty Form2 will show and get focus.
Now click in the middle of the text "123456" of the TMemo in Form1 - the focus will change back to Form1 and the cursor (caret) will be in the middle of the text "123456" where you clicked as expected.
Click on Form2 again to give it focus again.
Now click in the middle of the text "123456" of the TRichEdit in Form1 - the focus will change back to Form1, but the cursor (caret) won't be in the middle of the text "123456" where you clicked, but on the second empty line of the RichEdit (or wherever it was previously).
If you click a second time in the same place in the middle of the text "123456" of the TRichEdit, the cursor (caret) will now be moved there as expected.
So the TRichEdit control gets focus, but the cursor (caret) isn't moved as expected.
Note: This only happens when changing the focus from one form to another. Changing the focus from one control to a TRichEdit control in the same form doesn't exhibit this problematic behavior.
My question: How to avoid the need for this second click inside a TRichEdit and have the control behave like the TMemo in this regard.
Thanks in advance!
You can derive a new control, or subclass the richedit in any way you like, to intervene with the activation mechanism. Below sample interposer class sets the focus to the control before the mouse down message is posted when it is about to be activated by the left button of the mouse if the control is not already focused:
type
TRichEdit = class(comctrls.TRichEdit)
protected
procedure WMMouseActivate(var Message: TWMMouseActivate);
message WM_MOUSEACTIVATE;
end;
procedure TRichEdit.WMMouseActivate(var Message: TWMMouseActivate);
begin
if (GetFocus <> Handle) and (Message.MouseMsg = WM_LBUTTONDOWN) then
SetFocus;
inherited;
end;

Focus for key events on a TImage?

I'm building an editor which uses a TImage to display a picture and has mouse events to be able to draw, move, and resize boxes on the image. All this works perfectly. Now I'm trying to implement the ability to use the arrows on the keyboard to move the selected box, but A) TImage does not get any focus, and B) TImage does not have any key events (because it cannot get focus). I guess I could cheat and switch on the form's KeyPreview property and catch them there, but there's many other controls on this form and I'd need to make sure the user is intending to work with the image. For example, if user has focus in the TEdit control, the arrow keys shall only affect this memo, and not modifying the image.
So is there any way to put or fake some kind of focus in the TImage to recognize key events?
Only controls that inherit from TWinControl can receive keyboard focus.
TImage descents from TGraphicControl and cannot receive keyboard events.
You can put the Image on top of a panel which sits on top of another control e.g. TEdit and give that focus if the Image is selected.
Then just use the OnKeyPress event of the non-visible edit.
Make sure to disallow the tab key if you don't want that to change the focus to another control.
procedure TForm8.Image1Click(Sender: TObject);
begin
Edit1.SetFocus;
end;
procedure TForm8.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
if Key = #9 then Key = #0; //disable tab key.
case key of
//do stuff here
end; {case}
end;

Resources