Làm cách nào để GUI của tôi hoạt động tốt khi tỷ lệ phông chữ Windows lớn hơn 100%


107

Khi chọn kích thước phông chữ lớn trong bảng điều khiển Windows (như 125% hoặc 150%) thì sẽ có vấn đề trong ứng dụng VCL, mỗi khi một cái gì đó được thiết lập theo pixelwise.

Lấy cái TStatusBar.Panel. Tôi đã đặt chiều rộng của nó để nó chứa chính xác một nhãn, bây giờ với phông chữ lớn, nhãn "tràn". Cùng một vấn đề với các thành phần khác.

Một số máy tính xách tay mới từ Dell xuất xưởng đã có cài đặt mặc định là 125%, vì vậy trong khi trước đây, vấn đề này khá hiếm gặp thì nay nó thực sự quan trọng.

Có thể làm gì để khắc phục vấn đề này?

Câu trả lời:


56

Lưu ý: Vui lòng xem các câu trả lời khác vì chúng chứa các kỹ thuật rất có giá trị. Câu trả lời của tôi ở đây chỉ cung cấp những cảnh báo và cảnh báo chống lại việc cho rằng nhận biết DPI là dễ dàng.

Tôi thường tránh mở rộng nhận biết DPI với TForm.Scaled = True. Nhận thức về DPI chỉ quan trọng đối với tôi khi nó trở nên quan trọng đối với những khách hàng gọi cho tôi và sẵn sàng trả tiền cho nó. Lý do kỹ thuật đằng sau quan điểm đó là có nhận thức được DPI hay không, bạn đang mở cánh cửa vào một thế giới tổn thương. Nhiều điều khiển VCL tiêu chuẩn và bên thứ ba không hoạt động tốt ở DPI cao. Ngoại lệ đáng chú ý là các bộ phận VCL bao bọc Windows Common Controls hoạt động rất tốt ở DPI cao. Một số lượng lớn các điều khiển tùy chỉnh Delphi VCL của bên thứ ba và được tích hợp sẵn không hoạt động tốt hoặc ở mức DPI cao. Nếu bạn dự định bật TForm.

Bản thân Delphi được viết bằng Delphi. Nó đã bật cờ nhận biết DPI cao, đối với hầu hết các dạng, mặc dù ngay cả gần đây như trong Delphi XE2, chính các tác giả IDE đã quyết định KHÔNG bật cờ biểu hiện Nhận thức DPI cao đó. Lưu ý rằng trong Delphi XE4 trở lên, cờ nhận biết DPI CAO được bật và IDE có vẻ tốt.

Tôi khuyên bạn không nên sử dụng TForm.Scaled = true (là mặc định trong Delphi, vì vậy trừ khi bạn đã sửa đổi nó, hầu hết các biểu mẫu của bạn có Scaled = true) với cờ Nhận biết DPI cao (như được hiển thị trong câu trả lời của David) với Các ứng dụng VCL được tạo bằng trình thiết kế biểu mẫu delphi tích hợp sẵn.

Trước đây, tôi đã cố gắng tạo ra một mẫu tối thiểu về kiểu đứt gãy mà bạn có thể mong đợi để xem khi nào TForm.Scaled là true và khi nào việc chia tỷ lệ dạng Delphi gặp trục trặc. Những trục trặc này không phải luôn luôn và chỉ được kích hoạt bởi một giá trị DPI khác 96. Tôi không thể xác định danh sách đầy đủ những thứ khác, bao gồm các thay đổi về kích thước phông chữ Windows XP. Nhưng vì hầu hết các trục trặc này chỉ xuất hiện trong các ứng dụng của riêng tôi, trong các tình huống khá phức tạp, tôi đã quyết định cho bạn xem một số bằng chứng mà bạn có thể tự xác minh.

Delphi XE trông giống như thế này khi bạn đặt Tỷ lệ DPI thành "Phông chữ @ 200%" trong Windows 7 và Delphi XE2 cũng bị hỏng tương tự trên Windows 7 và 8, nhưng những trục trặc này dường như đã được khắc phục với Delphi XE4:

nhập mô tả hình ảnh ở đây

nhập mô tả hình ảnh ở đây

Đây hầu hết là các điều khiển VCL Chuẩn hoạt động sai ở DPI cao. Lưu ý rằng hầu hết mọi thứ đều chưa được mở rộng quy mô, vì vậy các nhà phát triển Delphi IDE đã quyết định bỏ qua nhận thức về DPI, cũng như tắt ảo hóa DPI. Như một sự lựa chọn thú vị.

Chỉ tắt ảo hóa DPI nếu muốn nguồn bổ sung mới này gây khó khăn và các lựa chọn khó khăn. Tôi đề nghị bạn để nó một mình. Lưu ý rằng các điều khiển chung của Windows hầu như hoạt động tốt. Lưu ý rằng điều khiển Delphi data-explorer là một trình bao bọc C # WinForms xung quanh một điều khiển chung Windows Tree tiêu chuẩn. Đó là một trục trặc thuần túy của microsoft và việc khắc phục nó có thể yêu cầu Embarcadero viết lại điều khiển cây .Net gốc thuần túy cho trình khám phá dữ liệu của họ hoặc viết một số mã thuộc tính DPI-kiểm tra và sửa đổi để thay đổi chiều cao mục trong điều khiển. Thậm chí không phải microsoft WinForms nào cũng có thể xử lý DPI cao một cách rõ ràng, tự động và không cần mã tùy chỉnh.

Cập nhật: Sự thật thú vị: Mặc dù IDE delphi dường như không được "ảo hóa", nhưng nó không sử dụng nội dung tệp kê khai do David hiển thị để đạt được "ảo hóa không DPI". Có lẽ nó đang sử dụng một số hàm API trong thời gian chạy.

Cập nhật 2: Để giải đáp cách tôi sẽ hỗ trợ 100% / 125% DPI, tôi sẽ đưa ra kế hoạch hai giai đoạn. Giai đoạn 1 là kiểm kê mã của tôi cho các điều khiển tùy chỉnh cần được sửa để có DPI cao, sau đó lập kế hoạch sửa chúng hoặc loại bỏ chúng. Giai đoạn 2 sẽ là lấy một số vùng mã của tôi được thiết kế dưới dạng biểu mẫu không có quản lý bố cục và thay đổi chúng sang biểu mẫu sử dụng một số loại quản lý bố cục để các thay đổi về DPI hoặc độ cao phông chữ có thể hoạt động mà không cần cắt bớt. Tôi nghi ngờ rằng công việc bố trí "liên kiểm soát" này sẽ phức tạp hơn nhiều trong hầu hết các ứng dụng so với công việc "kiểm soát nội bộ".

Cập nhật: Vào năm 2016, Delphi 10.1 Berlin mới nhất đang hoạt động tốt trên máy trạm 150 dpi của tôi.


5
Hàm API đó sẽ là SetProcessDPIAware.
David Heffernan

2
Thông minh. Cảm ơn vì dữ kiện mới. Tôi đề nghị bạn sửa đổi câu trả lời của mình để gợi ý rằng đó là một lộ trình có thể. Có thể khách hàng thậm chí có thể muốn định cấu hình tùy chọn đó (tắt nó đi nếu nó không hoạt động với họ).
Warren P

Màn hình giật gân của Delphi sử dụng DPI Virtualization, có thể là do lệnh gọi đến SetDPIAware là sau khi biểu mẫu Splash đã được hiển thị.
Warren P

6
RAD Studio là sự kết hợp lớn của các điều khiển VCL tiêu chuẩn, điều khiển tùy chỉnh, các biểu mẫu .NET WinForms và FireMonkey. Không có gì đáng ngạc nhiên, rằng có những vấn đề. Và đó là lý do tại sao RAD Studio không phải là một ví dụ điển hình.
Torbins

1
Nếu bạn nói đúng, thì chính VCL đã chui đầu vào cát. Ngay cả Microsoft cũng nhúng tay vào cát. Khung công tác duy nhất mà tôi từng sử dụng thực hiện công việc có thể thông qua từ xa là COCOA trên Mac.
Warren P

63

Cài đặt của bạn trong tệp .dfm sẽ được tăng tỷ lệ chính xác, miễn Scaledlà vậy True.

Nếu bạn đang đặt thứ nguyên trong mã thì bạn cần chia tỷ lệ chúng theo Screen.PixelsPerInchchia cho Form.PixelsPerInch. Sử dụng MulDivđể làm điều này.

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

Đây là những gì mà khung kiên trì biểu mẫu thực hiện khi ScaledTrue.

Trên thực tế, bạn có thể lập một đối số hợp lý để thay thế hàm này bằng một phiên bản mã hóa cứng giá trị 96 cho mẫu số. Điều này cho phép bạn sử dụng các giá trị kích thước tuyệt đối và không phải lo lắng về ý nghĩa thay đổi nếu bạn tình cờ thay đổi tỷ lệ phông chữ trên máy phát triển của mình và lưu lại tệp .dfm. Lý do quan trọng là thuộc PixelsPerInchtính được lưu trữ trong tệp .dfm là giá trị của máy mà tệp .dfm được lưu lần cuối.

const
  SmallFontsPixelsPerInch = 96;

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

Vì vậy, tiếp tục chủ đề, một điều cần cảnh giác nữa là nếu dự án của bạn được phát triển trên nhiều máy với các giá trị DPI khác nhau, bạn sẽ thấy rằng tỷ lệ mà Delphi sử dụng khi lưu tệp .dfm dẫn đến các điều khiển lang thang qua một loạt chỉnh sửa . Tại nơi làm việc của tôi, để tránh điều này, chúng tôi có một chính sách nghiêm ngặt rằng các biểu mẫu chỉ được chỉnh sửa ở 96dpi (tỷ lệ 100%).

Trên thực tế, phiên bản của tôi ScaleFromSmallFontsDimensioncũng cho phép khả năng phông chữ biểu mẫu khác nhau trong thời gian chạy so với bộ phông chữ đó trong thời gian chỉ định. Trên máy XP, biểu mẫu ứng dụng của tôi sử dụng 8pt Tahoma. Trên Vista trở lên, giao diện người dùng Segoe 9pt được sử dụng. Điều này cung cấp một mức độ tự do khác. Việc chia tỷ lệ phải tính đến điều này vì các giá trị thứ nguyên tuyệt đối được sử dụng trong mã nguồn được giả định là tương đối với đường cơ sở của 8pt Tahoma ở 96dpi.

Nếu bạn sử dụng bất kỳ hình ảnh hoặc glyphs nào trong giao diện người dùng của mình thì chúng cũng cần phải mở rộng quy mô. Một ví dụ phổ biến sẽ là glyphs được sử dụng trên thanh công cụ và menu. Bạn sẽ muốn cung cấp các glyph này dưới dạng tài nguyên biểu tượng được liên kết với tệp thực thi của bạn. Mỗi biểu tượng nên chứa một loạt các kích thước và sau đó trong thời gian chạy, bạn chọn kích thước thích hợp nhất và tải nó vào danh sách hình ảnh. Bạn có thể tìm thấy một số thông tin chi tiết về chủ đề đó tại đây: Làm cách nào để tải các biểu tượng từ một tài nguyên mà không bị răng cưa?

Một thủ thuật hữu ích khác là xác định thứ nguyên theo đơn vị tương đối, liên quan đến TextWidthhoặc TextHeight. Vì vậy, nếu bạn muốn thứ gì đó có kích thước khoảng 10 đường thẳng đứng, bạn có thể sử dụng 10*Canvas.TextHeight('Ag'). Đây là một số liệu rất thô và sẵn sàng vì nó không cho phép khoảng cách dòng, v.v. Tuy nhiên, thường thì tất cả những gì bạn cần làm là có thể sắp xếp GUI đúng quy mô PixelsPerInch.

Bạn cũng nên đánh dấu ứng dụng của mình là nhận biết DPI cao . Cách tốt nhất để làm điều này là thông qua tệp kê khai ứng dụng. Vì các công cụ xây dựng của Delphi không cho phép bạn tùy chỉnh tệp kê khai mà bạn sử dụng, điều này buộc bạn phải liên kết tài nguyên tệp kê khai của riêng mình.

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

Tập lệnh tài nguyên trông như thế này:

1 24 "Manifest.txt"

nơi Manifest.txtchứa tệp kê khai thực tế. Bạn cũng cần phải bao gồm phần comctl32 v6 và đặt requestedExecutionLevelthành asInvoker. Sau đó, bạn liên kết tài nguyên đã biên dịch này với ứng dụng của mình và đảm bảo rằng Delphi không cố gắng làm điều tương tự với tệp kê khai của nó. Trong Delphi hiện đại, bạn đạt được điều đó bằng cách đặt tùy chọn dự án Chủ đề thời gian chạy thành Không có.

Tệp kê khai là cách phù hợp để tuyên bố ứng dụng của bạn nhận biết DPI cao. Nếu bạn chỉ muốn dùng thử nhanh chóng mà không làm phiền đến tệp kê khai của mình, hãy gọi SetProcessDPIAware. Làm như vậy là điều đầu tiên bạn làm khi ứng dụng của bạn chạy. Tốt hơn là ở một trong các phần khởi tạo đơn vị ban đầu hoặc ở phần đầu tiên trong tệp .dpr của bạn.

Nếu bạn không khai báo ứng dụng của mình là có DPI cao thì Vista trở lên sẽ hiển thị ứng dụng ở chế độ kế thừa cho bất kỳ tỷ lệ phông chữ nào trên 125%. Điều này trông khá kinh khủng. Cố gắng tránh rơi vào cái bẫy đó.

Cập nhật DPI cho mỗi màn hình của Windows 8.1

Kể từ Windows 8.1, hiện đã có hệ điều hành hỗ trợ cài đặt DPI cho mỗi màn hình ( http://msdn.microsoft.com/en-ca/magazine/dn574798.aspx ). Đây là một vấn đề lớn đối với các thiết bị hiện đại có thể có các màn hình khác nhau được gắn với các khả năng rất khác nhau. Bạn có thể có màn hình máy tính xách tay DPI rất cao và máy chiếu bên ngoài có DPI thấp. Hỗ trợ một kịch bản như vậy thậm chí còn tốn nhiều công sức hơn so với mô tả ở trên.


2
Điều đó không phải lúc nào cũng đúng. Trên thực tế, Đặt Scaled = true, và sau đó đặt Nhận biết DPI cao cũng có thể gây ra một số lỗi lạ trong hầu hết các ứng dụng delphi. Tôi đã dành hàng trăm giờ để cố gắng làm cho các ứng dụng của mình hoạt động ở DPI cao và nhận thấy rằng tốt hơn là có pixel trông khủng khiếp hơn là các điều khiển bị cắt, di chuyển khỏi màn hình, thanh cuộn thừa hoặc thiếu trên các điều khiển khác nhau, v.v.
Warren P

@WarrenP Tôi nghĩ những vấn đề đó là riêng đối với ứng dụng của bạn. Kinh nghiệm cá nhân của tôi là ứng dụng Delphi của tôi hiển thị và chia tỷ lệ hoàn hảo ngay cả ở tỷ lệ phông chữ 200%.
David Heffernan

2
@WarrenP Vậy thì sao? Bạn hoàn toàn có thể sử dụng Delphi để xây dựng các ứng dụng hoạt động tốt hơn Delphi IDE.
David Heffernan

1
Tôi đã thấy rất nhiều hộp thoại có đường viền cố định được tạo bằng Delphi 5,6,7 và cài đặt được chia tỷ lệ true bị lỗi. Ẩn các nút ok, hủy bỏ, v.v. Thậm chí một số hộp thoại trong Delphi2006 nó cho rằng đã bị lỗi này. Trộn các thành phần Delphi bản địa và các thành phần cửa sổ cũng cho hiệu ứng lạ. Tôi luôn phát triển GUI theo tỷ lệ phông chữ 125% và đặt thuộc tính tỷ lệ thành false.
LU RD

2
Công cụ tuyệt vời. +1 cho thông tin tuyệt vời. Ý kiến của tôi (không làm điều đó) là thứ hai trong trọng cần phải biết cách để làm điều đó khi bạn muốn làm điều này ...
Warren P

42

Cũng cần lưu ý rằng việc tôn vinh DPI của người dùng chỉ là một phần nhỏ trong công việc thực sự của bạn:

tôn trọng kích thước phông chữ của người dùng

Trong nhiều thập kỷ, Windows đã giải quyết vấn đề này với khái niệm thực hiện bố cục bằng các Đơn vị hộp thoại , thay vì pixel. Một "đơn vị thoại" được định nghĩa như vậy của phông chữ mà nhân vật trung bình

  • 4 đơn vị hộp thoại (dlus) rộng và
  • 8 đơn vị hộp thoại (nhóm) cao

nhập mô tả hình ảnh ở đây

Delphi đưa ra khái niệm (lỗi) Scaled, trong đó biểu mẫu cố gắng tự động điều chỉnh dựa trên

  • Cài đặt Windows DPI của người dùng, câu
  • cài đặt DPI trên máy của nhà phát triển đã lưu biểu mẫu lần cuối

Điều đó không giải quyết được vấn đề khi người dùng sử dụng phông chữ khác với phông chữ bạn đã thiết kế biểu mẫu, ví dụ:

  • nhà phát triển đã thiết kế biểu mẫu bằng MS Sans Serif 8pt (trong đó ký tự trung bình là 6.21px x 13.00px96dpi)
  • người dùng đang chạy với Tahoma 8pt (trong đó ký tự trung bình là 5.94px x 13.00px96dpi)

    Như trường hợp của bất kỳ ai phát triển ứng dụng cho Windows 2000 hoặc Windows XP.

hoặc là

  • nhà phát triển đã thiết kế biểu mẫu với ** Tahoma 8pt * (trong đó ký tự trung bình là 5.94px x 13.00px96dpi)
  • một người dùng chạy với Segoe UI 9pt (trong đó ký tự trung bình là 6.67px x 15px96dpi)

Là một nhà phát triển giỏi, bạn sẽ tôn trọng sở thích phông chữ của người dùng. Điều này có nghĩa là bạn cũng cần mở rộng tất cả các điều khiển trên biểu mẫu của mình để phù hợp với kích thước phông chữ mới:

  • mở rộng mọi thứ theo chiều ngang 12,29% (6,67 / 5,94)
  • kéo dài mọi thứ theo chiều dọc 15,38% (15/13)

Scaled sẽ không giải quyết việc này cho bạn.

Nó trở nên tồi tệ hơn khi:

  • đã thiết kế biểu mẫu của bạn tại Segoe UI 9pt (mặc định Windows Vista, Windows 7, Windows 8)
  • người dùng đang chạy Segoe UI 14pt , (ví dụ: sở thích của tôi) là10.52px x 25px

Bây giờ bạn phải mở rộng mọi thứ

  • theo chiều ngang là 57,72%
  • theo chiều dọc là 66,66%

Scaled sẽ không giải quyết việc này cho bạn.


Nếu bạn thông minh, bạn có thể thấy việc tôn vinh DPI là không cần thiết:

  • biểu mẫu được thiết kế với Segoe UI 9pt @ 96dpi (6.67px x 15px)
  • người dùng chạy với Segoe UI 9pt @ 150dpi (10,52px x 25px)

Bạn không nên xem cài đặt DPI của người dùng, bạn nên xem kích thước phông chữ của họ . Hai người dùng đang chạy

  • Segoe UI 14pt @ 96dpi (10,52px x 25px)
  • Segoe UI 9pt @ 150dpi (10,52px x 25px)

đang chạy cùng một phông chữ . DPI chỉ là một thứ ảnh hưởng đến kích thước phông chữ; sở thích của người dùng là khác.

StandardizeFormFont

Clovis nhận thấy rằng tôi tham chiếu một hàm StandardizeFormFontsửa phông chữ trên một biểu mẫu và chia tỷ lệ nó thành kích thước phông chữ mới. Nó không phải là một hàm tiêu chuẩn, mà là một tập hợp các hàm hoàn thành nhiệm vụ đơn giản mà Borland chưa bao giờ xử lý.

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 có 6 phông chữ khác nhau; không có "cài đặt phông chữ" duy nhất trong Windows.
Nhưng chúng tôi biết từ kinh nghiệm rằng các biểu mẫu của chúng tôi phải tuân theo cài đặt Phông chữ Tiêu đề Biểu tượng

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;

Một khi chúng ta biết kích thước phông chữ, chúng tôi sẽ mở rộng các hình thức cho , chúng tôi nhận chiều cao font chữ hiện tại của hình thức ( bằng pixel ), và mở rộng quy mô của yếu tố đó.

Ví dụ: nếu tôi đang đặt biểu mẫu thành -16và biểu mẫu hiện đang ở -11, thì chúng tôi cần chia tỷ lệ toàn bộ biểu mẫu bằng:

-16 / -11 = 1.45454%

Quá trình tiêu chuẩn hóa xảy ra trong hai giai đoạn. Trước tiên hãy chia tỷ lệ biểu mẫu theo tỷ lệ của kích thước phông chữ mới: cũ. Sau đó thực sự thay đổi các điều khiển (đệ quy) để sử dụng phông chữ mới.

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;

Đây là công việc thực sự mở rộng một biểu mẫu. Nó hoạt động xung quanh các lỗi theo Form.ScaleByphương pháp riêng của Borland . Đầu tiên nó phải vô hiệu hóa tất cả các neo trên biểu mẫu, sau đó thực hiện điều chỉnh tỷ lệ, sau đó bật lại các neo:

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;

và sau đó chúng ta phải đệ quy thực sự sử dụng phông chữ mới:

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;

Với các neo bị vô hiệu hóa đệ quy:

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;

Và neo đang được bật lại đệ quy:

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;

Với công việc thực sự thay đổi phông chữ điều khiển sang trái:

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;

Đó là rất nhiều mã hơn bạn nghĩ; tôi biết. Điều đáng buồn là không có nhà phát triển Delphi nào trên trái đất, ngoại trừ tôi, người thực sự làm cho các ứng dụng của họ chính xác.

Kính gửi nhà phát triển Delphi : Đặt phông chữ Windows của bạn thành Segoe UI 14pt và sửa ứng dụng lỗi của bạn

Lưu ý : Bất kỳ mã nào được phát hành vào miền công cộng. Không cần ghi công.


1
Cảm ơn bạn đã trả lời, nhưng bạn đề xuất gì cho thế giới thực? Thực hiện thay đổi kích thước của tất cả các điều khiển theo cách thủ công?
LaBracca

3
"Điều đáng buồn là không có nhà phát triển Delphi nào trên trái đất, ngoại trừ tôi, người thực sự làm cho các ứng dụng của họ chính xác." Đó là một tuyên bố rất kiêu ngạo và không chính xác. Từ câu trả lời của tôi: Trên thực tế, phiên bản ScaleFromSmallFontsDimension của tôi cũng cho phép khả năng phông chữ biểu mẫu khác nhau trong thời gian chạy so với bộ đó vào thời gian chỉ định. Việc chia tỷ lệ phải tính đến điều này vì các giá trị thứ nguyên tuyệt đối được sử dụng trong mã nguồn được giả định là tương đối so với đường cơ sở của 8pt Tahoma ở 96dpi. Của bạn là một câu trả lời tốt, bạn nhớ, +1.
David Heffernan

1
@Ian Not me that said that. Nghe giống Warren.
David Heffernan

2
Điều này khá tuyệt vời, Ian. Cảm ơn.
Warren P

2
Gần đây đã chạy qua câu hỏi và câu trả lời này. Tôi đã thu thập tất cả mã của Ian vào một đơn vị làm việc tại đây: pastebin.com/dKpfnXLc và đăng nó lên Google+ tại đây: goo.gl/0ARdq9 Đăng ở đây trong trường hợp bất kỳ ai thấy điều này hữu ích.
W.Prins

11

Đây là món quà của tôi. Một chức năng có thể giúp bạn định vị theo chiều ngang của các phần tử trong bố cục GUI của bạn. Miễn phí cho tất cả.

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
Tôi rất vui vì bạn thích nó Warren. Đó là khoảng 15 tuổi khi không có giải pháp nào cho vấn đề tôi phải giải quyết. Và thậm chí ngày nay có thể có một tình huống mà nó có thể được áp dụng. B-)
avra
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.