Is it possible to change the Multiselect behaviour with a standard TListBox?
I would like items to only be multiselected by holding the Ctrl key, not the Shift key.
I have TActions which update depending on items selected, eg:
TAction(Sender).Enabled := ListBox1.ItemIndex <> -1;
The controls assigned to the action flicker when a listbox item is selected when holding shift to multiselect, this does not happen by holding ctrl key only.
So I would like to use only Ctrl key to multiselect.
Simply put, how can I:
Multiselect TListBox
Use Ctrl to multiselect
Shift has no effect
Thanks :)
Item selection is processed by the default window procedure of the underlying list box api control. Because of this there's no VCL property that would do this. Nevertheless, users might not like this, but you can change the behavior by handling WM_LBUTTONDOWN message that is post the the control which triggers item selection. Subclass the control in any way you like. Or, since WM_LBUTTONDOWN is posted, you can use OnMessage event of an ApplicationEvents component. Or, just as one example below, if the control is immediately parented by the form, you can use the notification sent to the parent:
type
TForm1 = class(TForm)
..
private
procedure WMParentNotify(var Msg: TWMParentNotify); message WM_PARENTNOTIFY;
..
procedure TForm1.WMParentNotify(var Msg: TWMParentNotify);
var
Pt: TPoint;
State: TKeyboardState;
begin
if (Msg.Event = WM_LBUTTONDOWN) then begin
Pt := SmallPointToPoint(SmallPoint(Msg.XPos, Msg.YPos));
MapWindowPoints(Handle, ListBox1.Handle, Pt, 1);
if PtInRect(ListBox1.ClientRect, Pt) then begin
GetKeyboardState(State);
State[VK_SHIFT] := 0;
SetKeyboardState(State);
end;
end;
inherited;
end;
Related
Hi guys I am a Delphi beginner and I have a simple question because finding something online for Delphi seems to be hard.
I have a CheckListBox and when I click on the checkbox of an Item I want to pass it to another form lets say form2 gets created. There I have 2 buttons:
Delete - it should delete the selected Item so the item where the form spawned on. How do I delete the selected Item?
Edit Entry - I need to pass the checked Item values to another form where I can edit the values and save it as a new one. How do I pass the values from the selected Item to another form?
Ty Faded.
Here's a better approach:
Create a new VCL application.
Add a TCheckListBox control with a few items on it:
Drop a TActionList component on the form.
Create two actions: aDelete and aRename. Sets their captions to Delete... and Rename... and their hints to Removes the selected item from the list. and Renames the selected item in the list..
Add the following code to their OnExecute handlers:
procedure TForm1.aDeleteExecute(Sender: TObject);
begin
if CheckListBox1.ItemIndex = -1 then
Exit;
if MessageBox(Handle, 'Do you want to delete the selected item?',
'Delete', MB_ICONQUESTION or MB_YESNO) <> IDYES then
Exit;
CheckListBox1.DeleteSelected;
end;
procedure TForm1.aRenameExecute(Sender: TObject);
var
S: string;
begin
if CheckListBox1.ItemIndex = -1 then
Exit;
S := CheckListBox1.Items[CheckListBox1.ItemIndex];
if InputQuery('Rename', 'Please enter the new name:', S) then
CheckListBox1.Items[CheckListBox1.ItemIndex] := S;
end;
Add the following code to their OnUpdate handlers:
procedure TForm1.aDeleteUpdate(Sender: TObject);
begin
aDelete.Enabled := CheckListBox1.ItemIndex <> -1;
end;
procedure TForm1.aRenameUpdate(Sender: TObject);
begin
aRename.Enabled := CheckListBox1.ItemIndex <> -1;
end;
Drop a TPopupMenu on the form. Name it pmList. Add two menu items. Set their Action properties to aDelete and aRename, respectively. This will automatically give the items the caption, hint, and hotkeys of the actions:
Now assign pmList to the check list box control's PopupMenu property.
Test the application. Notice that the context menu items are enabled only if an item is selected; otherwise, they are both disabled. (This is thanks to the OnUpdate handlers. It would be very sloppy of us to skip these. But notice that we still verify that an item is selected in the OnExceute handlers. In quality software, you always use both belt and braces.)
Of course, we must map the Del and F2 keys to the delete and rename actions. We could use the ShortCut properties of the actions, but that would make these keys delete and rename in this list even if another GUI control has focus, and that's very bad. Instead, we add an OnKeyDown handler to the check list box control itself:
procedure TForm1.CheckListBox1KeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
case Key of
VK_DELETE:
aDelete.Execute;
VK_F2:
aRename.Execute;
end;
end;
This is in Delphi Pascal
I have dynamical created a 2d array of buttons. I want it such that when you click these buttons, the button pressed will dissapear. (This is to make a minesweeper style game).
Currently i have this array of these buttons, and am trying to set the onclick event to a procedure and passing in the x and y indexes of this button to the procedure such that it can hide the correct button.
Here is what i am using to link to the procedure:
buttons[x,y].OnClick := buttonClicked(x,y);
The x and y are made from a for loop, in order that i can have a 2d array.
An this is the error I am getting.
E2010 Incompatible types: 'TNotifyEvent' and 'procedure, untyped pointer or untyped parameter'
Here is the Procedure:
procedure TMainPage.buttonClicked(buttonx: Integer; buttony: Integer);
begin
buttons[buttonx,buttony].Visible:=False;
end;
Im assuming that the problem is that you cannot send variables through on the OnClick event.
I am unsure of a way to do what i want, so guidance would be appreciated.
Currently i have this array of these buttons, and am trying to set the onclick event to a procedure and passing in the x and y indexes of this button to the procedure such that it can hide the correct button.
You will not be able to pass that information directly to the OnClick event handler using input parameters. The event is declared as a TNotifyEvent, so the only input parameter it can accept is a TObject for the object that triggers the event.
However, that object is the clicked TButton itself, so you can simply use it as-is:
procedure TMainPage.buttonClicked(Sender: TObject);
begin
TButton(Sender).Visible := False;
end;
If, for some reason, you actually needed the XY indexes within the array, you would have to pass those values to the event handler indirectly, such as with the button's Tag property:
buttons[x,y].Tag := Integer(SmallPoint(x, y));
...
procedure TMainPage.buttonClicked(Sender: TObject);
var
pt: TSmallPoint;
begin
pt := TSmallPoint(TButton(Sender).Tag);
// use pt.x and pt.y as needed...
end;
Though, a safer option would be to just derive a new class from TButton instead, and then you can add whatever you want to it:
type
TMyButton = class(TButton)
public
ArrayX, ArrayY: Integer;
end;
...
buttons[x,y] := TMyButton.Create(...);
buttons[x,y].ArrayX := x;
buttons[x,y].ArrayY := y;
...
procedure TMainPage.buttonClicked(Sender: TObject);
var
Btn: TMyButton;
begin
Btn := TMyButton(Sender);
// use Btn.ArrayX and Btn.ArrayY as needed...
end;
As Remy pointed out, there's a simpler way to have the button be made not visible.
To set the OnClick event when creating each new button, use the following:
buttons[x,y].OnClick := #buttonClicked;
Delphi 2010
I am using a OnFormKeyDown event on my main form, and I basically use the same event on a Dialog
//main form
procedure TfrmMain.FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
case Key of
VK_DOWN: btnLast.OnClick(Self);
VK_Up: btnFirst.OnClick(Self);
VK_Left: btnPrev.OnClick(Self);
VK_Right: btnNext.OnClick(Self);
end;
end;
procedure TfrmMain.mniShowOwnedClick(Sender: TObject);
var
I: Integer;
begin
frmMain.KeyPreview:= False;
frmOwned.KeyPreview:= True;
frmOwned.Owned2.Clear;
for I := 0 to Tags.Count - 1 do
if Owned.IndexOf(Tags.Names[I]) <> -1 then
frmOwned.Owned2.Add(Tags[I]);
if frmOwned.ShowModal = mrOK then
begin
frmMain.KeyPreview:= True;
frmOwned.KeyPreview:= False;
end;
end;
//Dialog
procedure TfrmOwned.FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
case Key of
VK_DOWN: btnLast.OnClick(Self);
VK_Up: btnFirst.OnClick(Self);
VK_Left: btnPrev.OnClick(Self);
VK_Right: btnNext.OnClick(Self);
end;
end;
The Form's OnKeyDown works fine, but I can't seem to get the dialogs to work
The problem is that these keys are used as dialog navigation keys. And as such, they never make their way to the OnKeyDown event.
To be honest I had a hard time understanding why they are firing for your main form's OnKeyDown event. I could not make that happen in my test environment. That's because I had added a button to the form. That's enough to mean that the arrow keys are treated as navigation keys. Try creating a an app with a single form and adding a few buttons. Then run the app and use the arrow keys to move the focus between the buttons. That's what I mean when I say that the arrow keys are treated as navigation keys.
I expect that the difference between your two forms is that the main form has nothing that can be navigated around by arrow keys, but the modal form does.
Now, you could stop the arrow keys being treated as navigation keys. Like this:
type
TMyForm = class(TForm)
....
protected
procedure CMDialogKey(var Message: TCMDialogKey); message CM_DIALOGKEY;
....
end;
....
procedure TMyForm.CMDialogKey(var Message: TCMDialogKey);
begin
case Message.CharCode of
VK_LEFT, VK_RIGHT, VK_UP, VK_DOWN:
Message.Result := 0;
else
inherited;
end;
end;
However, a better solution, in my view, is to stop trying to implement shortcuts using OnKeyDown events. That seems like the wrong solution. The right solution is to use actions. Create an action list. Add actions for first, last, previous and next actions. Give them the appropriate ShortCut properties. Assign those actions to your buttons. And the job is done.
One of the benefits of this is that you can stop trying to fake button click events. For what it is worth, calling OnClick is the wrong way to do that. Call the button's Click method if ever your really need to do that. However, use an action and it's all taken care of.
Another benefit is that you'll no longer need to pfaff around with KeyPreview. Simply put, if you want to implement short cuts, use TAction.ShortCut.
Please assist me: How to assign an up arrow keyboard shortcut to action or menu item, and keep it actual for navigating the list control (e.g. ListBox/Virtual Treeview/other) at the same time?
Thanks!
You comment:
And how about the Winamp player? It has Volume Up/Volume Down features assigned to the up arrow key and down arrow key correspondingly.. Okay, if that impossible in Delphi, then ...
but it certainly is possible, it just isn't a good idea to do it, and against the Windows User Experience Interaction Guidelines.
But if you're set on implementing it, here's how. Override the following method in your form class that contains the action components:
function IsShortCut(var Message: TWMKey): Boolean; override;
and in it you can prevent the Up and Down key from triggering the actions they are shortcuts for:
function TWeirdForm.IsShortCut(var Message: TWMKey): Boolean;
begin
if (Message.CharCode in [VK_UP, VK_DOWN])
// insert test whether message needs to go to the focused control instead
and (...)
then begin
// insert calls to code that should be executed instead
Result := False;
exit;
end;
inherited;
end;
Note that you should test for the correct shift state too, and check that your code doesn't break any other window behaviour users expect, like moving of the window with the arrow keys.
On the form properties set KeyPreview := true
then on KeyUp event of the form write event to check if you Up key is pressed and make it call the menu item (on this case menu item called Action1):
procedure TForm1.FormKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
if (Key = VK_UP) and (ActiveControl = ListBox1)then
Action11.Click;
end;
procedure TForm1.Action11Click(Sender: TObject);
begin
if ListBox1.ItemIndex >=0 then
ShowMessage(ListBox1.Items[ListBox1.ItemIndex]);
end;
If you need the Action1 to be executed even if they Current Control isn't the listbox, remove the and part of the IF statement
I have a custom component of type TSpeedButton that has two extra properties defined:
CommentHeading: string;
CommentText: string;
I set CommentHeading at design time.
When the speed button is pressed a memo is shown with a button beneath it for saving its contents. The procedure that handles this:
procedure CustomSpeedButton1Click(Sender: TObject);
begin
Receiver := CustomSpeedButton1.Name; // possibly used to save the memo text back to this speedbuttons property after comments are submitted
ViewComments(CustomSpeedButton1.CommentTitle,CustomSpeedButton1.CommentText);
end;
And the ViewComments procedure itself:
procedure ViewComments(comment_caption:string; comment_text:string);
begin
label15.Hide; // label showing editing in progress, hidden until user begins typing
Button1.Enabled := false; // the button for saving the memo text, hidden until user begins typing
CommentsBox.Visible := true; // pop up the comment box at the bottom of the form
CommentsBox.Caption := 'Comments: ' + comment_caption;
CommentsMemo.Text := comment_text; // if there are existing comments assign them to memo
end;
The contents of the memo need to be assigned to the CommentText property of the custom SpeedButton.
What I was initially thinking was that I could pass the component name to a variable when the custom SpeedButton gets pressed and then retrieve that name when the save button on the memo is pressed and use it to assign the memo text to the speedbuttons CommentText property. But then I realized that to do this I'd have to use some kind of case..of statement that checked for each possible speedbutton name and then assign the memo value to its properties and this just seems ridiculously tedious.
Is there an easier way to assign the memo text to the speedbutton that opened the memo to begin with?
Ultimately, you're asking how to tell the ViewComments function which button's properties it's working with.
Do you understand what the Sender parameter is doing in the OnClick event? It's telling the event handler which object's event is being handled. It's serving precisely the role that you're looking to bring to the ViewComments function.
That's what Mason was getting at in his answer. Rather than pass all the property values, pass the object itself:
procedure ViewComments(CommentButton: TCustomSpeedButton);
Then call it from all your buttons' event handlers:
procedure TForm1.CustomSpeedButton1Click(Sender: TObject);
begin
ViewComments(CustomSpeedButton1);
end;
procedure TForm1.CustomSpeedButton2Click(Sender: TObject);
begin
ViewComments(CustomSpeedButton2);
end;
No strings, no case statements, no lookups.
That should answer your question, but you can do it even better. Remember what I said before about the Sender parameter? When someone clicks the first button, the Sender parameter of that OnClick handler will be the button, so we can rewrite the first event handler like this:
procedure TForm1.CustomSpeedButton1Click(Sender: TObject);
begin
ViewComments(Sender as TCustomSpeedButton);
end;
And you can rewrite the second event handler like this:
procedure TForm1.CustomSpeedButton2Click(Sender: TObject);
begin
ViewComments(Sender as TCustomSpeedButton);
end;
Hmm. They're the same. Having two identical functions is wasteful, so get rid of one and rename the other so it doesn't sound button-specific:
procedure TForm1.CommentButtonClick(Sender: TObject);
begin
ViewComments(Sender as TCustomSpeedButton);
end;
Then set the OnClick properties of both buttons to refer to that one event handler. You can't do that just by double-clicking the property in the Object Inspector. You'll need to either type the name yourself, choose it from the drop-down list, or assign the event property at run time:
CustomSpeedButton1.OnClick := CommentButtonClick;
CustomSpeedButton2.OnClick := CommentButtonClick;
I'd also like to encourage you to use more meaningful names for your controls. That Label15 is particularly egregious. How can you remember that the fifteenth label is the one that indicates that editing is in progress? Call it EditInProgressLabel, for instance.
Since you're already passing extra variables around, why not just pass the SpeedButton itself? Then you won't need to look up the reference.
A small change to your code should do the trick:
procedure TForm1.CustomSpeedButton1Click(Sender: TObject);
var
btn: TCustomSpeedButton;
begin
btn := Sender as TCustomSpeedButton;
Receiver := btn.Name;
ViewComments(btn.CommentTitle, btn.CommentText);
end;
and after editing the comment:
procedure TForm1.StoreComments(comment: string);
var
btn: TCustomSpeedButton;
begin
btn := FindComponent(Receiver) as TCustomSpeedButton;
btn.CommentText := comment;
end;
You can also memorize the button itself instead of just it's name.