c++buildervclc++builder-11-alexandria

Overlay icons are painted stretched


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:

The good

__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):

the bad

//---------------------------------------------------------------------------
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:

More bad

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:

enter image description here

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 ..


Solution

  • 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;
    }