Windows 11自定义缩放场景下TForm尺寸存储恢复问题解决
问题描述
Windows 11新增了自定义屏幕缩放选项。在Delphi应用中,每次关闭窗体时会将TForm的Width、Height等参数存储到INI文件,下次打开时恢复。但开启自定义缩放后,存储的尺寸值会被缩放比例放大,而设置时会正确应用缩放,导致窗体每次打开都变大。请问是否需要手动除以缩放因子来补偿?同时求通过Windows API获取自定义缩放设置的方法。
原存储与恢复代码如下:
// 存储到文件代码 IniFile.WriteInteger('FORM','Width',AForm.Width); IniFile.WriteInteger('FORM','Top',AForm.Top); // 从文件恢复代码 StoredHeight:= IniFile.readInteger('FORM','Height',AForm.Height); StoredWidth := IniFile.readInteger('FORM','Width',AForm.Width); if StoredWidth >= Screen.Width then AForm.Width := Screen.Width else begin AForm.Width := StoredWidth; if StoredLeft < 0 then AForm.left := 0 else AForm.left := StoredLeft; end; if StoredHeight >= Screen.Height then AForm.Height := Screen.Height else begin AForm.Height := StoredHeight; if StoredTop < 0 then AForm.Top := 0 else AForm.Top := StoredTop; end;
解决方案
1. 是否需要手动补偿缩放?
是的,必须手动处理缩放因子。因为Delphi的TForm的Width/Height/Top/Left属性返回的是经过系统缩放后的设备像素值,而我们需要存储的是窗体的逻辑像素值(不受缩放影响的原始尺寸)。如果直接存储设备像素,下次在相同缩放比例下恢复时,系统会再次对该值应用缩放,导致窗体尺寸被重复放大。
正确逻辑:
- 存储时:将设备像素值除以缩放因子,得到逻辑像素值后写入INI
- 恢复时:从INI读取逻辑像素值,乘以缩放因子,得到设备像素值后设置给窗体
2. 通过Windows API获取缩放因子的方法
可以使用GetDpiForWindow或GetDpiForMonitorAPI获取对应窗口/显示器的缩放DPI,再计算缩放因子(缩放因子 = DPI / 96,系统默认DPI为96)。
兼容Windows 10及以上的缩放因子获取代码
uses Winapi.Windows; function GetFormScaleFactor(AForm: TForm): Double; var Dpi: UINT; begin // 获取窗体所在显示器的DPI Dpi := GetDpiForWindow(AForm.Handle); // 计算缩放因子 Result := Dpi / 96.0; end;
兼容旧版Windows的缩放因子获取代码
uses Winapi.Windows; function GetFormScaleFactor(AForm: TForm): Double; var hMonitor: HMONITOR; hDC: HDC; DpiX: Integer; begin hMonitor := MonitorFromWindow(AForm.Handle, MONITOR_DEFAULTTONEAREST); hDC := CreateDC(nil, nil, nil, nil); try DpiX := GetDeviceCaps(hDC, LOGPIXELSX); Result := DpiX / 96.0; finally DeleteDC(hDC); end; end;
3. 修改后的存储与恢复代码
存储逻辑
var ScaleFactor: Double; begin ScaleFactor := GetFormScaleFactor(AForm); // 转换为逻辑像素后存储 IniFile.WriteInteger('FORM','Width', Round(AForm.Width / ScaleFactor)); IniFile.WriteInteger('FORM','Height', Round(AForm.Height / ScaleFactor)); IniFile.WriteInteger('FORM','Top', Round(AForm.Top / ScaleFactor)); IniFile.WriteInteger('FORM','Left', Round(AForm.Left / ScaleFactor)); end;
恢复逻辑
var ScaleFactor: Double; StoredWidth, StoredHeight, StoredTop, StoredLeft: Integer; begin ScaleFactor := GetFormScaleFactor(AForm); // 读取逻辑像素值 StoredWidth := IniFile.ReadInteger('FORM','Width', Round(AForm.Width / ScaleFactor)); StoredHeight := IniFile.ReadInteger('FORM','Height', Round(AForm.Height / ScaleFactor)); StoredTop := IniFile.ReadInteger('FORM','Top', Round(AForm.Top / ScaleFactor)); StoredLeft := IniFile.ReadInteger('FORM','Left', Round(AForm.Left / ScaleFactor)); // 转换为设备像素后设置 if StoredWidth >= Round(Screen.Width / ScaleFactor) then AForm.Width := Screen.Width else begin AForm.Width := Round(StoredWidth * ScaleFactor); if StoredLeft < 0 then AForm.Left := 0 else AForm.Left := Round(StoredLeft * ScaleFactor); end; if StoredHeight >= Round(Screen.Height / ScaleFactor) then AForm.Height := Screen.Height else begin AForm.Height := Round(StoredHeight * ScaleFactor); if StoredTop < 0 then AForm.Top := 0 else AForm.Top := Round(StoredTop * ScaleFactor); end; end;
内容的提问来源于stack exchange,提问作者UnDiUdin
相关产品推荐
相关产品推荐

