Почему не меняется жирность или размер шрифта в FormEx для 1С 7.7 и как это исправить

Программист 1С v7.7 IT и автоматизация бизнеса
← На главную

При кастомизации и улучшении юзабилити интерфейсов в среде 1С:Предприятие 7.7 разработчики часто используют замечательную внешнюю компоненту FormEx (ФормЭкс). Она позволяет динамически управлять свойствами элементов диалога, включая цвета, видимость и, конечно же, шрифты. Однако при попытке программно изменить шрифт у элемента формы (например, текстового поля или кнопки) программисты нередко сталкиваются со странным поведением: какие бы параметры жирности или размера ни передавались в код, визуально шрифт либо вообще не меняется, либо отображается жирным по умолчанию, либо искажает пропорции интерфейса.

В этой статье мы подробно разберем, почему управление шрифтами через FormEx может работать некорректно, исследуем механизм взаимодействия компоненты с графической подсистемой Windows GDI и научимся правильно применять и уничтожать шрифты без риска зависания программы.

Анализ проблемы: типичная ошибка преждевременного удаления шрифта

Давайте рассмотрим типичный пример кода из практики разработчиков, в котором создается новый шрифт и назначается элементу формы (атрибуту "Комментарий") при ее открытии:


Процедура ПослеОткрытия() 
    РФ = СоздатьОбъект("РасширениеФормы");
    Шрифт = Сервис.СоздатьШрифт(, 13, 1);
    РФ.ПолучитьАтрибут("Комментарий").Шрифт = Шрифт;
    Сервис.УдалитьШрифт(Шрифт);
КонецПроцедуры

На первый взгляд, логика абсолютно верная и чистая. Мы создали объект шрифта с помощью метода СоздатьШрифт, присвоили его свойству Шрифт нужного элемента формы и сразу же вызвали УдалитьШрифт, чтобы освободить оперативную память компьютера. Однако именно в строке немедленного удаления кроется критическая ошибка, из-за которой вся верстка элемента "ломается".

Почему нельзя удалять шрифт сразу после назначения?

Выясним причину этого сбоя, заглянув под капот операционной системы Windows и графической библиотеки GDI (Graphics Device Interface), которую использует 1С 7.7 для прорисовки окон:

  1. Метод СоздатьШрифт не создает "готовые буквы" внутри элемента 1С. Он обращается к операционной системе Windows и регистрирует в ней новый системный графический объект. В ответ операционная система возвращает числовой идентификатор — дескриптор объекта (Handle). Именно это число хранится в переменной Шрифт.
  2. Когда мы пишем РФ.ПолучитьАтрибут("Комментарий").Шрифт = Шрифт;, мы просто передаем элементу управления этот числовой дескриптор. Элемент запоминает ссылку, но физический рендеринг (отрисовка) текста на экране происходит асинхронно — чуть позже, когда Windows начнет формировать картинку окна.
  3. Вызывая команду Сервис.УдалитьШрифт(Шрифт); в следующей строке, мы принудительно уничтожаем созданный GDI-объект в оперативной памяти Windows. Дескриптор становится недействительным.
  4. Когда операционная система наконец-то добирается до перерисовки нашего текстового поля, элемент управления пытается обратиться к объекту шрифта по запомненной ссылке. Обнаружив, что объекта больше не существует, Windows сбрасывает параметры отображения и подставляет стандартный шрифт по умолчанию (обычно жирный системный MS Sans Serif). В результате наши настройки высоты, наклона или жирности полностью игнорируются.

Правильный жизненный цикл шрифта в форме

Чтобы избежать этой проблемы, мы должны изменить подход к управлению временем жизни шрифта. Созданный графический объект должен существовать в памяти на протяжении всего времени, пока открыта форма и на ней отображаются элементы с этим шрифтом. Удалять объект нужно только тогда, когда форма закрывается.

Давайте разберем по шагам правильную реализацию этого алгоритма:

  1. Объявим переменную для хранения дескриптора шрифта на уровне модуля формы, чтобы она была доступна во всех процедурах.
  2. Создадим и назначим шрифт в процедуре ПослеОткрытия (или ПослеСозданияФормы).
  3. Уничтожим шрифт в предопределенной процедуре ПриЗакрытии формы.

Посмотрим на практический пример кода:


Перем глШрифтЭлемента; // Переменная модуля формы

Процедура ПослеОткрытия() 
    РФ = СоздатьОбъект("РасширениеФормы");
    
    // Создаем шрифт: имя "MS Sans Serif", высота 13, жирность 4 (NORMAL)
    глШрифтЭлемента = Сервис.СоздатьШрифт("MS Sans Serif", 13, 4);
    
    // Назначаем шрифт элементу управления
    РФ.ПолучитьАтрибут("Комментарий").Шрифт = глШрифтЭлемента;
КонецПроцедуры

Процедура ПриЗакрытии()
    // Уничтожаем объект шрифта только при закрытии формы
    Если ПустоеЗначение(глШрифтЭлемента) = 0 Тогда
        Сервис.УдалитьШрифт(глШрифтЭлемента);
    КонецЕсли;
КонецПроцедуры

Используя эту схему, мы гарантируем, что объект шрифта будет существовать ровно столько, сколько открыто окно, и Windows сможет корректно и без искажений отрисовывать интерфейс.

Нюансы работы параметра "Жирность" (Weight)

Давайте проанализируем ситуацию, когда программист меняет параметр жирности (третий параметр в функции СоздатьШрифт) от 1 до 6, но на экране ничего не меняется, и шрифт выглядит абсолютно одинаково. Разница становится заметна только при установке значения 7 (BOLD) и более.

Параметр "Жирность" в FormEx принимает значения от 1 (THIN — самый тонкий) до 9 (HEAVY — самый толстый), транслируя их в стандартные веса шрифтов Windows API (от 100 до 900 соответственно). Но тонкие градации часто не работают на практике по двум причинам:

  1. Отсутствие поддержки в самом файле шрифта. Большинство стандартных экранных гарнитур (например, MS Sans Serif, Tahoma, Arial) физически содержат в своих файлах только два начертания: Regular (обычный, вес 400) и Bold (жирный, вес 700). Если вы затребуете жирность 1 (Thin, вес 100), системный сопоставитель шрифтов Windows (Font Mapper) поймет, что такого начертания в системе нет, и автоматически подставит наиболее близкое доступное — то есть Regular (4).
  2. Ограничения платформы 1С:Предприятие 7.7. Внутренний механизм отрисовки диалогов старой платформы 1С обрабатывает жирность бинарно. Для него существует только два визуальных состояния: обычный (все системные веса менее 700) и жирный (вес 700 и выше). Именно поэтому реальную разницу на экране вы увидите только тогда, когда укажете параметр жирности равным 7 или выше.

Особенности работы в модальном режиме: выбор правильного события

Если ваша форма открывается модально, использование предопределенного события компоненты FormEx ПослеОткрытия() может приводить к нестабильному результату. В некоторых версиях компоненты в модальном режиме это событие может вообще не вызываться из-за особенностей перехвата сообщений Windows.

Для настройки визуального отображения элементов в модальных окнах рекомендуется использовать альтернативное событие — ПослеСозданияФормы(). Данное событие срабатывает, когда контекст формы и все элементы управления уже созданы в оперативной памяти компьютера, но само окно еще не выведено на экран монитора. Это не только гарантирует стабильное применение шрифтов в любых режимах открытия окон, но и предотвращает раздражающий эффект "мигания" элементов при их перерисовке.

Предотвращение утечек ресурсов GDI (GDI Leak)

Стоит помнить, что в операционной системе Windows установлен строгий лимит на количество одновременно открытых графических дескрипторов GDI (по умолчанию — 10 000 объектов на один запущенный процесс). Если вы будете создавать шрифты с помощью метода СоздатьШрифт и забывать удалять их в процедуре ПриЗакрытии, то ресурсы начнут накапливаться (произойдет утечка памяти GDI).

При достижении лимита программа 1С:Предприятие 7.7 начнет искажать интерфейс: перестанут прорисовываться кнопки, исчезнут иконки меню, а затем программа аварийно закроется без предупреждений и сохранения данных. Всегда следите за тем, чтобы каждому вызову создания шрифта соответствовал вызов его удаления при закрытии контекста.

Если один и тот же шрифт используется у вас во многих формах конфигурации, гораздо эффективнее и безопаснее создать его один раз глобально при запуске системы, а удалять только при выходе из программы:


// В Глобальном модуле
Перем глШрифтДляИнтерфейса Экспорт;

Процедура ПриНачалеРаботыСистемы()
    ЗагрузитьВнешнююКомпоненту("FormEx.dll");
    Сервис = СоздатьОбъект("Сервис");
    // Создаем глобальный шрифт для всей системы
    глШрифтДляИнтерфейса = Сервис.СоздатьШрифт("Tahoma", 11, 4);
КонецПроцедуры

Процедура ПриЗавершенииРаботыСистемы()
    // Освобождаем ресурсы операционной системы при выходе
    Если ПустоеЗначение(глШрифтДляИнтерфейса) = 0 Тогда
        Сервис = СоздатьОбъект("Сервис");
        Сервис.УдалитьШрифт(глШрифтДляИнтерфейса);
    КонецЕсли;
КонецПроцедуры

В локальных формах вам больше не придется писать код удаления — достаточно будет просто назначить глобальную переменную глШрифтДляИнтерфейса свойству Шрифт нужного элемента управления. Это полностью исключит риск утечки ресурсов и сделает работу приложения стабильной.

← На главную