我有3列的ListView和想要编辑的第三列,又名子项目[1]。如果我设置ListView.ReadOnly为True,它可以编辑所选项目的标题。有没有一种简单的方法做事情的子项目?我想远离加上顶部无边界的控制,做编辑。
本文地址 :CodeGo.net/463079/
-------------------------------------------------------------------------------------------------------------------------
1. 您可以使用TEDIT,一个和处理编辑的ListView的子项(在报告模式)OnClick 事件的ListView的。
试试这个范例
Const
USER_EDITLISTVIEW = WM_USER + 666;
type
TForm1 = class(TForm)
ListView1: TListView;
procedure FormCreate(Sender: TObject);
procedure ListView1Click(Sender: TObject);
private
ListViewEditor: TEdit;
LItem: TListitem;
procedure UserEditListView( Var Message: TMessage ); message USER_EDITLISTVIEW;
procedure ListViewEditorExit(Sender: TObject);
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
uses
CommCtrl;
const
EDIT_COLUMN = 2; //Index of the column to Edit
procedure TForm1.FormCreate(Sender: TObject);
Var
I : Integer;
Item : TListItem;
begin
for I := 0 to 9 do
begin
Item:=ListView1.Items.Add;
Item.Caption:=Format('%d.%d',[i,1]);
Item.SubItems.Add(Format('%d.%d',[i,2]));
Item.SubItems.Add(Format('%d.%d',[i,3]));
end;
//create the TEdit and assign the OnExit event
ListViewEditor:=TEdit.Create(Self);
ListViewEditor.Parent:=ListView1;
ListViewEditor.OnExit:=ListViewEditorExit;
ListViewEditor.Visible:=False;
end;
procedure TForm1.ListView1Click(Sender: TObject);
var
LPoint: TPoint;
LVHitTestInfo: TLVHitTestInfo;
begin
LPoint:= listview1.ScreenToClient(Mouse.CursorPos);
ZeroMemory( @LVHitTestInfo, SizeOf(LVHitTestInfo));
LVHitTestInfo.pt := LPoint;
//Check if the click was made in the column to edit
If (ListView1.perform( LVM_SUBITEMHITTEST, 0, LPARAM(@LVHitTestInfo))<>-1) and ( LVHitTestInfo.iSubItem = EDIT_COLUMN ) Then
PostMessage( self.Handle, USER_EDITLISTVIEW, LVHitTestInfo.iItem, 0 )
else
ListViewEditor.Visible:=False; //hide the TEdit
end;
procedure TForm1.ListViewEditorExit(Sender: TObject);
begin
If Assigned(LItem) Then
Begin
//assign the vslue of the TEdit to the Subitem
LItem.SubItems[ EDIT_COLUMN-1 ] := ListViewEditor.Text;
LItem := nil;
End;
end;
procedure TForm1.UserEditListView(var Message: TMessage);
var
LRect: TRect;
begin
LRect.Top := EDIT_COLUMN;
LRect.Left:= LVIR_BOUNDS;
listview1.Perform( LVM_GETSUBITEMRECT, Message.wparam, LPARAM(@LRect) );
MapWindowPoints( listview1.Handle, ListViewEditor.Parent.Handle, LRect, 2 );
//get the current Item to edit
LItem := listview1.Items[ Message.wparam ];
//set the text of the Edit
ListViewEditor.Text := LItem.Subitems[ EDIT_COLUMN-1];
//set the bounds of the TEdit
ListViewEditor.BoundsRect := LRect;
//Show the TEdit
ListViewEditor.Visible:=True;
end;
下面的代码xe测试 //编辑框时而出来,时而不出来,尤其是设置了smallimagelist来改变item高度后, 感觉不如上面的//Editable Listview control.txt 2.
我写的示例代码上CodeCentral,显示如何做到这一点。
怎么了TListView的内建的编辑器来编辑子项目
更新:
下面是更新的版本,现在:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics,
Controls, Forms, Dialogs, ComCtrls;
type
TForm1 = class(TForm)
ListView1: TListView;
procedure ListView1Editing(Sender: TObject; Item: TListItem; var AllowEdit: Boolean);
procedure ListView1Edited(Sender: TObject; Item: TListItem; var S: string);
procedure ListView1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
procedure ListView1DrawItem(Sender: TCustomListView; Item: TListItem; Rect: TRect; State: TOwnerDrawState);
private
{ Private declarations }
ColumnToEdit: Integer;
OldListViewEditProc: Pointer;
hListViewEditWnd: HWND;
ListViewEditWndProcPtr: Pointer;
procedure ListViewEditWndProc(var Message: TMessage);
public
{ Public declarations }
constructor Create(Owner: TComponent); override;
destructor Destroy; override;
end;
var
Form1: TForm1;
implementation
uses
Commctrl;
{$R *.dfm}
type
TListViewCoord = record
Item: Integer;
Column: Integer;
end;
TLVGetColumnAt = function(Item: TListItem; const Pt: TPoint): Integer;
TLVGetColumnRect = function(Item: TListItem; ColumnIndex: Integer; var Rect: TRect): Boolean;
TLVGetIndexesAt = function(ListView: TCustomListView; const Pt: TPoint; var Coord: TListViewCoord): Boolean;
// TCustomListViewAccess provides access to the protected members of TCustomListView
TCustomListViewAccess = class(TCustomListView);
var
// these will be assigned according to the version of COMCTL32.DLL being used
GetColumnAt: TLVGetColumnAt = nil;
GetColumnRect: TLVGetColumnRect = nil;
GetIndexesAt: TLVGetIndexesAt = nil;
//---------------------------------------------------------------------------
// GetComCtl32Version
//
// Purpose: Helper function to determine the version of CommCtrl32.dll that is loaded.
//---------------------------------------------------------------------------
var
ComCtl32Version: DWORD = 0;
function GetComCtl32Version: DWORD;
type
DLLVERSIONINFO = packed record
cbSize: DWORD;
dwMajorVersion: DWORD;
dwMinorVersion: DWORD;
dwBuildNumber: DWORD;
dwPlatformID: DWORD;
end;
DLLGETVERSIONPROC = function(var dvi: DLLVERSIONINFO): Integer; stdcall;
var
hComCtrl32: HMODULE;
lpDllGetVersion: DLLGETVERSIONPROC;
dvi: DLLVERSIONINFO;
FileName: array[0..MAX_PATH] of Char;
dwHandle: DWORD;
dwSize: DWORD;
pData: Pointer;
pVersion: Pointer;
uiLen: UINT;
begin
if ComCtl32Version = 0 then
begin
hComCtrl32 := GetModuleHandle('comctl32.dll');
if hComCtrl32 <> 0 then
begin
@lpDllGetVersion := GetProcAddress(hComCtrl32, 'DllGetVersion');
if @lpDllGetVersion <> nil then
begin
ZeroMemory(@dvi, SizeOf(dvi));
dvi.cbSize := SizeOf(dvi);
if lpDllGetVersion(dvi) >= 0 then
ComCtl32Version := MAKELONG(Word(dvi.dwMinorVersion), Word(dvi.dwMajorVersion));
end;
if ComCtl32Version = 0 then
begin
ZeroMemory(@FileName[0], SizeOf(FileName));
if GetModuleFileName(hComCtrl32, FileName, MAX_PATH) <> 0 then
begin
dwHandle := 0;
dwSize := GetFileVersionInfoSize(FileName, dwHandle);
if dwSize <> 0 then
begin
GetMem(pData, dwSize);
try
if GetFileVersionInfo(FileName, dwHandle, dwSize, pData) then
begin
pVersion := nil;
uiLen := 0;
if VerQueryValue(pData, '\', pVersion, uiLen) then
begin
with PVSFixedFileInfo(pVersion)^ do
ComCtl32Version := MAKELONG(LOWORD(dwFileVersionMS), HIWORD(dwFileVersionMS));
end;
end;
finally
FreeMem(pData);
end;
end;
end;
end;
end;
end;
Result := ComCtl32Version;
end;
//---------------------------------------------------------------------------
// Manual_GetColumnAt
//
// Purpose: Returns the column index at the specified coordinates,
// relative to the specified item
//---------------------------------------------------------------------------
function Manual_GetColumnAt(Item: TListItem; const Pt: TPoint): Integer;
var
LV: TCustomListViewAccess;
R: TRect;
I: Integer;
begin
LV := TCustomListViewAccess(Item.ListView);
// determine the dimensions of the current column value, and
// see if the coordinates are inside of the column value
// get the dimensions of the entire item
R := Item.DisplayRect(drBounds);
// loop through all of the columns looking for the value that was clicked on
for I := 0 to LV.Columns.Count-1 do
begin
R.Right := (R.Left + LV.Column[I].Width);
if PtInRect(R, Pt) then
begin
Result := I;
Exit;
end;
R.Left := R.Right;
end;
Result := -1;
end;
//---------------------------------------------------------------------------
// Manual_GetColumnRect
//
// Purpose: Calculate the dimensions of the specified column,
// relative to the specified item
//---------------------------------------------------------------------------
function Manual_GetColumnRect(Item: TListItem; ColumnIndex: Integer; var Rect: TRect): Boolean;
var
LV: TCustomListViewAccess;
I: Integer;
begin
Result := False;
LV := TCustomListViewAccess(Item.ListView);
// make sure the index is in the valid range
if (ColumnIndex >= 0) and (ColumnIndex < LV.Columns.Count) then
begin
// get the dimensions of the entire item
Rect := Item.DisplayRect(drBounds);
// loop through the columns calculating the desired offsets
for I := 0 to ColumnIndex-1 do
Rect.Left := (Rect.Left + LV.Column[i].Width);
Rect.Right := (Rect.Left + LV.Column[ColumnIndex].Width);
Result := True;
end;
end;
//---------------------------------------------------------------------------
// Manual_GetIndexesAt
//
// Purpose: Returns the Item and Column indexes at the specified coordinates
//---------------------------------------------------------------------------
function Manual_GetIndexesAt(ListView: TCustomListView; const Pt: TPoint; var Coord: TListViewCoord): Boolean;
var
Item: TListItem;
begin
Result := False;
Item := ListView.GetItemAt(Pt.x, Pt.y);
if Item <> nil then
begin
Coord.Item := Item.Index;
Coord.Column := Manual_GetColumnAt(Item, Pt);
Result := True;
end;
end;
//---------------------------------------------------------------------------
// ComCtl_GetColumnAt
//
// Purpose: Returns the column index at the specified coordinates, relative to the specified item
//---------------------------------------------------------------------------
function ComCtl_GetColumnAt(Item: TListItem; const Pt: TPoint): Integer;
var
HitTest: LV_HITTESTINFO;
begin
Result := -1;
ZeroMemory(@HitTest, SizeOf(HitTest));
HitTest.pt := Pt;
if ListView_SubItemHitTest(Item.ListView.Handle, @HitTest) > -1 then
begin
if HitTest.iItem = Item.Index then
Result := HitTest.iSubItem;
end;
end;
//---------------------------------------------------------------------------
// ComCtl_GetColumnRect
//
// Purpose: Calculate the dimensions of the specified column, relative to the specified item
//---------------------------------------------------------------------------
function ComCtl_GetColumnRect(Item: TListItem; ColumnIndex: Integer; var Rect: TRect): Boolean;
begin
Result := ListView_GetSubItemRect(Item.ListView.Handle, Item.Index, ColumnIndex, LVIR_BOUNDS, @Rect);
end;
//---------------------------------------------------------------------------
// ComCtl_GetIndexesAt
//
// Purpose: Returns the Item and Column indexes at the specified coordinates
//---------------------------------------------------------------------------
function ComCtl_GetIndexesAt(ListView: TCustomListView; const Pt: TPoint; var Coord: TListViewCoord): Boolean;
var
HitTest: LV_HITTESTINFO;
begin
Result := False;
ZeroMemory(@HitTest, SizeOf(HitTest));
HitTest.pt := Pt;
if ListView_SubItemHitTest(ListView.Handle, @HitTest) > -1 then
begin
Coord.Item := HitTest.iItem;
Coord.Column := HitTest.iSubItem;
Result := True;
end;
end;
//---------------------------------------------------------------------------
// TForm1 Constructor
//
// Purpose: Form constructor
//---------------------------------------------------------------------------
constructor TForm1.Create(Owner: TComponent);
begin
inherited Create(Owner);
// no editing yet
ColumnToEdit := -1;
OldListViewEditProc := nil;
hListViewEditWnd := 0;
ListViewEditWndProcPtr := MakeObjectInstance(ListViewEditWndProc);
if ListViewEditWndProcPtr = nil then
raise Exception.Create('Could not allocate memory for ListViewEditWndProc proxy');
if GetComCtl32Version >= DWORD(MAKELONG(70, 4)) then
begin
@GetColumnAt := @ComCtl_GetColumnAt;
@GetColumnRect := @ComCtl_GetColumnRect;
@GetIndexesAt := @ComCtl_GetIndexesAt;
end else
begin
@GetColumnAt := @Manual_GetColumnAt;
@GetColumnRect := @Manual_GetColumnRect;
@GetIndexesAt := @Manual_GetIndexesAt;
end;
end;
//---------------------------------------------------------------------------
// TForm1 Destructor
//
// Purpose: Form destructor
//---------------------------------------------------------------------------
destructor TForm1.Destroy;
begin
if ListViewEditWndProcPtr <> nil then
FreeObjectInstance(ListViewEditWndProcPtr);
inherited Destroy;
end;
//---------------------------------------------------------------------------
// ListViewEditWndProc
//
// Purpose: Custom Window Procedure for TListView's editor window
//---------------------------------------------------------------------------
procedure TForm1.ListViewEditWndProc(var Message: TMessage);
begin
if Message.Msg = WM_WINDOWPOSCHANGING then
begin
// this inline editor has a bad habit of re-positioning itself
// back on top of the Caption after every key typed in,
// so let's stop it from moving
with TWMWindowPosMsg(Message).WindowPos^ do flags := flags or SWP_NOMOVE;
Message.Result := 0;
end else
begin
// everything else
Message.Result := CallWindowProc(OldListViewEditProc, hListViewEditWnd,
Message.Msg, Message.WParam, Message.LParam);
end;
end;
//---------------------------------------------------------------------------
// ListView1DrawItem
//
// Purpose: Handler for the TListView::OnDrawItem event
//---------------------------------------------------------------------------
procedure TForm1.ListView1DrawItem(Sender: TCustomListView; Item: TListItem; Rect: TRect; State: TOwnerDrawState);
var
LV: TCustomListViewAccess;
R: TRect;
P: TPoint;
I: Integer;
S: String;
begin
LV := TCustomListViewAccess(Sender);
// erase the entire item to start fresh
R := Item.DisplayRect(drBounds);
LV.Canvas.Brush.Color := LV.Color;
LV.Canvas.FillRect(R);
// see if the mouse is currently held down, and if so update the marker as needed
if (GetKeyState(VK_LBUTTON) and $8000) <> 0 then
begin
// find the mouse cursor onscreen, convert the coordinates to client
// coordinates on the list view
GetCursorPos(P);
ColumnToEdit := GetColumnAt(Item, LV.ScreenToClient(P));
end;
// loop through all of the columns drawing each column
for I := 0 to LV.Columns.Count-1 do
begin
// determine the dimensions of the current column value
if not GetColumnRect(Item, I, R) then
Continue;
// mimic the default behavior by only drawing a value as highlighted if
// the entire item is selected, the particular column matches the marker,
// and the ListView is not already editing
if Item.Selected and (I = ColumnToEdit) and (not LV.IsEditing) then
begin
LV.Canvas.Brush.Color := clHighlight;
LV.Canvas.Font.Color := clHighlightText;
end else
begin
LV.Canvas.Brush.Color := LV.Color;
LV.Canvas.Font.Color := LV.Font.Color;
end;
LV.Canvas.FillRect(R);
// draw the column's text
if I = 0 then
S := Item.Caption
else
S := Item.SubItems[I-1];
LV.Canvas.TextRect(R, R.Left + 2, R.Top, S);
end;
end;
//---------------------------------------------------------------------------
// ListView1Edited
//
// Purpose: Handler for the TListView::OnEdited event
//---------------------------------------------------------------------------
procedure TForm1.ListView1Edited(Sender: TObject; Item: TListItem; var S: string);
begin
// ignore the Caption, let it do its default handling
if ColumnToEdit <= 0 then Exit;
// restore the previous window procedure for the inline editor
if hListViewEditWnd <> 0 then
begin
SetWindowLongPtr(hListViewEditWnd, GWL_WNDPROC, LONG_PTR(OldListViewEditProc));
hListViewEditWnd := 0;
end;
// assign the new text to the subitem being edited
Item.SubItems[ColumnToEdit-1] := S;
// prevent the default behavior from updating the Caption as well
S := Item.Caption;
end;
//---------------------------------------------------------------------------
// ListView1Editing
//
// Purpose: Handler for the TListView::OnEditing event
//---------------------------------------------------------------------------
procedure TForm1.ListView1Editing(Sender: TObject; Item: TListItem; var AllowEdit: Boolean);
var
Wnd: HWND;
R: TRect;
begin
// ignore the Caption, let it do its default handling
if ColumnToEdit <= 0 then Exit;
// get the inline editor's handle
Wnd := ListView_GetEditControl(ListView1.Handle);
if Wnd = 0 then Exit;
// determine the dimensions of the subitem being edited
if not GetColumnRect(Item, ColumnToEdit, R) then Exit;
// move the inline editor over the subitem
MoveWindow(Wnd, R.Left, R.Top - 2, R.Right-R.Left, (R.Bottom-R.Top) + 4, TRUE);
// update the inline editor's text with the subitem's text rather than the Caption
SetWindowText(Wnd, PChar(Item.SubItems[ColumnToEdit-1]));
// subclass the inline editor so we can catch its movements
hListViewEditWnd := Wnd;
OldListViewEditProc := Pointer(GetWindowLongPtr(Wnd, GWL_WNDPROC));
SetWindowLongPtr(Wnd, GWL_WNDPROC, LONG_PTR(ListViewEditWndProcPtr));
end;
//---------------------------------------------------------------------------
// ListView1MouseDown
//
// Purpose: Handler for the TListView::OnMouseDown event
//---------------------------------------------------------------------------
procedure TForm1.ListView1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
Coord: TListViewCoord;
begin
if GetIndexesAt(ListView1, Point(X, Y), Coord) then
begin
if Coord.Column <> ColumnToEdit then
begin
// update the marker
ColumnToEdit := Coord.Column;
// cancel the editing so that the listview won't go into
// its edit mode immediately upon clicking the new item
ListView1.Items[Coord.Item].CancelEdit;
// update the display with a new highlight selection
ListView1.Invalidate;
end;
end else
ColumnToEdit := -1;
end;
end.
//Editable Listview control.txt Editable Listview control Posted by zairon on November 6, 2007 Posted in: General, Programming. 5 Comments Few days ago I started renewing my PE editor’s gui, I wanted to replace some edit box controls with a listview control. Everything was going well until I had to edit the value inside a cell. With the original listview control you can change the text of the first subitem of a row only, but I would like to edit every single subitem. How can I solve the problem? I didn’t want to waste time solving the problem so I decided to take a look at the usual programming places starting from Code Project. Hm, nothing. I’m not so good in searching information through the net, but seems like there are working samples on mfc, .net and vb only. No win32 programming stuff… Well, I decided to give it a try subclassing the control. I have never subclass-ed a control before, it’s my first try. I don’t know if there’s a better approach. I don’t even know if it’s the correct way to solve the problem, but it seems to works well. Let’s start.
The steps to follow are: 1. Create an edit box that will be used to insert the new text 2. Set the new window procedure able to handle edit control’s messages 3. Apply/abort text modification
I use VS creating a win32 project. Add a listview control to your dialog setting “Edit labels” to FALSE. If you set the option to TRUE the OS will handle subitem modification, I prefer to avoid this behaviour.
The idea is to change the subitem’s text when a double click occours. I catch the event in the main window procedure calling the function (named SubClass_ListView_Editable) which subclasses the control.
The first step consists of creating the edit box over the clicked subitem. To create the edit box I need to know where to put it. LVM_GETSUBITEMRECT returns information about the rectangle for a subitem of a listview control. With this information I can create the new control:
ListView_GetSubItemRect(hListView, _lParam->iItem, _lParam->iSubItem, LVIR_LABEL, &r); // Time to create the new edit box hEditable = CreateWindowEx(0, "EDIT", "Edit me", WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT | ES_MULTILINE, r.left, r.top, r.right-r.left, r.bottom-r.top, _lParam->hdr.hwndFrom, NULL, hInst, 0);
“r” is defined as a RECT structure:
typedef struct _RECT { LONG left; // x-coordinate of the upper-left corner of the rectangle LONG top; // y-coordinate of the upper-left corner of the rectangle LONG right; // x-coordinate of the lower-right corner of the rectangle LONG bottom; // y-coordinate of the lower-right corner of the rectangle } RECT, *PRECT;
As you can see I use “r” inside CreateWindowEx function specifying the coordinates of the new control. The third parameter of CreateWindowEx is the text that will be shown in the control. I use a static text but you can leave it blank or display the subitem’s text, it’s up to you. Now, the new control has been created and I’m going to set some features:
SendMessage(hEditable, EM_LIMITTEXT, 8, 0); // It accepts no more than 8 chars SendMessage(hEditable, EM_SETSEL, 0, 8); // Text selected SetFocus(hEditable); // Focus to the new box
If you don’t need a particular behaviour (limit text, accept only numbers…) you can avoid the first two calls but I think the third one is useful, it gives the focus to the new edit box.
The control is complete, I have to add the new window procedure. This can be done using SetWindoLong function:
LONG SetWindowLong( HWND hWnd, // Handle of the new edit control int nIndex, // The attribute to change LONG dwNewLong // The new value );
The function changes an attribute of a specified window, in this case I’m going to change the address of the dialog procedure. The aim is to add a new window procedure for handling edit box’s messages only. I’ll pass over all the other messages forwarding them towards the old window procedure.
wpOld = (WNDPROC)SetWindowLong(hEditable, GWL_WNDPROC, SubClass_ListView_WndProc); SetProp(hEditable, "WP_OLD", (HANDLE)wpOld);
SubClass_ListView_WndProc represents the new dialog procedure. I have to save the address of the original window procedure because I have to restore it when I’ll destroy the edit box. To save the address I use SetProp function, but if you prefer you can use global variables. To end this piece of code I save some more useful information: row and column of the subitem to change:
SetProp(hEditable, "ITEM", (HANDLE)_lParam->iItem); SetProp(hEditable, "SUBITEM", (HANDLE)_lParam->iSubItem);
Which kind of messages will I have to catch? WM_KEYDOWN and WM_DESTROY only, all the other messages are passed to the original window procedure in this way:
return CallWindowProc((WNDPROC)GetProp(hEditable, "WP_OLD"), hwnd, uMsg, wParam, lParam);
I catch WM_KEYDOWN because I have to handle ENTER ans ESC key. When you hit ENTER the text will be saved in the subitem, and when you click ESC the operation will be aborted:
case WM_KEYDOWN: if (LOWORD(wParam) == VK_RETURN) { ... // Item and suibtem to change LvItem.iItem = GetProp(hEditable, "ITEM"); LvItem.iSubItem = GetProp(hEditable, "SUBITEM"); // Where to store the new text LvItem.pszText = text; // Get new text and set it in the subitem GetWindowText(hEditable, text, sizeof(text)); SendMessage(hListView, LVM_SETITEMTEXT, (WPARAM)GetProp(hEditable, "ITEM"), (LPARAM)&LvItem); DestroyWindow(hEditable); } else if (LOWORD(wParam) == VK_ESCAPE) DestroyWindow(hEditable); break;
If you press ESC the edit box will be destroyed without changing the subitem’s text. When ENTER is pressed I simply get the text storing it in the subitem. After that I can destroy the edit box. There’s something more to say about VK_RETURN. Look at CreateWindowEx parameters, there’s a ES_MULTILINE value. The value is necessary otherwise the application refuses to catch ENTER. The last thing I check in the new window procedure is WM_DESTROY message, I simply remove the saved properties and then I restore the original window procedure calling SetWindowLong again.
What about mouse click when I’m changing the subitem’s value? It’s like VK_ESCAPE key, I remove the edit box aborting the operation. See the attached source code for details.
The picture represents the subclassing method in action:
The code can be optimized for sure, just fix/change/remove/add everything you need. Feel free to post your comment/suggestion/criticism here. Download the VS project from here
|