Подтвердить что ты не робот

Как получить точные текстовые поля, используемые TextRenderer

System.Windows.Forms.TextRenderer.DrawText метод отображает форматированный текст с левым или правым заполнением или без него в зависимости от значения параметра flags:

  • TextFormatFlags.NoPadding - плотно вставляется в ограничивающий прямоугольник,
  • TextFormatFlags.GlyphOverhangPadding - добавляет левое и правое поля,
  • TextFormatFlags.LeftAndRightPadding - добавляет еще большие поля.

Теперь, мой вопрос: , как я могу получить точное количество дополнений (слева и справа), добавленных DrawText к тексту для заданного контекста устройства, строки, шрифта и т.д.

Я выкопал в .NET 4 с .NET Reflector и обнаружил, что TextRenderer вычисляет "навесу навеса", которая составляет 1/6 от высоты шрифта, а затем умножает это значение для вычисления левого и правого полей с использованием этих коэффициентов:

  • left 1.0, right 1.5 для TextFormatFlags.GlyphOverhangPadding,
  • left 2.0, справа 2.5 для TextFormatFlags.LeftAndRightPadding.

Полученные значения округляются и передаются в собственные функции API DrawTextExA или DrawTextExW. Трудно воссоздать этот процесс, потому что высота шрифта берется не из System.Drawing.Font, а из System.Windows.Forms.Internal.WindowsFont, и эти классы возвращают разные значения для одного и того же шрифта. И еще много других внутренних классов BCL из пространства имен System.Windows.Forms.Internal. Декомпиляция всех из них и повторное использование их кода в моем приложении не является вариантом, потому что это будет серьезная зависимость реализации .NET. Поэтому мне нужно знать, есть ли какой-либо публичный API в WinForms или, по крайней мере, какие функции Windows я могу использовать для получения значений полей слева и справа.


Примечание.. Я пытался использовать TextRenderer.MeasureText с заполнением и без него и сравнивать результаты, но это дало мне только сумму левого и правого полей, и я нуждаюсь в них отдельно.


Примечание 2: Если вам интересно, зачем мне это нужно: я хочу нарисовать одну строку с несколькими шрифтами/цветами. Это включает вызов DrawText один раз для каждой равномерно форматированной подстроки с параметром NoPadding (так что текст не распространяется), но я также хочу добавить вручную обычный GlyphOverhangPadding в самом начале и в самом конце всего мульти- формат текста.

4b9b3361

Ответ 1

Значение для вычисления левого и правого полей - TEXTMETRIC.tmHeight, которое можно получить с помощью Win32 API.

Однако я обнаружил, что tmHeight - это всего лишь высота строки шрифта в пикселях, поэтому эти три подхода дадут вам одинаковое значение (вы можете использовать то, что вам нравится в вашем коде):

int apiHeight = GetTextMetrics(graphics, font).tmHeight;
int gdiHeight = TextRenderer.MeasureString(...).Height;
int gdipHeight = (int)Math.Ceiling(font.GetHeight(graphics));

Чтобы получить левое и правое поля, мы используем тот же код, что и TextRenderer под капотом:

private const float ItalicPaddingFactor = 0.5f;

...

float overhangPadding = (gdiHeight / 6.0f);

//NOTE: proper margins for TextFormatFlags.LeftAndRightPadding flag
//int leftMargin = (int)Math.Ceiling(overhangPadding);
//int rightMargin = (int)Math.Ceiling(overhangPadding * (2 + ItalicPaddingFactor));

//NOTE: proper margins for TextFormatFlags.GlyphOverhangPadding flag
int leftMargin = (int)Math.Ceiling(overhangPadding);
int rightMargin = (int)Math.Ceiling(overhangPadding * (1 + ItalicPaddingFactor));

Size sizeOverhangPadding = TextRenderer.MeasureText(e.Graphics, "ABC", font, Size.Empty, TextFormatFlags.GlyphOverhangPadding);
Size sizeNoPadding = TextRenderer.MeasureText(e.Graphics, "ABC", font, Size.Empty, TextFormatFlags.NoPadding);

int overallPadding = (sizeOverhangPadding.Width - sizeNoPadding.Width);

Теперь вы можете легко проверить, что

(leftMargin + rightMargin) == overallPadding

Просто отметить:

Мне нужно было решить эту проблему, чтобы реализовать функцию "Поиск ярлыка" в элементе управления ListView, который использует визуализацию текста GDI:

enter image description here

Работает как шарм:)

Ответ 2

Этот ответ - отрывок отсюда - http://www.techyv.com/questions/how-get-exact-text-margins-used-textrenderer#comment-35164

Если вы когда-либо хотели, чтобы Label или TextBox в Windows Forms выполняли немного больше, как в Интернете, то вы, вероятно, поняли, что нет интуитивного способа сделать ярлык или TextBox автоматически отрегулировать его высоту, чтобы соответствовать текст, который он содержит. Хотя это может быть неинтуитивным, это определенно не невозможно.

В этом примере я буду использовать TextBox (вы можете так же легко использовать ярлык), который прикреплен к вершине формы. Чтобы использовать это, добавьте aTextBox с именем MyTextBox в форму и установите Dock to DockStyle.Верхний. Подключите событие Resize для TextBox к этому обработчику событий.

private void MyTextBox_Resize( object sender, EventArgs e )
{
    // Grab a reference to the TextBox
    TextBox tb = sender as TextBox; 

    // Figure out how much space is used for borders, etc. 
    int chromeHeight = tb.Height - tb.ClientSize.Height;

    // Create a proposed size that is very tall, but exact in width. 
    Size proposedSize = new Size( tb.ClientSize.Width, int.MaxValue );

    // Measure the text using the TextRenderer
    Size textSize = TextRenderer.MeasureText( tb.Text, tb.Font,
        proposedSize, TextFormatFlags.TextBoxControl
         | TextFormatFlags.WordBreak );

    // Adjust the height to include the text height, chrome height,
    // and vertical margin
    tb.Height = chromeHeight + textSize.Height 
        + tb.Margin.Vertical; 
}

Если вы хотите изменить размер ярлыка или текстового поля, который не закреплен (например, тот, который находится в FlowLayoutPanel или другой панели или просто помещен в форму), тогда вы можете обрабатывать размер формы даже взамен, и просто измените свойства Control непосредственно.

Ответ 3

Это может показаться (очень) грубым, но это единственная собственная реализация, о которой я могу думать: DrawText обращается к IDeviceContext, который реализуется Graphics. Теперь мы можем воспользоваться этим с помощью следующего кода:

Bitmap bmp = new Bitmap(....);
Graphics graphic = Graphics.FromImage(bmp);
textRenderer.DrawText(graphic,....);
graphic.Dispose();

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

Это не проверено, а основано на следующих источниках:

http://msdn.microsoft.com/en-us/library/4ftkekek.aspx

http://msdn.microsoft.com/en-us/library/system.drawing.idevicecontext.aspx

http://msdn.microsoft.com/en-us/library/system.drawing.graphics.aspx

http://www.pcreview.co.uk/forums/graphics-bitmap-t1399954.html

Ответ 4

Я сделал нечто подобное несколько лет назад, чтобы выделить результаты поиска (шаблон поиска выделен жирным шрифтом и т.д.). Моя реализация была в DevExpress, поэтому код может быть неактуальным. Если вы считаете это полезным, я могу его скопировать, просто нужно найти эту реализацию.

В System.Windows.Forms класс для использования будет Graphics. Он имеет метод MeasureCharacterRanges(), который принимает StringFormat (начните с GenericTypographic и идите оттуда). Это гораздо более уместно, чем TextRenderer для отображения полной строки путем цепочки деталей с разными стилями, шрифтами или кистями.

Вы пошли дальше, чем я, с фактическим измерением заполнения. Элементы управления DevExpress дали вам прямоугольник, ограничивающий текст, для начала, чтобы это было сделано для меня.

Вот статья Пьера Арно, которая пришла ко мне в Google, которая касается этой области. К сожалению, ссылка GDI + "Gory details" нарушена.

Cheers,
Jonno

Ответ 5

Исправление состоит в том, чтобы вычислить, что добавит MeasureText:

var formatFlags FormatFlags =
    TextFormatFlags.NoPadding |
    TextFormatFlags.SingleLine;

int largeWidth = TextRenderer.MeasureText(
    "  ",
    font,
    new Size(int.MaxValue, int.MaxValue),
    formatFlags
).Width;
int smallWidth = TextRenderer.MeasureText(
    " ",
    font,
    new Size(int.MaxValue, int.MaxValue),
    formatFlags
).Width;

int extra = smallWidth - (largeWidth - smallWidth);

Мы вычисляем ширину одного пространства и ширину двух пространств. Оба имеют дополнительную ширину, поэтому мы можем экстраполировать добавленную дополнительную ширину. Добавленная ширина, по-видимому, всегда одна и та же, поэтому вычитание extra из каждой ширины, возвращаемой MeasureText, дает ожидаемые результаты.