TListView
中存在错误。
读取列Width
会导致listview尝试直接从基础Windows LISTVIEW
控件获取列宽 - 在Win32控件的列初始化之前。
由于列尚未初始化,因此列表视图的LVM_GETCOLUMNWIDTH
消息失败,返回零。 TListView
认为宽度 为零,并且所有列都为零。
这个错误是在Delphi 5之后的某个时候引入的。
将包含三列的报表样式列表视图添加到表单:
将OnResize
事件处理程序添加到listview:
procedure TForm1.ListView1Resize(Sender: TObject);
begin
{
Any column you attempt to read the width of
will **cause** the width to become zero
}
ListView1.Columns[0].Width;
// ListView1.Columns[1].Width;
ListView1.Columns[2].Width;
end;
运行它:
TListColumn
代码尝试直接读取Windows listview
类的列宽,然后将列添加添加到Windows listview
控制
格式化代码以提高可读性:
function TListColumn.GetWidth: TWidth;
var
IsStreaming: Boolean;
LOwner: TCustomListView;
begin
LOwner := TListColumns(Collection).Owner;
IsStreaming := [csReading, csWriting, csLoading] * LOwner.ComponentState <> [];
if (
(FWidth = 0)
and (LOwner.HandleAllocated or not IsStreaming)
)
or
(
(not AutoSize)
and LOwner.HandleAllocated
and (LOwner.ViewStyle = vsReport)
and (FWidth <> LVSCW_AUTOSIZE)
and (LOwner.ValidHeaderHandle)
) then
begin
FWidth := ListView_GetColumnWidth(LOwner.Handle, FOrderTag);
end;
Result := FWidth;
end;
在dfm
反序列化期间构建表单时会出现问题:
ComCtrls.TListColumn.GetWidth: TWidth;
TForm1.ListView1Resize(Sender: TObject);
Windows.CreateWindowEx(...)
Controls.TWinControl.CreateWindowHandle(const Params: TCreateParams);
Controls.TWinControl.CreateWnd;
ComCtrls.TCustomListView.CreateWnd;
问题是TCustomListView.CreatWnd
在CreateWnd
调用之后的某时添加了列:
procedure TCustomListView.CreateWnd;
begin
inherited CreateWnd; //triggers a call to OnResize, trying to read the column widths
//...
Columns.UpdateCols; //add the columns
//...
end;
TListColumn.GetWidth
中的代码并未意识到列尚未初始化。
Delphi 5使用类似的TCustomListView
构造:
procedure TCustomListView.CreateWnd;
begin
inherited CreateWnd; //triggers a call to OnResize
//...
Columns.UpdateCols;
//...
end;
除了 Delphi 5 之外没有尝试过自己的想法,并且过度思考:
function TListColumn.GetWidth: TWidth;
begin
if FWidth = 0 then
FWidth := ListView_GetColumnWidth(TListColumns(Collection).Owner.Handle, Index);
Result := FWidth;
end;
如果我们有宽度,请使用它。
为什么TListColumn.GetWidth
发生了变化?他们试图解决什么错误?我看到他们没有评论他们的代码更改,因此无法从VCL来源告诉他们的理由是什么。
更重要的是,我该如何解决?我无法从OnResize
中删除代码,但我可以创建TFixedListView
自定义控件;除了我必须从头开始重写所有内容以便它使用TFixedListViewColumn
类。
那不好。
最重要的问题:Embarcadero如何解决这个问题?什么是应该在TListColumn.GetWidth
中修复错误的代码? ComponentState
是空的。他们似乎必须引入一个新变量:
FAreColumnsInitialized: Boolean;
或者他们可以把代码放回原来的样子。
您建议他们将代码修改为什么?
只有在启用视觉样式时才会出现此错误。
Windows有一条WM_PARENTNOTIFY
消息,&#34;通知父母控件生命周期内的重要事件&#34; 。对于listview
,它会发送listview在内部使用的header
控件的句柄。 Delphi然后保存此标题hwnd
:
procedure TCustomListView.WMParentNotify(var Message: TWMParentNotify);
begin
with Message do
if (Event = WM_CREATE) and (FHeaderHandle = 0) then
begin
FHeaderHandle := ChildWnd;
//...
end;
inherited;
end;
在禁用主题的情况下,Windows在构建周期中稍后之前不会发送WM_PARENTNOTIFY
消息。这意味着如果禁用主题,TListColumn
将无法满足允许其与列表视图对话的标准之一:
if (
(FWidth = 0)
and (LOwner.HandleAllocated or not IsStreaming)
)
or
(
(not AutoSize)
and LOwner.HandleAllocated
and (LOwner.ViewStyle = vsReport)
and (FWidth <> LVSCW_AUTOSIZE)
and (LOwner.ValidHeaderHandle) //<--- invalid
) then
begin
FWidth := ListView_GetColumnWidth(LOwner.Handle, FOrderTag);
end;
但是当我们使用新版本的Windows listview
控件时,Windows恰好在构造中发送WM_PARENTNOTIFY
消息:
if (
(FWidth = 0)
and (LOwner.HandleAllocated or not IsStreaming)
)
or
(
(not AutoSize)
and LOwner.HandleAllocated
and (LOwner.ViewStyle = vsReport)
and (FWidth <> LVSCW_AUTOSIZE)
and (LOwner.ValidHeaderHandle) //<--- Valid!
) then
begin
FWidth := ListView_GetColumnWidth(LOwner.Handle, FOrderTag);
end;
即使标题句柄有效,也不意味着列已添加。
似乎VCL修复是使用WM_PARENTNOTIY
作为将列添加到列表视图的正确机会:
procedure TCustomListView.WMParentNotify(var Message: TWMParentNotify);
begin
with Message do
if (Event = WM_CREATE) and (FHeaderHandle = 0) then
begin
FHeaderHandle := ChildWnd;
UpdateCols; //20140822 Ian Boyd Fixed QC123456 where the columns aren't usable in time
//...
end;
inherited;
end;
查看Windows 2000源代码,ListView有一些注释可以识别那里存在的一些不良应用程序:
lvrept.c
BOOL_PTR NEAR ListView_CreateHeader(LV* plv)
{
...
plv->hwndHdr = CreateWindowEx(0L, c_szHeaderClass, // WC_HEADER,
NULL, dwStyle, 0, 0, 0, 0, plv->ci.hwnd, (HMENU)LVID_HEADER, GetWindowInstance(plv->ci.hwnd), NULL);
if (plv->hwndHdr)
{
NMLVHEADERCREATED nmhc;
nmhc.hwndHdr = plv->hwndHdr;
// some apps blow up if a notify is sent before the control is fully created.
CCSendNotify(&plv->ci, LVN_HEADERCREATED, &nmhc.hdr);
plv->hwndHdr = nmhc.hwndHdr;
}
...
}