叠加图标被拉伸

Overlay icons are painted stretched

我正在将 C++Builder 2009 项目移植到 C++Builder 11。

出于某种奇怪的原因,覆盖图标在继承自 TTreeView 的自定义对象中被拉伸绘制。 当使用 C++Builder 2009 构建时,它显然可以正常工作。 我没有在里面做任何定制绘画。

当我创建一个新项目时,在设计时添加一个 TTreeView 和 TImageList,不要更改任何默认设置,只需添加两个图标和两个项目,按照下面的图像和代码,一切似乎都工作正常:

__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
ImageList1->Overlay(0, 0) ;
TreeView1->Items->Item[1]->OverlayIndex = 0 ;
}

当我创建自己的 TTreeView 后代并执行相同操作时,覆盖图标被拉长(在右侧):

//---------------------------------------------------------------------------
class MyTreeView : public TTreeView
{
public :
        MyTreeView(TPanel *TreeViewLocation)
            : TTreeView(TreeViewLocation)
            {
            Parent = TreeViewLocation ;
            Align  = alClient ;
            }

        virtual __fastcall ~MyTreeView() {}

};
//---------------------------------------------------------------------------

MyTreeView *TreeView2 ;

//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
ImageList1->Overlay(0, 0) ;
TreeView1->Items->Item[1]->OverlayIndex = 0 ;

TreeView2 = new MyTreeView(Panel1) ;
TreeView2->Items->Add(NULL, L"Item1")->ImageIndex = 1 ;
TreeView2->Items->Add(NULL, L"Item2")->ImageIndex = 1 ;
TreeView2->Images = ImageList1 ;
TreeView2->Items->Item[1]->OverlayIndex = 0 ;
}
//---------------------------------------------------------------------------

在移植的项目中使用相同的图标和ImageList:

就好像 TTreeView 要求 TImageList 以错误的 Canvas 尺寸绘制覆盖图标?

仅供参考,这是使用 C++Builder 2009 构建的完全相同代码的结果:

编辑

剧情变厚了

我刚刚在 OS 的范围内(在 VM VirtualBox 中)测试了这个,我没有在旧的 OS(XP、Vista、W7、W8 甚至 W10)上看到问题(较旧的不是最新版本))。 但是,我确实在我的 W10 开发系统(最新)上看到了它 我还在 VirtualBox 的 W11 上测试了它,问题也存在。所以这不仅仅是我的系统,它与最新的 Windows 更新有关。 超级讨厌..

您在这里看到的问题并不完全是因为 TTreeView 后代具有拉伸的覆盖图标。事实上,拉伸的覆盖图标来自任何 TTreeview(库存或继承),其中 Images 属性 被分配 在组件流进程之外 。您的 designer-placed 树视图没有显示问题,因为它的 Images 属性 是在从 DFM(组件流处理)读取时设置的。如果您在对象检查器中为 TreeView1 取消链接 Images 属性 并在代码中分配它,那么该树视图将显示完全相同的问题。

好的,那么现在我们重新定义了问题,到底是怎么回事?

TCustomTreeView.SetImages 中,要做的第一件事是调用 SetDPIScaling(True),如果在运行时在具有 window 句柄的树视图上完成(并且如果 Per Monitor V2 处于活动状态)发送 CCM_DPISCALE 到树视图(MS says 在 Tree-View 控件 中启用自动高每英寸点数 (dpi) 缩放。这样做的净效果(在 100% 缩放显示中)是覆盖图标缩放到它们似乎预期的大小的两倍,否则应该是。

看起来这可能是解决 RSP-24440 in RAD Studio 10.4, and looks like it is also reported (or very nearly the same issue is reported) in RSP-36397 的工作的意外副作用。

我们可以解决这个问题吗?是的!在分配给任何树视图的 Images 属性 之前,您必须设置图像列表的受保护 Scaled 属性(此 Scaled 属性 似乎保留 FalseTImageList 在所有正常情况下),然后这将导致树视图跟随对 SetDPIScaling(True) 的调用和对 SetDPIScaling(False) 的另一个调用,然后问题就没有了发生。

啊,但是Scaled是受保护的属性,那我们怎么设置呢?

在 Delphi 中,您可以使用 起重机 class,像这样:

type
  TImageListHoist = class(TImageList);
...
var
  TreeView2: MyTreeView;

procedure TForm1.FormCreate(Sender: TObject);
var
  Node: TTreeNode;
begin
  // There can be 4 overlay indices, numbered 0-3
  ImageList1.Overlay(1, 0); // image index 1 is to be overlay index 0
  TImageListHoist(ImageList1).Scaled := True; // set Scaled to True using the hoist class

  TreeView1.Images := ImageList1; // connect the image list
  Node := TreeView1.Items.Item[0];
  Node.OverlayIndex := 0; // which overlay mask from the image list to use
  Node := TreeView1.Items.Item[1];
  Node.OverlayIndex := 0;

  TreeView2 := MyTreeView.CreateHere(Panel1);
  TreeView2.Width := 180;
  TreeView2.Images := ImageList1; // connect the image list
  Node := TreeView2.Items.Add(nil, 'TreeView2 Item1');
  Node.ImageIndex := 0;
  Node.SelectedIndex := 0;
  Node.OverlayIndex := 0;
  Node := TreeView2.Items.Add(nil, 'TreeView2 Item2');
  Node.ImageIndex := 0;
  Node.SelectedIndex := 0;
  Node.OverlayIndex := 0;
end;

在 C++ 中,您需要创建一个浅层后代 class,它只为您访问受保护的成员并进行适当的转换:

MyTreeView *TreeView2;

class MyImageList: public TImageList
{
public:
    void SetScaledTrue() { Scaled = true; }
};
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
    TTreeNode *Node;

    // There can be 4 overlay indices, numbered 0-3
    ImageList1->Overlay(1, 0); // image index 1 is to be overlay index 0
    ((MyImageList *)ImageList1)->SetScaledTrue(); // use the temp class to set Scaled
    
    TreeView1->Images = ImageList1; // connect the image list
    Node = TreeView1->Items->Item[0];
    Node->OverlayIndex = 0; // which overlay mask from the image list to use
    Node = TreeView1->Items->Item[1];
    Node->OverlayIndex = 0;

    TreeView2 = new MyTreeView(Panel1);
    TreeView2->Width = 180;
    TreeView2->Images = ImageList1; // connect the image list
    Node = TreeView2->Items->Add(NULL, L"TreeView2 Item1");
    Node->ImageIndex = 0;
    Node->SelectedIndex = 0;
    Node->OverlayIndex = 0;
    Node = TreeView2->Items->Add(NULL, L"TreeView2 Item2");
    Node->ImageIndex = 0;
    Node->SelectedIndex = 0;
    Node->OverlayIndex = 0;
}