使用自定义绘制时,Delphi列表视图控件是否存在错误?

14

QC#101189

我正在尝试在Delphi TListView中自定义绘制进度条,正如NGLN在另一个SO问题的答案建议的那样。这个方法很好,但是在使用Vista引入的新资源管理器主题来绘制时与热跟踪交互存在问题。

热跟踪绘画和Delphi自定义绘制事件似乎会互相干扰。例如,我看到的输出结果如下:

enter image description here

列1中的文本应该显示为"Item 3",但被破坏了。看起来像是对列表视图控件的Delphi封装器的一个错误,但同样可能是我的操作有误!

虽然我一直在XE2中开发这个项目,但相同的行为在2010年和可能是XE中也出现了。

以下是重现该行为的代码:

Pascal文件

unit Unit1;

interface

uses
  Windows, Classes, Controls, Forms, CommCtrl, ComCtrls;

type
  TForm1 = class(TForm)
    ListView: TListView;
    procedure FormCreate(Sender: TObject);
    procedure ListViewCustomDrawSubItem(Sender: TCustomListView;
      Item: TListItem; SubItem: Integer; State: TCustomDrawState;
      var DefaultDraw: Boolean);
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
  ListView.RowSelect := True;
  ListView.Items.Add.Caption := 'Item 1';
  ListView.Items.Add.Caption := 'Item 2';
  ListView.Items.Add.Caption := 'Item 3';
end;

procedure TForm1.ListViewCustomDrawSubItem(Sender: TCustomListView;
  Item: TListItem; SubItem: Integer; State: TCustomDrawState;
  var DefaultDraw: Boolean);
var
  R: TRect;
begin
  DefaultDraw := False;
  ListView_GetSubItemRect(Sender.Handle, Item.Index, SubItem, LVIR_BOUNDS, @R);
  Sender.Canvas.MoveTo(R.Left, R.Top);
  Sender.Canvas.LineTo(R.Right-1, R.Bottom-1);
end;

end.

表单文件

object Form1: TForm1
  Caption = 'Custom Draw List View Bug'
  ClientHeight = 290
  ClientWidth = 554
  OnCreate = FormCreate
  object ListView: TListView
    Align = alClient
    Columns = <
      item
        Caption = 'Column 1'
        Width = 250
      end
      item
        Caption = 'Column 2'
        Width = 250
      end>
    ViewStyle = vsReport
    OnCustomDrawSubItem = ListViewCustomDrawSubItem
  end
end

3
我只能说:使用Virtual TreeView,而不是TListView。 TListView很奇怪且速度慢,您必须一路与Windows抗争。 - gabr
1
@gabr TListView在虚拟模式下并不会很慢。而且我更喜欢使用本地控件以获得最佳的外观和感觉。尽管有个人偏好,但至少我想追踪一下这个问题并提交QC报告。 - David Heffernan
1
@Sertac 是的,移除“explorer”窗口主题可以停止热跟踪并解决问题。当然,现在控件看起来很丑陋!! - David Heffernan
1
@David - 哈哈哈,很抱歉我不能帮忙。我不记得细节了,但是我想我曾经尝试追踪类似的问题时失败过。 - Sertac Akyuz
2
@David - 把 SetBkMode(Sender.Canvas.Handle, TRANSPARENT); 放到 TForm1.ListViewCustomDrawSubItem 中有帮助吗? - Sertac Akyuz
显示剩余14条评论
2个回答

13

这是一个解决缺陷行为的方法,而不是回答VCL中是否存在错误的问题,并且还有一些思考。

这个解决方案是在进行自定义绘图后将由公共控件分配的设备上下文的项目绘制周期的背景模式设置为透明的:

procedure TForm1.ListViewCustomDrawSubItem(Sender: TCustomListView;
  Item: TListItem; SubItem: Integer; State: TCustomDrawState;
  var DefaultDraw: Boolean);
var
  R: TRect;
begin
  if not [CustomDrawing] then  // <- If we're not gonna do anything do not
    Exit;                      //    fiddle with the DC in any way

  DefaultDraw := False;
  ListView_GetSubItemRect(Sender.Handle, Item.Index, SubItem, LVIR_BOUNDS, @R);
  Sender.Canvas.MoveTo(R.Left, R.Top);
  Sender.Canvas.LineTo(R.Right-1, R.Bottom-1);

  SetBkMode(Sender.Canvas.Handle, TRANSPARENT); // <- will effect the next [sub]item
end; 
在[sub]项目绘制周期中,绘制总是从上到下进行的,具有较低索引的项目在具有较高索引的项目之前发送NM_CUSTOMDRAW通知。当鼠标从一行移动到另一行时,需要重新绘制两行 - 失去热点状态的行和获得热点状态的行。似乎,在自定义绘制生效时,绘制失去热点状态的行会使DC处于一个不理想的状态。当鼠标向上移动时,由于该项最后绘制,所以这不是问题。
自定义绘制ListView和TreeView控件与自定义绘制其他控件不同,并且有些复杂(请参见:Custom Draw With List-View and Tree-View Controls)。但是,您完全控制整个过程。VCL中'comctrls.pas'中TCustomListView.CNNotifyNM_CUSTOMDRAW case的代码同样复杂。但是,尽管提供了一堆自定义绘图处理程序(其中一半是高级),但您无法控制VCL的操作。例如,您不能返回所需的CDRF_xxx或不能设置所需的clrTextBk。我个人倾向于认为,Delphi列表视图控件存在错误/设计问题,但我除了找到解决方法以外,没有更具体的想法。

2
@David - 不用谢!尽管我有这个看法,但当新版本的comctl32.dll到来时,测试解决方法可能是个好主意。 - Sertac Akyuz
在Delphi 10.1 Berlin中,绕过仍然可以解决此错误。 - Jerry Dodge
我正在使用C++,但很遗憾这个建议对我没有任何作用;在我的情况下,当受影响时,我得到了一个白色的填充。此外,如果我在DrawTextW()调用前加上SetBkMode(TRANSPARENT),然后在之后将其重置为先前的值,移动鼠标会导致可怕的透明重绘,尽管我的处理程序在之前也填充了矩形!如果我使用OPAQUE代替,就会出现像David原来的问题中显示的那样的问题,即黑色矩形。我不确定列表视图在做什么以至于如此奇怪,但它确实在做些什么...也不确定是否应该提出一个单独的问题。 - andlabs
在上面的Marjan中,添加'LVS_EX_DOUBLEBUFFER'并不能解决它。 - andlabs
还有一些事情:我尝试在AlphaBlend()GdiAlphaBlend()上断点,看看是不是listview在调用它,但实际上并没有;我尝试查看ROP2模式,但它没有改变;我尝试在prepaint、item prepaint和subitem prepaint阶段使用CDRF_NOERASE,但也没有任何效果(虽然我还没有尝试过这些阶段的组合);我尝试使用ExtTextOutW()代替DrawTextW(),但除了重新绘制文本的位置之外,什么都没有改变。这种情况在非自绘ListView中不会发生,所以我不明白。今晚我会尝试更多的方法。 - andlabs
抱歉造成了这么多噪音;事实证明这可能是我的错。我最终重写了我的绘图代码,将其放在一个地方并更好地格式化它,摆脱了CDRF_NEWFONT(无论如何我都要自己绘制整个控件,除非我应该无论如何返回它?),并且只在CDDS_SUBITEM | CDDS_ITEMPREPAINT上进行绘制。我猜测我的错误是在正常的CDDS_ITEMPREPAINT上绘制了某些内容,但我不记得确切的问题是什么了。还是谢谢! - andlabs

0
我对文本位置上的黑色矩形一无所知,但是你代码中的 DefaultDraw := False; 是导致热点跟踪缺失的原因。只有在 subitem <> 0 时才会调用 OnCustomDrawSubItem,因此第一列默认绘制,而第二列使用了你的代码。第一列的自定义绘制可以通过 OnCustomDrawItem 实现。

很抱歉,我之前用一个没想好的例子误导了您。在 Q 的第一个版本中,FillRect 只是用来让黑色矩形出现的。我已经修改了问题并更改了代码和屏幕截图,以明确表明黑色矩形才是问题所在。实际上,DefaultDraw 的值与黑色矩形无关。 - David Heffernan
我明白了。通过你修改的示例,现在看起来修复了热跟踪被截断的问题。这个问题实际上是由于绘制矩形而不是默认绘制设置引起的。顺便提一下:在我的系统上,只有当你将鼠标从较低索引的项目移动到较高索引的项目时,才会出现黑色文本矩形,而当你从较高索引的项目移动到较低索引的项目时则不会出现。 - Uwe Raabe
你只需要进行一些GDI输出就可以实现这个。我选择了FillRect,但显然这是一个糟糕的选择。是的,我看到了与你相同的问题,即从低索引到高索引。对于造成的困惑,我感到抱歉。 - David Heffernan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接