如果您读取column.width,XE6 TListView列宽将变为零

时间:2014-08-22 14:27:19

标签: delphi delphi-xe6

TListView中存在错误。

读取列Width会导致listview尝试直接从基础Windows LISTVIEW控件获取列宽 - 在Win32控件的列初始化之前。

由于列尚未初始化,因此列表视图的LVM_GETCOLUMNWIDTH消息失败,返回TListView认为宽度 为零,并且所有列都为零。

这个错误是在Delphi 5之后的某个时候引入的。

重现步骤

将包含三列的报表样式列表视图添加到表单:

enter image description here

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;

运行它:

enter image description here

Bug

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.CreatWndCreateWnd调用之后的某时添加了列:

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中没有失败?

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;

Bonus Chatter

查看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;
   }
   ...
}

0 个答案:

没有答案