Firemonkey ListView OnMouseDown event - delphi

in VCL we can use this ( To get a ListItem ):
TForm.ListView1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
li: TListItem;
lv : TListView;
begin
lv := TListView(Sender);
li := lv.GetItemAt(X, Y); // we get our ListItem
end;
But with the Firemonkey ListView i don't see any GetItemAt function .
So please how can we get a ListItem by using OnMouseDown event of the ListView ( in firemonkey ) .
many thanks .

There's no ready made solution to find the item with OnMouseDown. I suggest you use OnItemClick instead:
procedure TForm1.ListView1ItemClick(const Sender: TObject; const AItem: TListViewItem);
which gives you a reference to the item

As Tom said, there's no ready made solution. But if you can live with FMX source changes you can insert that into FMX.ListView
function TListViewBase.GetItemAt(P: TPointF):TListItem;
var
ItemAt: Integer;
begin
ItemAt := FindItemAbsoluteAt(Round(FScrollViewPos + P.Y - (LocalRect.Top + FSideSpace)));
if (ItemAt >= 0) and (ItemAt < Adapter.Count) then
Result := Adapter[ItemAt]
else
Result := nil
end;

I find the TListViewItem on rigth click by searching if the current position of the mouse is inside the item rect.
I use only Y because all my itmes are only text but you can extend the complexity by using the X value too.
In this example i set the selected item with the rightclick.
procedure TForm2.lwPlayListMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Single);
var
I: Integer;
rct: TRectF;
begin
if ssRight in Shift then
for I := 0 to lwPlayList.Items.Count-1 do
Begin
rct := lwPlayList.GetItemRect(I);
if ( rct.Bottom >= Y) and (Y >= rct.Top ) then
Begin
lwPlayList.ItemIndex := I;
lwPlayList.Selected := lwPlayList.Items[I];
End;
End;
end;

Normally, ListView.ItemIndex property is correct, so you can rely on it. But you can write OnItemClick event, to simply grab AItem.Index in a field and then use it in your double click event.
procedure TForm1.ListView1ItemClick(const Sender: TObject;
const AItem: TListViewItem);
begin
FClickedIndex := AItem.Index;
end;
procedure TForm1.ListView1DblClick(Sender: TObject);
var
Index: Integer;
SelectedItemText: string;
begin
// it is correct as well
//Index := ListView1.ItemIndex;
Index := FClickedIndex;
SelectedItemText := ListView1.Items[Index].Objects.TextObject.Text;
// do something with the index, item or text.
end;

Related

How to select nothing by clicking the empty background?

I have a Virtual Treeview (e.g. TVirtualStringTree).
The user can select a row
but it would be nice if they could also do the intuitiave thing of clicking "nowhere" to select no row
Note: Of course multiselect is off; because they can only select zero or one items
MCRE:
procedure TForm6.FormCreate(Sender: TObject);
var
vst: TVirtualStringTree;
begin
vst := VirtualStringTree1;
vst.RootNodeCount := 5;
vst.TreeOptions.SelectionOptions := vst.TreeOptions.SelectionOptions + [toFullRowSelect];
vst.Header.Options := vst.Header.Options + [hoVisible];
vst.Header.Columns.Add;
vst.Header.Columns.Add;
vst.Header.Columns.Add;
vst.Header.Columns.Add;
vst.Header.Columns.Add;
end;
This should work out-of-the-box if toAlwaysSelectNode is not set and toMultiSelect is set in TreeOption.SelectionOptions. Tested wit latest source.
In other cases simply call ClearSelection():
procedure TVisibilityForm.VST2MouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
if TBaseVirtualTree(Sender).GetNodeAt(Point(X, Y)) = nil then
TBaseVirtualTree(Sender).ClearSelection();
end;
This procedure in OnMouseDown should work regardless of the settings, you just need toRightClickSelect in TreeOptions.SelectionsOptions for right click selection, otherwise it doesn't work properly.
procedure VSTMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
if Button in [mbLeft, mbRight] then
VST.FocusedNode := VST.GetNodeAt(X, Y);
if Assigned(VST.FocusedNode) then
VST.TreeOptions.PaintOptions := VST.TreeOptions.PaintOptions - [toAlwaysHideSelection]
else
VST.TreeOptions.PaintOptions := VST.TreeOptions.PaintOptions + [toAlwaysHideSelection];
end;

OnMouseMove in TDrawGrid on touch screen

Program use on touch screen Windows-tablet. Have a grid, where we need to select some cells in a row: target cursor over cell, click left button of mouse, holding it, pull the mouse to the side, and then release left button of mouse (at the same time code in OnDrawCell is drawing cells).
By mouse or by touchpad of notebook works very well. But on tablet's touchscreen doesn't work at all.
I use TDrawGrid and OnMouseDown, OnMouseMove, OnMouseUp events.
In Shift use all options: ssLeft, ssTouch, ssPen. Look at full code:
procedure TfmMain.GridMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
var
ACol, ARow: Integer;
ARect: TRect;
begin
(Sender as TDrawGrid).MouseToCell(X, Y, ACol, ARow);
ARect := (Sender as TDrawGrid).CellRect(ACol, ARow);
pmIsLeft := X<(ARect.Left+((ARect.Right-ARect.Left) div 2));
pmCol := ACol;
pmRow := ARow;
if (ssLeft in Shift) or (ssTouch in Shift) or (ssPen in Shift) then
begin
ChooseDaysInGridRowIndex := ARow;
SetLength(ChooseDays, 0);
end;
end;
procedure TfmMain.GridMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
var
ACol, ARow: Integer;
begin
if (ssLeft in Shift) or (ssTouch in Shift) or (ssPen in Shift) then
begin
(Sender as TDrawGrid).MouseToCell(X, Y, ACol, ARow);
if (ChooseDaysInGridRowIndex>0) and
(ChooseDaysInGridRowIndex<=(Sender as TDrawGrid).RowCount-1) and
((PagesDays[pagesBuildings.ActivePageIndex][ARow, ACol][0].ReservID<=0) or
((Length(ChooseDays)=0) and ((PagesDays[pagesBuildings.ActivePageIndex][ARow, ACol][1].ReservID<=0)))) then
begin
SetLength(ChooseDays, Length(ChooseDays)+1);
ChooseDays[High(ChooseDays)] := Point(ACol, ChooseDaysInGridRowIndex);
InvalidateRect((Sender as TDrawGrid).Handle,
(Sender as TDrawGrid).CellRect(ACol, ARow),
True);
end;
ChooseDaysInGrid := True;
end;
end;
procedure TfmMain.GridMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
var
ACol, ARow: Integer;
begin
if ChooseDaysInGrid then
begin
(Sender as TDrawGrid).MouseToCell(X, Y, ACol, ARow);
ChooseDaysInGrid := False;
fmGuestArrival.roomID := GridRowTitles[(Sender as TDrawGrid).Tag] [ARow].RoomID;
// Integer((Sender as TDrawGrid).Objects[0, ARow]);
if Length(ChooseDays)>0 then
begin
fmGuestArrival.dateArrival.DateTime :=
IncDay(StartDatePeriod, ChooseDays[0].X-1);
if Length(ChooseDays)>1 then
begin
fmGuestArrival.dateDeparture.DateTime :=
IncDay(StartDatePeriod, ChooseDays[High(ChooseDays)].X - 1);
end
else
begin
fmGuestArrival.dateDeparture.DateTime :=
IncDay(fmGuestArrival.dateArrival.DateTime, 1);
end;
end;
fmGuestArrival.IsEditing := False;
fmGuestArrival.cbStatus.ItemIndex := 0;
fmGuestArrival.ShowModal;
end;
end;
Add Gesturing support by adding a TGestureManager (GestureManager1). Then assign GestureManager1 to the Touch.GestureManager property of the TDrawGrid. Open Touch.Gestures.Standard property of the TDrawGrid and select the gestures you want to be notified of. Create an OnGesture event and add code as needed.
The details are documented by Embarcadero

How to get the text under the cursor in a TDbGrid

Screen width is just not enough to display some text fields. I don't know how to auto-wrap them and I doubt that it can be easily done.
So, I thought that I would do something like
procedure TForm1.FormMouseMove(Sender: TObject;
Shift: TShiftState; X,Y: Integer);
var column, row : Integer;
begin
myDbGrid.MouseToCell(X, Y, column, row);
myDbGrid.Hinst := myDbGrid.Cells(column, row); // <==== ooops
end;
or, maybe do it in OnShowHint and get the mouse coords & translate them to column & row (more efficient)
but, of course, TDbGrid doesn't have Cells. Any idea how I can set the hint for the control as the user moves the mouse over the "cells" of the grid?
Use this code:
type
THackGrid = class(TDBGrid);
procedure TForm1.DBGrid1MouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
var
Cell: TGridCoord;
ActRec: Integer;
begin
Cell := DBGrid1.MouseCoord(X, Y);
if dgIndicator in DBGrid1.Options then
Dec(Cell.X);
if dgTitles in DBGrid1.Options then
Dec(Cell.Y);
if THackGrid(DBGrid1).DataLink.Active and (Cell.X >= 0) and
(Cell.Y >= 0) then
begin
ActRec := THackGrid(DBGrid1).DataLink.ActiveRecord;
try
THackGrid(DBGrid1).DataLink.ActiveRecord := Cell.Y;
Caption := DBGrid1.Columns[Cell.X].Field.AsString;
finally
THackGrid(DBGrid1).DataLink.ActiveRecord := ActRec;
end;
end;
end;
This is code directly taken (albeit simplified) from a program of mine which displays as a hint one of the values of the dataset connected to the grid.
procedure TMainForm.DBGrid1MouseMove(Sender: TObject; Shift: TShiftState;
X, Y: Integer);
var
MousePos: TGridCoord; // X = Column, Y = Row
begin
MousePos:= DBGrid1.MouseCoord (X, Y);
if mousepos.X = 6 // we are over the 'tops' field
then mainform.hint:= qPeopleTops.asstring; // show for current person
end;

How to drag report in ppviewer?

Anyone know how to drag the report in TppViewer? (Delphi 7) i try to use the dagdrop event and dragover event of ppviewer but failed, anyone can help?
procedure Tfrm1.ppviewer1DragOver(Sender, Source: TObject; X,
Y: Integer; State: TDragState; var Accept: Boolean);
begin
inherited;
Accept := Source IS TppViewer;
end;
procedure Tfrm1.ppviewer1DragDrop(Sender, Source: TObject; X,
Y: Integer);
begin
inherited;
if Source is TppViewer then begin
TppViewer(Source).Left := X;
TppViewer(Source).Top := Y;
end;
end;
This answer assumes that you are trying to scroll in the report, by dragging.
TReportPreviewer is the Form
ReportViewer is the ppViewer
Dragging is a Boolean
SaveX, SaveY are Integer
procedure TReportPreviewer.ReportViewerMouseDown(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
Dragging := true;
SaveX := X;
SaveY := Y;
end;
procedure TReportPreviewer.ReportViewerMouseMove(Sender: TObject;
Shift: TShiftState; X, Y: Integer);
begin
if Dragging then
begin
if ReportViewer.ScrollBox.Visible then
ReportViewer.ScrollBox.HorzScrollBar.Position := ReportViewer.ScrollBox.HorzScrollBar.Position - (X - SaveX);
if ReportViewer.ScrollBox.Visible then
ReportViewer.ScrollBox.VertScrollBar.Position := ReportViewer.ScrollBox.VertScrollBar.Position - (Y - SaveY);
SaveX := X;
SaveY := Y;
end;
end;
procedure TReportPreviewer.ReportViewerMouseUp(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
Dragging := false;
end;
I tried using ScrollBy instead of moving the scrollbar position, but it seemed to reset for some reason.
Are you trying to drag a report file into the Viewer? if so biased on the following advice:
How to Drop Images from Windows Explorer to a TImage control
Delphi - Drag & Drop with ListView
WM_DROPFILES Message
You can achieve this by using the following code:
procedure TMainForm.FormCreate(Sender: TObject);
begin
//Tell Windows that the Report Viewer accepts files
ShellAPI.DragAcceptFiles(ppViewer1.Handle,True);
Application.OnMessage := ApplicationMessage;
end;
procedure TMainForm.ApplicationMessage(var Msg: TMsg; var Handled: Boolean);
begin
if (Msg.hwnd = ppViewer1.Handle) and (Msg.message = WM_DROPFILES) then
begin
Handled := ReportFileDrop(Msg);
end;
end;
function TMainForm.ReportFileDrop(var Msg: TMsg):Boolean ;
var
numFiles : longInt;
buffer : array[0..MAX_PATH] of char;
l_file:String;
l_filemsg:TWMDROPFILES;
begin
Result := False;
//Convert the TMsg into a TWMDROPFILES record
l_filemsg.Msg := Msg.message;
l_filemsg.Drop := Msg.wParam;
l_filemsg.Unused := Msg.lParam;
l_filemsg.Result := 0;
numFiles := DragQueryFile(l_filemsg.Drop, $FFFFFFFF, nil, 0) ;
if numFiles > 1 then
begin
ShowMessage('You can drop only one file at a time!') ;
end
else
begin
try
DragQueryFile(l_filemsg.Drop, 0, #buffer, sizeof(buffer)) ;
l_file := buffer;
//Only try and load the report if the file has the correct extension
if (Length(l_file) > 0) and (ExtractFileExt(LowerCase(l_file)) = '.rtm') then
begin
//Load the Report
Result := True;
end;
except
//Handle errors
end;
end;
end;

Scroll TTreeView while dragging over/near the edges

I have a TTreeView that can have lots of nodes, when a lot of nodes are expanded the tree uses a lot of screen space.
Now suppose I want to drag a node that is near the bottom of the TreeView to the top, I can't physically see the top part of the TreeView because the node I am selecting is at the bottom. When dragging the node to the top of the TreeView I would like the TreeView to automatically scroll with me when dragging, by default this does not seem to happen.
A perfect example of this behaviour is seen in Windows Explorer. If you try to drag a file or folder, when you hover the dragged item (node) it automatically scrolls up or down depending on cursor position.
Hope that makes sense.
PS, I already know how to drag nodes, I want the TreeView to scroll with me when dragging if hovering near the top or bottom of the TreeView.
Thanks.
This is the code I use. It will work for any TWinControl descendent: list box, tree view, list view etc.
type
TAutoScrollTimer = class(TTimer)
private
FControl: TWinControl;
FScrollCount: Integer;
procedure InitialiseTimer;
procedure Timer(Sender: TObject);
public
constructor Create(Control: TWinControl);
end;
{ TAutoScrollTimer }
constructor TAutoScrollTimer.Create(Control: TWinControl);
begin
inherited Create(Control);
FControl := Control;
InitialiseTimer;
end;
procedure TAutoScrollTimer.InitialiseTimer;
begin
FScrollCount := 0;
Interval := 250;
Enabled := True;
OnTimer := Timer;
end;
procedure TAutoScrollTimer.Timer(Sender: TObject);
procedure DoScroll;
var
WindowEdgeTolerance: Integer;
Pos: TPoint;
begin
WindowEdgeTolerance := Min(25, FControl.Height div 4);
GetCursorPos(Pos);
Pos := FControl.ScreenToClient(Pos);
if not InRange(Pos.X, 0, FControl.Width) then begin
exit;
end;
if Pos.Y<WindowEdgeTolerance then begin
SendMessage(FControl.Handle, WM_VSCROLL, SB_LINEUP, 0);
end else if Pos.Y>FControl.Height-WindowEdgeTolerance then begin
SendMessage(FControl.Handle, WM_VSCROLL, SB_LINEDOWN, 0);
end else begin
InitialiseTimer;
exit;
end;
if FScrollCount<50 then begin
inc(FScrollCount);
if FScrollCount mod 5=0 then begin
//speed up the scrolling by reducing the timer interval
Interval := MulDiv(Interval, 3, 4);
end;
end;
if Win32MajorVersion<6 then begin
//in XP we need to clear up transient "fluff"; results in flickering so only do it in XP where it is needed
FControl.Invalidate;
end;
end;
begin
if Mouse.IsDragging then begin
DoScroll;
end else begin
Free;
end;
end;
Then to use it you add an OnStartDrag event handler for the control and implement it like this:
procedure TMyForm.SomeControlStartDrag(Sender: TObject; var DragObject: TDragObject);
begin
TAutoScrollTimer.Create(Sender as TWinControl);
end;
Here's an alternative based on the fact that the selected node always automatically scrolls in view.
type
TForm1 = class(TForm)
TreeView1: TTreeView;
TreeView2: TTreeView;
procedure TreeViewDragOver(Sender, Source: TObject; X, Y: Integer;
State: TDragState; var Accept: Boolean);
procedure TreeViewEndDrag(Sender, Target: TObject; X, Y: Integer);
procedure TreeViewMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
private
FDragNode: TTreeNode;
FNodeHeight: Integer;
end;
...
procedure TForm1.TreeViewMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
with TTreeView(Sender) do
begin
FDragNode := GetNodeAt(X, Y);
if FDragNode <> nil then
begin
Selected := FDragNode;
with FDragNode.DisplayRect(False) do
FNodeHeight := Bottom - Top;
BeginDrag(False, Mouse.DragThreshold);
end;
end;
end;
procedure TForm1.TreeViewDragOver(Sender, Source: TObject; X, Y: Integer;
State: TDragState; var Accept: Boolean);
var
Pt: TPoint;
DropNode: TTreeNode;
begin
Accept := Source is TTreeView;
if Accept then
with TTreeView(Source) do
begin
if Sender <> Source then
Pt := ScreenToClient(Mouse.CursorPos)
else
Pt := Point(X, Y);
if Pt.Y < FNodeHeight then
DropNode := Selected.GetPrevVisible
else if Pt.Y > (ClientHeight - FNodeHeight) then
DropNode := Selected.GetNextVisible
else
DropNode := GetNodeAt(Pt.X, Pt.Y);
if DropNode <> nil then
Selected := DropNode;
end;
end;
procedure TForm1.TreeViewEndDrag(Sender, Target: TObject; X, Y: Integer);
var
DropNode: TTreeNode;
begin
with TTreeView(Sender) do
if Target <> nil then
begin
DropNode := Selected;
DropNode := Items.Insert(DropNode, '');
DropNode.Assign(FDragNode);
Selected := DropNode;
Items.Delete(FDragNode);
end
else
Selected := FDragNode;
end;
You may want to link the OnDragOver event handler to the parent of the TreeView too, which results in scrolling ánd dropping when the mouse is outside the TreeView. If you dó want the scrolling, but not the dropping when the mouse is outside the TreeView, then check if Target = Sender in the OnEndDrag event handler.
Just to be complete, workarounds like in the other answers are not required anymore. Later versions have an option for this:
TreeOptions.AutoOptions.toAutoScroll := True

Resources