I've written a program in Delphi 10.4. The main part of the UI is just a TMemo. When the user types something in it, the app will automatically copy the text in the TMemo to clipboard. It looks like this:
This auto copy part works well. However, I also want to let the user change dark theme or light theme by a shortcut. I enabled a dark theme and a light theme.
The code looks like this:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Clipbrd, System.Actions,
Vcl.ActnList, Vcl.Themes;
type
TForm1 = class(TForm)
txt: TMemo;
ActionList1: TActionList;
act_change_theme: TAction;
procedure txtChange(Sender: TObject);
procedure act_change_themeExecute(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
var
is_dark: Boolean;
implementation
{$R *.dfm}
function ShortCut(Key: Word; Shift: TShiftState): TShortCut;
begin
Result := 0;
if HiByte(Key) <> 0 then
Exit; // if Key is national character then it can't be used as shortcut
Result := Key;
if ssShift in Shift then
Inc(Result, scShift); // this is identical to "+" scShift
if ssCtrl in Shift then
Inc(Result, scCtrl);
if ssAlt in Shift then
Inc(Result, scAlt);
end;
procedure TForm1.act_change_themeExecute(Sender: TObject);
begin
if is_dark then
begin
TStyleManager.TrySetStyle('Windows', false);
is_dark := false;
end
else
begin
TStyleManager.TrySetStyle('Carbon', false);
is_dark := true;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
is_dark := false;
act_change_theme.ShortCut := ShortCut(Word('d'), [ssCtrl]);
end;
procedure TForm1.txtChange(Sender: TObject);
begin
try
Clipboard.AsText := txt.Lines.GetText;
except
on E: Exception do
end;
end;
end.
However, when I press ctrl+d, nothing happened. I tried to debug it and I found that ctrl+d never triggers the action's shortcut. Why this happened? How to fix it? I've used the shortcut function in the past and it worked.
Try Word('D'), or the constant vkD, instead of Word('d'). Shortcuts use virtual key codes, and letters are represented as virtual keys using their capital values. Typing an Uppercase or Lowercase letter into an edit control uses the same virtual key, it is the current shift state that determines the case of the letter when the key is translated into a text character.
Also note that the VCL has its own ShortCut() function (and also TextToShortCut()) in the Vcl.Menus unit for creating TShortCut values, so you don't need to write your own function.
See Representing Keys and Shortcuts, especially Representing Shortcuts as Instances of TShortCut.
Also, your TAction is clearly placed on the Form at design-time, so you should simply assign its ShortCut using the Object Inspector, rather than in code. Then these details would be handled for you automatically by the framework.
Related
I have an simple application with a panel contains 2 labels (eg. A & B) and a button C created by Delphi 2009.
Label A will display the cursor position when I move the mouse inside the area of panel. Label B just display the static text (caption will not change during app run)
If I move the mouse inside the panel, the label A will flicker.
When I enable 'Double buffer' of form, the flicker is lost. Button C wil demonstrate to enable/disable 'Double buffer' property
I would like to question 'Why does the label in panel flicker? What is the root cause? How can we solve this problem thoroughly?'
Here is my code:
unit DemoFlicker;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
System.StrUtils,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls;
type
TForm1 = class(TForm)
pnlCtr: TPanel;
btnDoubleBuffer: TButton;
lblName: TLabel;
lblNumber: TLabel;
procedure btnDoubleBufferClick(Sender: TObject);
procedure pnlCtrMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
FDoubleBuffer: Boolean;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.btnDoubleBufferClick(Sender: TObject);
begin
FDoubleBuffer := not FDoubleBuffer;
Self.DoubleBuffered := FDoubleBuffer;
if FDoubleBuffer then
begin
btnDoubleBuffer.Caption := 'Not Apply Double Buffer';
end
else
begin
btnDoubleBuffer.Caption := 'Apply Double Buffer';
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
FDoubleBuffer := False;
Self.DoubleBuffered := False;
end;
procedure TForm1.pnlCtrMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
var
mousePos: string;
begin
mousePos := Format('(X=%d, Y=%d)', [Mouse.CursorPos.X, Mouse.CursorPos.Y]);
lblNumber.Caption := mousePos ;
end;
end.
Flicker occurs because of how the drawing is done. Without double buffer, the background is drawn (painted) and then the label is drawn. So at a moment you see only the background only and then you seen the label above the background. If you repeat the update, it flickers.
When using "double buffer", the drawing is done in an invisible buffer and then when drawing is complete, the buffer is rendered on screen. So you only see the complete image at once and no flickering.
To solve the problem, use double buffering as you found it by yourself.
You may also create a new component which does all drawing itself in his Paint procedure.
I've had the same problem, Delphi 10.4
Setting the enclosing TPanel in this way -
DoubleBuffered = True
ParentBackground = False
seems to fix it.
The VCL has become way too complicated in its implementation, especially since Themes were introduced, making its behavior very buggy and unpredictable IMHO.
Trying to save code. I want to display text etc on an image on the form at OnActivate then print the same text on clicking button (Real program is more complicated). To save writing code twice I tried the enclosed code but it won't compile at the "Obj.Canvas" line. If I comment out this line and the enclosed line the program runs but the Obj value is ().
I've tried several other approaches but none work. Can anyone tell me where I'm going wrong.
Badger
unit Unit7;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls, printers;
type
TForm7 = class(TForm)
Print: TButton;
Image1: TImage;
PrintDialog1: TPrintDialog;
procedure FormActivate(Sender: TObject);
procedure PrintClick(Sender: TObject);
private
{ Private declarations }
public
DH,DW:Extended;
Procedure DoLayout(Obj:TObject);
{ Public declarations }
end;
var
Form7: TForm7;
implementation
{$R *.dfm}
procedure TForm7.FormActivate(Sender: TObject);
begin
DoLayout(Image1);
end;
procedure TForm7.PrintClick(Sender: TObject);
begin
if PrintDialog1.Execute then
begin
printer.BeginDoc;
DoLayout(Printer);
Printer.EndDoc;
end;
end;
procedure TForm7.DoLayout(Obj:TObject);
begin
if Obj =Printer then //when you run the program Obj is ()
begin
DW:=Printer.PageWidth/Image1.Width;
DH:=Printer.PageHeight/Image1.Height;
end
else
begin
DH:=1;
DW:=1;
end;
With Obj.canvas do //Error here when compiled - tried commenting it out
begin
TextOut(Int(DH*50),Int(DW*30),'This is the text'); //commented this out too
end;
end;
end.
The TPrinter class and the TImage class don't share a common ancestor class except for TObject, as a result that's what you're passing in.
A suggested refactoring is to change the DoLayout code to accept the canvas that you want to use, as well as an parameter to determine if it's a printer or an image that you're passing in e.g.
procedure TForm7.DoLayout(aCanvas : TCanvas; bPrinter : boolean);
begin
if bPrinter then //when you run the program Obj is ()
begin
DW:=Printer.PageWidth/Image1.Width;
DH:=Printer.PageHeight/Image1.Height;
end
else
begin
DH:=1;
DW:=1;
end;
With aCanvas do
begin
TextOut(Int(DH*50),Int(DW*30),'This is the text');
end;
end;
then when you call it, use the printer canvas explicitly, or the image canvas:
DoLayout(Printer.canvas, true);
or
DoLayout(Image1.canvas, false);
this is just a rough estimation based on your code; I don't have a delphi compiler to hand to verify it.
I want to make simple program that sets Edit1.Text to "6" (for example, but with usage of DLL - thats important). Here's the code:
Unit:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm1 = class(TForm)
Edit1: TEdit;
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
a:integer;
implementation
procedure test; external 'lib.dll' name 'test';
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
test;
Edit1.Text:=Inttostr(a);
end;
end.
And the DLL file:
library lib;
uses
Winapi.Windows, System.SysUtils;
var
a:integer;
procedure test;
begin
a:=6;
end;
exports
test;
{$R *.res}
begin
end.
The problem is, that Edit1.Text is still 0. Can you help me, please?
You've got two different variables, one in the DLL and one in the executable. That they are both named a is incidental. Setting one has no impact on the other.
Make the DLL export a function that returns the value:
function GetValue: Integer; stdcall;
begin
Result := 6;
end;
Import it like this:
function GetValue: Integer; stdcall; external dllname;
And call it like this:
Edit1.Text := IntToStr(GetValue);
No doubt the real code will do more than return the value 6 but that's no problem. You can return anything you like. They key point is that you pass the value from the DLL to the host using a function return value.
it works good:
in MainUnit
implementation
procedure Test(Edit1: TEdit); stdcall; external 'dll_proj.dll';
in DLL
exports Test;
Procedure Test(Object1: TEdit); stdcall;
var i:integer;
begin
for i:= 0 to 100 do
begin
Object1.Text:= IntToStr(i);
Application.Processmessages();
Sleep(100);
end;
end;
Building on your original code and adding a few more buttons, here's a demonstration of how you can use procedures (or functions if you prefer) and have them play nicely with a DLL.
Note that the name option is not required unless you wish to change the function's name or use overloading - so I've commented it out.
implementation
procedure test(var a : integer); external 'lib.dll' {name 'test'};
procedure test2(ptr_a : pinteger); external 'lib.dll';
procedure test3(ptr_a : pinteger); external 'lib.dll';
procedure test4(ptr_a : pinteger = nil); external 'lib.dll';
{$R *.dfm}
procedure TForm14.Button1Click(Sender: TObject);
begin
test(a);
Edit1.Text:=Inttostr(a);
end;
procedure TForm14.Button2Click(Sender: TObject);
begin
test2(#a);
Edit1.Text:=Inttostr(a);
end;
procedure TForm14.Button3Click(Sender: TObject);
begin
test3(#a);
Edit1.Text:=Inttostr(a);
end;
procedure TForm14.Button4Click(Sender: TObject);
begin
test4(#a);
test4;
Edit1.Text:=Inttostr(a);
end;
end.
... and the library body ...
var
local_a:integer;
local_Ptr_a:pinteger;
procedure test(var a : integer);
begin
a:=6;
end;
procedure test2(ptr_a : pinteger);
begin
inc(ptr_a^);
end;
procedure test3(ptr_a : pinteger);
begin
inc(local_a);
ptr_a^:=local_a;
end;
procedure test4(ptr_a : pinteger = nil);
begin
if ptr_a = nil then
inc(local_ptr_a^)
else
local_ptr_a := ptr_a;
end;
exports test;
exports test2;
exports test3;
exports test4;
{$R *.res}
begin
local_a := 4;
end.
So - to a little explanation.
First test : using a var-parameter to return a value from a procedure. No problem there.
Second test : pass the address of the receiving variable as a pointer. I've added a little curl here - incrementing the value for entertainment er,...value.
Third test : This is showing how local values owned by the DLL can be used. The local value is initialised by the assignment of 4 at the end. The procedure itself uses the same mechanism as the second test to return the value from the DLL's local variables to the main routine's variables.
Note that test3 assigns to the program's variable (1 + the value stored in the DLL's memory, ) hence pressing button3 will actually changes a; so pressing buttons 1-2-2-3-2 will use the value from 3 for the last change, not the prior-value-from-2.
Final test: this where we can get a little more clever. It uses the optional-parameters mechanism to vary the detailed operation.
First you eecute the procedure with a parameter, being the address of (or pointer to) a variable of the appropriate type. The procedure stores that address in the DLL's memory area.
Next you can execute the procedure with no parameters and it will increment the integer to which the stored pointer is pointing. Purely for convenience, I've established the variable's address on each button-push, but once the address has been stored, it doesn't matter whether that address was set a few microseconds ago or a week, test4; will increment the integer value at that address - whatever it is. Set the address using test4(#b); then test4; will increment b - whichever integer was last pointed to when the procedure was executed with a parameter.
executing test4; without having at sometime prior executed a test4(#something) or where something is now out-of-scope (like perhaps a local variable in a procedure) is very likely to cause an access violation.
I would like to have a KeyPreview functionality within Frames, I mean, that when the input (say, one of the controls of the frame is selected, or the mouse is inside) is in a frame (which would have several panels and other controls) then the keys pressed by the user are first processed by the frame.
Is there a way to do this? I haven't found a property similar to KeyPreview in TFrame.
I'm using version XE5 of RAD Studio, altough I mostly work with C++Builder.
Thanks to my recent "When does a ShortCut fire"-investigation, I have worked out a stand alone solution for your Frame.
In short: all key messages enter in TWinControl.CNKeyDwon of the active control. That method calls TWinControl.IsMenuKey which traverses all parents while determining whether the message is a ShortCut. Is does so by calling its GetPopupMenu.IsShortCut method. I have overridden the Frame's GetPopupMenu method by creating one if it is not present. Note that at all time you still can add a PopupMenu to the Frame yourself. By subclassing TPopupMenu and overriding the IsShortCut method, the Frame's KeyDown method is called, which serves as the KeyPreview functionality you require. (I could also have assigned the OnKeyDdown event handler).
unit Unit2;
interface
uses
Winapi.Messages, System.Classes, Vcl.Controls, Vcl.Forms, Vcl.Menus,
Vcl.StdCtrls;
type
TPopupMenu = class(Vcl.Menus.TPopupMenu)
public
function IsShortCut(var Message: TWMKey): Boolean; override;
end;
TFrame2 = class(TFrame)
Label1: TLabel;
Edit1: TEdit;
private
FPreviewPopup: TPopupMenu;
protected
function GetPopupMenu: Vcl.Menus.TPopupMenu; override;
procedure KeyDown(var Key: Word; Shift: TShiftState); override;
end;
implementation
{$R *.dfm}
{ TPopupMenu }
function TPopupMenu.IsShortCut(var Message: TWMKey): Boolean;
var
ShiftState: TShiftState;
begin
ShiftState := KeyDataToShiftState(Message.KeyData);
TFrame2(Owner).KeyDown(Message.CharCode, ShiftState);
Result := Message.CharCode = 0;
if not Result then
Result := inherited IsShortCut(Message);
end;
{ TFrame2 }
function TFrame2.GetPopupMenu: Vcl.Menus.TPopupMenu;
begin
Result := inherited GetPopUpMenu;
if Result = nil then
begin
if FPreviewPopup = nil then
FPreviewPopup := TPopupMenu.Create(Self);
Result := FPreviewPopup;
end;
end;
procedure TFrame2.KeyDown(var Key: Word; Shift: TShiftState);
begin
if (Key = Ord('X')) and (ssCtrl in Shift) then
begin
Label1.Caption := 'OH NO, DON''T DO THAT!';
Key := 0;
end;
end;
end.
If you only have one frame on the form at the time you could make use of forms KeyPreview ability and forward the necessary information to the frame.
If you are only forwarding the information you don't need to make any changes to original VCL code just make a modified TFrame class. So there is no wory that you might break whole VCL doing it.
Here is a quick code example:
MainForm code:
unit Unit2;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Unit3, Vcl.ExtCtrls, Vcl.StdCtrls;
type
TForm2 = class(TForm)
Panel1: TPanel;
ModifiedFrame: TModifiedFrame;
Edit1: TEdit;
procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form2: TForm2;
implementation
{$R *.dfm}
procedure TForm2.FormCreate(Sender: TObject);
begin
//This is required since I'm asigning frames OnKeyDown event method manually
ModifiedFrame.OnKeyDown := ModifiedFrame.FrameKeyDown;
end;
procedure TForm2.FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
//Forward key down information to ModifiedFrame
ModifiedFrame.DoKeyDown(Sender, Key, Shift);
if Key = 0 then
MessageDlg('Key was handled by the modified frame!',mtInformation,[mbOK],0)
else
MessageDlg('Key was not handled!',mtInformation,[mbOK],0);
end;
end.
ModifiedFrame code:
unit Unit3;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TModifiedFrame = class(TFrame)
Edit1: TEdit;
//Normally this method would be added by the Delphi IDE when you set the
//OnKeyDown event but here I created this manually in order to avoid crating
//design package with modified frame
procedure FrameKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
private
{ Private declarations }
FOnKeyDown: TKeyEvent;
public
{ Public declarations }
//This is used to recieve forwarded key down information from the Form
procedure DoKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
published
//Property to alow setting the OnKeyDown event at design-time
//NOTE: In order for this to work properly you have to put this modified
//frame class into separate unti and register it as new design time component
property OnKeyDown: TKeyEvent read FOnKeyDown write FOnKeyDown;
end;
implementation
{$R *.dfm}
procedure TModifiedFrame.DoKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
//Check to see if OnKeyDownEvent has been assigned. If it is foward the key down
//information to the event procedure
if Assigned(FOnKeyDown) then FOnKeyDown(Self, Key, Shift);
end;
procedure TModifiedFrame.FrameKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
//Do something
if Key = VK_RETURN then
begin
MessageBeep(0);
Key := 0;
end;
end;
end.
Using simillar approach you can forward other key events.
It is doable, if you are willing to change VCL code.
KeyPreview is handled in TWinControl.DoKeyDown method. As it can be seen from the code control that has focus will lookup its parent form and invoke its DoKeyDown method if KeyPreview is turned on.
function TWinControl.DoKeyDown(var Message: TWMKey): Boolean;
var
ShiftState: TShiftState;
Form, FormParent: TCustomForm;
LCharCode: Word;
begin
Result := True;
// Insert modification here
{ First give the immediate parent form a try at the Message }
Form := GetParentForm(Self, False);
if (Form <> nil) and (Form <> Self) then
begin
if Form.KeyPreview and TWinControl(Form).DoKeyDown(Message) then
Exit;
{ If that didn't work, see if that Form has a parent (ie: it is docked) }
if Form.Parent <> nil then
begin
FormParent := GetParentForm(Form);
if (FormParent <> nil) and (FormParent <> Form) and
FormParent.KeyPreview and TWinControl(FormParent).DoKeyDown(Message) then
Exit;
end;
end;
with Message do
begin
ShiftState := KeyDataToShiftState(KeyData);
if not (csNoStdEvents in ControlStyle) then
begin
LCharCode := CharCode;
KeyDown(LCharCode, ShiftState);
CharCode := LCharCode;
if LCharCode = 0 then Exit;
end;
end;
Result := False;
end;
To change that behavior, you would need to either change TWinControl.DoKeyDown code to scan for frames too or intercept WM_KEYDOWN and WM_SYSKEYDOWN for every TWinControl descendant you want to use, and finally add KeyPreview field to base Frame class.
Probably best option would be to declare IKeyPreview interface and when scanning for parent forms/frames test if parent implements that interface. If none found, you can fall back to original code. That would contain VCL code changes only to TWinControl.DoKeyDown method, and you can easily implement interface in Frames where needed.
Note: On Windows control that has focus receives key events. So above modifications would be able to find frame only if some of its controls has focus. Whether or not mouse would be over the frame or not would not have any influence on functionality.
More detailed code would look like this:
Interface definition that Frame would have to implement:
IKeyPreview = interface
['{D7318B16-04FF-43BE-8E99-6BE8663827EE}']
function GetKeyPreview: boolean;
property KeyPreview: boolean read GetKeyPreview;
end;
Function for finding parent frame that implements IKeyPreview interface, should be put somewhere in Vcl.Controls implementation section:
function GetParentKeyPreview(Control: TWinControl): IKeyPreview;
var
Parent: TWinControl;
begin
Result := nil;
Parent := Control.Parent;
while Assigned(Parent) do
begin
if Parent is TCustomForm then Parent := nil
else
if Supports(Parent, IKeyPreview, Result) then Parent := nil
else Parent := Parent.Parent;
end;
end;
TWinControl.DoKeyDown modification (insert in above original code):
var
PreviewParent: IKeyPreview;
PreviewParent := GetParentKeyPreview(Self);
if PreviewParent <> nil then
begin
if PreviewParent.KeyPreview and TWinControl(PreviewParent).DoKeyDown(Message) then
Exit;
end;
I'm trying to implement the following functionality:
when mouse comes over the combobox, it opens automatically.
when mouse leaves the combobox area (not only the combo, but dropdown list too), it closes automatically.
First point was quite easy:
procedure TForm1.ComboTimeUnitsMouseEnter(Sender: TObject);
begin
ComboTimeUnits.DroppedDown := True;
end;
The second point, though, I cannot do it. I tried:
procedure TForm1.ComboTimeUnitsMouseLeave(Sender: TObject);
begin
ComboTimeUnits.DroppedDown := False;
end;
But when the mouse is over the combobox, it acts very strange, appearing and disappearing, becoming unusable.
I tried AutoCloseUp property, with no result. Now I'm out of ideas and google couldn't help.
Can someone point me in the right direction?
There is no simple solution to your Combo Box (CB) request. I recall that the drop down List of a Windows CB is child to the screen and not the CB. The reason for this is to be able to display the drop down list outside of the client window as illustrated below. Pretty good stuff if you ask me.
Suggested solution
Here's a go at trying to use the existing TComboBox. TLama's "ugly code" is more elegant than mine because he uses an interceptor class. My suggestion below does however solve an additional case, namely the listbox does not roll up when the mouse moves up and crosses the boundry between the ListBox back to the Combobox.
unit main;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.AppEvnts;
type
TFormMain = class(TForm)
ComboBox1: TComboBox;
Label1: TLabel;
Label2: TLabel;
procedure ComboBox1MouseEnter(Sender: TObject);
procedure ComboBox1CloseUp(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
FActiveCb : TComboBox; //Stores a reference to the currently active CB. If nil then no CB is in use
FActiveCbInfo : TComboBoxInfo; //stores relevant Handles used by the currently active CB
procedure ApplicationEvents1Idle(Sender: TObject; var Done: Boolean);
public
{ Public declarations }
end;
var
FormMain: TFormMain;
implementation
{$R *.dfm}
procedure TFormMain.FormCreate(Sender: TObject);
begin
FActiveCb := nil;
FActiveCbInfo.cbSize := sizeof(TComboBoxInfo);
Application.OnIdle := Self.ApplicationEvents1Idle;
end;
procedure TFormMain.ComboBox1CloseUp(Sender: TObject);
begin
FActiveCb := nil;
end;
procedure TFormMain.ComboBox1MouseEnter(Sender: TObject);
begin
FActiveCb := TComboBox(Sender);
FActiveCb.DroppedDown := true;
GetComboBoxInfo(FActiveCb.Handle, FActiveCbInfo); //Get CB's handles
end;
procedure TFormMain.ApplicationEvents1Idle(Sender: TObject; var Done: Boolean);
var w : THandle;
begin
//Check if the mouse cursor is within the CB, it's Edit Box or it's List Box
w := WindowFromPoint(Mouse.CursorPos);
with FActiveCbInfo do
if Assigned(FActiveCb) and (w <> hwndList) and (w <> hwndCombo) and (w <> hwndItem) then
FActiveCb.DroppedDown := false;
end;
end.
How to add additional CBs
Drop a new combobox on the form.
Assign ComboBox1MouseEnter proc to the OnMouseEnter event
Assign ComboBox1CloseUp proc to the OnCloseUp event
Issues
There are however certain issues that remain to be solved:
ListBox dissapears when user clicks
Text in the CB cannot be selected using the mouse
For sure more issues...