Як зробити так, щоб мій графічний інтерфейс поводився добре, коли масштаб шрифту Windows перевищує 100%


107

Вибираючи великі розміри шрифту на панелі керування Windows (наприклад, 125% або 150%), у програмі VCL виникають проблеми, кожен раз, коли щось встановлюється піксельно.

Візьміть TStatusBar.Panel. Я встановив його ширину так, щоб вона містила рівно одну мітку, тепер з великими шрифтами мітка "переповнює". Така ж проблема і з іншими компонентами.

Деякі нові ноутбуки від Dell поставляються вже зі 125% за замовчуванням, тому в минулому ця проблема була досить рідкісною, зараз вона дійсно важлива.

Що можна зробити, щоб подолати цю проблему?

Відповіді:


56

Примітка. Будь ласка, подивіться інші відповіді, оскільки вони містять дуже цінні методи. Моя відповідь тут надає лише застереження та застереження проти припущення, що усвідомлення DPI є простим.

Я, як правило, уникаю масштабування, усвідомленого DPI TForm.Scaled = True . Поінформованість про ІПП важлива для мене лише тоді, коли вона стає важливою для клієнтів, які мене дзвонять і готові платити за це. Технічна причина цієї точки зору полягає в тому, що усвідомлення DPI чи ні, ви відкриваєте вікно у світ поранення. Багато стандартних і сторонніх контрольних пристроїв VCL не працюють добре у високому DPI. Примітним винятком є ​​те, що деталі VCL, що обробляють Windows Common Controls, надзвичайно добре працюють при високій DPI. Величезна кількість сторонніх і вбудованих користувальницьких Delphi VCL керувань не працюють добре або взагалі при високій DPI. Якщо ви плануєте ввімкнути TForm.Scaled, обов'язково пройдіть тест на 96, 125 та 150 DPI для кожної форми у вашому проекті та кожної третьої сторони та вбудованого контролю, який ви використовуєте.

Сама Delphi написана на Delphi. У більшості форм увімкнено прапор поінформованості High DPI, хоча навіть зовсім недавно, як і в Delphi XE2, автори IDE вирішили НЕ включати прапор маніфесту High DPI Awareness. Зауважте, що в Delphi XE4 та пізніших версіях увімкнено прапор HIGH DPI, і IDE виглядає добре.

Я пропоную вам не використовувати TForm.Scaled = true (що за замовчуванням у Delphi, тому, якщо ви не змінили його, більшість форм має масштаб = true) з прапорами Aware High DPI (як показано у відповідях Девіда) з Програми VCL, побудовані за допомогою вбудованого дизайнера форм delphi.

У минулому я намагався зробити мінімальний зразок виду поломки, який ви можете розраховувати, коли TForm.Scaled відповідає дійсності, і коли масштабування форми Delphi має проблеми. Ці глюки не завжди і викликаються лише значенням DPI, ніж 96. Я не зміг визначити повний перелік інших речей, що включає зміни розміру шрифту Windows XP. Але оскільки більшість цих глюків з'являються лише в моїх власних програмах, у досить складних ситуаціях я вирішив показати вам деякі докази, які ви можете перевірити самі.

Delphi XE виглядає так, коли ви встановите масштабування DPI на "Шрифти @ 200%" в Windows 7, а Delphi XE2 аналогічно зламано в Windows 7 і 8, але ці глюки, здається, виправлені, як і в Delphi XE4:

введіть тут опис зображення

введіть тут опис зображення

Це здебільшого стандартні контролі VCL, які погано поводяться при високому DPI. Зауважте, що більшість речей взагалі не були масштабовані, тому розробники Delphi IDE вирішили ігнорувати обізнаність щодо DPI, а також вимкнути віртуалізацію DPI. Такий цікавий вибір.

Вимкніть віртуалізацію DPI, лише якщо хочете цього нового додаткового джерела болю та важкого вибору. Я пропоную вам залишити це в спокої. Зауважте, що загальні елементи керування Windows, здається, працюють нормально. Зауважте, що керування даними провідника Delphi - це обгортка C # WinForms навколо стандартного елемента керування Windows Tree. Це чистий глюк мікрософт, і для його виправлення може знадобитися Embarcadero переписати чисте нативне управління деревом. Навіть microsoft WinForms не може обробляти високу DPI чисто, автоматично та без спеціального коду kludge.

Оновлення: Цікавий фактоїд: Хоча IDE delphi не є «віртуалізованим», він не використовує вміст маніфестів, показаний Девідом, для досягнення «не-віртуалізації DPI». Можливо, він використовує якусь функцію API під час виконання.

Оновлення 2: У відповідь на те, як я підтримаю 100% / 125% DPI, я б створив двофазний план. Етап 1 - це інвентаризація мого коду для користувацьких елементів керування, які потрібно виправити для високої DPI, а потім скласти план їх виправлення або припинення. Фаза 2 повинна взяти деякі області мого коду, які розроблені як форми без управління компонуванням, і змінити їх на форми, які використовують якесь управління компонуванням, щоб зміни DPI або висоти шрифту могли працювати без відсікання. Я підозрюю, що ця схема "міжконтрольного" макетування була б набагато складнішою у більшості програм, ніж робота "внутрішньоконтрольної".

Оновлення: У 2016 році остання версія Delphi 10.1 Берлін добре працює на моїй робочій станції 150 dpi.


5
Ця функція API була б SetProcessDPIAware.
Девід Геффернан

2
Відмінно. Дякую за новий фактїд. Я пропоную вам змінити свою відповідь, щоб запропонувати це як один із можливих маршрутів. Можливо, клієнти можуть навіть захотіти налаштувати цю опцію (вимкніть її, якщо вона не працює для них).
Warren P

Екран сплеску Delphi використовує віртуалізацію DPI, ймовірно, тому, що виклик до SetDPIAware відбувається після того, як форма Splash вже стала видимою.
Warren P

6
RAD Studio - це велика комбінація стандартних елементів управління VCL, спеціальних елементів керування, .NET WinForms та форм FireMonkey. Не дивно, що є проблеми. І тому RAD Studio не є хорошим прикладом.
Торбінс

1
Якщо ви маєте рацію, саме VCL має голову в піску. Навіть Microsoft має голову в піску. Єдиний фреймворк, який я коли-небудь використовував, який виконує роботу з віддаленою прохідністю в цьому, - це COCOA на Mac.
Warren P

63

Ваші налаштування у файлі .dfm будуть масштабовані правильно, поки Scaledє True.

Якщо ви встановлюєте розміри в коді, їх потрібно масштабувати на Screen.PixelsPerInchділення на Form.PixelsPerInch. Використовуйте MulDivдля цього.

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

Ось що робить форма стійкості форми, коли Scaledє True.

Насправді ви можете зробити спільний аргумент щодо заміни цієї функції версією, яка жорстко кодує значення знаменника 96. Це дозволяє використовувати абсолютні значення розмірів і не турбуватися про зміну значення, якщо вам трапиться змінити масштабування шрифту на вашій розроблювальній машині та повторно зберегти файл .dfm. Причина, яка має значення, полягає в тому, що PixelsPerInchвластивість, що зберігається у файлі .dfm, - це значення машини, на якій востаннє збережено файл .dfm.

const
  SmallFontsPixelsPerInch = 96;

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

Отже, продовжуючи тему, варто також насторожитися, що якщо ваш проект розробляється на декількох машинах з різними значеннями DPI, ви побачите, що масштабування, яке Delphi використовує під час збереження .dfm-файлів, призводить до контролю за тиском на серію редагувань . У моєму місці роботи, щоб уникнути цього, у нас є сувора політика, згідно з якою форми редагуються лише в 96dpi (100% -не масштабування).

Насправді моя версія ScaleFromSmallFontsDimensionтакож враховує можливість того, що шрифт форми відрізняється під час виконання від встановленого на час проекту. На комп'ютерах XP форми моєї програми використовують 8-кратну Tahoma. На Vista і вище використовується 9-кратний інтерфейс Segoe. Це забезпечує ще один ступінь свободи. Масштабування має враховувати це, оскільки вважається, що значення абсолютних розмірів, використані у вихідному коді, є відносними до базової лінії 8-кратною Tahoma при 96dpi.

Якщо ви використовуєте будь-які зображення чи гліфи у своєму інтерфейсі, вони також мають масштабувати масштаб. Поширеним прикладом можуть бути гліфи, які використовуються на панелях інструментів та меню. Ви хочете надати ці гліфи як ресурси значків, пов’язаних із вашим виконуваним файлом. Кожна піктограма повинна містити діапазон розмірів, а потім під час виконання вибираєте найбільш відповідний розмір і завантажуєте його у список зображень. Деякі деталі на цю тему можна знайти тут: Як завантажувати піктограми з ресурсу, не страждаючи від збитку?

Ще одна корисна хитрість - визначити розміри у відносних одиницях відносно 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 ви цього досягаєте, встановивши для параметра «Теми виконання» параметр «Нічого».

Маніфест - це правильний спосіб оголосити ваш додаток про те, що він знає високу DPI. Якщо ви просто хочете спробувати це швидко, не псуючи свій маніфест, зателефонуйте 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
Це не завжди так. Насправді, встановлення масштабування = вірно, а потім встановлення відомості про високу DPI також може спричинити деякий дивний злом у більшості програм delphi. Я витратив сотні годин на те, щоб мої програми працювали у високому DPI, і я виявив, що краще мати жахливу пікселізацію, ніж обрізати елементи керування, перенести екран, зайві або відсутні панелі прокрутки на різних елементах управління тощо
Warren P

@WarrenP Я думаю, що ці проблеми стосуються вашої програми. Мій особистий досвід полягає в тому, що моя програма Delphi ідеально відображає та масштабує навіть при 200% -ному масштабуванні шрифту.
Девід Геффернан

2
@WarrenP Так що? Цілком можливо використовувати Delphi для створення додатків, які ведуть себе краще, ніж Delphi IDE.
Девід Геффернан

1
Я бачив безліч діалогів із фіксованими рамками, створеними за допомогою Delphi 5,6,7, і масштабовану настройку справді не вдається. Приховування гараздів, скасування кнопок тощо. Навіть деякі діалоги в Delphi2006, напевно, це укусили. Змішування вбудованих компонентів Delphi та компонентів Windows також дає дивні ефекти. Я завжди розробляю графічний інтерфейс у 125% -ному масштабі шрифту і ставлю масштабовану властивість неправдивою.
LU RD

2
Чудові речі. +1 для фантастичної інформації. Моя думка (не робіть цього) займає друге значення за потребою знати, ЯК це робити, коли ви хочете це зробити ...
Warren P

42

Також важливо зазначити, що шанування DPI користувача - це лише підмножина вашої реальної роботи:

вшанування розміру шрифту користувача

Протягом десятиліть Windows вирішує цю проблему поняттям, що виконує компонування за допомогою діалогових одиниць , а не пікселів. «Діалог одиниця» визначається так , що шрифт в середньому характеру є

  • 4 діалогових одиниці (dlus) в ширину та
  • 8 діалогових блоків (кластер) високий

введіть тут опис зображення

Delphi постачається з (баггі) поняттям Scaled, де форма намагається автоматично налаштувати на основі

  • Налаштування користувача DPI для Windows, вірші
  • налаштування DPI на машині розробника, який востаннє зберігав форму

Це не вирішує проблему, коли користувач використовує шрифт, відмінний від того, для якого ви створили форму, наприклад:

  • розробник розробив форму за допомогою MS Sans Serif 8pt (де середній символ становить 6.21px x 13.00px96dpi)
  • користувач, що працює з Tahoma 8pt (де середній символ - 5.94px x 13.00px96dpi)

    Як це було у всіх, хто розробляє додаток для Windows 2000 або Windows XP.

або

  • розробник розробив форму з ** Tahoma 8pt * (де середній символ - 5.94px x 13.00px96dpi)
  • користувач, що працює з Segoe UI 9pt (де середній символ - 6.67px x 15px96dpi)

Як хороший розробник, ви збираєтеся шанувати переваги шрифту вашого користувача. Це означає, що вам також потрібно масштабувати всі елементи керування вашої форми, щоб відповідати новому розміру шрифту:

  • розширити все по горизонталі на 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 14pt @ 96dpi (10.52px x 25px)
  • Інтерфейс Segoe 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 немає єдиного "налаштування шрифту".
Але з досвіду ми знаємо, що наші форми повинні відповідати налаштуванню Font Title Font

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методом Borland . Спочатку слід вимкнути всі анкери у формі, потім виконати масштабування, а потім знову включити якіри:

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 і виправте свою програму- помилку

Примітка : Будь-який код передається у загальнодоступне надбання. Атрибуція не потрібна.


1
Дякую за відповідь, але що ви пропонуєте для реального світу? Чи вручну змінити розмір усіх елементів керування?
LaBracca

3
"Сумно в тому, що на землі немає розробника Delphi, крім мене, який насправді робить їх програми правильними". Це дуже зарозуміле твердження, яке є невірним. З моєї відповіді: Насправді моя версія ScaleFromSmallFontsDimension також враховує можливість того, що шрифт форми відрізняється під час виконання від встановленого на час проекту. Масштабування має враховувати це, оскільки вважається, що значення абсолютних розмірів, використані у вихідному коді, є відносними до базової лінії 8-кратною Tahoma при 96dpi. Ваші - хороша відповідь, пам’ятайте, +1.
Девід Геффернан

1
@Ian Не я це сказав. Здається, як Уоррен.
Девід Геффернан

2
Це досить приголомшливо, Ян. Дякую.
Warren P

2
Нещодавно натрапив на це питання та відповідь. Я зібрав увесь код Ian у робочий підрозділ тут: pastebin.com/dKpfnXLc та розмістив його на Google+ тут: goo.gl/0ARdq9 Опублікувавши тут, якщо хтось вважає це корисним.
W.Prins

11

Ось мій подарунок. Функція, яка може допомогти вам у горизонтальному розташуванні елементів у ваших графічних інтерфейсах. Безкоштовно для всіх.

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 років, коли не було доступних рішень для проблеми, яку мені довелося вирішити. І навіть сьогодні може виникнути ситуація, коли це можна застосувати. В-)
авра
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.