打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
InplaceEdit TListView subitem 就地编辑列表视图子项
我有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 Editprocedure 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;interfaceuses 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;implementationuses 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





本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
ListView用法[delphi
使用 TListView 控件(4)
Delphi listview 点击列头排序
DBGrid及ListView点击标题栏自动排序
Delphi小技巧杂记
delphi中当月第一天最后一天的函数等函数
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服