Как получить точные текстовые поля, используемые 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
в самом начале и в самом конце всего мульти- формат текста.
Ответы
Ответ 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
, дает ожидаемые результаты.