以编程方式在 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()
处理程序中,您正在设置特定项目的 Selected
和 Focused
属性,但您需要在 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)
(*) 当然,您需要启用双缓冲以避免所有可怕的视觉故障。
我在 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()
处理程序中,您正在设置特定项目的 Selected
和 Focused
属性,但您需要在 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)
(*) 当然,您需要启用双缓冲以避免所有可怕的视觉故障。