以编程方式在 Delphi ListView 中选择一行后,使用 shift 箭头进行多项选择

Multiple selection using shift arrow broken after programmatically selecting a line in Delphi ListView

我在 Delphi 中同时使用所有者绘制和数据列表视图,如果我 select 在第一次以编程方式更改 select 后立即使用 shift 箭头,我注意到一个奇怪的问题编辑 selection.

考虑以下 window 我试图用最少的代码显示问题的地方:

这里是复制问题的最小 Delphi 代码:

unit Main;

//--------------------------------------------------------------------------------------------------
//    I N T E R F A C E
//--------------------------------------------------------------------------------------------------

interface uses Classes,
               ComCtrls,
               Controls,
               Dialogs,
               ExtCtrls,
               Forms,
               Graphics,
               Messages,
               StdCtrls,
               SysUtils,
               Variants,
               Windows;

//--------------------------------------------------------------------------------------------------
//    T Y P E      D E F I N I T I O N S
//--------------------------------------------------------------------------------------------------

type TMainForm = class(TForm)

    listView             : TListView;

    bottomPanel          : TPanel;

        position10Button : TButton;

    procedure FormCreate(
                  sender : TObject);

    //----------------------------------------------------------------------------------------------
    //    LIST VIEW EVENT HANDLERS
    //----------------------------------------------------------------------------------------------

    procedure ListViewData(
                  sender : TObject;
                  item   : TListItem);

    procedure ListViewDrawItem(
                  sender : TCustomListView;
                  item   : TListItem;
                  rect   : TRect;
                  state  : TOwnerDrawState);

    //----------------------------------------------------------------------------------------------
    //    POSITION BUTTON HANDLER
    //----------------------------------------------------------------------------------------------

    procedure Position10ButtonClick(
                  sender : TObject);

private

    //----------------------------------------------------------------------------------------------
    //    WINDOWS MESSAGE HANDLERS
    //----------------------------------------------------------------------------------------------

    procedure WMMeasureItem(
                  var msg : TWMMeasureItem);    message WM_MEASUREITEM;

private

    //----------------------------------------------------------------------------------------------
    //    DRAWING
    //----------------------------------------------------------------------------------------------

    procedure DrawHighlightRect(
                  canvas : TCanvas;
                  rect   : TRect;
                  color  : TColor);

end;

//--------------------------------------------------------------------------------------------------
//    G L O B A L     V A R I A B L E S
//--------------------------------------------------------------------------------------------------

var MainForm : TMainForm;

//--------------------------------------------------------------------------------------------------
//    I M P L E M E N T A T I O N
//--------------------------------------------------------------------------------------------------

implementation uses CommCtrl;

{$R *.dfm}

//--------------------------------------------------------------------------------------------------
//    F O R M     C R E A T E
//--------------------------------------------------------------------------------------------------

procedure TMainForm.FormCreate(
                        sender : TObject);
begin
  //  Set double buffering for listview.

  listView.doubleBuffered := TRUE;

  //  Set listview count: 20 lines.

  listView.items.count := 20;

  //  Set focus on listview.

  WINDOWS.SetFocus(
              listView.handle);
end;

//--------------------------------------------------------------------------------------------------
//    FORM CONTROLS EVENT HANDLERS
//--------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
//    LIST VIEW EVENT HANDLERS
//--------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
//    L I S T V I E W     D A T A
//--------------------------------------------------------------------------------------------------

procedure TMainForm.ListViewData(
                        sender : TObject;
                        item   : TListItem);

begin
  if item = NIL then EXIT;

  item.caption := SYSUTILS.IntToStr(item.index);
end;

//--------------------------------------------------------------------------------------------------
//    L I S T V I E W     D R A W     I T E M
//--------------------------------------------------------------------------------------------------

procedure TMainForm.ListViewDrawItem(
                        sender : TCustomListView;
                        item   : TListItem;
                        rect   : TRect;
                        state  : TOwnerDrawState);

  const TEXT_MARGIN = 7;

  var drawRect : TRect;

begin
  //  Draw focus rectangle for selected item.

  if item.selected then
    begin
      drawRect := rect;

      Inc( drawRect.top,   1);
      Dec( drawRect.bottom,1);

      DrawHighlightRect(
          sender.canvas,
          drawRect,
          clBlack);
    end;

  //  Prepare brush to draw text.

  sender.canvas.brush.style := bsClear;

  //  Draw text.

  drawRect       := rect;
  drawRect.left  := TEXT_MARGIN;

  WINDOWS.DrawText(
              sender.canvas.handle,
              PCHAR(item.caption),
              Length( item.caption),
              drawRect,
              DT_SINGLELINE or
              DT_LEFT       or
              DT_VCENTER);
end;

//--------------------------------------------------------------------------------------------------
//    P O S I T I O N     1 0     B U T T O N     C L I C K
//--------------------------------------------------------------------------------------------------

procedure TMainForm.Position10ButtonClick(
                        sender : TObject);
begin
  WINDOWS.SetFocus(
              listView.handle);

  //  Unselect all.

  listView.ClearSelection;

  //  Select and focus line 10.

  listview.items[10].selected := TRUE;
  listview.items[10].focused  := TRUE;
end;

//--------------------------------------------------------------------------------------------------
//    WINDOWS MESSAGE HANDLERS
//--------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
//    W M     M E A S U R E     I T E M
//--------------------------------------------------------------------------------------------------

procedure TMainForm.WMMeasureItem(
                        var msg : TWMMeasureItem);
begin
  inherited;

  //  Set height of list view items.

  if msg.IDCtl = listView.handle then msg.measureItemStruct^.itemHeight := 25;
end;

//--------------------------------------------------------------------------------------------------
//    D R A W     H I G H L I G H T     R E C T
//--------------------------------------------------------------------------------------------------

procedure TMainForm.DrawHighlightRect(
                        canvas : TCanvas;
                        rect   : TRect;
                        color  : TColor);

  var topLeft              : TPoint;
  var topRight             : TPoint;
  var bottomRight          : TPoint;
  var bottomLeft           : TPoint;

begin
  //  Prepare pen.

  canvas.pen.style := psSolid;
  canvas.pen.width := 1;
  canvas.pen.mode  := pmCopy;

  //  Compute outer rectangle points.

  topLeft.x     := rect.left;
  topLeft.y     := rect.top;

  topRight.x    := rect.right;
  topRight.y    := rect.top;

  bottomRight.x := rect.right;
  bottomRight.y := rect.bottom;

  bottomLeft.x  := rect.left;
  bottomLeft.y  := rect.bottom;

  //  Draw rectangle.

  canvas.pen.color := color;

  canvas.PolyLine( [ topLeft, topRight, bottomRight, bottomLeft, topLeft]);

  //  Compute inner rectangle points.

  topLeft.x     := rect.left   + 1;
  topLeft.y     := rect.top    + 1;

  topRight.x    := rect.right  - 1;
  topRight.y    := rect.top    + 1;

  bottomRight.x := rect.right  - 1;
  bottomRight.y := rect.bottom - 1;

  bottomLeft.x  := rect.left   + 1;
  bottomLeft.y  := rect.bottom - 1;

  //  Draw rectangle.

  canvas.pen.color := color;

  canvas.PolyLine( [ topLeft, topRight, bottomRight, bottomLeft, topLeft]);
end;

//--------------------------------------------------------------------------------------------------

end.

[编辑] 正如 Andreas Rejbrand 所指出的,非所有者绘制非所有者数据列表视图也存在此问题。

unit Main;

//--------------------------------------------------------------------------------------------------
//    I N T E R F A C E
//--------------------------------------------------------------------------------------------------


interface uses Classes,
               ComCtrls,
               Controls,
               Dialogs,
               ExtCtrls,
               Forms,
               Graphics,
               Messages,
               StdCtrls,
               SysUtils,
               Variants,
               Windows;

//--------------------------------------------------------------------------------------------------
//    T Y P E      D E F I N I T I O N S
//--------------------------------------------------------------------------------------------------

type TMainForm = class(TForm)

    listView             : TListView;

    bottomPanel          : TPanel;

        position10Button : TButton;

    procedure FormCreate(
                  sender : TObject);

    //----------------------------------------------------------------------------------------------
    //    POSITION BUTTON HANDLER
    //----------------------------------------------------------------------------------------------

    procedure Position10ButtonClick(
                  sender : TObject);

end;

//--------------------------------------------------------------------------------------------------
//    G L O B A L     V A R I A B L E S
//--------------------------------------------------------------------------------------------------

var MainForm : TMainForm;

//--------------------------------------------------------------------------------------------------
//    I M P L E M E N T A T I O N
//--------------------------------------------------------------------------------------------------

implementation uses CommCtrl;

{$R *.dfm}

//--------------------------------------------------------------------------------------------------
//    F O R M     C R E A T E
//--------------------------------------------------------------------------------------------------

procedure TMainForm.FormCreate(
                        sender : TObject);

  var index   : integer;
  var newItem : TListItem;

begin
  //  Set double buffering for listview.

  listView.doubleBuffered := TRUE;

  for index := 0 to 19 do
    begin
      newItem := listview.items.Add;
      newItem.caption := SYSUTILS.IntToStr( index);
    end;

  //  Set focus on listview.

  WINDOWS.SetFocus(
              listView.handle);
end;

//--------------------------------------------------------------------------------------------------
//    FORM CONTROLS EVENT HANDLERS
//--------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
//    P O S I T I O N     1 0     B U T T O N     C L I C K
//--------------------------------------------------------------------------------------------------

procedure TMainForm.Position10ButtonClick(
                        sender : TObject);
begin
  WINDOWS.SetFocus(
              listView.handle);

  //  Unselect all.

  listView.ClearSelection;

  //  Select and focus line 10.

  listview.items[10].selected := TRUE;
  listview.items[10].focused  := TRUE;
end;

//--------------------------------------------------------------------------------------------------

end.

在您的 ListViewDrawItem() 处理程序中,if item.selected then 应该是 if odSelected in state then

而且,在您的 Position10ButtonClick() 处理程序中,您正在设置特定项目的 SelectedFocused 属性,但您需要在 OnData 中进行这些分配事件,而不是你目前没有做的事情。您需要将选择详细信息保存在旁边的某个位置,然后在 OnData 事件中应用该信息。您还需要处理 OnSelectItem 事件并保存它在用户更改当前选择时提供给您的详细信息。

尝试这样的事情:

type
  MyListItemInfo = record
    Caption: String;
    Selected: Boolean;
    Focused: Boolean;
  end;

private
  MyListItems: array of MyListItemInfo;

procedure TMainForm.FormCreate(
                        Sender : TObject);
var
  I: Integer;
begin
  //  Set double buffering for listview.

  ListView.DoubleBuffered := True;

  //  Set listview count: 20 lines.

  SetLength(MyListItems, 20);

  for I := Low(MyListItems) to High(MyListItems) do
  begin
    MyListItems[I].Caption := SysUtils.IntToStr(I);
    MyListItems[I].Selected := False;
    MyListItems[I].Focused := False;
  end;

  ListView.Items.Count := Length(MyListItems);

  //  Set focus on listview.

  ListView.SetFocus;
end;

procedure TMainForm.ListViewSelectItem(
                        Sender  : TObject;
                        Item    : TListItem;
                        Selected: Boolean);
var
  I: Integer;
begin
  if Item <> nil then
  begin
    MyListItems[Item.Index].Selected := Selected;
    ListView.UpdateItems(Item.Index, Item.Index);
  end else
  begin
    for I := 0 to listView.Items.Count-1 do
      MyListItems[I].Selected := Selected;
    ListView.Invalidate;
  end;
end;

procedure TMainForm.ListViewData(
                        Sender : TObject;
                        Item   : TListItem);

begin
  Item.Caption := MyListItems[Item.Index].Caption;
  Item.Selected := MyListItems[Item.Index].Selected;
  item.Focused := MyListItems[Item.Index].Focused;
end;

procedure TMainForm.Position10ButtonClick(
                        Sender : TObject);
var
  I: Integer;
begin
  ListView.SetFocus;

  //  Unselect all.

  ListView.ClearSelection;

  for I := Low(MyListItems) to High(MyListItems) do
  begin
    MyListItems[I].Selected := False;
    MyListItems[I].Focused := False;
  end;

  //  Select and focus line 10.

  MyListItems[10].Selected := True;
  MyListItems[10].Focused := True;

  ListView.Invalidate;
end;

请注意,您 Q 中的 "minimal" 示例包含 很多 不必要的代码。您可以在没有所有者图纸和所有者数据的情况下重现此问题。只需在表单上放置一个新的 TListView 控件,在 IDE 中添加一些项目,然后将 MultiSelect 设置为 True。 (*)

现在,诀窍是使用 LVM_SETSELECTIONMARK message, or the ListView_SetSelectionMark 函数(在 Delphi 中):

ListView1.ClearSelection;
ListView1.ItemIndex := 10;
ListView_SetSelectionMark(ListView1.Handle, 10)

(*) 当然,您需要启用双缓冲以避免所有可怕的视觉故障。