Is there a way to implement a toggle-like selection mode for a TListView?
Clicking on an item, make it selected, click on another item make it selected as well. Clicking on the previous item, make it unselected and keeping the second one selected.
This does not seem to work:
procedure TForm1.ListView1Changing(Sender: TObject; Item: TListItem; Change: TItemChange; var AllowChange: Boolean);
begin
ListView1.OnChanging := nil;
try
if Change = ctState then begin
AllowChange := False;
if NOT Item.Selected then begin
Item.Selected := True;
end else begin
Item.Selected := False;
end;
end;
finally
ListView1.OnChanging := ListView1Changing;
end;
end;
I did not give up that easily, the following code seems to work:
Set MultiSelect to True, tested with ViewStyle = vsReport.
var
LastItem: TListItem;
HotItem: TListItem;
PreviousItemWasSelected: Boolean;
ShiftDown: Boolean;
CtrlDown: Boolean;
procedure TForm1.ListView1Changing(Sender: TObject; Item: TListItem; Change: TItemChange; var AllowChange: Boolean);
var
LVChangingEvent: TLVChangingEvent;
begin
if CtrlDown then begin
Exit;
end;
LVChangingEvent := (Sender as TListView).OnChanging;
(Sender as TListView).OnChanging := nil;
try
if Change = ctState then begin
if ShiftDown then begin
if Item.Selected then begin
AllowChange := False;
Exit;
end;
end;
if LastItem = Item then begin
LastItem := nil;
Exit;
end;
if Item.Focused then begin
Item.Selected := NOT Item.Selected;
LastItem := Item;
Exit;
end;
AllowChange := False;
if NOT ShiftDown then begin
if HotItem <> Item then begin
Exit;
end;
end;
if NOT Item.Selected then begin
Item.Selected := True;
Item.Focused := True;
PreviousItemWasSelected := True;
end else if Item.Selected then begin
Item.Selected := False;
PreviousItemWasSelected := False;
end;
LastItem := Item;
end;
finally
(Sender as TListView).OnChanging := LVChangingEvent;
end;
end;
procedure TForm1.ListView1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
var
ListView: TListView;
LVChangingEvent: TLVChangingEvent;
CurrentNextSelected: Boolean;
begin
if ssShift in Shift then begin
ShiftDown := True;
end;
if ssCtrl in Shift then begin
CtrlDown := True;
end;
ListView := Sender as TListView;
if ListView.IsEditing then begin
Exit;
end;
if Key = VK_DOWN then begin
if NOT Assigned(ListView.ItemFocused) then begin
Exit;
end;
if ListView.ItemFocused.Index = ListView.Items.Count - 1 then begin
Exit;
end;
LVChangingEvent := ListView.OnChanging;
ListView.OnChanging := nil;
try
CurrentNextSelected := ListView.Items[ListView.ItemFocused.Index + 1].Selected;
ListView.ItemFocused := ListView.Items[ListView.ItemFocused.Index + 1];
ListView.ItemFocused.Selected := True;
ListView.Items[ListView.ItemFocused.Index - 1].Selected := PreviousItemWasSelected;
PreviousItemWasSelected := CurrentNextSelected;
Key := 0;
finally
ListView.OnChanging := LVChangingEvent;
end;
end;
if Key = VK_UP then begin
if NOT Assigned(ListView.ItemFocused) then begin
Exit;
end;
if ListView.ItemFocused.Index = 0 then begin
Exit;
end;
LVChangingEvent := ListView.OnChanging;
ListView.OnChanging := nil;
try
CurrentNextSelected := ListView.Items[ListView.ItemFocused.Index - 1].Selected;
ListView.ItemFocused := ListView.Items[ListView.ItemFocused.Index - 1];
ListView.ItemFocused.Selected := True;
ListView.Items[ListView.ItemFocused.Index + 1].Selected := PreviousItemWasSelected;
PreviousItemWasSelected := CurrentNextSelected;
Key := 0;
finally
ListView.OnChanging := LVChangingEvent;
end;
end;
end;
procedure TForm1.ListView1KeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
ShiftDown := ssShift in Shift;
CtrlDown := ssCtrl in Shift;
end;
procedure TForm1.ListView1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
begin
HotItem := (Sender as TListView).GetItemAt(X, Y);
end;
But it seems there is a little issue that there is some kind of delay, maybe for the edit caption functionality, that quickly toggleing the same item is not possible. To toggle a specific item, a couple of 100 ms is needed to wait before the toggle.
If somebody has a nicer solution please share it! Thank you!