สิ่งสำคัญที่ควรทราบคือการให้เกียรติ DPI ของผู้ใช้เป็นเพียงส่วนย่อยของงานจริงของคุณ:
เคารพขนาดตัวอักษรของผู้ใช้
เป็นเวลาหลายสิบปีที่ Windows ได้แก้ไขปัญหานี้ด้วยแนวคิดที่มีการจัดวางรูปแบบโดยใช้Dialog Unitsแทนที่จะเป็นพิกเซล มีการกำหนด"หน่วยโต้ตอบ"เพื่อให้อักขระเฉลี่ยของแบบอักษรเป็น
- หน่วยโต้ตอบ 4 หน่วย (dlus) กว้างและ
- 8 หน่วยโต้ตอบ (คลัสเตอร์) สูง
Delphi มาพร้อมกับแนวคิด (buggy) Scaled
โดยที่แบบฟอร์มจะพยายามปรับโดยอัตโนมัติตามรูปแบบ
- การตั้งค่า Windows DPI ของผู้ใช้โองการ
- การตั้งค่า DPI บนเครื่องของนักพัฒนาซอฟต์แวร์ที่บันทึกแบบฟอร์มล่าสุด
นั่นไม่ได้ช่วยแก้ปัญหาเมื่อผู้ใช้ใช้ฟอนต์ที่แตกต่างจากที่คุณออกแบบฟอร์มเช่น:
หรือ
- นักพัฒนาออกแบบฟอร์มด้วย ** Tahoma 8pt * (โดยที่อักขระเฉลี่ยอยู่
5.94px x 13.00px
ที่ 96dpi)
- ผู้ใช้ที่รันด้วยSegoe UI 9pt (โดยที่อักขระเฉลี่ยอยู่
6.67px x 15px
ที่ 96dpi)
ในฐานะนักพัฒนาที่ดีคุณจะต้องปฏิบัติตามแบบอักษรของผู้ใช้ของคุณ ซึ่งหมายความว่าคุณต้องปรับขนาดตัวควบคุมทั้งหมดในแบบฟอร์มของคุณให้ตรงกับขนาดฟอนต์ใหม่:
- ขยายทุกอย่างในแนวนอน 12.29% (6.67 / 5.94)
- ยืดทุกอย่างในแนวตั้ง 15.38% (15/13)
Scaled
จะไม่จัดการสิ่งนี้ให้คุณ
มันแย่ลงเมื่อ:
- ออกแบบฟอร์มของคุณที่Segoe UI 9pt (ค่าเริ่มต้นของ Windows Vista, Windows 7, Windows 8)
- ผู้ใช้กำลังเรียกใช้Segoe UI 14pt (เช่นค่ากำหนดของฉัน) ซึ่งเป็นไฟล์
10.52px x 25px
ตอนนี้คุณต้องปรับขนาดทุกอย่าง
- ในแนวนอน 57.72%
- ในแนวตั้ง 66.66%
Scaled
จะไม่จัดการสิ่งนี้ให้คุณ
หากคุณฉลาดคุณจะเห็นว่าการให้เกียรติ DPI นั้นไม่สัมพันธ์กันอย่างไร:
- แบบฟอร์มที่ออกแบบด้วย Segoe UI 9pt @ 96dpi (6.67px x 15px)
- ผู้ใช้ที่รันด้วย Segoe UI 9pt @ 150dpi (10.52px x 25px)
คุณไม่ควรจะดูที่การตั้งค่า DPI ของผู้ใช้ที่คุณควรจะมองไปที่พวกเขาขนาดตัวอักษร ผู้ใช้สองคนกำลังทำงาน
- Segoe UI 14pt @ 96dpi (10.52px x 25px)
- Segoe UI 9pt @ 150dpi (10.52px x 25px)
กำลังเรียกใช้ตัวอักษรเดียวกัน DPI เป็นเพียงสิ่งหนึ่งที่มีผลต่อขนาดตัวอักษร ความชอบของผู้ใช้อื่น ๆ
StandardizeFormFont
โคลวิสสังเกตว่าฉันอ้างอิงฟังก์ชันStandardizeFormFont
ที่แก้ไขฟอนต์บนฟอร์มและปรับขนาดเป็นขนาดฟอนต์ใหม่ ไม่ใช่ฟังก์ชันมาตรฐาน แต่เป็นชุดฟังก์ชันทั้งหมดที่ช่วยให้งานง่าย ๆ ที่บอร์แลนด์ไม่เคยจัดการ
function StandardizeFormFont(AForm: TForm): Real;
var
preferredFontName: string;
preferredFontHeight: Integer;
begin
GetUserFontPreference({out}preferredFontName, {out}preferredFontHeight);
//e.g. "Segoe UI",
Result := Toolkit.StandardizeFormFont(AForm, PreferredFontName, PreferredFontHeight);
end;
Windows มี 6 แบบอักษรที่แตกต่างกัน ไม่มี "การตั้งค่าแบบอักษร" เดียวใน Windows
แต่เราทราบจากประสบการณ์ว่าแบบฟอร์มของเราควรเป็นไปตามการตั้งค่าแบบอักษรของชื่อไอคอน
procedure GetUserFontPreference(out FaceName: string; out PixelHeight: Integer);
var
font: TFont;
begin
font := Toolkit.GetIconTitleFont;
try
FaceName := font.Name; //e.g. "Segoe UI"
//Dogfood testing: use a larger font than we're used to; to force us to actually test it
if IsDebuggerPresent then
font.Size := font.Size+1;
PixelHeight := font.Height; //e.g. -16
finally
font.Free;
end;
end;
เมื่อเรารู้ว่าขนาดตัวอักษรที่เราจะปรับขนาดรูปแบบในการที่เราได้รับความสูงของตัวอักษรรูปแบบในปัจจุบัน ( พิกเซล ) และไต่ขึ้นโดยปัจจัยที่
ตัวอย่างเช่นหากฉันกำลังตั้งค่าแบบฟอร์ม-16
และขณะนี้ฟอร์มอยู่ที่-11
เราจำเป็นต้องปรับขนาดฟอร์มทั้งหมดโดย:
-16 / -11 = 1.45454%
การกำหนดมาตรฐานเกิดขึ้นในสองขั้นตอน ขั้นแรกปรับขนาดรูปแบบตามอัตราส่วนของขนาดตัวอักษรใหม่: เก่า จากนั้นเปลี่ยนการควบคุม (เรียกซ้ำ) เพื่อใช้แบบอักษรใหม่
function StandardizeFormFont(AForm: TForm; FontName: string; FontHeight: Integer): Real;
var
oldHeight: Integer;
begin
Assert(Assigned(AForm));
if (AForm.Scaled) then
begin
OutputDebugString(PChar('WARNING: StandardizeFormFont: Form "'+GetControlName(AForm)+'" is set to Scaled. Proper form scaling requires VCL scaling to be disabled, unless you implement scaling by overriding the protected ChangeScale() method of the form.'));
end;
if (AForm.AutoScroll) then
begin
if AForm.WindowState = wsNormal then
begin
OutputDebugString(PChar('WARNING: StandardizeFormFont: Form "'+GetControlName(AForm)+'" is set to AutoScroll. Form designed size will be suseptable to changes in Windows form caption height (e.g. 2000 vs XP).'));
if IsDebuggerPresent then
Windows.DebugBreak; //Some forms would like it (to fix maximizing problem)
end;
end;
if (not AForm.ShowHint) then
begin
AForm.ShowHint := True;
OutputDebugString(PChar('INFORMATION: StandardizeFormFont: Turning on form "'+GetControlName(AForm)+'" hints. (ShowHint := True)'));
if IsDebuggerPresent then
Windows.DebugBreak; //Some forms would like it (to fix maximizing problem)
end;
oldHeight := AForm.Font.Height;
//Scale the form to the new font size
// if (FontHeight <> oldHeight) then For compatibility, it's safer to trigger a call to ChangeScale, since a lot of people will be assuming it always is called
begin
ScaleForm(AForm, FontHeight, oldHeight);
end;
//Now change all controls to actually use the new font
Toolkit.StandardizeFont_ControlCore(AForm, g_ForceClearType, FontName, FontHeight,
AForm.Font.Name, AForm.Font.Size);
//Return the scaling ratio, so any hard-coded values can be multiplied
Result := FontHeight / oldHeight;
end;
นี่คืองานของการปรับขนาดแบบฟอร์ม มันทำงานกับจุดบกพร่องในForm.ScaleBy
วิธีการของบอร์แลนด์ ก่อนอื่นต้องปิดการใช้งานจุดยึดทั้งหมดในแบบฟอร์มจากนั้นทำการปรับขนาดจากนั้นเปิดใช้งานจุดยึดอีกครั้ง:
TAnchorsArray = array of TAnchors;
procedure ScaleForm(const AForm: TForm; const M, D: Integer);
var
aAnchorStorage: TAnchorsArray;
RectBefore, RectAfter: TRect;
x, y: Integer;
monitorInfo: TMonitorInfo;
workArea: TRect;
begin
if (M = 0) and (D = 0) then
Exit;
RectBefore := AForm.BoundsRect;
SetLength(aAnchorStorage, 0);
aAnchorStorage := DisableAnchors(AForm);
try
AForm.ScaleBy(M, D);
finally
EnableAnchors(AForm, aAnchorStorage);
end;
RectAfter := AForm.BoundsRect;
case AForm.Position of
poScreenCenter, poDesktopCenter, poMainFormCenter, poOwnerFormCenter,
poDesigned: //i think i really want everything else to also follow the nudging rules...why did i exclude poDesigned
begin
//This was only nudging by one quarter the difference, rather than one half the difference
// x := RectAfter.Left - ((RectAfter.Right-RectBefore.Right) div 2);
// y := RectAfter.Top - ((RectAfter.Bottom-RectBefore.Bottom) div 2);
x := RectAfter.Left - ((RectAfter.Right-RectAfter.Left) - (RectBefore.Right-RectBefore.Left)) div 2;
y := RectAfter.Top - ((RectAfter.Bottom-RectAfter.Top)-(RectBefore.Bottom-RectBefore.Top)) div 2;
end;
else
//poDesigned, poDefault, poDefaultPosOnly, poDefaultSizeOnly:
x := RectAfter.Left;
y := RectAfter.Top;
end;
if AForm.Monitor <> nil then
begin
monitorInfo.cbSize := SizeOf(monitorInfo);
if GetMonitorInfo(AForm.Monitor.Handle, @monitorInfo) then
workArea := monitorInfo.rcWork
else
begin
OutputDebugString(PChar(SysErrorMessage(GetLastError)));
workArea := Rect(AForm.Monitor.Left, AForm.Monitor.Top, AForm.Monitor.Left+AForm.Monitor.Width, AForm.Monitor.Top+AForm.Monitor.Height);
end;
// If the form is off the right or bottom of the screen then we need to pull it back
if RectAfter.Right > workArea.Right then
x := workArea.Right - (RectAfter.Right-RectAfter.Left); //rightEdge - widthOfForm
if RectAfter.Bottom > workArea.Bottom then
y := workArea.Bottom - (RectAfter.Bottom-RectAfter.Top); //bottomEdge - heightOfForm
x := Max(x, workArea.Left); //don't go beyond left edge
y := Max(y, workArea.Top); //don't go above top edge
end
else
begin
x := Max(x, 0); //don't go beyond left edge
y := Max(y, 0); //don't go above top edge
end;
AForm.SetBounds(x, y,
RectAfter.Right-RectAfter.Left, //Width
RectAfter.Bottom-RectAfter.Top); //Height
end;
จากนั้นเราต้องใช้แบบอักษรใหม่ซ้ำ ๆ:
procedure StandardizeFont_ControlCore(AControl: TControl; ForceClearType: Boolean;
FontName: string; FontSize: Integer;
ForceFontIfName: string; ForceFontIfSize: Integer);
const
CLEARTYPE_QUALITY = 5;
var
i: Integer;
RunComponent: TComponent;
AControlFont: TFont;
begin
if not Assigned(AControl) then
Exit;
if (AControl is TStatusBar) then
begin
TStatusBar(AControl).UseSystemFont := False; //force...
TStatusBar(AControl).UseSystemFont := True; //...it
end
else
begin
AControlFont := Toolkit.GetControlFont(AControl);
if not Assigned(AControlFont) then
Exit;
StandardizeFont_ControlFontCore(AControlFont, ForceClearType,
FontName, FontSize,
ForceFontIfName, ForceFontIfSize);
end;
{ If a panel has a toolbar on it, the toolbar won't paint properly. So this idea won't work.
if (not Toolkit.IsRemoteSession) and (AControl is TWinControl) and (not (AControl is TToolBar)) then
TWinControl(AControl).DoubleBuffered := True;
}
//Iterate children
for i := 0 to AControl.ComponentCount-1 do
begin
RunComponent := AControl.Components[i];
if RunComponent is TControl then
StandardizeFont_ControlCore(
TControl(RunComponent), ForceClearType,
FontName, FontSize,
ForceFontIfName, ForceFontIfSize);
end;
end;
เมื่อจุดยึดถูกปิดใช้งานซ้ำ:
function DisableAnchors(ParentControl: TWinControl): TAnchorsArray;
var
StartingIndex: Integer;
begin
StartingIndex := 0;
DisableAnchors_Core(ParentControl, Result, StartingIndex);
end;
procedure DisableAnchors_Core(ParentControl: TWinControl; var aAnchorStorage: TAnchorsArray; var StartingIndex: Integer);
var
iCounter: integer;
ChildControl: TControl;
begin
if (StartingIndex+ParentControl.ControlCount+1) > (Length(aAnchorStorage)) then
SetLength(aAnchorStorage, StartingIndex+ParentControl.ControlCount+1);
for iCounter := 0 to ParentControl.ControlCount - 1 do
begin
ChildControl := ParentControl.Controls[iCounter];
aAnchorStorage[StartingIndex] := ChildControl.Anchors;
//doesn't work for set of stacked top-aligned panels
// if ([akRight, akBottom ] * ChildControl.Anchors) <> [] then
// ChildControl.Anchors := [akLeft, akTop];
if (ChildControl.Anchors) <> [akTop, akLeft] then
ChildControl.Anchors := [akLeft, akTop];
// if ([akTop, akBottom] * ChildControl.Anchors) = [akTop, akBottom] then
// ChildControl.Anchors := ChildControl.Anchors - [akBottom];
Inc(StartingIndex);
end;
//Add children
for iCounter := 0 to ParentControl.ControlCount - 1 do
begin
ChildControl := ParentControl.Controls[iCounter];
if ChildControl is TWinControl then
DisableAnchors_Core(TWinControl(ChildControl), aAnchorStorage, StartingIndex);
end;
end;
และจุดยึดถูกเปิดใช้งานซ้ำ:
procedure EnableAnchors(ParentControl: TWinControl; aAnchorStorage: TAnchorsArray);
var
StartingIndex: Integer;
begin
StartingIndex := 0;
EnableAnchors_Core(ParentControl, aAnchorStorage, StartingIndex);
end;
procedure EnableAnchors_Core(ParentControl: TWinControl; aAnchorStorage: TAnchorsArray; var StartingIndex: Integer);
var
iCounter: integer;
ChildControl: TControl;
begin
for iCounter := 0 to ParentControl.ControlCount - 1 do
begin
ChildControl := ParentControl.Controls[iCounter];
ChildControl.Anchors := aAnchorStorage[StartingIndex];
Inc(StartingIndex);
end;
//Restore children
for iCounter := 0 to ParentControl.ControlCount - 1 do
begin
ChildControl := ParentControl.Controls[iCounter];
if ChildControl is TWinControl then
EnableAnchors_Core(TWinControl(ChildControl), aAnchorStorage, StartingIndex);
end;
end;
ด้วยการทำงานของการเปลี่ยนแบบอักษรตัวควบคุมที่เหลือเป็น:
procedure StandardizeFont_ControlFontCore(AControlFont: TFont; ForceClearType: Boolean;
FontName: string; FontSize: Integer;
ForceFontIfName: string; ForceFontIfSize: Integer);
const
CLEARTYPE_QUALITY = 5;
var
CanChangeName: Boolean;
CanChangeSize: Boolean;
lf: TLogFont;
begin
if not Assigned(AControlFont) then
Exit;
{$IFDEF ForceClearType}
ForceClearType := True;
{$ELSE}
if g_ForceClearType then
ForceClearType := True;
{$ENDIF}
//Standardize the font if it's currently
// "MS Shell Dlg 2" (meaning whoever it was opted into the 'change me' system
// "MS Sans Serif" (the Delphi default)
// "Tahoma" (when they wanted to match the OS, but "MS Shell Dlg 2" should have been used)
// "MS Shell Dlg" (the 9x name)
CanChangeName :=
(FontName <> '')
and
(AControlFont.Name <> FontName)
and
(
(
(ForceFontIfName <> '')
and
(AControlFont.Name = ForceFontIfName)
)
or
(
(ForceFontIfName = '')
and
(
(AControlFont.Name = 'MS Sans Serif') or
(AControlFont.Name = 'Tahoma') or
(AControlFont.Name = 'MS Shell Dlg 2') or
(AControlFont.Name = 'MS Shell Dlg')
)
)
);
CanChangeSize :=
(
//there is a font size
(FontSize <> 0)
and
(
//the font is at it's default size, or we're specifying what it's default size is
(AControlFont.Size = 8)
or
((ForceFontIfSize <> 0) and (AControlFont.Size = ForceFontIfSize))
)
and
//the font size (or height) is not equal
(
//negative for height (px)
((FontSize < 0) and (AControlFont.Height <> FontSize))
or
//positive for size (pt)
((FontSize > 0) and (AControlFont.Size <> FontSize))
)
and
//no point in using default font's size if they're not using the face
(
(AControlFont.Name = FontName)
or
CanChangeName
)
);
if CanChangeName or CanChangeSize or ForceClearType then
begin
if GetObject(AControlFont.Handle, SizeOf(TLogFont), @lf) <> 0 then
begin
//Change the font attributes and put it back
if CanChangeName then
StrPLCopy(Addr(lf.lfFaceName[0]), FontName, LF_FACESIZE);
if CanChangeSize then
lf.lfHeight := FontSize;
if ForceClearType then
lf.lfQuality := CLEARTYPE_QUALITY;
AControlFont.Handle := CreateFontIndirect(lf);
end
else
begin
if CanChangeName then
AControlFont.Name := FontName;
if CanChangeSize then
begin
if FontSize > 0 then
AControlFont.Size := FontSize
else if FontSize < 0 then
AControlFont.Height := FontSize;
end;
end;
end;
end;
นั่นเป็นโค้ดที่มากกว่าที่คุณคิดไว้ ฉันรู้ว่า. สิ่งที่น่าเศร้าคือไม่มีผู้พัฒนา Delphi บนโลกนี้ยกเว้นฉันซึ่งทำให้แอปพลิเคชันของพวกเขาถูกต้อง
เรียนผู้พัฒนา Delphi : ตั้งค่าแบบอักษร Windows ของคุณเป็นSegoe UI 14ptและแก้ไขแอปพลิเคชัน buggy ของคุณ
หมายเหตุ : รหัสใด ๆ ถูกเผยแพร่สู่สาธารณสมบัติ ไม่จำเป็นต้องแสดงที่มา
SetProcessDPIAware
ที่จะเป็น