Selecting Listitems with Drag not working - delphi

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.

Related

Select all text inside memo independent of click position

I'm experiencing some problems programmatically selecting all the text inside a TMemo component
I have an OnClick event handler that looks something like this
procedure TForm8.Memo1Click(Sender: TObject);
begin
Memo1.SelectAll;
end;
If I click inside the memo where the text ends, everything is selected.
But If I click somewhere at a position before where the text ends, the selection goes from 0 to the clicked position instead of selecting everything.
I have tried messing around with the memo's SelStart, SetLength and CaretPosition properties before calling SelectAll, but I haven't got this to work.
Interestingly enough, when I put a breakpoint inside the TCustomMemo.SelectAll procedure and press ctrl+a then SelectAll is also called inside TStyledMemo.KeyDown, but then everything is selected. It seems clicking inside the memo does something that makes SelectAll behave differently somehow.
I'm on Delphi 11.0
Does anybody know if it's possible what I'm trying to do?

How to have multiple TSpeedButtons down?

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.

Combo box - typing selection then clicking out of focus - doesn't select the typed item

I'm having an issue with the combo box. I have an event handler for OnClick which refreshes data based on what item was selected. The problem is when this scenario occurs:
Drop-down the combo box to list the various options
Type on the keyboard to find a matching item
Combo box changes this selection and calls the OnClick event
My screen refreshes due to this selection / event
Click somewhere outside of the combo box to take the focus away from it
Combo box goes back to the previous selection, even though OnClick was already called
Even though Combo box changed back to prior selection, OnClick isn't called again
After this, Combo Box shows different value than what my data actually represents
So when you open a combo box, and type a few letters on the keyboard to find the item in the drop-down list, OnClick is fired which refreshes my screen. But when you click somewhere outside the combo box (thus taking the focus away from it), the combo box changes back to whatever value was previously selected, instead of what I had typed. And at the same time, the OnClick event isn't fired, so the combo box shows the incorrect value compared to what I had loaded on the screen.
How do I make the combo box stay on the selected item in this scenario of typing the item on the keyboard?
In my code, I deal with this using the OnCloseUp event. Well, in fact I'm using a sub-classed combo for my drop-down lists and they override both the Change and CloseUp methods:
procedure TMyDropDownList.Change;
begin
RespondToChange;
inherited;
end;
procedure TMyDropDownList.CloseUp;
begin
RespondToChange;
inherited;
end;
The RespondToChange method reacts to the new ItemIndex value. If it is expensive to react to every single change whilst the combo is dropped down, then you might consider omitting the call to RespondToChange from the Change method.
You could use OnExit to make the entry with the keyboard jive with the Index on the ComboBox; where VarS is assigned OnChange and is the answer you would like to keep:
procedure TForm1.ComboBox1Exit(Sender: TObject);
begin
{ Windows keyboard select bug, force use of selected }
ComboBox1.ItemIndex := ComboBox1.Items.IndexOf(VarS);
end;
I would call this a bug in the ComboBox design.

Detect click in gutter (left margin) of TDbGrid?

I have a descendant of a TDBGrid that allows multiple rows to be selected.
I'd like to turn this mode on when they click in the gutter, and off when they click in any cell in the grid.
Is there a way in in the OnMouseDown event handler I can detect where they're clicking?
OnMouseDown is difficult; you can get the coordinates via the X and Y parameters to the event, and convert to a row and column by typecasting the TDBGrid to it's ancestor TCustomGrid:
var
Coord: TGridCoord;
begin
Coord := TCustomGrid(DBGrid1).MouseCoord(X, Y);
if Coord.X = 0 then
// We're in the "gutter"
end;
However, it seems that OnMouseDown only fires for TDBGrid when the header row is clicked.
OnCellClick seems like a possible alternative, but it only fires on actual cells (excluding the gutter and header row), so it won't work. Neither will OnColEnter, as it doesn't fire when you'd want it to either.
It looks like your best option would be to use the standard Ctrl and Shift modifiers with the left mouse button to do your multiple selections, like every other app in Windows that does multi-select.

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