You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Delphi中OwnerData=True的TListView如何实现复选框?

解决方案:OwnerData=True的TListView实现复选框功能

在Delphi VCL的虚拟模式(OwnerData=True)下,TListView的Checkboxes属性无法直接生效——虚拟模式下控件不会自动处理复选框的绘制与状态管理。以下是两种可靠的实现方式:

方法一:自绘复选框(推荐)

通过自定义绘制事件手动绘制系统风格复选框,同时维护数据集合的选中状态,并处理鼠标点击切换逻辑。

完整代码示例

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ComCtrls;

type
  TMyData = class
  public
    FChecked: Boolean;
    FCaption: string;
    constructor Create(const ACaption: string; AChecked: Boolean = False);
  end;

  TForm1 = class(TForm)
    ListView1: TListView;
    procedure FormCreate(Sender: TObject);
    procedure ListView1Data(Sender: TObject; Item: TListItem);
    procedure ListView1CustomDrawItem(Sender: TCustomListView; Item: TListItem;
      State: TCustomDrawState; var DefaultDraw: Boolean);
    procedure ListView1MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure FormDestroy(Sender: TObject);
  private
    MyData: TList<TMyData>;
    function GetCheckBoxRect(Item: TListItem): TRect;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

{ TMyData }

constructor TMyData.Create(const ACaption: string; AChecked: Boolean);
begin
  FCaption := ACaption;
  FChecked := AChecked;
end;

{ TForm1 }

procedure TForm1.FormCreate(Sender: TObject);
var
  I: Integer;
begin
  ListView1.ViewStyle := vsReport;
  ListView1.OwnerData := True;
  // Report视图必须至少添加一列,否则无法正常显示行
  ListView1.Columns.Add.Caption := '项目';
  ListView1.Columns[0].Width := 200;

  // 初始化测试数据
  MyData := TList<TMyData>.Create;
  for I := 1 to 10 do
    MyData.Add(TMyData.Create('Item ' + IntToStr(I), I mod 2 = 0));
end;

procedure TForm1.FormDestroy(Sender: TObject);
var
  Data: TMyData;
begin
  for Data in MyData do
    Data.Free;
  MyData.Free;
end;

procedure TForm1.ListView1Data(Sender: TObject; Item: TListItem);
begin
  Item.Caption := MyData[Item.Index].FCaption;
  // 记录选中状态,虚拟模式下控件不会自动绘制复选框
  Item.Checked := MyData[Item.Index].FChecked;
end;

// 获取复选框的绘制区域
function TForm1.GetCheckBoxRect(Item: TListItem): TRect;
var
  ItemRect: TRect;
begin
  ItemRect := Item.DisplayRect(drBounds);
  Result.Left := ItemRect.Left + 4;
  Result.Top := ItemRect.Top + (ItemRect.Bottom - ItemRect.Top - GetSystemMetrics(SM_CXMENUCHECK)) div 2;
  Result.Right := Result.Left + GetSystemMetrics(SM_CXMENUCHECK);
  Result.Bottom := Result.Top + GetSystemMetrics(SM_CYMENUCHECK);
end;

// 自定义绘制复选框
procedure TForm1.ListView1CustomDrawItem(Sender: TCustomListView; Item: TListItem;
  State: TCustomDrawState; var DefaultDraw: Boolean);
var
  CheckRect: TRect;
begin
  // 保留默认绘制逻辑(仅替换复选框部分)
  DefaultDraw := True;

  CheckRect := GetCheckBoxRect(Item);
  // 根据状态绘制系统风格复选框
  if Item.Checked then
    DrawFrameControl(ListView1.Canvas.Handle, CheckRect, DFC_BUTTON, DFCS_BUTTONCHECK or DFCS_CHECKED)
  else
    DrawFrameControl(ListView1.Canvas.Handle, CheckRect, DFC_BUTTON, DFCS_BUTTONCHECK);
end;

// 处理鼠标点击切换复选状态
procedure TForm1.ListView1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  HitInfo: THitTests;
  Item: TListItem;
  CheckRect: TRect;
begin
  if Button <> mbLeft then Exit;

  HitInfo := ListView1.GetHitTestInfoAt(X, Y);
  // 判断是否点击复选框区域(模拟htOnCheckBox)
  if HitInfo = [htOnIcon] then
  begin
    Item := ListView1.GetItemAt(X, Y);
    if Assigned(Item) then
    begin
      // 切换数据集合的选中状态
      MyData[Item.Index].FChecked := not MyData[Item.Index].FChecked;
      // 更新Item状态并局部重绘
      Item.Checked := MyData[Item.Index].FChecked;
      ListView1.InvalidateRect(GetCheckBoxRect(Item));
    end;
  end;
end;

end.

关键说明

  • 必须添加列:Report视图模式下,ListView至少需要一列才能正确渲染行内容,否则会出现绘制异常。
  • 系统风格绘制:通过DrawFrameControl API绘制原生复选框,保证与系统视觉风格一致。
  • 局部重绘优化:切换状态后仅重绘复选框区域,避免整列表闪烁。

方法二:使用Windows API设置复选状态

若不想自定义绘制,可通过发送LVM_SETITEMSTATE消息强制设置复选状态,需确保已添加列并正确设置控件属性。

核心代码片段

在ListView1Data事件中替换原有逻辑:

procedure TForm1.ListView1Data(Sender: TObject; Item: TListItem);
var
  ItemState: TLVItem;
begin
  Item.Caption := MyData[Item.Index].FCaption;
  
  // 通过API强制设置复选状态
  ZeroMemory(@ItemState, SizeOf(ItemState));
  ItemState.mask := LVIF_STATE;
  ItemState.iItem := Item.Index;
  if MyData[Item.Index].FChecked then
    ItemState.state := LVIS_SELECTED or LVIS_CHECKED
  else
    ItemState.state := 0;
  ListView1.Perform(LVM_SETITEMSTATE, Item.Index, LPARAM(@ItemState));
end;

这种方法依赖系统原生处理,但部分Delphi版本或特殊场景下可能存在兼容性问题,自绘方法稳定性更高。

内容的提问来源于stack exchange,提问作者Xel Naga

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 03:50:09