Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 2 years ago.
Improve this question
I have a TComboBox in Delphi 10.3. I have a Combobox with over 30 items. I need to code a different action for each item of the Combobox. At the moment I'm using if-else statements. As there are 30 different items the if statements are going to be way too long. Is there a quicker way to do this?
This entirely depends on your situation. It is almost impossible to answer your Q without knowing your precise scenario.
Nevertheless, here are a few ideas. Maybe they are relevant to your situation, maybe they are not.
Trivial parameterisation by index
In the best case scenario, your 30 actions can be parameterised. For instance, suppose the items of the combo box are
Show 1
Show 10
Show 100
Show 1000
...
which will display a message box with the given number. In this scenario, you don't need 30 different procedures (here each represented by a simple call to ShowMessage):
procedure TForm1.btnNextClick(Sender: TObject);
begin
case ComboBox1.ItemIndex of
0:
ShowMessage('1');
1:
ShowMessage('10');
2:
ShowMessage('100');
3:
ShowMessage('1000');
// ...
end;
end;
Instead, you should use only one procedure, but with a parameter:
procedure TForm1.btnNextClick(Sender: TObject);
begin
if ComboBox1.ItemIndex <> -1 then
ShowMessage(IntPower(10, ComboBox1.ItemIndex).ToString)
end;
Parameterisation by the associated object
If the action cannot be described by the item's index alone, you can use the object pointer associated with each item. Maybe it is enough to use it to store an integer:
procedure TForm1.FormCreate(Sender: TObject);
begin
ComboBox1.Items.BeginUpdate;
try
ComboBox1.Items.Clear;
ComboBox1.Items.AddObject('Show 51', TObject(51));
ComboBox1.Items.AddObject('Show 111', TObject(111));
ComboBox1.Items.AddObject('Show 856', TObject(856));
ComboBox1.Items.AddObject('Show 1000', TObject(1000));
finally
ComboBox1.Items.EndUpdate;
end;
end;
procedure TForm1.btnNextClick(Sender: TObject);
begin
if ComboBox1.ItemIndex <> -1 then
ShowMessage(Integer(ComboBox1.Items.Objects[ComboBox1.ItemIndex]).ToString);
end;
Otherwise, you can let it be a true pointer to some object with any amount of data (integers, strings, ...).
Unrelated procedures
The examples above all require that the procedures can be parameterised, i.e. replaced by a single procedure with a parameter. If this is not the case, if the procedures are completely unrelated, you need a different approach. But again, which approach is most suitable depends on your precise situation.
Here are a few examples.
Simple case statement
At design time, set the items to Play sound, Run Notepad, and Show Start Menu.
procedure PlaySound;
begin
MessageBeep(MB_ICONINFORMATION);
end;
procedure RunNotepad;
begin
ShellExecute(Form1.Handle, nil, 'notepad', nil, nil, SW_SHOWNORMAL)
end;
procedure ShowStartMenu;
begin
Form1.Perform(WM_SYSCOMMAND, SC_TASKLIST, 0)
end;
procedure TForm1.btnNextClick(Sender: TObject);
begin
case ComboBox1.ItemIndex of
0:
PlaySound;
1:
RunNotepad;
2:
ShowStartMenu;
end;
end;
Storing procedural pointers with the items
procedure PlaySound;
begin
MessageBeep(MB_ICONINFORMATION);
end;
procedure RunNotepad;
begin
ShellExecute(Form1.Handle, nil, 'notepad', nil, nil, SW_SHOWNORMAL)
end;
procedure ShowStartMenu;
begin
Form1.Perform(WM_SYSCOMMAND, SC_TASKLIST, 0)
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
ComboBox1.Items.BeginUpdate;
try
ComboBox1.Items.Clear;
ComboBox1.Items.AddObject('Play sound', TObject(#PlaySound));
ComboBox1.Items.AddObject('Run notepad', TObject(#RunNotepad));
ComboBox1.Items.AddObject('ShowStartMenu', TObject(#ShowStartMenu));
finally
ComboBox1.Items.EndUpdate;
end;
end;
procedure TForm1.btnNextClick(Sender: TObject);
begin
if ComboBox1.ItemIndex <> -1 then
TProcedure(ComboBox1.Items.Objects[ComboBox1.ItemIndex])();
end;
Benefit: no risk of confusing the indices; the actions are "attached" to the items.
Using a dictionary of commands
Maybe your application has a global set of commands, denoted by English words. Then you might want to use a dictionary to get the procedure associated with a word. This can be used for the combo box as well. At design time, let there be three items: beep, write, and start:
type
TForm1 = class(TForm)
ComboBox1: TComboBox;
btnNext: TButton;
procedure btnNextClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
FCommands: TDictionary<string, TProcedure>;
public
end;
procedure PlaySound;
begin
MessageBeep(MB_ICONINFORMATION);
end;
procedure RunNotepad;
begin
ShellExecute(Form1.Handle, nil, 'notepad', nil, nil, SW_SHOWNORMAL)
end;
procedure ShowStartMenu;
begin
Form1.Perform(WM_SYSCOMMAND, SC_TASKLIST, 0)
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
FCommands := TDictionary<string, TProcedure>.Create;
FCommands.Add('beep', PlaySound);
FCommands.Add('write', RunNotepad);
FCommands.Add('start', ShowStartMenu);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FCommands.Free;
end;
procedure TForm1.btnNextClick(Sender: TObject);
var
Cmd: TProcedure;
begin
if
(ComboBox1.ItemIndex <> -1)
and
FCommands.TryGetValue(ComboBox1.Items[ComboBox1.ItemIndex], Cmd)
then
Cmd();
end;
Related
I'm trying to free a component when i click it. So, i've written the simplest code i could imagine to achieve this: a procedure that frees it's sender. But on Delphi 7 (Tried on Delphi XE 10 and it worked with no errors) it sometimes throws an Access Violation or Abstract Error randomly. The easiest way to replicate this is to insert like 30 Buttons and assign an onclick procedure with the code below, then click them.
I've tried the two codes below, both on onclick:
procedure FreeMe(Sender: TObject);
begin
TButton(Sender).Free;
end;
or
procedure FreeMe(Sender: TObject);
begin
(Sender as TButton).Free;
end;
You need to delay the freeing until after the button's OnClick event handier has fully exited. It is important that the freeing happens when the object being freed is idle and not in the middle of processing anything.
One way to do that is to use PostMessage(), eg:
var
MyReleaseWnd: HWND;
procedure TMyMainForm.FormCreate(Sender: TObject);
begin
MyReleaseWnd := AllocateHWnd(MyReleaseWndProc);
end;
procedure TMyMainForm.FormDestroy(Sender: TObject);
begin
DeallocateHWnd(MyReleaseWnd);
end;
procedure TMyMainForm.MyReleaseWndProc(var Message: TMessage);
begin
if Message.Msg = CM_RELEASE then
TObject(Msg.LParam).Free
else
Message.Result := DefWindowProc(MyReleaseWnd, Message.Msg, Message.WParam, Message.LParam);
end;
procedure DelayFreeMe(Sender: TObject);
begin
PostMessage(MyReleaseWnd, CM_RELEASE, 0, LPARAM(Sender));
end;
Alternatively, in 10.2 Tokyo and later, you can use TThread.ForceQueue() instead:
procedure DelayFreeMe(Sender: TObject);
begin
TThread.ForceQueue(nil, Sender.Free);
end;
Either way, you can then do this:
procedure TSomeForm.ButtonClick(Sender: TObject);
begin
DelayFreeMe(Sender);
end;
I am super newbie and tried to write following code which sets every TEdit.Text to one mentioned in code
procedure TForm2.Button1Click(Sender: TObject);
var
i : integer;
Edit : TEdit;
begin
for i := 0 to Edit.ComponentCount - 1 do
begin
with Edit.Components[i] do
begin
Text := 'Done';
end;
end;
end;
What am I doing wrong ?
Here are the mistakes that I can see:
You never assign a value to Edit.
Typically the form owns all the components, and so a TEdit will have zero owned components.
Edit.Components[i] is of type TComponent which does not have a Text property. If your code compiles, then Text is actually that of the form. The lesson you should learn from this point is never to use with ever again.
You should solve this using code like this:
procedure TForm2.Button1Click(Sender: TObject);
var
i: Integer;
Edit: TEdit;
begin
for i := 0 to ComponentCount-1 do begin
if Components[i] is TEdit then begin
Edit := TEdit(Components[i]);
Edit.Text := 'Done';
end;
end;
end;
Note that here we are using ComponentCount and Components[] from the form itself. We have removed the evil with statement. And we have had to cast the component to a reference of type TEdit, after first using the is operator to check the type of the component.
This approach will work so long as the form owns all the edits found within it. However, if you create controls dynamically, or if you use frames or parented forms, then this approach, based on ownership via Components[] will not yield all the controls. In such more complex cases you would need to iterate using the parent/child relationship using ControlCount and Controls[].
What am I doing wrong? Just about everything. What I think you are trying to achieve is to put the same text in all TEdits on the form. So you need to go through all the components in TForm (not Edit!) and and check that each is really a Tedit, and if so assign the text. Like this:
procedure TForm2.Button1Click(Sender: TObject);
var
i:integer;
begin
for i := 0 to ComponentCount - 1 do
begin
if Components[ I ] is TEdit then
begin
(Components[ I ] as TEdit).Text := 'Done';
end;
end;
end;
As an aside - avoid using 'with'. It just cases ambiguity and confusion.
You could iterate over all child controls of the form, and if the current control is a TEdit, then you set its text. If the current control is a windowed control, it might have child controls of its own, and you need to redo same thing for this control. Hence, let's use recursion:
procedure SetAllEdits(AParent: TWinControl; const AText: string);
var
i: Integer;
begin
for i := 0 to AParent.ControlCount - 1 do
if AParent.Controls[i] is TCustomEdit then
TCustomEdit(AParent.Controls[i]).Text := AText
else if AParent.Controls[i] is TWinControl then
SetAllEdits(TWinControl(AParent.Controls[i]), AText);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
SetAllEdits(Self, 'test');
end;
There are other ways, like subclassing the edit control and having the new class respond to broadcasted messages.
Answer from D. Heffernan is already good, I'm trying to make it easier to understand for beginner.
In this code, we do "typecast" TEdit to TComponent by command: aEdit := TEdit(aComponent), because TEdit is inherited from TComponent.
What you get from iteration (for ...) is TComponent, not TEdit. You get TEdit by "typecast" it.
procedure TForm2.Button1Click(Sender: TObject);
var
i : Integer;
aComponent : TComponent;
aEdit : TEdit;
begin
for i := 0 to ComponentCount-1 do
begin
aComponent := Components[i];
if aComponent is TEdit then
begin
aEdit := TEdit(aComponent);
aEdit.Text := 'Done';
end;
end;
end;
Given a TForm with a TListBox on it, the following works:
procedure TForm1.FormCreate(Sender: TObject);
procedure _WorkOnListBox;
begin
ListBox.Items.Append('Test');
end;
begin
_WorkOnListBox;
end;
As does the following:
procedure TForm1.DoWithoutListBoxEvents(AProc: TProc);
begin
ListBox.Items.BeginUpdate;
try
AProc;
finally
ListBox.Items.EndUpdate;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
DoWithoutListBoxEvents(procedure
begin
LayersListBox.Items.Append('Test');
end);
end;
But the following does not:
procedure TForm1.FormCreate(Sender: TObject);
procedure _WorkOnListBox;
begin
ListBox.Items.Append('Test');
end;
begin
DoWithoutListBoxEvents(_WorkOnListBox);
end;
I get an E2555 Cannot capture symbol '_WorkOnListBox'. Why? Is there any way to get the DoWithoutListBoxEvents to work without using an anonymous procedure? Although I think it looks elegant with it, I'm trying to stay FPC compatible.
DoWithoutEvents() takes a TProc as input:
type
TProc = procedure;
Only a standalone non-class procedure and an anonymous procedure can be assigned to a TProc. _WorkOnForm is neither of those, it is a local procedure instead. A local procedure has special compiler handling that ties it to its parent's stack frame. Thus, _WorkOnForm is not compatible with TProc.
I have a Delphi 2007 project that has run fine on Windos XP, Vista and "7" for years. It was an upgrade from Delphi 5 thus "MainFormOnTaskBar" was "false" by default (I never changed it in DPR). In this scenario, the system-wide hot key worked "system-wide" with following code in main form's OnCreate event handler.
HotKey_xyz := GlobalAddAtom('Hotkey_xyz');
if NOT RegisterHotKey(Self.Handle, HotKey_xyz, MOD_CONTROL, VK_F12) then
ShowMessage('Unable to register Control-F12 as system-wide hot key') ;
(I have GlobalDeleteAtom() and UnregisterHotKey() in Form.OnDestroy as expected.)
Now, I need a Form to show its own button on Taskbar, so I set "Application.MainFormOnTaskBar := True" in DPR. This works as expected. However, this has the side-effect that Control-F12 does NOT work system-wide, it works ONLY IF my application has focus (so, it is NOT "system-wide" anymore.)
I have extensively searched the 'Net have found many articles regarding how/why "MainFormOnTaskBar" affects certain subform/modal form behaviors. However, I have found nothing regarding its effect on a "System-Wide Hot Key" issue that I describe above. I have tested and retested my application with MainFormOnTaskBar set to true and false while all else remains exactly the same. I can positively verify that the above described issue with System-wide hot key relates to MainFormOnTaskBar flag.
I will greatly appreciate any guidance regarding a work-around. I do need BOTH - a system-wide hot key AND a form with its own button on taskbar.
Thank You very much.
TApplication.MainFormOnTaskbar has no effect on system-wide hotkeys at all. I can positively confirm that. I am able to receive WM_HOTKEY messages regardless of what MainFormOnTaskbar is set to, regardless of whether the app is focused or not, etc. So whatever you are seeing is not what you think is happening.
Most likely, the Form's Handle is simply being recreated behind your back after you have called RegisterHotKey(), so you lose the HWND that would receive the WM_HOTKEY messages. Instead of using the OnCreate event, you should override the Form's CreateWindowHandle() and DestroyWindowHandle() methods instead to ensure the hot key is always registered for the Form's current HWND no matter what happens to it (you should always do that whenever you tie any kind of data to the Form's Handle), eg:
type
TForm1 = class(TForm)
private
HotKey_xyz: WORD;
procedure WMHotKey(var Message: TMessage); message WM_HOTKEY;
protected
procedure CreateWindowHandle(const Params: TCreateParams); override;
procedure DestroyWindowHandle; override;
end;
procedure TForm1.CreateWindowHandle(const Params: TCreateParams);
begin
inherited;
HotKey_xyz := GlobalAddAtom('Hotkey_xyz');
if HotKey_xyz <> 0 then
RegisterHotKey(Self.Handle, HotKey_xyz, MOD_CONTROL, VK_F12);
end;
procedure TForm1.DestroyWindowHandle(const Params: TCreateParams);
begin
if HotKey_xyz <> 0 then
begin
UnregisterHotKey(Self.Handle, HotKey_xyz);
GlobalDeleteAtom(HotKey_xyz);
HotKey_xyz := 0;
end;
inherited;
end;
procedure TForm1.WMHotKey(var Message: TMessage);
begin
...
end;
A better option is to use AllocateHWnd() to allocate a separate dedicated HWND just for handling the hot key messages (then you can use the OnCreate and OnDestroy events again), eg:
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
HotKey_xyz: WORD;
HotKeyWnd: HWND;
procedure HotKeyWndProc(var Message: TMessage);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
HotKeyWnd := AllocateHwnd(HotKeyWndProc);
HotKey_xyz := GlobalAddAtom('Hotkey_xyz');
if HotKey_xyz <> 0 then
RegisterHotKey(HotKeyWnd, HotKey_xyz, MOD_CONTROL, VK_F12);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
if HotKey_xyz <> 0 then
begin
UnregisterHotKey(HotKeyWnd, HotKey_xyz);
GlobalDeleteAtom(HotKey_xyz);
HotKey_xyz := 0;
end;
if HotKeyWnd <> 0 then
begin
DeallocateHWnd(HotKeyWnd);
HotKeyWnd := 0;
end;
end;
procedure TForm1.HotKeyWndProc(var Message: TMessage);
begin
if Message.Msg = WM_HOTKEY then
begin
...
end else
Message.Result := DefWindowProc(HotKeyWnd, Message.Msg, Message.WParam, Message.LParam);
end;
English Translation (been a while, so may not be entirely accurate; used google translate for the parts I had trouble with):
I'm working on a Visual Component in Delphi (it's not a standard Delphi component) which possesses a property called PopupMenu. I associated the property PopupMenu in the component with the PopupMenu, but when I click the right button [of the mouse], I see nothing.
I also tried to force it to display with this code:
x:= Mouse.CursorPos.X;
y:= Mouse.CursorPos.Y;
// //showmessage(inttostr(x)) PopupMenu1.Popup(x,y);
I have two questions:
How do you know that the right click of the mouse is active? Have any of you encountered this type of problem? Thank you for your answers.
Thanks
EDIT
Here is the procedure that I'm using to execute the PopupMenu1: procedure
TForm6.GeckoBrowser1DOMMouseDown(Sender: TObject; Key: Word);
var x,y:integer;
begin
if key=VK_RBUTTON then begin
x:= Mouse.CursorPos.X;
y:= Mouse.CursorPos.Y;
//showmessage(inttostr(x)) PopupMenu1.Popup(x,y);
end;
end;
This will never work. You cannot mix code in a form with the component code.
I would suggest something like this:
interface
type
TGeckoBrowser = class(....
private
FPopupmenu: TPopupMenu;
protected
...
procedure MouseUp(Sender: TObject; Key: Word); override;
...
published
property PopupMenu: TPopupMenu read FPopupMenu write FPopupMenu;
end;
implementation
....
procedure TGeckoBrowser.MouseUp(Sender: TObject; Key: Word);
var
x,y: integer;
begin
inherited;
if (key=VK_RBUTTON) and Assigned(PopupMenu) then begin
x:= Mouse.CursorPos.X;
y:= Mouse.CursorPos.Y;
PopupMenu.Popup(x,y);
end; {if}
end;
or if you do not want the OnMouseUp to fire when a popup menu appears do:
implementation
....
procedure TGeckoBrowser.MouseUp(Sender: TObject; Key: Word);
var
x,y: integer;
begin
if (key=VK_RBUTTON) and Assigned(PopupMenu) then begin
x:= Mouse.CursorPos.X;
y:= Mouse.CursorPos.Y;
PopupMenu.Popup(x,y);
end {if}
else inherited;
end;
See the difference? Popupmenu is now a part (well linked part anyway) of your component and not something that just happens to be on the same form.