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.
Related
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.
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.
I am trying to figure out a way to select rows in a TListview by dragging over them (like dragging a window over icons in Windows Explorer to select them). This is not about drag and drop, only dragging.
I don't think I need an action on MouseDown since that row is already selected when I click, but I think I need something like:
OnMouseMove check if X,Y is above a row
If so, change its IsSelected to true
If not, do nothing
I don't know if anything is needed on MouseUp
The question is, how can I check if the mouse x.y is within/above a Listview row?
Use GetItemAt method of TListView (and maybe also TListView.GetHitTestInfoAt).
Parts of my stringgrid are eligible drop targets, and some are not (first row is column headings, first column is a sort of index and subsequent columns may be dropped to). I have that coded and working.
Now I am thinking that it might be nice to gve a visual indiation to the user as he drags the mouse over a cell which is a potential drop target. I woudl like to highlight the first cell in the row and column of the cell over which he is currently hovering (or possibly the entire row and column, forming a sort of crosshair; I am as yet undecided). I reckon I can code that in OnDrawCell.
I had thought to use OnMouseMove and cehck if Dragging then, but ...
My problem is that when I am dragging the OnMouseMove event never gets called.
Is there any other way to know when the cursor is hovering over a strigngrid during a drag operation?
The OnDragOver event is specifically designed for doing this; it's called automatically, and provides the X and Y coordinates where the mouse pointer is located. There's a code sample available at that link location that demonstrates using it as well - it's for a TListBox, but the principle is the same.
procedure TForm1.FormCreate(Sender: TObject);
begin
ListBox1.Items.Add('Not');
ListBox1.Items.Add('In');
ListBox1.Items.Add('Alphabetical');
ListBox1.Items.Add('Order');
end;
// This OnDragOver event handler allows the list box to
// accept a dropped label.
procedure TForm1.ListBox1DragOver(Sender, Source: TObject; X, Y: Integer;
State: TDragState; var Accept: Boolean);
begin
Accept := Source is TLabel;
end;
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.