Key press getting lost in menu loop - delphi

I want to build a menu form that acts similar to ribbon keytips - you can
press and hold Alt, then press and release e. g. d, then release Alt to trigger an action or
press and release Alt, then press and release d to trigger the same action
I took inspiration at Hidden Main Menu in a delphi program automatically shown using Alt key and came up with the following demo:
unit Unit1;
interface
uses
Windows,
Messages,
SysUtils,
Variants,
Classes,
Graphics,
Controls,
Forms,
Dialogs,
ImgList,
StdCtrls;
type
TForm1 = class(TForm)
Label1: TLabel;
procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
strict private
FShowKeyTips: Boolean;
procedure UpdateKeyTipState(AShowKeyTips: Boolean);
procedure WMExitMenuLoop(var Message: TMessage); message WM_EXITMENULOOP;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
uses
ShellAPI,
Menus;
{ TForm1 }
constructor TForm1.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
Label1.Caption := 'Dummy';
end;
destructor TForm1.Destroy;
begin
inherited Destroy;
end;
procedure TForm1.WMExitMenuLoop(var Message: TMessage);
begin
UpdateKeyTipState(False);
end;
procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
const
MAPVK_VK_TO_CHAR = 2;
// Adapted from dxBar.pas:
function IsTextCharForKeyTip(AKey: Word): Boolean;
var
ARes: UINT;
begin
ARes := MapVirtualKey(AKey, MAPVK_VK_TO_CHAR);
Result := ((ARes and $FFFF0000) = 0) and (Char(ARes) <> ' ') and (Char(ARes) in [#32..#255]);
end;
var
hk: string;
CheckKeyTips: Boolean;
begin
if (Key = VK_MENU) or (Key = VK_F10) then
begin
UpdateKeyTipState(True);
Exit;
end;
if FShowKeyTips then
CheckKeyTips := True
else
CheckKeyTips := Shift = [ssAlt];
if CheckKeyTips and IsTextCharForKeyTip(Key) then
begin
hk := Char(Key); // TODO: Handle analogouos to TdxBarItemLink.IsAccel?
if SameText(hk, 'd') then
begin
Caption := Caption + '+';
Key := 0;
Exit;
end;
end;
end;
procedure TForm1.UpdateKeyTipState(AShowKeyTips: Boolean);
begin
if FShowKeyTips = AShowKeyTips then
Exit;
FShowKeyTips := AShowKeyTips;
if AShowKeyTips then
Label1.Caption := 'Dummy (d)'
else
Label1.Caption := 'Dummy';
end;
end.
(Create a standard VCL app, add Label1 to Form1 and replace the contents of Unit1.pas with the above.)
The first bullet point works (adds a + to the form caption), however I can't make the second one work. I can't find where the d gets handled. I tried WM_(SYS)KEYDOWN, CM_DIALOGCHAR and more to no avail.
Any ideas?

As documented the Alt key, when pressed and released alone, "toggles in and out of menu bar mode". This is true even if your form does not have a window menu, the system menu is sufficient for the system to put the window into a modal menu loop. In this mode a non-accelerator will generate a WM_MENUCHAR message:
Sent when a menu is active and the user presses a key that does not
correspond to any mnemonic or accelerator key.
This is the message that you're looking for, read the character from the User field. And you don't have to track the Alt key, since the window being in a modal menu loop means the Alt key has been pressed once. Otherwise a key down message is generated instead of a menu character message.
Note that if your form does not have a system menu (in BorderIcons uncheck biSystemMenu) and a window menu, a regular WM_KEYDOWN will be sent which you're already handling.

Related

Logging MenuItem OnClick Event

I have a project (Delphi 10 Seattle, win32) with a many menus and many items in those menus. Some of the menu items are created at design time, some of them at run time.
What I'm looking to do is log some information about the TMenuItem, such as the name/caption, timestamp, etc. when the OnClick event is triggered.
I could simply add a procedure call to the start of every function which is assigned to the TMenuItem OnClick event but I was wondering if there was a more elegant solution.
Also to note, I have tried Embarcadero's AppAnalytics but I found it didn't give me the information or flexibility I wanted and was rather pricey.
Edit: I'll add some more information detailing what options I have considered (which I probably should've done to start with).
The simple adding a function to every menuitem click I want to log, which would mean doing this for a lot of functions and would have to add it to every new menu item added.
procedure TSomeForm.SomeMenuItem1Click(Sender: TObject);
var
item : TMenuItem;
begin
item := Sender as TMenuItem;
LogMenuItem(item); // Simple log function added to the start of each menuitem click
end;
By 'more elegant solution' I mean would it be possible to add a 'hook' so that all TMenuItem OnClick events triggered another procedure (which would do the logging) before calling the procedure assigned to the OnClick Event.
Or another option I considered was creating a class which inherited from TMenuItem which would override TMenuItem.Click and do the logging before generating the OnClick event. But then I didn't know how that would work for the design time menu items without a lot of work remaking the menus.
This is much easier to achieve using actions. That has the benefit that you'll pick up actions invoked by UI elements other than menus, for instance toolbars, buttons etc.
Use an action list, or an action manager, as you prefer. For example, with an action list the action list object has an OnExecute event that fires when any action is executed. You can listen for that event and there log the details of the action being executed.
I absolutely agree that actions is the way to go, but for completeness sake and those cases where you quickly want to debug an application using old-style menus, here's a unit that you can use with menu items. It will even work if the menu items have actions linked to them, but it won't work for any other controls with actions like a TActionMainMenuBar. All the debugging code is in this unit to keep your normal code clutter-free. Just add the unit to the uses clause and call StartMenuLogging with any applicable component, e.g. a menu component, form component or even Application! Any menu items in the tree under it will be hooked. So you can potentially debug all menu-clicks in all forms with just those two lines in your production code. You can use StopMenuLogging to stop, but it's optional. Warning: This unit was not tested properly - I took an old debug unit I wrote and cleaned it up for this purpose and just tested it superficially.
unit LogMenuClicks;
interface
uses
Classes;
procedure StartMenuLogging(AComponent: TComponent);
procedure StopMenuLogging(AComponent: TComponent);
procedure StopAllMenuLogging;
implementation
uses
SysUtils,
Menus;
type
PLoggedItem = ^TLoggedItem;
TLoggedItem = record
Item: TMenuItem;
OldClickEvent: TNotifyEvent;
end;
TLogManager = class(TComponent)
private
FList: TList;
FLog: TFileStream;
procedure Delete(Index: Integer);
function FindControl(AItem: TMenuItem): Integer;
procedure LogClick(Sender: TObject);
protected
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure AddControl(AItem: TMenuItem);
procedure RemoveControl(AItem: TMenuItem);
end;
var
LogMan: TLogManager = nil;
{ TLogManager }
constructor TLogManager.Create(AOwner: TComponent);
begin
inherited;
FLog := TFileStream.Create(ChangeFileExt(ParamStr(0), '.log'), fmCreate or fmShareDenyWrite);
FList := TList.Create;
end;
destructor TLogManager.Destroy;
var
i: Integer;
begin
i := FList.Count - 1;
while i >= 0 do
Delete(i);
FList.Free;
FLog.Free;
inherited;
end;
procedure TLogManager.Notification(AComponent: TComponent; Operation: TOperation);
begin
if Operation = opRemove then
RemoveControl(TMenuItem(AComponent));
inherited;
end;
procedure TLogManager.Delete(Index: Integer);
var
li: PLoggedItem;
begin
li := FList[Index];
with li^ do
begin
Item.RemoveFreeNotification(Self);
Item.OnClick := OldClickEvent;
end;
Dispose(li);
FList.Delete(Index);
end;
function TLogManager.FindControl(AItem: TMenuItem): Integer;
begin
Result := FList.Count - 1;
while (Result >= 0) and (PLoggedItem(FList[Result]).Item <> AItem) do
Dec(Result);
end;
procedure TLogManager.AddControl(AItem: TMenuItem);
var
li: PLoggedItem;
begin
if not Assigned(AItem) then
Exit;
if FindControl(AItem) >= 0 then
Exit;
New(li);
li.Item := AItem;
li.OldClickEvent := AItem.OnClick;
AItem.OnClick := LogClick;
FList.Add(li);
AItem.FreeNotification(Self);
end;
procedure TLogManager.RemoveControl(AItem: TMenuItem);
var
i: Integer;
begin
if Assigned(AItem) then
begin
i := FindControl(AItem);
if i >= 0 then
Delete(i);
end;
end;
procedure TLogManager.LogClick(Sender: TObject);
var
s: string;
begin
s := Format('%s: %s' + sLineBreak, [TComponent(Sender).Name, FormatDateTime('', Now)]);
FLog.WriteBuffer(s[1], Length(s));
PLoggedItem(FList[FindControl(TMenuItem(Sender))]).OldClickEvent(Sender);
end;
procedure StartMenuLogging(AComponent: TComponent);
procedure CheckControls(Comp: TComponent);
var
i: Integer;
begin
if Comp is TMenuItem then
LogMan.AddControl(TMenuItem(Comp))
else
for i := 0 to Comp.ComponentCount - 1 do
CheckControls(Comp.Components[i]);
end;
begin
if not Assigned(LogMan) then
LogMan := TLogManager.Create(nil);
CheckControls(AComponent);
end;
procedure StopMenuLogging(AComponent: TComponent);
procedure CheckControls(Comp: TComponent);
var
i: Integer;
begin
if Comp is TMenuItem then
LogMan.RemoveControl(TMenuItem(Comp))
else
for i := 0 to Comp.ComponentCount - 1 do
CheckControls(Comp.Components[i]);
end;
begin
if Assigned(LogMan) then
CheckControls(AComponent);
end;
procedure StopAllMenuLogging;
begin
LogMan.Free;
end;
initialization
finalization
if Assigned(LogMan) then
LogMan.Free;
end.

How can I drag & drop a file from the shell? [duplicate]

This question already has answers here:
Cross-application drag-and-drop in Delphi
(2 answers)
Closed 8 years ago.
I am trying to drag and drop a video file (like .avi) from desktop But ı can not take it to the my program.But when ı try to drag and drop inside my program it works fine.For ex: I have an edittext and a listbox inside my pro and ı can move text that inside edittext to listbox.I could not get what is the difference ??
I take the video using openDialog.But ı wanna change it with drag and drop.
procedure TForm1.Button1Click(Sender: TObject);
begin
if OpenDialog1.Execute then
begin
MediaPlayer1.DeviceType:=dtAutoSelect;
MediaPlayer1.FileName := OpenDialog1.FileName;
Label1.Caption := ExtractFileExt(MediaPlayer1.FileName);
MediaPlayer1.Open;
MediaPlayer1.Display:=Self;
MediaPlayer1.DisplayRect := Rect(panel1.Left,panel1.Top,panel1.Width,panel1.Height);
panel1.Visible:=false;
MediaPlayer1.Play;
end;
end;
Here is a simple demo how to drag&drop files from Windows Explorer into a ListBox (for Delphi XE):
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
ListBox1: TListBox;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
protected
procedure WMDropFiles(var Msg: TMessage); message WM_DROPFILES;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
uses ShellAPI;
procedure TForm1.FormCreate(Sender: TObject);
begin
DragAcceptFiles(Handle, True);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
DragAcceptFiles(Handle, False);
end;
procedure TForm1.WMDropFiles(var Msg: TMessage);
var
hDrop: THandle;
FileCount: Integer;
NameLen: Integer;
I: Integer;
S: string;
begin
hDrop:= Msg.wParam;
FileCount:= DragQueryFile (hDrop , $FFFFFFFF, nil, 0);
for I:= 0 to FileCount - 1 do begin
NameLen:= DragQueryFile(hDrop, I, nil, 0) + 1;
SetLength(S, NameLen);
DragQueryFile(hDrop, I, Pointer(S), NameLen);
Listbox1.Items.Add (S);
end;
DragFinish(hDrop);
end;
end.
You can also use DropMaster from Raize software.
You can catch the WM_DROPFILES message.
First, set that your form will "accept" files from dragging in the FormCreate procedure:
DragAcceptFiles(Self.Handle, True);
After, declare the procedure in the desired form class:
procedure WMDropFiles(var Msg: TMessage); message WM_DROPFILES;
Finally, fill the procedure body as follows:
procedure TForm1.WMDropFiles(var Msg: TMessage);
begin
// do your job with the help of DragQueryFile function
DragFinish(Msg.WParam);
end
Alternatively, check out "The Drag and Drop Component Suite for Delphi" by Anders Melander. It works as-is with 32-bit and with some tweaking can be made to work with 64-bit as well (read the blog - it has been upgraded by 3rd parties).

How to disable view source option in Chromium Embedded?

Is it possible to disable view source option in Delphi Chromium Embedded ?
I haven't found anything suitable in properties/methods list.
There are no direct settings or events allowing to hide Chromium popup menu items. However you have at least few options how to continue, you can for instance:
1. Tell user that the View source option is forbidden and decline the action
You can decide what action will you allow or decline in the OnMenuAction event handler, where if you assign True to the Result parameter the action is declined. The following code checks that you have performed the view source action and if so, decline the action and show the information message:
type
TCefMenuId = TCefHandlerMenuId;
procedure TForm1.Chromium1MenuAction(Sender: TObject;
const browser: ICefBrowser; menuId: TCefMenuId; out Result: Boolean);
begin
if menuId = MENU_ID_VIEWSOURCE then
begin
Result := True;
ShowMessage('View page source is not allowed!');
end;
end;
2. Fake the menu item to something custom by changing menu item's caption with its action
You can take advantage of the menu item for something else by changing the menu item's caption and executing some custom action. The following sample code shows how to change the view source menu item into the about box menu item:
type
TCefMenuId = TCefHandlerMenuId;
procedure TForm1.Chromium1GetMenuLabel(Sender: TObject;
const browser: ICefBrowser; menuId: TCefMenuId; var caption: ustring;
out Result: Boolean);
begin
if menuId = MENU_ID_VIEWSOURCE then
caption := 'About my application...';
end;
procedure TForm1.Chromium1MenuAction(Sender: TObject;
const browser: ICefBrowser; menuId: TCefMenuId; out Result: Boolean);
begin
if menuId = MENU_ID_VIEWSOURCE then
begin
Result := True;
ShowMessage('About box...!');
end;
end;
3. Create you own custom page (frame) popup menu
You can create your own popup menu, but you need to consider that this menu is quite hardcoded, so you will need to maintain it if you'll need to have it the same with each new version of Delphi Chromium wrapper. Here is the code how to create the page menu without view source menu item:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Menus, cefvcl, ceflib;
type
PCefMenuInfo = PCefHandlerMenuInfo;
type
TForm1 = class(TForm)
Chromium1: TChromium;
procedure FormCreate(Sender: TObject);
procedure Chromium1BeforeMenu(Sender: TObject; const browser: ICefBrowser;
const menuInfo: PCefMenuInfo; out Result: Boolean);
private
PageMenu: TPopupMenu;
procedure OnNavigateBackMenuItemClick(Sender: TObject);
procedure OnNavigateForwardMenuItemClick(Sender: TObject);
procedure OnPrintMenuItemClick(Sender: TObject);
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.OnNavigateBackMenuItemClick(Sender: TObject);
begin
Chromium1.Browser.GoBack;
end;
procedure TForm1.OnNavigateForwardMenuItemClick(Sender: TObject);
begin
Chromium1.Browser.GoForward;
end;
procedure TForm1.OnPrintMenuItemClick(Sender: TObject);
begin
Chromium1.Browser.GetFocusedFrame.Print;
end;
procedure TForm1.Chromium1BeforeMenu(Sender: TObject;
const browser: ICefBrowser; const menuInfo: PCefMenuInfo;
out Result: Boolean);
begin
if menuInfo.typeFlags = MENUTYPE_PAGE then
begin
Result := True;
PageMenu.Items[0].Enabled := browser.CanGoBack;
PageMenu.Items[1].Enabled := browser.CanGoForward;
PageMenu.Popup(menuInfo^.x, menuInfo^.y);
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
var
MenuItem: TMenuItem;
begin
PageMenu := TPopupMenu.Create(Self);
MenuItem := TMenuItem.Create(PageMenu);
MenuItem.Caption := 'Back';
MenuItem.OnClick := OnNavigateBackMenuItemClick;
PageMenu.Items.Add(MenuItem);
MenuItem := TMenuItem.Create(PageMenu);
MenuItem.Caption := 'Forward';
MenuItem.OnClick := OnNavigateForwardMenuItemClick;
PageMenu.Items.Add(MenuItem);
MenuItem := TMenuItem.Create(PageMenu);
MenuItem.Caption := '-';
PageMenu.Items.Add(MenuItem);
MenuItem := TMenuItem.Create(PageMenu);
MenuItem.Caption := 'Print';
MenuItem.OnClick := OnPrintMenuItemClick;
PageMenu.Items.Add(MenuItem);
Chromium1.Load('www.stackoverflow.com');
end;
end.
Footnote
The type definitions used in all code samples are there because I've noticed that some version of Delphi Chromium has wrong event handler definitions.
Probably things changed over years, today a direct method exists:
uses
ceflib;
[..]
implementation
procedure TForm1.Chromium1BeforeContextMenu(Sender: TObject;
const browser: ICefBrowser; const frame: ICefFrame;
const params: ICefContextMenuParams; const model: ICefMenuModel);
begin
//model.Clear;
model.Remove(Integer(MENU_ID_VIEW_SOURCE));
end;
You can use model.Clear if you want to completely get rid of popup menu.

Delphi VirtualKey to WideString/UNICODE using TNT controls on non-unicode Delphi 7

I am using this code to convert a virtual key to WideString:
function VKeytoWideString (Key : Word) : WideString;
var
WBuff : array [0..255] of WideChar;
KeyboardState : TKeyboardState;
UResult : Integer;
begin
Result := '';
GetKeyBoardState (KeyboardState);
ZeroMemory(#WBuff[0], SizeOf(WBuff));
UResult := ToUnicode(key, MapVirtualKey(key, 0), KeyboardState, WBuff, Length(WBuff), 0);
if UResult > 0 then
SetString(Result, WBuff, UResult)
else if UResult = -1 then
Result := WBuff;
end;
It works fine on my PC, but on a Chinese PC I get this:
It converts the Chinese chars to Hanyu Pinyin. I think the function actually returns the raw input of the keyboard and not what the user actually wants to type in.
How should I handle this?
As per the comments, here is an example of how you can avoid the problem by handling KeyPress events instead of manually converting KeyDown events. The TNT controls don't provide a WideChar KeyPress event, but it's fairly easy to add. Ideally, you should not put the extensions to TTntMemo and TTntForm in derived classes as I've done here, but instead modify the TNT source code.
The form contains two TTntMemo controls. Pressing keys in the first will log the events in the second.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, TntForms, StdCtrls, TntStdCtrls;
type
TKeyPressWEvent = procedure(Sender: TObject; var Key: WideChar) of object;
TTntMemo = class(TntStdCtrls.TTntMemo)
private
FOnKeyPressW: TKeyPressWEvent;
procedure WMChar(var Msg: TWMChar); message WM_CHAR;
protected
function DoKeyPressW(var Message: TWMKey): Boolean;
procedure KeyPressW(var Key: WideChar);
published
property OnKeyPressW: TKeyPressWEvent read FOnKeyPressW write FOnKeyPressW;
end;
TTntForm = class(TntForms.TTntForm)
private
FOnKeyPressW: TKeyPressWEvent;
procedure WMChar(var Msg: TWMChar); message WM_CHAR;
protected
function DoKeyPressW(var Message: TWMKey): Boolean;
procedure KeyPressW(var Key: WideChar);
published
property OnKeyPressW: TKeyPressWEvent read FOnKeyPressW write FOnKeyPressW;
end;
TForm1 = class(TTntForm)
TntMemo1: TTntMemo;
TntMemo2: TTntMemo;
procedure FormCreate(Sender: TObject);
procedure FormKeyPressW(Sender: TObject; var Key: WideChar);
procedure TntMemo1KeyPressW(Sender: TObject; var Key: WideChar);
end;
var
Form1: TForm1;
implementation
uses
TntControls;
{$R *.dfm}
type
TWinControlAccess = class(TWinControl);
TTntFormAccess = class(TTntForm);
function TntControl_DoKeyPressW(Self: TWinControl; var Message: TWMKey;
KeyPressW: Pointer): Boolean;
type
TKeyPressWProc = procedure(Self: TWinControl; var Key: WideChar);
var
Form: TCustomForm;
Ch: WideChar;
begin
Result := True;
Form := GetParentForm(Self);
if (Form <> nil) and (Form <> Self) and Form.KeyPreview then
begin
if (Form is TTntForm) and TTntFormAccess(Form).DoKeyPressW(Message) then Exit;
if TWinControlAccess(Form).DoKeyPress(Message) then Exit;
end;
if not (csNoStdEvents in Self.ControlStyle) then
begin
Ch := GetWideCharFromWMCharMsg(Message);
TKeyPressWProc(KeyPressW)(Self, Ch);
SetWideCharForWMCharMsg(Message, Ch);
if Ch = #0 then Exit;
end;
Result := False;
end;
{ TTntMemo }
function TTntMemo.DoKeyPressW(var Message: TWMKey): Boolean;
begin
Result := TntControl_DoKeyPressW(Self, Message, #TTntMemo.KeyPressW);
end;
procedure TTntMemo.KeyPressW(var Key: WideChar);
begin
if Assigned(FOnKeyPressW) then FOnKeyPressW(Self, Key);
end;
procedure TTntMemo.WMChar(var Msg: TWMChar);
begin
if not DoKeyPressW(Msg) then inherited;
end;
{ TTntForm }
function TTntForm.DoKeyPressW(var Message: TWMKey): Boolean;
begin
Result := TntControl_DoKeyPressW(Self, Message, #TTntForm.KeyPressW);
end;
procedure TTntForm.KeyPressW(var Key: WideChar);
begin
if Assigned(FOnKeyPressW) then FOnKeyPressW(Self, Key);
end;
procedure TTntForm.WMChar(var Msg: TWMChar);
begin
if not DoKeyPressW(Msg) then inherited;
end;
{ TForm1 }
procedure TForm1.FormCreate(Sender: TObject);
begin
Self.OnKeyPressW := FormKeyPressW;
TntMemo1.OnKeyPressW := TntMemo1KeyPressW;
end;
procedure TForm1.FormKeyPressW(Sender: TObject; var Key: WideChar);
begin
TntMemo2.Lines.Add(WideString('FormKeyPress: ') + Key);
end;
procedure TForm1.TntMemo1KeyPressW(Sender: TObject; var Key: WideChar);
begin
TntMemo2.Lines.Add(WideString('TntMemo1KeyPress: ') + Key);
end;
end.
I haven’t got much experience with typing Chinese either, but I suspect this is the IME (Input Method Editor) kicking in. That’s what allows Chinese users to type in pinyin, which will then be translated into ideographic characters (otherwise, you’d need a keyboard with some 1000+ keys...)
The Virtual Keycodes are directly related to the keyboard, and so will, of necessity, only correspond to the entered keys. So your function works fine: it converts a VKEY code to a WideChar. To do what you want, you’ll have to write a second function, which would convert pinyin to characters.
If you want to do this specifically for Chinese, I’ll bet there’s functions for doing this out there. If you want to make it more generic, and independent of locale and language, then perhaps it’s possible to interface with the IME for the relevant TMemo, but if so, I haven’t got a clue. My best guess would be to search MSDN for IME.
But, to echo hvd’s comment: what do you want to accomplish here?
Wouldn’t it be easier to just copy the text of the TMemo?

How can I check whether a SHAutoComplete( ) list box is currently shown?

I'm using the SHAutoComplete() function from the Shell Lightweight Utility Functions
library to enable path auto completion for edit fields in a modal dialog.
The dialog should close when the Esc key is pressed, but only if auto completion is not active.
How can I check whether a completion list is currently shown for the focused edit control?
Edit:
I'm using Delphi 2009 on Windows XP 64. The code posted by David
procedure TMyForm.FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if Key = VK_ESCAPE then
ModalResult := mrCancel;
end;
does not work for me - the dialog gets closed.
I have tried on several systems, with strange results:
on my PC with Windows XP 64 the dialog closes while the list is dropped down
on Windows XP Pro in a VMware virtual machine the dialog closes too
but
on my laptop with Windows 7 the dialog does not close
on Windows 2000 Pro in a VMware virtual machine the dialog does not close
Since this is so erratic I chose to write a small component that forces the correct behaviour even if the OS doesn't provide it.
The component can be used like this:
procedure TForm2.FormCreate(Sender: TObject);
const
SHACF_FILESYS_DIRS = $00000020;
begin
SHAutoComplete(Edit1.Handle, SHACF_FILESYS_DIRS or SHACF_USETAB);
fAutoSuggestDropdownChecker := TAutoSuggestDropdownChecker.Create(Self);
end;
procedure TForm2.FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if Key = VK_ESCAPE then begin
if not fAutoSuggestDropdownChecker.DroppedDown then
ModalResult := mrCancel;
end;
end;
but it is important that the Cancel button does not have the Cancel property set.
The component itself works by hooking into application message handling and using window enumeration for the current thread to check for a visible window with the "Auto-Suggest Dropdown" class name. If this exists and is visible then the auto completion list is dropped down.
unit uAutoSuggestDropdownCheck;
interface
uses
Windows, Classes, Messages, Forms;
type
TAutoSuggestDropdownChecker = class(TComponent)
private
fDroppedDown: boolean;
fSaveMessageEvent: TMessageEvent;
procedure AppOnMessage(var AMsg: TMsg; var AHandled: Boolean);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
property DroppedDown: boolean read fDroppedDown;
end;
implementation
////////////////////////////////////////////////////////////////////////////////
function EnumThreadWindowsProc(AWnd: HWND; AParam: LPARAM): BOOL; stdcall;
var
WndClassName: string;
FoundAndVisiblePtr: PInteger;
begin
SetLength(WndClassName, 1024);
GetClassName(AWnd, PChar(WndClassName), Length(WndClassName));
WndClassName := PChar(WndClassName);
if WndClassName = 'Auto-Suggest Dropdown' then begin
FoundAndVisiblePtr := PInteger(AParam);
FoundAndVisiblePtr^ := Ord(IsWindowVisible(AWnd));
Result := False;
end else
Result := True;
end;
function IsAutoSuggestDropdownVisible: boolean;
var
FoundAndVisible: integer;
begin
FoundAndVisible := 0;
EnumThreadWindows(GetCurrentThreadId, #EnumThreadWindowsProc,
LParam(#FoundAndVisible));
Result := FoundAndVisible > 0;
end;
////////////////////////////////////////////////////////////////////////////////
// TAutoSuggestDropdownChecker
////////////////////////////////////////////////////////////////////////////////
constructor TAutoSuggestDropdownChecker.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
fSaveMessageEvent := Application.OnMessage;
Application.OnMessage := AppOnMessage;
end;
destructor TAutoSuggestDropdownChecker.Destroy;
begin
if (TMethod(fSaveMessageEvent).Code = TMethod(Application.OnMessage).Code)
and (TMethod(fSaveMessageEvent).Data = TMethod(Application.OnMessage).Data)
then begin
Application.OnMessage := fSaveMessageEvent;
end;
fSaveMessageEvent := nil;
inherited;
end;
procedure TAutoSuggestDropdownChecker.AppOnMessage(var AMsg: TMsg;
var AHandled: Boolean);
begin
if ((AMsg.message >= WM_KEYFIRST) and (AMsg.message <= WM_KEYLAST))
or ((AMsg.message >= WM_MOUSEFIRST) and (AMsg.message <= WM_MOUSELAST))
or (AMsg.message = WM_CANCELMODE)
then
fDroppedDown := IsAutoSuggestDropdownVisible
end;
end.
The code as posted here is only proof-of-concept but could serve as starting point for those struggling with the same problem.
I can't reproduce your problem. The following OnKeyDown handler, combined with KeyPreview := True gives the desired behaviour in an otherwise empty form.
procedure TMyForm.FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if Key=VK_ESCAPE then
ModalResult := mrCancel;
end;
I guess there is something else in your form that is closing the dialog.

Resources