How can I disable popup of a popup menu?
My problem is that I have a nice context menu for my listbox but if I shift-rightclick an item, I execute some code, and I don't want the popup menu popping up afterwards.
But if it is a normal rightclick on the listbox, then of course I want the popup menu.
There's no "OnContextPopup" in Delphi, and no "Handled" parameter either. One way of doing this in Delphi is as follows:
Locate the PopupMenu on your form, click it, go to the events tab of the object inspector, double-click the cell to the right of "OnPopup" and insert the following code (the line right after begin):
PROCEDURE TForm1.PopupMenu1Popup(Sender : TObject);
BEGIN
IF GetKeyState(VK_SHIFT) AND $8000<>0 THEN Abort
END;
this will suppress the popup menu, if any of the two SHIFT keys are pressed when the menu is about to pop up...
Another way (spurred by TLama's comment above) is to locate the OnContextPopup event for the control you want to suppress the popup for (in this case your ListBox), double click it (to create/jump to the event handler) and insert the following code:
PROCEDURE TForm1.ListBox1ContextPopup(Sender : TObject ; Point : TPoint ; VAR Handled : BOOLEAN);
BEGIN
Handled:=(GetKeyState(VK_SHIFT) AND $8000<>0)
END;
The main difference between these two methods is that the first one suppresses the popup, no matter which control the popup menu is attempting to pop up over, whereas the second method allows you to only suppress the popup for specific controls (as you can assign the same popup menu to several controls).
Related
I have built a MCV of this problem, and I'm happy to upload it. I'll try to describe the problem first.
Create a main window and place a TDBGrid connected to a table through any database available. OnShow of the window connect to the database and open the table.
Create a button on the main window that launches a non-modal window.
On the non-modal window create a button that launches a modal window.
Follow these steps carefully to replicate the problem.
Run the application.
Put focus into the grid and use your mouse wheel to scroll up and down.
Press the button to launch the non-modal window.
While the non-modal window is open, click back into the grid in the main window and again use your mouse wheel to scroll up and down.
While still focused in the grid, click the button on the non-modal window to launch the modal window.
While the modal window is open, hover over the grid and use your mouse wheel. You'll see that the grid scrolls up and down.
This does not happen on Windows 7, but does on Windows 10. It may seem innocuous, but it's particularly dangerous when you have a few layers of parent child relationships built across the 3 windows.
Let's say the modal window contains grand-children of the main window. If the user launches the modal window with the intent of editing specific grand-children, and accidentally uses their mouse wheel and moves the grand-parent on the main window, they are now editing grand-children that they didn't intend to.
It should be noted that between step 4 and 5, if you do not put focus in the grid before launching the modal window, this problem does not occur. I have tried setting focus programmatically into a control on the non-modal window before showing the modal window with no success.
This is an error in VCL code. To duplicate the problem in a normal application, as noted in the comments, one need to have Windows 10's inactive window scrolling feature enabled, or software providing similar functionality in earlier OS.
However it is possible to demonstrate the problem without special requirements. This would be a simpler reproduction than in the question.
Drop a stringgrid and a button on a form, the button having the following code in its click handler:
procedure TForm1.Button1Click(Sender: TObject);
begin
StringGrid1.Enabled := False;
SetFocusedControl(StringGrid1);
end;
Click the button and hover your mouse over the grid and scroll. Disabled as it is, the grid shouldn't scroll, but it does.
Below is a more appropriate problem reproduction to the case in the question. That's because the modal window disables the form containing the grid which is then posted the mouse wheel message. The wheel message is synthesized for environments that doesn't have the mentioned requirements.
procedure TForm1.Button1Click(Sender: TObject);
var
Pt: TPoint;
begin
Enabled := False;
Pt := Point(1, 1);
MapWindowPoints(StringGrid1.Handle, HWND_DESKTOP, Pt, 1);
SetFocusedControl(StringGrid1);
Perform(WM_MOUSEWHEEL, MakeWParam(0, WORD(-120)), MakeLParam(Pt.X, Pt.Y));
end;
Press Ctrl+F2 after you observe that the grid scrolls.
The root cause of the problem is, VCL does not care if a control is enabled or not while deciding if it is the focused control, and while making it perform a mouse wheel message. Mutating the mouse wheel message to a CM_MOUSEWHEEL and completely bypassing the default window procedure, VCL should have been performing these checks.
For a workaround, you can set the focused control to a different control before launching the modal form:
MainForm.SetFocusedControl(MainForm);
OtherForm.ShowModal;
You can't set ActiveControl here, because that one has the necessary visibility/state check in place.
If you don't want to be coupled with the main form, you can put a mouse wheel message handler to the main form:
type
TMainForm = class(TForm)
...
protected
procedure WMMouseWheel(var Message: TWMMouseWheel); message WM_MOUSEWHEEL;
...
procedure TMainForm.WMMouseWheel(var Message: TWMMouseWheel);
begin
if IsWindowEnabled(Handle) then
inherited;
end;
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.
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?
I have 2 memo's on a form and 1 Tpopupmenu I have assigned the popup menu as the popup menu for both memos. The pop up menu has a couple of menuitem assigned to it, when I right mouse click on either of the memo's and click on one of the menu items on its onclick event I would like to be able to know which memo is the one where the menu was invoked from but cannot figure out how to find out which memo it was!!
Could anyone give me a pointer on how to detect the underlying memo.
thanks
colin
You can use the PopupComponent property of the popup menu:
procedure TForm1.PopupItemClick(Sender: TObject);
begin
if PopupMenu1.PopupComponent = Memo1 then
..
else
..
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.