调整Windows屏幕缩放后TTreeView复选框选中状态丢失求助
Delphi 11/12中TTreeView缩放后复选框状态丢失的解决方案
以下是针对Windows屏幕缩放调整后TTreeView复选框状态丢失问题的几种可行解决方案:
方案1:监听DPI变化消息,手动保存并恢复状态
Windows在屏幕缩放比例变更时会发送WM_DPICHANGED消息,我们可以通过捕获该消息,在控件重绘前后分别保存和恢复所有节点的复选状态。
实现代码示例:
unit MainForm; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ComCtrls, System.Generics.Collections; type TForm1 = class(TForm) TreeView1: TTreeView; private FNodeCheckedStates: TDictionary<TTreeNode, Boolean>; procedure WMDpiChanged(var Message: TMessage); message WM_DPICHANGED; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; end; var Form1: TForm1; implementation {$R *.dfm} constructor TForm1.Create(AOwner: TComponent); begin inherited; FNodeCheckedStates := TDictionary<TTreeNode, Boolean>.Create; end; destructor TForm1.Destroy; begin FNodeCheckedStates.Free; inherited; end; procedure TForm1.WMDpiChanged(var Message: TMessage); var Node: TTreeNode; begin // 1. 保存当前所有节点的复选状态 FNodeCheckedStates.Clear; Node := TreeView1.Items.GetFirstNode; while Node <> nil do begin FNodeCheckedStates.Add(Node, Node.Checked); Node := Node.GetNext; end; // 2. 执行系统默认的DPI缩放处理逻辑 inherited; // 3. 恢复节点的复选状态 Node := TreeView1.Items.GetFirstNode; while Node <> nil do begin if FNodeCheckedStates.ContainsKey(Node) then Node.Checked := FNodeCheckedStates[Node]; Node := Node.GetNext; end; end; end.
方案2:自定义TTreeView子类,重载DPI相关处理方法
通过继承原生TTreeView,重载CreateWnd和WM_DPICHANGED方法,确保控件句柄重建或DPI变化时,状态能被正确保存和恢复。
实现代码示例:
unit CustomTreeView; interface uses Winapi.Windows, Winapi.Messages, System.Classes, Vcl.ComCtrls, System.Generics.Collections; type TCustomTreeView = class(TTreeView) private FCheckedStates: TDictionary<TTreeNode, Boolean>; procedure SaveCheckedStates; procedure RestoreCheckedStates; procedure WMDpiChanged(var Message: TMessage); message WM_DPICHANGED; protected procedure CreateWnd; override; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; end; implementation constructor TCustomTreeView.Create(AOwner: TComponent); begin inherited; FCheckedStates := TDictionary<TTreeNode, Boolean>.Create; end; destructor TCustomTreeView.Destroy; begin FCheckedStates.Free; inherited; end; procedure TCustomTreeView.SaveCheckedStates; var Node: TTreeNode; begin FCheckedStates.Clear; Node := Items.GetFirstNode; while Node <> nil do begin FCheckedStates.Add(Node, Node.Checked); Node := Node.GetNext; end; end; procedure TCustomTreeView.RestoreCheckedStates; var Node: TTreeNode; begin Node := Items.GetFirstNode; while Node <> nil do begin if FCheckedStates.ContainsKey(Node) then Node.Checked := FCheckedStates[Node]; Node := Node.GetNext; end; end; procedure TCustomTreeView.WMDpiChanged(var Message: TMessage); begin SaveCheckedStates; inherited; RestoreCheckedStates; end; procedure TCustomTreeView.CreateWnd; begin SaveCheckedStates; inherited; RestoreCheckedStates; end; end.
使用时,将窗体中的原生TTreeView替换为这个自定义控件即可。
方案3:安装官方补丁
该问题属于Delphi 11/12的已知VCL控件DPI适配缺陷,Embarcadero可能已发布相关修复补丁。可以通过RAD Studio的补丁管理工具,检查并安装对应版本的最新累积补丁,直接解决底层问题。
注意事项
- 如果TreeView节点是动态加载的,保存状态时需确保遍历所有已加载的节点;
- 若节点存在动态删除的场景,需同步更新保存状态的字典,避免无效节点引用;
- 自定义控件时,确保继承自正确的VCL基类,避免引发兼容性问题。
内容的提问来源于stack exchange,提问作者Stepel
相关产品推荐
相关产品推荐

