Listbox multiselect losing focus with OnKeyUp - delphi

I have Two listboxes and both with Multiselect:=True. I have a popup menu that Copy/Paste to/from the Clipboard for the selected items. All works as intended.
I wanted to add keyboard CtrlC (Copy) and Ctrl+V (Paste) but after selecting items, then using Ctrl-C, the Selected items all lose selection and the first item in the list is selected and it gets copied to the Clipboard.
I am using the KeyPreview and main form OnKeyUp
if (ssCtrl in Shift) then
begin
case Char(Key) of
'c','C' : puCopyClick(Sender);
'v','V' : puPasteClick(Sender);
end;
Exit;
end;
case Key of
VK_Delete : puDeleteClick(Self);
end;
Exit;
How can I make the Ctrl+C etc work as the popup does?
Thanks

The best way to handle shortcut keys is to let the menu items handle them. You say that you have a popup menu that has these actions. Use the Shortcut property of the menu item to associate that menu item with the shortcut key.
That allows you to remove all the manual keyboard event handling and let the framework do it for you. That has many benefits. Not least of which is that the event will fire when the key goes down rather than when it goes up as you currently have it.
Even better would be to use actions which if I recall correctly do exist in Delphi 5. These allow you to associate a single action, for example copy to clipboard with multiple independent UI elements. For example that action can be associated with a main menu, a popup menu, and a shortcut key.

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.

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.

How to avoid the "Open IME" popup in a StringGrid?

In a StringGrid, sometimes I get the unwanted menu below when I right-click. Is this a Windows popup?
How to I prevent this popup from appearing rather than my own?
I have goAlwaysShowEditor in my Options.
I have set StringGrid.PopupMenu to my popup.
I've set StringGrid.OnMouseDown to show my popup if it's a right click.
You can override the virtual CreateEditor method like this way (not a good solution though, I know :-):
type
TStringGrid = class(Grids.TStringGrid)
protected
function CreateEditor: TInplaceEdit; override;
end;
implementation
function TStringGrid.CreateEditor: TInplaceEdit;
begin
Result := inherited CreateEditor;
TMaskEdit(Result).PopupMenu := Form1.PopupMenu1;
end;
That is the popup menu found in every Windows EDIT control. Possible the world's most known menu (the only competition comes from the system menu). You want it, because your user's expect it (and need it). When you edit the text in a cell, the TStringGrid control actually creates a standard Windows EDIT control, which is great. And thus you get its popup menu.
In addition, to show your own popup menu (when you are not editing a cell), you don't need to set the OnMouseDown handler. It is enough to set the PopupMenu property. In fact, it is very bad to use the OnMouseDown handler to trigger a popup menu, because then the menu will only be shown when the user right-clicks the control (and not, for instance, when he presses the "context" button on his keyboard).
If you really want your own popup menu to show, even when the user is editing a cell, you really have to give him his usual options for undo, copy, cut, paste, Unicode stuff, etc., manually. Surely you don't want that?

Set 'view-only' shortcuts for menu items?

We know that if we set a shortcut (for example, Ctrl + F2) to a TMenuItem, the menu item will be executed automatically once the shortcut specified is pressed, and the shortcut description will be also shown when the menu is displayed.
But is there a way to have the shortcut descriptions visible on the menu items but make the menu don't respond to the shortcuts automatically?
You might ask me why I want this, here is the situation:
In a multiple document (like firefox's multiple tabs) program, there are multiple instances of TPopupMenu thus multiple TMenuItem objects have the same shortcuts, but I only want the menuitems in the active document window respond to the shortcuts.
Edit 1: Sorry, I wanted to simplify my question and I described it wrongly - actually, I use TActionList and link the actions to menu items.
Edit 2: Just found: I think I can use TApplicationEvents.OnShortCut Event to intercept the shortcuts before they are being dispatched to the menus/actions... I'll try and will update my questions when I get a result.
Use the tab (#9) character to indicate the shorcut part of the text in standard menus. You can set the Caption property of the menu item or the action component that the menu item is bound to by either editing the 'dfm' or at run-time to include the tab character:
procedure TForm1.FormCreate(Sender: TObject);
begin
Action1.Caption :=
Action1.Caption + #9 + ShortCutToText(ShortCut(VK_F2, [ssCtrl]));
Unless you also assign to the ShortCut property itself of the menu item or the action, the click/execute event will not be fired.

How do I make a shortcut key do different things depending on the active tab page?

Each TTabSheet on my TPageControl has a TToolBar on it. Each tool bar has a TToolButton that should respond to the same keyboard shortcut. How do I provide hotkeys so that the right button is invoked for the current page?
On the first tab sheet, Ctrl+T should make something happen, but upon switching to the second tab, Ctrl+T should make something else happen instead.
Is this a time to toggle TActionList.State between asNormal and asSuspended when tab sheets are shown or hidden?
If you want Ctrl+T simply to flip between active TabSheets on a PageControl, then create a single Action, with a Ctrl+T shortcut, and flip between pages as required:
procedure TForm1.actNextPageExecute(Sender: TObject)
var
nextPageIndex: Integer;
begin
nextPageIndex := PageControl1.ActivePageIndex+1;
if (nextPageIndex > PageControl1.Pages.Count-1) then
nextPageIndex := 0;
PageControl1.ActivePageIndex := nextPageIndex;
end;
If you want one TAction to do different things depending on what control initiated it, just look at the action's ActionComponent property. Hook all the controls to the same action.
An alternative would be to have multiple actions with the same shortcut and enable or disable them in the Update event based on what is visible or focused.

Resources