I need to enable or disable a button depending on whether at least a row is selected in the list or not.
Below is the code to reproduce this issue. The list is populated using the OnData event and it allows multiple rows to be selected.
I thought that I could use OnSelectItem to detect when the user changes the selection and then use the TListView SelCount function to detect the number of selected rows.
The problem is that SelCount returns 0 when the user selects multiple rows. This works fine if the list is populated manually (i.e. not through the OnData event).
Any ideas?
Thanks
Update: using the OnChange event instead seems to do the trick. Still it would be interesting to understand why SelCount returns 0 when multiple rows are selected (from within the SelectItem event).
Another Update: I posted a test project: https://dl.dropboxusercontent.com/u/35370420/TestListView2.zip as well as a screenshot:
To reproduce this issue run the app, select Item1, then SHIFT+Click on Item2. The button is disabled. My intention was to enable the button dynamically as long as there is at least one item selected in the list. If there is no selected item the button is disabled.
PAS file:
unit MainUnit;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ComCtrls, Vcl.StdCtrls;
type
TForm3 = class(TForm)
ListView1: TListView;
Button1: TButton;
procedure FormCreate(Sender: TObject);
procedure ListView1Data(Sender: TObject; Item: TListItem);
procedure ListView1SelectItem(Sender: TObject; Item: TListItem; Selected: Boolean);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form3: TForm3;
implementation
{$R *.dfm}
procedure TForm3.FormCreate(Sender: TObject);
begin
ListView1.Items.Count := 5;
end;
procedure TForm3.ListView1Data(Sender: TObject; Item: TListItem);
begin
Item.Caption := String.Format('Item%d', [Item.Index]);
end;
procedure TForm3.ListView1SelectItem(Sender: TObject; Item: TListItem; Selected: Boolean);
begin
Button1.Enabled := ListView1.SelCount > 0;
OutputDebugString(pchar(String.Format('SelCount = %d', [ListView1.SelCount])));
end;
end.
Form:
object Form3: TForm3
Left = 0
Top = 0
Caption = 'Form3'
ClientHeight = 600
ClientWidth = 952
Color = clBtnFace
DoubleBuffered = True
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
OnCreate = FormCreate
PixelsPerInch = 96
TextHeight = 13
object ListView1: TListView
Left = 168
Top = 160
Width = 250
Height = 150
Columns = <
item
AutoSize = True
Caption = 'Test'
end>
HideSelection = False
MultiSelect = True
OwnerData = True
TabOrder = 0
ViewStyle = vsReport
OnData = ListView1Data
OnSelectItem = ListView1SelectItem
end
object Button1: TButton
Left = 168
Top = 120
Width = 75
Height = 25
Caption = 'Some Action'
Enabled = False
TabOrder = 1
end
end
The root issue is that when you SHIFT+Click multiple items, you will NOT get any OnSelectItem
events for the items that have become selected. The SHIFT+Click causes all list view items to be unselected first, triggering a single OnSelectItem
event with Item=nil
and Selected=False
, before the new items then become selected. At the time of that event, TListView.SelCount
really is 0, so you disable your button, but then there are no further OnSelectItem
events to tell you that new items have been selected, so you do not check SelCount
again to re-enable the button.
The OnSelectItem
event is triggered in reply to the LVN_ITEMCHANGED
notification when a single item changes state between selected and unselected, or when ALL items in the entire ListView change to the same selected/unselected state. However, in virtual mode, when multiple consecutive items change to the same state at the same time, Windows can instead send a single LVN_ODSTATECHANGED
notification for that range of items. TListLiew
does not trigger OnSelectItem
when it receives LVN_ODSTATECHANGED
, it triggers OnDataStateChange
instead, eg:
procedure TForm3.ListView1DataStateChange(Sender: TObject; StartIndex, EndIndex: Integer; OldState, NewState: TItemStates);
begin
if (NewState * [isSelected]) <> (OldState * [isSelected]) then
Button1.Enabled := ListView1.SelCount > 0;
end;
So you need to use both OnSelectItem
and OnDataStateChange
to handle all possible select/unselect state changes.
The best solution is to not enable/disable the TButton
manually on individual item state changes. Drop a TActionManager
on the Form, create a new TAction
and assign it to the TButton.Action
property, and then use the TAction.OnUpdate
event to enable/disable the TAction
based on the current TListView.SelCount
, eg:
procedure TForm3.MyActionUpdate(Sender: TObject);
begin
MyAction.Enabled := ListView1.SelCount > 0;
end;
That will automatically enable/disable the associated TButton
every time the main message queue goes idle, including after ListView notification messages have been processed. This way, you can keep the TButton
updated no matter what combination of input is used to select/unselect ListView items.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With