I am porting a C++Builder 2009 project to C++Builder 11.
For some strange reason overlay icons are painted stretched in a custom made object that inherits from TTreeView
.
It obviously works properly when built with C++Builder 2009.
I don't do any custom painting in it.
When I create a new project, add a TTreeView and TImageList at design time, don't change any default settings, just add two icons and two items, per below image and code, everything appears to work fine:
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
ImageList1->Overlay(0, 0) ;
TreeView1->Items->Item[1]->OverlayIndex = 0 ;
}
When I create my own TTreeView
descendant and do the same, the overlay icon is stretched out (on the right):
//---------------------------------------------------------------------------
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 ;
}
//---------------------------------------------------------------------------
Using the same icon and ImageList in the ported project:
It's as if TTreeView
asks TImageList
to paint the overlay icon with wrong Canvas dimensions ?
FYI, here's the result from the exact same code built with C++Builder 2009:
EDIT
The plot thickens
I just tested this on a range of OS (in a VM VirtualBox) and I did not see the problem on older OS (XP, Vista, W7, W8 and even W10 (older not up to date version)). However, I do see it on my W10 development system (up to date) I also tested it on W11 in a VirtualBox and there the the problem also exists. So it's not just my system, it's related to latest Windows updates. Super annoying ..
The issue you are seeing here is not quite that TTreeView
descendants have stretched overlay icons. In fact stretched overlay icons come from any TTreeview
(stock or inherited) where the Images
property is assigned outside of the component streaming process. Your designer-placed treeview did not show the issue as its Images
property is set while being read from the DFM (the component streaming process). If you unlink the Images
property for TreeView1
in the Object Inspector and assign it in code, then that treeview will exhibit exactly the same issue.
OK, so now we have redefined the problem, just what is going on?
In TCustomTreeView.SetImages
the first thing done is call SetDPIScaling(True)
, which if done on a treeview with a window handle at runtime (and if Per Monitor V2 is active) sends CCM_DPISCALE
to the treeview (MS says this Enables automatic high dots per inch (dpi) scaling in Tree-View controls). The net effect of this (in a 100% scaled display) is that overlay icons are scaled to twice the size they seem to be intended to be and otherwise would be.
It looks like this might have been an unintended side effect of work to resolve 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.
Can we work around this issue? Yes! You must set the image lists's protected Scaled
property before it is assigned to any treeview's Images
property (this Scaled
property appears to remain False
in a TImageList
under all normal circumstances), and this will then cause the tree view to follow the call to SetDPIScaling(True)
with another call to SetDPIScaling(False)
and the problem does not then occur.
Ah, but Scaled
is a protected property, so how do we set it?
In Delphi you can use a hoist class, like this:
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;
In C++ you need to create a shallow descendant class that simply accesses the protected member for you and cast appropriately:
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;
}