I\'m trying to implement a auto-complete/search box similar to Visual Studio\'s Go To
member search:
However, my formatting of the bold
When TextRenderer is used to render text in a non-generic Graphics context, this context needs to be considered: for this reason, TextRenderer provides overloads of both MeasureText and DrawText that accept a Graphics context (IDeviceContext) argument.
The Graphics context contains information that TextRenderer can use to better adapt to the DC specifics.
Also, we need to pass to the methods a combination of TextFormatFlags values that define how we want to measure and/or render the Text.
TextFormatFlags.NoPadding
, otherwise the Text will be stretched to fill the drawing bounds.TextFormatFlags.LeftAndRightPadding
to add a predefined padding to the text. The padding this setting applies (based on the Font kerning), matches the distance between the text and the borders of standard Controls (e.g., the ListBox or ListView)More information about TextFormatFlags
is (partially :) available in the Docs.
I've moved all the drawing parts to a single method, RenderText()
.
All measures and drawings are performed here: this way, it should be simpler to understand what is going on when the items are drawn.
The code in the DrawItem
handler calls this method, passing some value that are proper when specific conditions are met (as changing the FontStyle
, the alternative ForeColor
of parts of the Text etc.)
Resulting in:
► The Font used here is Microsoft YaHei UI, 12pt
. Of course you can use whatever other Font, but the System Font series with the UI
appendix are designed (well) for this.
► Remember to dispose of the Graphics objects you create, it's very important, more important when theses objects are used to provide custom functionality to Controls, so probably constantly generated. Don't count on the Garbage Collector for this, it can do nothing for you in this context.
EDIT: Code optimization.
string searchTerm = string.Empty;
TextFormatFlags format = TextFormatFlags.Top | TextFormatFlags.Left |
TextFormatFlags.NoClipping | TextFormatFlags.NoPadding;
private Size RenderText(string text, DrawItemEventArgs e, FontStyle style, Color altForeColor, Point offset)
{
var color = altForeColor == Color.Empty ? e.ForeColor : altForeColor;
using (var font = new Font(e.Font, style)) {
var textSize = TextRenderer.MeasureText(e.Graphics, text, font, e.Bounds.Size, format);
var rect = new Rectangle(offset, e.Bounds.Size);
TextRenderer.DrawText(e.Graphics, text, font, rect, color, e.BackColor, format);
return textSize;
}
}
private IEnumerable<(string Text, bool Selected)> BuildDrawingString(string itemContent, string pattern)
{
if (pattern.Length == 0) {
yield return (itemContent, false);
}
else {
var matches = Regex.Split(itemContent, $"(?i){pattern}");
int pos = itemContent.IndexOf(pattern, StringComparison.CurrentCultureIgnoreCase);
for (int i = 0; i < matches.Length; i++) {
if (matches[i].Length == 0 && i < matches.Length - 1) {
yield return (itemContent.Substring(pos, pattern.Length), matches[i].Length > 0 ? false : true);
}
else {
yield return (matches[i], false);
if (i < matches.Length - 1) {
yield return (itemContent.Substring(pos, pattern.Length), true);
}
}
}
}
}
private void comboBoxItems_DrawItem(object sender, DrawItemEventArgs e)
{
var listItem = (sender as ComboBox).Items[e.Index] as DocGenFieldItem;
e.DrawBackground();
int drawingPosition = 0;
foreach (var part in BuildDrawingString(listItem.Display, searchTerm)) {
var style = part.Selected ? FontStyle.Bold : FontStyle.Regular;
drawingPosition += RenderText(part.Text, e, style, Color.Empty, new Point(drawingPosition, e.Bounds.Y)).Width;
}
var offsetBottom = new Point(0, e.Bounds.Bottom - e.Font.Height - 2);
var valueSize = RenderText("Value: ", e, FontStyle.Bold, Color.FromArgb(64, 64, 64), offsetBottom);
offsetBottom.Offset(valueSize.Width, 0);
RenderText(listItem.Value, e, FontStyle.Regular, Color.FromArgb(63, 63, 63), offsetBottom);
e.DrawFocusRectangle();
}
private void comboBoxItems_MeasureItem(object sender, MeasureItemEventArgs e)
=> e.ItemHeight = (sender as Control).Font.Height * 2 + 4;
In relation to Graphics.MeasureString()
and Graphics.DrawString()
methods used in the question before the update:
Graphics.TextRenderingHint = TextRenderingHint.AntiAlias
doesn't work really well when Text is rendered with Graphics.DrawString()
: use TextRenderingHint.ClearTypeGridFit
instead.Microsoft Sans Serif
as Font, use Segoe UI
or Microsoft YaHei UI
instead (for example): these Fonts are much better weighted and explicitly designed for this (the UI
suffix gives it away).