How to have multiple TSpeedButtons down? - delphi

I'm trying to use several TSpeedButtons on a TFlowPanel to show a list of options that the user can choose. This is how I create those buttons:
procedure FillOptions(ButtonPanel: TFlowPanel; Options: TStrings; Action: TNotifyEvent);
var Option: string;
Button: TSpeedButton;
begin
for Option in Options do begin
Button:= TSpeedButton.Create(ButtonPanel);
Button.Caption := Option;
Button.Width := Canvas.TextWidth(Option) + 20;
Button.GroupIndex := 99;
Button.AllowAllUp := True;
Button.OnClick := Action;
Button.Parent := ButtonPanel;
end;
end;
The problem is that I can't get to chose more than one option. When I click a button it goes down but the button previously chosen goes up.
What am I forgetting to set so more than one button can be down at the same time ?.
Thank you.

The documenatation on TSpeedButton.GroupIndex fully explains this (emphasis mine):
When GroupIndex is 0, the button behaves independently of all other buttons on the form. When the user clicks such a speed button, the button appears pressed (in its clicked state) and then returns to its normal up state when the user releases the mouse button.
When GroupIndex is greater than 0, the button remains selected (in its down state) when clicked by the user. When the user clicks a selected button, it returns to the up state, unless AllowAllUp is false. Setting the GroupIndex property of a single speed button to a value greater than 0 causes the button to behave as a two-state button when AllowAllUp is true.
Speed buttons with the same GroupIndex property value (other than 0) work together as a group. When the user clicks one of these buttons, it remains selected until the user clicks another speed button belonging to the same group. Speed buttons used in this way can present mutually exclusive choices to the user.
In other words, this is how speed buttons work. If five speed buttons belong to the same group, at most one of them can be selected (down) at any given point in time.
The AllowAllUp property merely makes it possible to have zero buttons selected (down); there is no way to have more than one down at the same time, unless they have different values of GroupIndex.
So, you need to give each button its own non-zero GroupIndex and also make sure that they all have AllowAllUp = True. Then each button will toggle individually.

Related

Why does an unclicked RadioButton click event happen before the program closes? [duplicate]

I have some TRadioButtons on Form2 and call Form2 from Form1 with this code:
procedure TForm1.btnCallForm2Click(Sender:TObject);
begin
Form2.RadioButton2.Checked:= true;
Form2.ShowModal;
end;
If user clicked btnCallForm2, Form2 will be displayed, then user clicks RadioButton3 and closes the form, and then reopen it by clicking btnCallForm2 again.
Now Form2 displayed again but RadioButton3 is checked instead of RadioButton2.
Q: What is this behavior, is it a bug? How to set checked my desired RadioButton instead of what is selected by the user in previous turn?
This is not a bug. The reason that you have this "strange" behavior is that if the Form2 is not destroyed, then next time it becomes visible (e.g. ShowModal) it remembers which control had the focus.
In your case the last control in focus is the RadioButton3 (because you clicked on it to change the "checked" state). So even if you are changing the "checked" state back to RadioButton2, the focus will be restored to RadioButton3 when the form is next activated. To restore the focus, the control is sent a WM_SETFOCUS. Read the rest from documentation for default message processing for button controls:
WM_SETFOCUS   Draws a focus rectangle on the button getting the focus.
For radio buttons and automatic radio buttons, the parent window is
sent a BN_CLICKED notification code.
This BN_CLICK notification (WM_COMMAND message) sets the state of the radio button to checked.
The rationale behind this behavior can be found while navigating the radio buttons with the keyboard. When you press up/down arrow while on one of the radio buttons, the next radio button that receives the focus becomes checked.
This behavior only applies to radio buttons, e.g., try the same with another control (e.g. check box), that its state is not altered when it has focus. You will see that everything is working as expected
As kobik was suggesting, a fast and easy solution would be to set the ActiveControl to nil before showing the Form2
Form2.ActiveControl := nil;
or in the light of the documentation:
Form2.ActiveControl := Form2.RadioButton2;
or you could destoy and recreate the form as following:
Remove the Form2 from the AutoCreated Forms in Project->Options->Forms and create it manually in the ButtonClick event
procedure TForm1.btnCallForm2Click(Sender:TObject)
begin
Form2 := TForm2.Create(nil);
try
Form2.RadioButton2.Checked:= true;
Form2.ShowModal;
finally
FreeAndNil(Form2);
end;
end;

Selecting Listitems with Drag not working

On the MouseMove event I need to select items in the Listview if my mouse click is dragging over them. My code however is not working. When I click and drag only the first item I click gets selected.
In the MouseMove event:
//If left mouse button is depressed
if(GetAsyncKeyState(VK_LBUTTON) = 1) then
begin
LListItem := NestingResultsListView.GetItemAt(x,y);
//If the item is not selected, select it.
if not LListItem.Selected then
begin
LListItem.Selected := true;
end;
end;
TListView has a MultiSelect property, check that in the Object Inspector and then you can select multiple items with mouse dragging, you don't need to code it yourself.
First of all, GetAsyncKeyState returns the 'down' state in most significant bit of the word, so you should write something like GetAsyncKeyState(VK_BUTTON) and $8000 <> 0.
Second, using GetAsyncKeyState for mouse buttons is not a very good thing since it checks for physical buttons (and if a user is left-handed and remaps the buttons he will be confused as your code expects left mouse button to be pressed). A better way would be to remember pressed mouse buttons in OnMouseDown event and update/reset them in OnMouseUp event.

Making sure at least 1 item in a TPopup menu is always selected

I have TPopup menu with 3 items setup with the following properties.
AutoCheck = True
RadioItem = True
and for the first menu item I set the Checked property to True as well so the menu has at least 1 item selected with it is initially displayed. This is all good and well, but if I click on the item that is currently selected, the check (dot) goes away and I want to make it so that at least 1 item in the popup is selected at all times. How do I go about doing that?
The items in the group should also have the same non-zero GroupIndex. This, together with RadioItem should make the items behave like a radio group.
However, it seems that AutoCheck doesn't respect GroupIndex and RadioItem properties, since it will clear the check when you click on a currently checked item.
(Arguably, this could be considered a bug in the VCL.)
Here's a workaround:
Set AutoCheck to False, assign all items the same non-zero GroupIndex and a common OnClick handler to check the clicked item like this:
procedure TForm1.ItemClick(Sender: TObject);
begin
(Sender as TMenuItem).Checked := True;
end;
Or use Actions, with them this seems to work as expected.
You can set following OnClick handler to all your radio group Menu Items:
procedure TForm1.MenuItemClickHandler(Sender: TObject);
begin
if Sender is TMenuItem then
begin
if not (Sender as TMenuItem).Checked then (Sender as TMenuItem).Checked:=True;
end;
end;

How do I make a shortcut key do different things depending on the active tab page?

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.

TListBox Drag and Drop problems with MultiSelect enabled

I have a TListBox with multiselect and ExtendedSelect both set to true. I need to be able to drag multiple items in the list box to re-arrange them. My problem is what happens when the user clicks on an item that is already selected without holding down the CTRL or SHIFT key.
Case 1: DragMode is set to dmManual
The selection is cleared before the mouse down. This will not allow multiple items to be dragged.
Case 2: DragMode is set to dmAutomatic
The MouseDown event never fires. The selection is not cleared so dragging is OK, but the user cannot clear the selection by clicking on one of the selected items. This really causes a problem if all the items are selected or the next item the user wants to select was part of the current selection.
Note that this problem only happens if you assign something to the DragObject in the OnStartDrag procedure. I think the problem would go away if OnStartDrag would only start after the user moves the mouse. I have Mouse.DragImmediate := false set but I still get the StartDrag fired as soon as I click on an item in the list box.
I am using Delphi 7 for this project but I see the same behavior in Delphi 2007.
I have played with this for a while. And observe the same effects.
I would use Case2 and add a (Select All/Deselect All) button to the list. It even adds extra functionality and solves the most annoying part of the problem.
Use Case 2 and when the TListBox.OnMouseUp event fires check to see if multiple items are selected and were dragged. If multiple items are selected, but weren't dragged, then deselect all items apart from the clicked item.
I would use this method because Windows Explorer works this way.
Bit of a kludge but this works. DragMode on the ListBox is set to dmAutomatic.
procedure TForm1.ListBox1DragDrop(Sender, Source: TObject; X, Y: Integer);
var
iDropIdx, i: Integer;
pDropPoint: TPoint;
slSelected: TStrings;
begin
{Which item is being dropped onto?}
pDropPoint := Point(X, Y);
iDropIdx := ListBox1.ItemAtPos(pDropPoint, False);
slSelected := TStringList.Create;
try
{Copy the selected items to another string list}
for i := 0 to Pred(ListBox1.Items.Count) do
begin
if (ListBox1.Selected[i]) then
slSelected.Append(ListBox1.Items[i]);
end;
{Find the selected items in the listbox and swap them with the drop target}
for i := 0 to Pred(slSelected.Count) do
begin
ListBox1.Items.Exchange(ListBox1.Items.IndexOf(slSelected[i]), iDropIdx);
inc(iDropIdx);
end;
finally
slSelected.Free;
end;
end;
I'm not sure why this makes a difference but if I change the DragObject to be a TDragControlObjectEx (instead of a TDragObjectEx) I get the behavior I am looking for. Drag mode is set to Automatic.
I tried to look and see what this was affecting but I could not figure it out.

Resources