Wie kann ich sicherstellen, dass sich meine GUI gut verhält, wenn die Windows-Schriftartenskalierung größer als 100% ist?

107

Wenn Sie in der Windows-Systemsteuerung große Schriftgrößen auswählen (z. B. 125% oder 150%), treten in einer VCL-Anwendung jedes Mal Probleme auf, wenn etwas pixelweise festgelegt wurde.

Nimm das TStatusBar.Panel. Ich habe seine Breite so eingestellt, dass es genau ein Etikett enthält, jetzt mit großen Schriftarten das Etikett "überläuft". Gleiches Problem mit anderen Komponenten.

Einige neue Laptops von Dell werden bereits mit 125% als Standardeinstellung ausgeliefert. Während dieses Problem in der Vergangenheit eher selten war, ist es jetzt wirklich wichtig.

Was kann getan werden, um dieses Problem zu lösen?

LaBracca
quelle

Antworten:

56

Hinweis: Bitte beachten Sie die anderen Antworten, da diese sehr wertvolle Techniken enthalten. Meine Antwort hier enthält nur Vorbehalte und Vorsichtsmaßnahmen gegen die Annahme, dass das DPI-Bewusstsein einfach ist.

Ich vermeide generell DPI-fähige Skalierung mit TForm.Scaled = True. Das DPI-Bewusstsein ist für mich nur dann wichtig, wenn es für Kunden wichtig wird, die mich anrufen und bereit sind, dafür zu zahlen. Der technische Grund für diese Sichtweise ist, dass DPI-Bewusstsein oder nicht, Sie ein Fenster in eine Welt voller Verletzungen öffnen. Viele Standard- und VCL-Steuerelemente von Drittanbietern funktionieren in High DPI nicht gut. Die bemerkenswerte Ausnahme, dass die VCL-Teile, die Windows Common Controls umschließen, bei hohen DPI bemerkenswert gut funktionieren. Eine große Anzahl von benutzerdefinierten Delphi VCL-Steuerelementen von Drittanbietern funktioniert bei hohen DPI-Werten nicht oder überhaupt nicht. Wenn Sie TForm.Scaled aktivieren möchten, testen Sie es mit 96, 125 und 150 DPI für jedes einzelne Formular in Ihrem Projekt sowie für jeden einzelnen Drittanbieter und jede integrierte Steuerung, die Sie verwenden.

Delphi selbst ist in Delphi geschrieben. Für die meisten Formulare ist das High DPI Awareness-Flag aktiviert, obwohl die IDE-Autoren selbst in Delphi XE2 selbst noch entschieden haben, das High DPI Awareness-Manifest NICHT zu aktivieren. Beachten Sie, dass in Delphi XE4 und höher das HIGH DPI-Erkennungsflag aktiviert ist und die IDE gut aussieht.

Ich schlage vor, dass Sie TForm.Scaled = true (dies ist eine Standardeinstellung in Delphi. Wenn Sie es nicht geändert haben, haben die meisten Ihrer Formulare Scaled = true) mit den High DPI Aware-Flags (wie in Davids Antworten gezeigt) mit verwenden VCL-Anwendungen, die mit dem integrierten Delphi-Formular-Designer erstellt wurden.

Ich habe in der Vergangenheit versucht, eine minimale Stichprobe der Art von Bruch zu erstellen, die zu erwarten ist, wenn TForm.Scaled wahr ist und wenn die Delphi-Formskalierung einen Fehler aufweist. Diese Störungen werden nicht immer und nur durch einen anderen DPI-Wert als 96 ausgelöst. Ich konnte keine vollständige Liste anderer Dinge ermitteln, einschließlich Änderungen der Windows XP-Schriftgröße. Da die meisten dieser Störungen jedoch nur in meinen eigenen Anwendungen auftreten, habe ich mich in relativ komplexen Situationen entschlossen, Ihnen einige Beweise zu zeigen, die Sie selbst überprüfen können.

Delphi XE sieht folgendermaßen aus, wenn Sie die DPI-Skalierung in Windows 7 auf "Fonts @ 200%" einstellen und Delphi XE2 unter Windows 7 und 8 ebenfalls fehlerhaft ist. Diese Störungen scheinen jedoch ab Delphi XE4 behoben zu sein:

Geben Sie hier die Bildbeschreibung ein

Geben Sie hier die Bildbeschreibung ein

Dies sind meistens Standard-VCL-Steuerelemente, die sich bei hohen DPI-Werten schlecht verhalten. Beachten Sie, dass die meisten Dinge überhaupt nicht skaliert wurden. Daher haben die Delphi IDE-Entwickler beschlossen, die DPI-Erkennung zu ignorieren und die DPI-Virtualisierung zu deaktivieren. Eine so interessante Wahl.

Deaktivieren Sie die DPI-Virtualisierung nur, wenn Sie diese neue zusätzliche Schmerzquelle und schwierige Entscheidungen wünschen. Ich schlage vor, Sie lassen es in Ruhe. Beachten Sie, dass die allgemeinen Windows-Steuerelemente meistens einwandfrei funktionieren. Beachten Sie, dass das Delphi Data Explorer-Steuerelement ein C # WinForms-Wrapper um ein allgemeines Standard-Windows Tree-Steuerelement ist. Dies ist ein reiner Microsoft-Fehler, und zur Behebung dieses Problems muss Embarcadero möglicherweise ein reines natives .Net-Baumsteuerelement für den Datenexplorer neu schreiben oder einen Code für DPI-Überprüfungs- und -Eigenschaftseigenschaften schreiben, um die Elementhöhen im Steuerelement zu ändern. Nicht einmal Microsoft WinForms kann hohe DPI sauber, automatisch und ohne benutzerdefinierten Kludge-Code verarbeiten.

Update: Interessantes Faktoid: Während die Delphi-IDE nicht "virtualisiert" zu sein scheint, verwendet sie nicht den von David gezeigten Manifest-Inhalt, um eine "Nicht-DPI-Virtualisierung" zu erreichen. Möglicherweise wird zur Laufzeit eine API-Funktion verwendet.

Update 2: Als Antwort darauf, wie ich 100% / 125% DPI unterstützen würde, würde ich einen Zwei-Phasen-Plan erstellen. In Phase 1 wird mein Code für benutzerdefinierte Steuerelemente inventarisiert, die für hohe DPI repariert werden müssen, und anschließend ein Plan erstellt, um sie zu reparieren oder auslaufen zu lassen. Phase 2 besteht darin, einige Bereiche meines Codes, die als Formulare ohne Layoutverwaltung konzipiert sind, in Formulare umzuwandeln, die eine Art Layoutverwaltung verwenden, damit Änderungen der DPI oder der Schrifthöhe ohne Beschneiden funktionieren können. Ich vermute, dass diese "Inter-Control" -Layout-Arbeit in den meisten Anwendungen weitaus komplexer wäre als die "Intra-Control" -Arbeit.

Update: 2016 funktioniert das neueste Delphi 10.1 Berlin gut auf meiner 150-dpi-Workstation.

Warren P.
quelle
5
Diese API-Funktion wäre SetProcessDPIAware.
David Heffernan
2
Ausgezeichnet. Danke für das neue Faktoid. Ich schlage vor, Sie ändern Ihre Antwort, um dies als eine mögliche Route vorzuschlagen. Es kann sein, dass Kunden diese Option sogar konfigurieren möchten (deaktivieren Sie sie, wenn sie für sie nicht funktioniert).
Warren P
Der Begrüßungsbildschirm von Delphi verwendet DPI-Virtualisierung, wahrscheinlich weil der Aufruf von SetDPIAware erfolgt, nachdem das Begrüßungsformular bereits sichtbar gemacht wurde.
Warren P
6
RAD Studio ist eine große Mischung aus Standard-VCL-Steuerelementen, benutzerdefinierten Steuerelementen, .NET WinForms und FireMonkey-Formularen. Es ist nicht überraschend, dass es Probleme gibt. Und deshalb ist RAD Studio kein gutes Beispiel.
Torbins
1
Wenn Sie Recht haben, ist es die VCL selbst, die ihren Kopf im Sand hat. Sogar Microsoft hat den Kopf im Sand. Das einzige Framework, das ich jemals verwendet habe, um einen remote passablen Job zu erledigen, ist COCOA auf dem Mac.
Warren P
63

Ihre Einstellungen in der .dfm Datei wird korrekt skaliert, so lange Scaledist True.

Wenn Sie Dimensionen im Code festlegen, müssen Sie sie durch Screen.PixelsPerInchdividiert durch skalieren Form.PixelsPerInch. Verwenden MulDivSie dazu.

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

Dies ist, was das Form Persistence Framework tut, wenn es Scaledist True.

Tatsächlich können Sie ein schlüssiges Argument dafür vorbringen, diese Funktion durch eine Version zu ersetzen, die einen Wert von 96 für den Nenner fest codiert. Auf diese Weise können Sie absolute Bemaßungswerte verwenden und müssen sich keine Gedanken über die Änderung der Bedeutung machen, wenn Sie die Schriftartenskalierung auf Ihrem Entwicklungscomputer ändern und die .dfm-Datei erneut speichern. Der Grund dafür ist, dass die PixelsPerInchin der .dfm-Datei gespeicherte Eigenschaft der Wert des Computers ist, auf dem die .dfm-Datei zuletzt gespeichert wurde.

const
  SmallFontsPixelsPerInch = 96;

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

Wenn Sie das Thema fortsetzen, sollten Sie auch bedenken, dass wenn Ihr Projekt auf mehreren Computern mit unterschiedlichen DPI-Werten entwickelt wird, die Skalierung, die Delphi beim Speichern von .dfm-Dateien verwendet, dazu führt, dass Steuerelemente über eine Reihe von Änderungen wandern . Um dies zu vermeiden, haben wir an meinem Arbeitsplatz die strikte Richtlinie, dass Formulare immer nur mit 96 dpi (100% Skalierung) bearbeitet werden.

Tatsächlich ScaleFromSmallFontsDimensionberücksichtigt meine Version von auch die Möglichkeit, dass sich die Formularschrift zur Laufzeit von der zur Entwurfszeit festgelegten unterscheidet. Auf XP-Computern verwenden die Formulare meiner Anwendung 8pt Tahoma. Unter Vista und höher wird 9pt Segoe UI verwendet. Dies bietet noch einen weiteren Freiheitsgrad. Die Skalierung muss dies berücksichtigen, da angenommen wird, dass die im Quellcode verwendeten absoluten Dimensionswerte relativ zur Basislinie von 8pt Tahoma bei 96 dpi sind.

Wenn Sie Bilder oder Glyphen in Ihrer Benutzeroberfläche verwenden, müssen diese ebenfalls skaliert werden. Ein häufiges Beispiel sind die Glyphen, die in Symbolleisten und Menüs verwendet werden. Sie möchten diese Glyphen als Symbolressourcen bereitstellen, die mit Ihrer ausführbaren Datei verknüpft sind. Jedes Symbol sollte eine Reihe von Größen enthalten. Zur Laufzeit wählen Sie die am besten geeignete Größe aus und laden sie in eine Bildliste. Einige Details zu diesem Thema finden Sie hier: Wie lade ich Symbole aus einer Ressource, ohne unter Aliasing zu leiden?

Ein weiterer nützlicher Trick besteht darin, Dimensionen in relativen Einheiten relativ zu TextWidthoder zu definieren TextHeight. Wenn Sie also möchten, dass etwas ungefähr 10 vertikale Linien groß ist, können Sie es verwenden 10*Canvas.TextHeight('Ag'). Dies ist eine sehr grobe und fertige Metrik, da sie keinen Zeilenabstand usw. zulässt. Oft müssen Sie jedoch nur sicherstellen, dass die GUI korrekt skaliert PixelsPerInch.

Sie sollten Ihre Anwendung auch als hoch DPI-fähig markieren . Der beste Weg, dies zu tun, ist über das Anwendungsmanifest. Da Sie mit den Build-Tools von Delphi das verwendete Manifest nicht anpassen können, müssen Sie Ihre eigene Manifest-Ressource verknüpfen.

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

Das Ressourcenskript sieht folgendermaßen aus:

1 24 "Manifest.txt"

wo Manifest.txtenthält das eigentliche Manifest. Sie würde auch den comctl32 v6 Abschnitt und Set enthalten müssen requestedExecutionLevelzu asInvoker. Anschließend verknüpfen Sie diese kompilierte Ressource mit Ihrer App und stellen sicher, dass Delphi nicht versucht, dasselbe mit seinem Manifest zu tun. Im modernen Delphi erreichen Sie dies, indem Sie die Projektoption Runtime Themes auf None setzen.

Das Manifest ist der richtige Weg, um Ihre App als hoch DPI-fähig zu deklarieren. Wenn Sie es nur schnell ausprobieren möchten, ohne Ihr Manifest zu beeinträchtigen, rufen Sie an SetProcessDPIAware. Tun Sie dies als erstes, wenn Ihre App ausgeführt wird. Am besten in einem der frühen Abschnitte zur Einheiteninitialisierung oder als erstes in Ihrer .dpr-Datei.

Wenn Sie Ihre App nicht als hoch DPI-fähig deklarieren, wird sie von Vista und höher in einem Legacy-Modus für alle Schriftarten skaliert, die über 125% liegen. Das sieht ziemlich schrecklich aus. Versuche zu vermeiden, in diese Falle zu tappen.

Windows 8.1 pro Monitor DPI-Update

Ab Windows 8.1 gibt es jetzt Betriebssystemunterstützung für DPI-Einstellungen pro Monitor ( http://msdn.microsoft.com/en-ca/magazine/dn574798.aspx ). Dies ist ein großes Problem für moderne Geräte, an die möglicherweise unterschiedliche Displays mit sehr unterschiedlichen Funktionen angeschlossen sind. Möglicherweise haben Sie einen Laptop-Bildschirm mit sehr hoher DPI und einen externen Projektor mit niedriger DPI. Die Unterstützung eines solchen Szenarios erfordert noch mehr Arbeit als oben beschrieben.

David Heffernan
quelle
2
Das ist nicht immer wahr. Tatsächlich kann das Festlegen von Scaled = true und das anschließende Festlegen der High DPI-Funktion auch bei den meisten Delphi-Anwendungen zu merkwürdigen Fehlern führen. Ich habe Hunderte von Stunden damit verbracht, meine Apps mit hoher DPI zum Laufen zu bringen, und festgestellt, dass es besser ist, die schrecklich aussehende Pixelung zu haben, als Steuerelemente zuzuschneiden, vom Bildschirm zu entfernen, zusätzliche oder fehlende Bildlaufleisten auf verschiedenen Steuerelementen usw.
Warren P
@ WarrenP Ich denke, diese Probleme sind speziell für Ihre App. Meine persönliche Erfahrung ist, dass meine Delphi-App auch bei einer Schriftgröße von 200% perfekt angezeigt und skaliert wird.
David Heffernan
2
@ WarrenP Na und? Mit Delphi können Sie problemlos Apps erstellen, die sich besser verhalten als die Delphi-IDE.
David Heffernan
1
Ich habe viele Dialoge mit festen Rahmen gesehen, die mit Delphi 5,6,7 erstellt wurden, und die skalierte Einstellung ist wahr, um fehlzuschlagen. OK ausblenden, Schaltflächen abbrechen usw. Sogar einige Dialoge in Delphi2006 waren davon betroffen. Das Mischen von nativen Delphi-Komponenten und Windows-Komponenten führt ebenfalls zu seltsamen Effekten. Ich entwickle die GUI immer in einer 125% -Schriftartenskalierung und setze die skalierte Eigenschaft auf false.
LU RD
2
Tolles Zeug. +1 für fantastische Informationen. Meine Meinung (tu es nicht) ist an zweiter Stelle nach der Notwendigkeit zu wissen, wie es zu tun ist, wenn du dies tun willst ...
Warren P
42

Es ist auch wichtig zu beachten, dass die Einhaltung der DPI des Benutzers nur eine Teilmenge Ihres eigentlichen Jobs ist:

Berücksichtigung der Schriftgröße des Benutzers

Seit Jahrzehnten löst Windows dieses Problem mit dem Gedanken, das Layout mithilfe von Dialogeinheiten anstelle von Pixeln durchzuführen . Eine "Dialogeinheit" ist so definiert, dass das durchschnittliche Zeichen der Schriftart ist

  • 4 Dialogeinheiten (dlus) breit und
  • 8 Dialogeinheiten (Clus) hoch

Geben Sie hier die Bildbeschreibung ein

Delphi wird mit einer (fehlerhaften) Vorstellung von ausgeliefert Scaled, bei der ein Formular versucht, sich basierend auf dem automatisch anzupassen

  • Windows DPI-Einstellungen des Benutzers, Verse
  • Die DPI-Einstellung auf dem Computer des Entwicklers, der das Formular zuletzt gespeichert hat

Dies löst das Problem nicht, wenn der Benutzer eine andere Schriftart verwendet als die, mit der Sie das Formular entworfen haben, z.

  • Der Entwickler hat das Formular mit MS Sans Serif 8pt entworfen (wobei das durchschnittliche Zeichen 6.21px x 13.00pxbei 96 dpi liegt).
  • Benutzer, der mit Tahoma 8pt läuft (wobei das durchschnittliche Zeichen 5.94px x 13.00pxbei 96 dpi liegt)

    Wie bei jedem, der eine Anwendung für Windows 2000 oder Windows XP entwickelt.

oder

  • Entwickler entwarf das Formular mit ** Tahoma 8pt * (wobei das durchschnittliche Zeichen 5.94px x 13.00pxbei 96 dpi liegt)
  • ein Benutzer, der mit Segoe UI 9pt ausgeführt wird (wobei das durchschnittliche Zeichen 6.67px x 15pxbei 96 dpi liegt)

Als guter Entwickler werden Sie die Schriftarteneinstellungen Ihres Benutzers berücksichtigen. Dies bedeutet, dass Sie auch alle Steuerelemente in Ihrem Formular skalieren müssen, um sie an die neue Schriftgröße anzupassen:

  • Erweitern Sie alles horizontal um 12,29% (6,67 / 5,94).
  • Dehnen Sie alles vertikal um 15,38% (15/13)

Scaled Ich werde das nicht für dich erledigen.

Es wird schlimmer, wenn:

  • hat Ihr Formular unter Segoe UI 9pt entworfen (Standardeinstellung für Windows Vista, Windows 7, Windows 8)
  • Der Benutzer führt Segoe UI 14pt aus (z. B. meine Präferenz)10.52px x 25px

Jetzt müssen Sie alles skalieren

  • horizontal um 57,72%
  • vertikal um 66,66%

Scaled Ich werde das nicht für dich erledigen.


Wenn Sie schlau sind, können Sie sehen, wie irrelevant es ist, DPI zu ehren:

  • Formular mit Segoe UI 9pt @ 96dpi (6,67 x 15 Pixel)
  • Benutzer, der mit Segoe UI 9pt @ 150dpi (10,52 x 25 Pixel) ausgeführt wird

Sie sollten nicht auf die DPI-Einstellung des Benutzers achten, sondern auf dessen Schriftgröße . Zwei Benutzer laufen

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

führen die gleiche Schriftart aus . DPI ist nur eine Sache, die die Schriftgröße beeinflusst. Die Vorlieben des Benutzers sind die anderen.

StandardizeFormFont

Clovis bemerkte, dass ich auf eine Funktion verweise StandardizeFormFont, die die Schriftart in einem Formular korrigiert und auf die neue Schriftgröße skaliert. Es ist keine Standardfunktion, sondern eine ganze Reihe von Funktionen, die die einfache Aufgabe erfüllen, die Borland nie erledigt hat.

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 hat 6 verschiedene Schriftarten; In Windows gibt es keine einzige "Schriftarteinstellung".
Wir wissen jedoch aus Erfahrung, dass unsere Formulare der Einstellung für die Symbolschriftart folgen sollten

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;

Sobald wir die Schriftgröße wissen , werden wir die Form skalieren zu , so erhalten wir die aktuelle Schrifthöhe des Formulars ( in Pixeln ) und skalieren um diesen Faktor auf.

Wenn ich beispielsweise das Formular auf -16setze und das Formular sich derzeit in befindet -11, müssen wir das gesamte Formular skalieren nach:

-16 / -11 = 1.45454%

Die Standardisierung erfolgt in zwei Phasen. Skalieren Sie das Formular zunächst nach dem Verhältnis der neuen zu den alten Schriftgrößen. Ändern Sie dann tatsächlich die Steuerelemente (rekursiv), um die neue Schriftart zu verwenden.

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;

Hier ist die Aufgabe, ein Formular tatsächlich zu skalieren. Es umgeht Fehler in Borlands eigener Form.ScaleByMethode. Zuerst müssen alle Anker im Formular deaktiviert, dann die Skalierung durchgeführt und dann die Anker wieder aktiviert werden:

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;

und dann müssen wir rekursiv tatsächlich verwenden die neue Schriftart:

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;

Wenn die Anker rekursiv deaktiviert sind:

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;

Und Anker werden rekursiv wieder aktiviert:

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;

Mit der Arbeit, eine Steuerelementschrift tatsächlich zu ändern, bleibt Folgendes übrig:

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;

Das ist viel mehr Code, als Sie gedacht haben. ich weiß. Das Traurige ist, dass es keinen Delphi-Entwickler auf der Welt gibt, außer mir, der seine Anwendungen tatsächlich korrekt macht.

Sehr geehrter Delphi-Entwickler, stellen Sie Ihre Windows-Schriftart auf Segoe UI 14pt ein und beheben Sie Ihre fehlerhafte Anwendung

Hinweis : Jeder Code wird öffentlich zugänglich gemacht. Keine Zuordnung erforderlich.

Ian Boyd
quelle
1
Danke für die Antwort, aber was schlagen Sie für die reale Welt vor? Eine Größenänderung aller Steuerelemente manuell implementieren?
LaBracca
3
"Das Traurige ist, dass es keinen Delphi-Entwickler auf der Welt gibt, außer mir, der seine Anwendungen tatsächlich korrekt macht." Das ist eine sehr arrogante Aussage, die falsch ist. Aus meiner Antwort: Tatsächlich berücksichtigt meine Version von ScaleFromSmallFontsDimension auch die Möglichkeit, dass sich die Formularschrift zur Laufzeit von der zur Entwurfszeit festgelegten unterscheidet. Die Skalierung muss dies berücksichtigen, da angenommen wird, dass die im Quellcode verwendeten absoluten Dimensionswerte relativ zur Basislinie von 8pt Tahoma bei 96 dpi sind. Dein ist eine gute Antwort wohlgemerkt, +1.
David Heffernan
1
@ Ian Nicht ich, der das gesagt hat. Klingt nach Warren.
David Heffernan
2
Das ist ziemlich großartig, Ian. Vielen Dank.
Warren P
2
Vor kurzem über diese Frage und Antwort gestoßen. Ich habe alle hier Code in eine Arbeitseinheit Ian gesammelt: pastebin.com/dKpfnXLc und hier bout it auf Google+ gepostet: goo.gl/0ARdq9 hier Posting falls jemand diese nützlich findet.
W.Prins
11

Hier ist mein Geschenk. Eine Funktion, die Ihnen bei der horizontalen Positionierung von Elementen in Ihren GUI-Layouts helfen kann. Frei für alle.

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;
avra
quelle
2
Ich bin froh, dass es dir gefällt, Warren. Es ist ungefähr 15 Jahre alt, als es keine Lösungen für das Problem gab, das ich lösen musste. Und selbst heute kann es eine Situation geben, in der es angewendet werden kann. B-)
avra