ฉันจะทำให้ GUI ทำงานได้ดีได้อย่างไรเมื่อการปรับขนาดแบบอักษรของ Windows มากกว่า 100%


108

เมื่อเลือกขนาดตัวอักษรขนาดใหญ่ในแผงควบคุมของ Windows (เช่น 125% หรือ 150%) จะมีปัญหาในแอปพลิเคชัน VCL ทุกครั้งที่มีการตั้งค่าแบบพิกเซล

ใช้TStatusBar.Panel. ฉันตั้งค่าความกว้างเพื่อให้มีป้ายกำกับเดียวตอนนี้มีแบบอักษรขนาดใหญ่ป้ายกำกับ "ล้น" ปัญหาเดียวกันกับส่วนประกอบอื่น ๆ

แล็ปท็อปใหม่บางรุ่นจาก Dell จัดส่งไปแล้ว 125% เป็นค่าเริ่มต้นดังนั้นในอดีตปัญหานี้ค่อนข้างหายากตอนนี้มันสำคัญมาก

จะทำอะไรได้บ้างเพื่อเอาชนะปัญหานี้?

คำตอบ:


56

หมายเหตุ: โปรดดูคำตอบอื่น ๆ เนื่องจากมีเทคนิคที่มีประโยชน์มาก คำตอบของฉันที่นี่มีเพียงคำเตือนและข้อควรระวังในการสมมติว่าการรับรู้ DPI เป็นเรื่องง่าย

โดยทั่วไปฉันจะหลีกเลี่ยงการปรับขนาด DPI โดยใช้TForm.Scaled = True. การรับรู้ DPI มีความสำคัญต่อฉันก็ต่อเมื่อมีความสำคัญต่อลูกค้าที่โทรหาฉันและยินดีจ่ายเงิน เหตุผลทางเทคนิคเบื้องหลังมุมมองนั้นคือการรับรู้ DPI หรือไม่คุณกำลังเปิดหน้าต่างสู่โลกแห่งความเจ็บปวด การควบคุม VCL มาตรฐานและบุคคลที่สามจำนวนมากทำงานได้ไม่ดีใน High DPI ข้อยกเว้นที่น่าสังเกตคือชิ้นส่วน VCL ที่รวม Windows Common Controls ทำงานได้ดีอย่างน่าทึ่งที่ DPI สูง การควบคุมแบบกำหนดเองของบุคคลที่สามและ Delphi VCL ในตัวจำนวนมากทำงานได้ไม่ดีหรือที่ DPI สูง หากคุณวางแผนที่จะเปิด TForm การปรับขนาดให้แน่ใจว่าได้ทดสอบที่ 96, 125 และ 150 DPI สำหรับทุกรูปแบบเดียวในโปรเจ็กต์ของคุณและบุคคลที่สามรายเดียวและการควบคุมในตัวที่คุณใช้

เดลฟีเองเขียนด้วยภาษาเดลฟี มีการเปิดแฟล็กการรับรู้ DPI สูงสำหรับรูปแบบส่วนใหญ่แม้ว่าเมื่อเร็ว ๆ นี้ใน Delphi XE2 ผู้เขียน IDE เองก็ตัดสินใจที่จะไม่เปิดการตั้งค่าสถานะรายการการรับรู้ DPI สูง โปรดทราบว่าใน Delphi XE4 และใหม่กว่าธงการรับรู้ DPI สูงจะเปิดอยู่และ IDE ก็ดูดี

ฉันขอแนะนำว่าคุณอย่าใช้ TForm.Scaled = true (ซึ่งเป็นค่าเริ่มต้นใน Delphi ดังนั้นหากคุณไม่ได้แก้ไขแบบฟอร์มส่วนใหญ่ของคุณจะมี Scaled = true) พร้อมกับแฟล็ก DPI Aware สูง (ดังแสดงในคำตอบของ David) ด้วย แอปพลิเคชัน VCL ที่สร้างขึ้นโดยใช้ตัวออกแบบฟอร์มเดลฟีในตัว

ที่ผ่านมาฉันได้ลองทำตัวอย่างน้อยที่สุดของการแตกหักที่คุณคาดหวังว่าจะได้เห็นเมื่อ TForm.Scaled เป็นจริงและเมื่อการปรับขนาดแบบฟอร์ม Delphi มีข้อผิดพลาด ข้อบกพร่องเหล่านี้ไม่ได้เกิดขึ้นเสมอไปและถูกเรียกใช้โดยค่า DPI ที่ไม่ใช่ 96 เท่านั้นฉันไม่สามารถระบุรายการอื่น ๆ ทั้งหมดได้ซึ่งรวมถึงการเปลี่ยนแปลงขนาดฟอนต์ของ Windows XP แต่เนื่องจากข้อบกพร่องเหล่านี้ส่วนใหญ่ปรากฏเฉพาะในแอปพลิเคชันของฉันเองในสถานการณ์ที่ค่อนข้างซับซ้อนฉันจึงตัดสินใจแสดงหลักฐานบางอย่างที่คุณสามารถยืนยันตัวเองได้

Delphi XE จะมีลักษณะเช่นนี้เมื่อคุณตั้งค่า DPI Scaling เป็น "Fonts @ 200%" ใน Windows 7 และ Delphi XE2 เสียใน Windows 7 และ 8 ในทำนองเดียวกัน แต่ข้อบกพร่องเหล่านี้ดูเหมือนจะได้รับการแก้ไขเมื่อ Delphi XE4:

ใส่คำอธิบายภาพที่นี่

ใส่คำอธิบายภาพที่นี่

สิ่งเหล่านี้ส่วนใหญ่เป็นการควบคุม VCL มาตรฐานที่ทำงานผิดปกติที่ DPI สูง โปรดทราบว่าสิ่งต่างๆส่วนใหญ่ไม่ได้รับการปรับขนาดเลยดังนั้นผู้พัฒนา Delphi IDE จึงตัดสินใจที่จะเพิกเฉยต่อการรับรู้ DPI รวมถึงการปิด DPI virtualization เป็นทางเลือกที่น่าสนใจ

ปิด DPI virtualization เฉพาะเมื่อต้องการแหล่งความเจ็บปวดเพิ่มเติมใหม่และทางเลือกที่ยาก ฉันขอแนะนำให้คุณปล่อยไว้คนเดียว โปรดทราบว่าการควบคุมทั่วไปของ Windows ส่วนใหญ่ดูเหมือนจะทำงานได้ดี โปรดสังเกตว่าตัวควบคุม Delphi data-explorer เป็น C # WinForms wrapper รอบ ๆ ตัวควบคุมทั่วไปของ Windows Tree มาตรฐาน นั่นเป็นความผิดพลาดของ Microsoft อย่างแท้จริงและการแก้ไขอาจต้องใช้ Embarcadero เพื่อเขียนการควบคุมต้นไม้. แม้แต่ Microsoft WinForms ก็ไม่สามารถจัดการ DPI ที่สูงได้อย่างหมดจดโดยอัตโนมัติและไม่มีรหัส kludge ที่กำหนดเอง

อัปเดต: ข้อเท็จจริงที่น่าสนใจ: แม้ว่า delphi IDE ดูเหมือนจะไม่เป็น "เวอร์ชวลไลซ์" แต่ก็ไม่ได้ใช้เนื้อหารายการที่ David แสดงเพื่อให้ได้ "non-DPI-virtualization" บางทีอาจใช้ฟังก์ชัน API บางอย่างในขณะรันไทม์

อัปเดต 2: เพื่อตอบสนองต่อวิธีที่ฉันจะรองรับ DPI 100% / 125% ฉันจะคิดแผนสองเฟส ขั้นตอนที่ 1 คือการจัดเก็บรหัสของฉันสำหรับการควบคุมแบบกำหนดเองที่ต้องได้รับการแก้ไขสำหรับ DPI ที่สูงจากนั้นวางแผนที่จะแก้ไขหรือตัดทอนออก ขั้นตอนที่ 2 จะใช้พื้นที่บางส่วนของโค้ดของฉันซึ่งได้รับการออกแบบเป็นแบบฟอร์มโดยไม่มีการจัดการเค้าโครงและเปลี่ยนเป็นรูปแบบที่ใช้การจัดการเค้าโครงบางประเภทเพื่อให้การเปลี่ยนแปลง DPI หรือความสูงของแบบอักษรสามารถทำงานได้โดยไม่ต้องตัด ฉันสงสัยว่างานเค้าโครง "การควบคุมระหว่างกัน" นี้จะซับซ้อนกว่าในแอปพลิเคชันส่วนใหญ่มากกว่างาน "การควบคุมภายใน"

อัปเดต:ในปี 2559 Delphi 10.1 Berlin ล่าสุดทำงานได้ดีบนเวิร์กสเตชัน 150 dpi ของฉัน


5
ฟังก์ชัน API SetProcessDPIAwareที่จะเป็น
David Heffernan

2
ยอดเยี่ยม. ขอบคุณสำหรับ factoid ใหม่ ฉันขอแนะนำให้คุณแก้ไขคำตอบของคุณเพื่อแนะนำเส้นทางที่เป็นไปได้ อาจเป็นไปได้ว่าลูกค้าอาจต้องการกำหนดค่าตัวเลือกนั้น (ปิดหากไม่ได้ผลสำหรับพวกเขา)
Warren P

หน้าจอสแปลชของ Delphi ใช้ DPI Virtualization อาจเป็นเพราะการเรียก SetDPIAware เกิดขึ้นหลังจากที่มีการแสดงแบบฟอร์ม Splash แล้ว
Warren P

6
RAD Studio เป็นการผสมผสานระหว่างการควบคุม VCL มาตรฐานการควบคุมแบบกำหนดเองรูปแบบ. NET WinForms และ FireMonkey ไม่น่าแปลกใจที่มีปัญหา และนั่นคือสาเหตุที่ RAD Studio ไม่ใช่ตัวอย่างที่ดี
Torbins

1
ถ้าคุณพูดถูกก็คือ VCL นั่นเองที่จมอยู่ในทราย แม้แต่ไมโครซอฟท์ก็ยังก้มหน้าอยู่บนผืนทราย เฟรมเวิร์กเดียวที่ฉันเคยใช้ที่ทำงานผ่านระยะไกลได้ที่นี่คือ COCOA บน Mac
Warren P

64

การตั้งค่าของคุณในไฟล์ .dfm จะได้รับการปรับขนาดขึ้นอย่างถูกต้องตราบใดที่เป็นScaledTrue

หากคุณกำลังตั้งมิติในรหัสแล้วคุณจะต้องปรับขนาดพวกเขาโดยการหารด้วยScreen.PixelsPerInch Form.PixelsPerInchใช้MulDivทำสิ่งนี้

function TMyForm.ScaleDimension(const X: Integer): Integer;
begin
  Result := MulDiv(X, Screen.PixelsPerInch, PixelsPerInch);
end;

นี่คือสิ่งที่กรอบรูปแบบการติดตาไม่เมื่อเป็นScaledTrue

ในความเป็นจริงคุณสามารถสร้างอาร์กิวเมนต์แบบฟันเฟืองเพื่อแทนที่ฟังก์ชันนี้ด้วยเวอร์ชันที่ฮาร์ดโค้ดเป็นค่า 96 สำหรับตัวส่วน สิ่งนี้ช่วยให้คุณใช้ค่ามิติข้อมูลสัมบูรณ์และไม่ต้องกังวลว่าความหมายจะเปลี่ยนไปหากคุณเปลี่ยนมาตราส่วนแบบอักษรในเครื่องพัฒนาของคุณและบันทึกไฟล์. dfm อีกครั้ง สาเหตุที่สำคัญคือPixelsPerInchคุณสมบัติที่จัดเก็บในไฟล์. pdfm คือค่าของเครื่องที่บันทึกไฟล์. pdfm ครั้งล่าสุด

const
  SmallFontsPixelsPerInch = 96;

function ScaleFromSmallFontsDimension(const X: Integer): Integer;
begin
  Result := MulDiv(X, Screen.PixelsPerInch, SmallFontsPixelsPerInch);
end;

ดังนั้นการดำเนินการตามธีมต่อไปสิ่งที่ต้องระวังอีกอย่างหนึ่งก็คือหากโปรเจ็กต์ของคุณได้รับการพัฒนาบนเครื่องหลายเครื่องที่มีค่า DPI ที่แตกต่างกันคุณจะพบว่าการปรับขนาดที่ Delphi ใช้เมื่อบันทึกไฟล์. pdfm ส่งผลให้การควบคุมหลงทางในการแก้ไขหลายชุด . ที่ที่ทำงานของฉันเพื่อหลีกเลี่ยงปัญหานี้เรามีนโยบายที่เข้มงวดว่าจะแก้ไขแบบฟอร์มที่ 96dpi เท่านั้น (ปรับขนาด 100%)

ในความเป็นจริงเวอร์ชันของฉันScaleFromSmallFontsDimensionยังให้ค่าเผื่อความเป็นไปได้ของแบบอักษรในรูปแบบที่แตกต่างกันที่รันไทม์จากชุดนั้นในเวลาที่กำหนด บนเครื่อง XP แบบฟอร์มแอปพลิเคชันของฉันใช้ 8pt Tahoma บน Vista ขึ้นไป 9pt Segoe UI ใช้ สิ่งนี้ให้เสรีภาพอีกระดับหนึ่ง การปรับมาตราส่วนต้องคำนึงถึงสิ่งนี้เนื่องจากค่ามิติสัมบูรณ์ที่ใช้ในซอร์สโค้ดจะถือว่าสัมพันธ์กับค่าพื้นฐานของ 8pt Tahoma ที่ 96dpi

หากคุณใช้ภาพหรือร่ายมนตร์ใด ๆ ใน UI ของคุณสิ่งเหล่านี้จำเป็นต้องปรับขนาดด้วย ตัวอย่างทั่วไปคือร่ายมนตร์ที่ใช้บนแถบเครื่องมือและเมนู คุณจะต้องให้สัญลักษณ์เหล่านี้เป็นทรัพยากรไอคอนที่เชื่อมโยงกับไฟล์ปฏิบัติการของคุณ แต่ละไอคอนควรมีหลายขนาดจากนั้นเมื่อรันไทม์คุณเลือกขนาดที่เหมาะสมที่สุดและโหลดลงในรายการรูปภาพ สามารถดูรายละเอียดบางอย่างเกี่ยวกับหัวข้อนั้นได้ที่นี่: ฉันจะโหลดไอคอนจากแหล่งข้อมูลโดยไม่ต้องใช้นามแฝงได้อย่างไร

อีกเคล็ดลับที่เป็นประโยชน์ในการกำหนดขนาดในหน่วยญาติเทียบกับหรือTextWidth TextHeightดังนั้นหากคุณต้องการให้บางสิ่งมีขนาดประมาณ 10 เส้นแนวตั้งคุณสามารถ10*Canvas.TextHeight('Ag')ใช้ได้ นี่เป็นเมตริกที่หยาบและพร้อมใช้งานมากเนื่องจากไม่อนุญาตให้มีการเว้นบรรทัดและอื่น ๆ แต่มักจะเป็นสิ่งที่คุณต้องทำคือการสามารถที่จะจัดให้มีการชั่งน้ำหนักว่า GUI PixelsPerInchได้อย่างถูกต้องด้วย

นอกจากนี้คุณควรทำเครื่องหมายใบสมัครของคุณเป็นDPI สูงตระหนัก วิธีที่ดีที่สุดคือผ่านรายการแอปพลิเคชัน เนื่องจากเครื่องมือสร้างของ Delphi ไม่อนุญาตให้คุณปรับแต่งรายการคุณจึงใช้สิ่งนี้บังคับให้คุณเชื่อมโยงทรัพยากรรายการของคุณเอง

<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <asmv3:application xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
    <asmv3:windowsSettings
         xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
      <dpiAware>true</dpiAware>
    </asmv3:windowsSettings>
  </asmv3:application>
</assembly>

สคริปต์ทรัพยากรมีลักษณะดังนี้:

1 24 "Manifest.txt"

ที่Manifest.txtมีรายการจริง คุณจะต้องรวมส่วน comctl32 v6 และตั้งค่าrequestedExecutionLevelเป็นasInvoker. จากนั้นคุณเชื่อมโยงทรัพยากรที่คอมไพล์นี้กับแอปของคุณและตรวจสอบให้แน่ใจว่า Delphi ไม่ได้พยายามทำสิ่งเดียวกันกับรายการ ใน Delphi สมัยใหม่คุณสามารถทำได้โดยการตั้งค่าตัวเลือกโปรเจ็กต์ Runtime Themes เป็น None

ไฟล์ Manifest เป็นวิธีที่ถูกต้องในการประกาศให้แอปของคุณมี DPI สูง หากคุณต้องการทดลองใช้งานอย่างรวดเร็วโดยไม่ต้องยุ่งกับไฟล์ Manifest โทรSetProcessDPIAware. ให้ทำเป็นสิ่งแรกที่คุณทำเมื่อแอปของคุณทำงาน โดยเฉพาะอย่างยิ่งในส่วนการเริ่มต้นหน่วยแรกหรือเป็นสิ่งแรกในไฟล์. dpr ของคุณ

หากคุณไม่ประกาศว่าแอปของคุณมี DPI สูงจากนั้น Vista ขึ้นไปจะแสดงผลในโหมดเดิมสำหรับแบบอักษรใด ๆ ที่มีขนาดสูงกว่า 125% นี่ดูน่ากลัวทีเดียว พยายามหลีกเลี่ยงการตกหลุมพรางนั้น

Windows 8.1 ต่อการอัปเดต DPI ของจอภาพ

สำหรับ Windows 8.1 ตอนนี้ระบบปฏิบัติการรองรับการตั้งค่า DPI ต่อจอภาพ ( http://msdn.microsoft.com/en-ca/magazine/dn574798.aspx ) นี่เป็นปัญหาใหญ่สำหรับอุปกรณ์สมัยใหม่ซึ่งอาจมีจอแสดงผลที่แตกต่างกันมาพร้อมกับความสามารถที่แตกต่างกันมาก คุณอาจมีหน้าจอแล็ปท็อป DPI ที่สูงมากและโปรเจ็กเตอร์ภายนอก DPI ต่ำ การสนับสนุนสถานการณ์ดังกล่าวใช้เวลาทำงานมากกว่าที่อธิบายไว้ข้างต้น


2
นั่นไม่จริงเสมอไป ในความเป็นจริงการตั้งค่ามาตราส่วน = true จากนั้นการตั้งค่า DPI สูงอาจทำให้เกิดการแตกแปลก ๆ ในแอปพลิเคชันเดลฟีส่วนใหญ่ ฉันใช้เวลาหลายร้อยชั่วโมงในการพยายามให้แอปทำงานใน DPI ที่สูงและพบว่าการมีพิกเซลที่ดูแย่กว่าการควบคุมการครอบตัดย้ายออกจากหน้าจอแถบเลื่อนพิเศษหรือขาดหายไปในการควบคุมต่างๆ ฯลฯ
Warren P

@WarrenP ฉันคิดว่าปัญหาเหล่านั้นเกิดขึ้นกับแอปของคุณโดยเฉพาะ ประสบการณ์ส่วนตัวของฉันคือแอป Delphi ของฉันแสดงและปรับขนาดได้อย่างสมบูรณ์แบบแม้จะปรับขนาดแบบอักษร 200%
David Heffernan

2
@WarrenP แล้วไง เป็นไปได้อย่างสมบูรณ์แบบที่จะใช้ Delphi เพื่อสร้างแอปที่ทำงานได้ดีกว่า Delphi IDE
David Heffernan

1
ฉันได้เห็นกล่องโต้ตอบจำนวนมากที่มีเส้นขอบคงที่ที่สร้างขึ้นด้วย Delphi 5,6,7 และการตั้งค่าที่ปรับขนาดเป็นจริงล้มเหลว การซ่อนตกลงปุ่มยกเลิก ฯลฯ แม้แต่กล่องโต้ตอบบางรายการใน Delphi2006 ก็คิดว่าถูกกัดโดยสิ่งนี้ การผสมส่วนประกอบ Delphi ดั้งเดิมและส่วนประกอบของ windows ยังให้เอฟเฟกต์แปลก ๆ ฉันมักจะพัฒนา GUI ในการปรับขนาดฟอนต์ 125% และทำให้คุณสมบัติที่ปรับขนาดเป็นเท็จ
LU RD

2
สิ่งที่ดี +1 สำหรับข้อมูลที่ยอดเยี่ยม ความคิดเห็นของฉัน (อย่าทำ) มีความสำคัญรองลงมาจากความจำเป็นที่จะต้องรู้ว่าจะทำอย่างไรเมื่อคุณต้องการทำสิ่งนี้ ...
Warren P

42

สิ่งสำคัญที่ควรทราบคือการให้เกียรติ DPI ของผู้ใช้เป็นเพียงส่วนย่อยของงานจริงของคุณ:

เคารพขนาดตัวอักษรของผู้ใช้

เป็นเวลาหลายสิบปีที่ Windows ได้แก้ไขปัญหานี้ด้วยแนวคิดที่มีการจัดวางรูปแบบโดยใช้Dialog Unitsแทนที่จะเป็นพิกเซล มีการกำหนด"หน่วยโต้ตอบ"เพื่อให้อักขระเฉลี่ยของแบบอักษรเป็น

  • หน่วยโต้ตอบ 4 หน่วย (dlus) กว้างและ
  • 8 หน่วยโต้ตอบ (คลัสเตอร์) สูง

ใส่คำอธิบายภาพที่นี่

Delphi มาพร้อมกับแนวคิด (buggy) Scaledโดยที่แบบฟอร์มจะพยายามปรับโดยอัตโนมัติตามรูปแบบ

  • การตั้งค่า Windows DPI ของผู้ใช้โองการ
  • การตั้งค่า DPI บนเครื่องของนักพัฒนาซอฟต์แวร์ที่บันทึกแบบฟอร์มล่าสุด

นั่นไม่ได้ช่วยแก้ปัญหาเมื่อผู้ใช้ใช้ฟอนต์ที่แตกต่างจากที่คุณออกแบบฟอร์มเช่น:

  • ผู้พัฒนาออกแบบฟอร์มด้วยMS Sans Serif 8pt (โดยที่ค่าเฉลี่ยอยู่6.21px x 13.00pxที่ 96dpi)
  • ผู้ใช้ที่รันด้วยTahoma 8pt (โดยที่อักขระเฉลี่ยอยู่5.94px x 13.00pxที่ 96dpi)

    เช่นเดียวกับผู้ที่พัฒนาแอปพลิเคชันสำหรับ Windows 2000 หรือ Windows XP

หรือ

  • นักพัฒนาออกแบบฟอร์มด้วย ** 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 ของคุณ

หมายเหตุ : รหัสใด ๆ ถูกเผยแพร่สู่สาธารณสมบัติ ไม่จำเป็นต้องแสดงที่มา


1
ขอบคุณสำหรับคำตอบ แต่คุณแนะนำอะไรสำหรับโลกแห่งความจริง ปรับขนาดการควบคุมทั้งหมดด้วยตนเองหรือไม่
LaBracca

3
"สิ่งที่น่าเศร้าคือไม่มีผู้พัฒนา Delphi บนโลกนี้ยกเว้นฉันที่ทำให้แอปพลิเคชันของพวกเขาถูกต้อง" นั่นเป็นคำพูดที่หยิ่งผยองซึ่งไม่ถูกต้อง จากคำตอบของฉัน: อันที่จริง ScaleFromSmallFontsDimension เวอร์ชันของฉันยังให้การเผื่อความเป็นไปได้ของแบบอักษรในรูปแบบที่แตกต่างกันที่รันไทม์จากชุดนั้นที่ designtime การปรับมาตราส่วนต้องคำนึงถึงสิ่งนี้เนื่องจากค่ามิติสัมบูรณ์ที่ใช้ในซอร์สโค้ดจะถือว่าสัมพันธ์กับค่าพื้นฐานของ 8pt Tahoma ที่ 96dpi ของคุณคือคำตอบที่ดีในใจคุณ +1
David Heffernan

1
@ เอียนไม่ใช่ฉันที่พูดแบบนั้น ฟังดูเหมือนวอร์เรน
David Heffernan

2
นี่มันยอดเยี่ยมทีเดียวเอียน ขอบคุณ.
Warren P

2
เมื่อเร็ว ๆ นี้พบคำถามและคำตอบนี้ ฉันได้รวบรวมรหัสทั้งหมดของ Ian ไว้ในหน่วยการทำงานที่นี่: pastebin.com/dKpfnXLc และโพสต์เกี่ยวกับการแข่งขันบน Google+ ที่นี่: goo.gl/0ARdq9โพสต์ที่นี่เผื่อว่าใครเห็นว่ามีประโยชน์
W.Prins

11

นี่คือของขวัญของฉัน ฟังก์ชันที่สามารถช่วยคุณในการวางตำแหน่งองค์ประกอบแนวนอนในเค้าโครง GUI ของคุณ ฟรีสำหรับทุก.

function CenterInParent(Place,NumberOfPlaces,ObjectWidth,ParentWidth,CropPercent: Integer): Integer;
  {returns formated centered position of an object relative to parent.
  Place          - P order number of an object beeing centered
  NumberOfPlaces - NOP total number of places available for object beeing centered
  ObjectWidth    - OW width of an object beeing centered
  ParentWidth    - PW width of an parent
  CropPercent    - CP percentage of safe margin on both sides which we want to omit from calculation
  +-----------------------------------------------------+
  |                                                     |
  |        +--------+       +---+      +--------+       |
  |        |        |       |   |      |        |       |
  |        +--------+       +---+      +--------+       |
  |     |              |             |            |     |
  +-----------------------------------------------------+
  |     |<---------------------A----------------->|     |
  |<-C->|<------B----->|<-----B----->|<-----B---->|<-C->|
  |                    |<-D>|
  |<----------E------------>|

  A = PW-C   B = A/NOP  C=(CP*PW)/100  D = (B-OW)/2
  E = C+(P-1)*B+D }

var
  A, B, C, D: Integer;
begin
  C := Trunc((CropPercent*ParentWidth)/100);
  A := ParentWidth - C;
  B := Trunc(A/NumberOfPlaces);
  D := Trunc((B-ObjectWidth)/2);
  Result := C+(Place-1)*B+D;
end;

2
ฉันดีใจที่คุณชอบวอร์เรน อายุประมาณ 15 ปีเมื่อไม่มีวิธีแก้ไขสำหรับปัญหาที่ฉันต้องแก้ไข และแม้ในปัจจุบันอาจมีสถานการณ์ที่สามารถนำไปใช้ B-)
avra
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.