Ter*_*rry 4 .net c# graphics combobox winforms
我正在尝试实现一个类似于 Visual StudioGo To
成员搜索的自动完成/搜索框:
但是,我的bold
文本格式及其间距计算不正确。我将省略它的自动完成功能,只包含通过硬编码搜索词来格式化结果的代码。
由 确定的间距e.Graphics.MeasureString
似乎没有返回正确的值。我试图StringFormat.GenericTypographic
从这个问题中使用,我离得更近了,但仍然不正确。
这是我的下拉列表的显示,其中匹配的术语(以粗体显示)很容易表明我的格式位置计算已关闭(f
显然侵占了i
)。
除此之外,如果我将鼠标悬停在一个项目上,它会重绘我的文本而不加粗。我也想就此打住。
更新:我更改了我的代码以使用,TextRenderer
但现在看起来更糟。
现在我连接的每场比赛前后似乎都有额外的空间。
更新代码如下:
private void Form1_Load( object sender, EventArgs e )
{
var docGenFields = new[] {
new DocGenFieldItem { Display = $"Profile.date-birth.value", Value = "5/9/1973", FieldCode = $"Profile.date-birth.value" },
new DocGenFieldItem { Display = $"Profile.date-birth.text", Value = "Birth Date", FieldCode = $"Profile.date-birth.text" },
new DocGenFieldItem { Display = $"Profile.date-birth.raw-value", Value = "1973-05-09", FieldCode = $"Profile.date-birth.raw-value" },
new DocGenFieldItem { Display = $"Profile.name-first.value", Value = "Terry", FieldCode = $"Profile.name-first.value" },
new DocGenFieldItem { Display = $"Profile.name-first.text", Value = "First Name", FieldCode = $"Profile.name-first.text" },
new DocGenFieldItem { Display = $"Profile.name-first.raw-value", Value = "Terry", FieldCode = $"Profile.name-first.raw-value" },
new DocGenFieldItem { Display = $"Profile.name-first.value", Value = "Minnesota", FieldCode = $"Profile.state.value" },
new DocGenFieldItem { Display = $"Profile.name-first.text", Value = "State", FieldCode = $"Profile.state.text" },
new DocGenFieldItem { Display = $"Profile.name-first.raw-value", Value = "MN", FieldCode = $"Profile.state.raw-value" }
};
comboBoxItems.FormattingEnabled = true;
comboBoxItems.DrawMode = DrawMode.OwnerDrawVariable;
comboBoxItems.DropDownHeight = 44 * 5;
// comboBoxItems.Font = new Font( "Microsoft Sans Serif", 12F, FontStyle.Regular, GraphicsUnit.Point, 0 );
comboBoxItems.Font = new Font( "Segoe UI", 12F, FontStyle.Regular, GraphicsUnit.Point, 0 );
comboBoxItems.Items.AddRange( docGenFields );
comboBoxItems.DrawItem += new DrawItemEventHandler( comboBoxItems_DrawItem );
comboBoxItems.MeasureItem += new MeasureItemEventHandler( comboBoxItems_MeasureItem );
}
private void comboBoxItems_DrawItem( object sender, DrawItemEventArgs e )
{
// Draw the background of the item.
e.DrawBackground();
var listItem = comboBoxItems.Items[ e.Index ] as DocGenFieldItem;
var searchTerm = "P";
var matches = Regex.Split( listItem.Display, "(?i)" + searchTerm );
var bold = new Font( e.Font.FontFamily, e.Font.Size, FontStyle.Bold );
// e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
var currentCharacter = 0;
// float currentX = 0;
var currentX = 0;
var currentMatch = 0;
var keyLength = searchTerm.Length;
foreach ( var m in matches )
{
// If search term characters are first (like StartsWith) or last (like EndsWith) characters
// then the match will be empty. So if not empty, then need to render the characters 'between'
// matches of search term in regular font
if ( !string.IsNullOrEmpty( m ) )
{
// var p = new PointF( e.Bounds.X + currentX, e.Bounds.Y );
// var mWidth = e.Graphics.MeasureString( m, e.Font, p, StringFormat.GenericTypographic );
// e.Graphics.DrawString( m, e.Font, Brushes.Black, p );
var p = new Point( currentX, e.Bounds.Y );
var mWidth = TextRenderer.MeasureText( e.Graphics, m, e.Font );
TextRenderer.DrawText( e.Graphics, m, e.Font, p, System.Drawing.Color.Black );
currentX += mWidth.Width;
currentCharacter += m.Length;
}
currentMatch++;
// Render the search term characters (need to use 'substring' of current text to maintain
// original case of text) *bold* in between matches.
// string.IsNullOrEmpty( m ) && currentMatch == 1 - If the search term matches ENTIRE value
// then currentMatch will = matches.Length (1) but the match of 'm' will be empty.
if ( currentMatch < matches.Length || ( string.IsNullOrEmpty( m ) && currentMatch == 1 ) )
{
var mValue = listItem.Display.Substring( currentCharacter, keyLength );
// var p = new PointF( e.Bounds.X + currentX, e.Bounds.Y );
// var mWidth = e.Graphics.MeasureString( mValue, bold, p, StringFormat.GenericTypographic );
// e.Graphics.DrawString( mValue, bold, Brushes.Black, p, StringFormat.GenericTypographic );
var p = new Point( currentX, e.Bounds.Y );
var mWidth = TextRenderer.MeasureText( e.Graphics, mValue, bold );
TextRenderer.DrawText( e.Graphics, mValue, bold, p, System.Drawing.Color.Black );
currentX += mWidth.Width;
currentCharacter += keyLength;
}
}
// Render a secondary 'info' line in the dropdown
var b = new SolidBrush( ColorTranslator.FromHtml( "#636363" ) );
var valueWidth = e.Graphics.MeasureString( "Value: ", bold );
e.Graphics.DrawString( "Value: ", bold, b,
new RectangleF( e.Bounds.X, e.Bounds.Y + 21, e.Bounds.Width, e.Bounds.Height )
);
e.Graphics.DrawString( listItem.Value, e.Font, b,
new RectangleF( e.Bounds.X + valueWidth.Width, e.Bounds.Y + 21, e.Bounds.Width, 21 )
);
// Draw the focus rectangle if the mouse hovers over an item.
e.DrawFocusRectangle();
}
private void comboBoxItems_MeasureItem( object sender, MeasureItemEventArgs e )
{
e.ItemHeight = 44;
}
Run Code Online (Sandbox Code Playgroud)
当TextRenderer用于在非通用 Graphics 上下文中呈现文本时,需要考虑此上下文:因此,TextRenderer 提供了接受 Graphics 上下文 ( IDeviceContext ) 参数的MeasureText和DrawText 的重载。
Graphics 上下文包含 TextRenderer 可以用来更好地适应 DC 细节的信息。
此外,我们需要将TextFormatFlags值的组合传递给方法,这些值定义了我们想要如何测量和/或呈现文本。
TextFormatFlags.NoPadding
,否则文本将被拉伸以填充绘图边界。TextFormatFlags.LeftAndRightPadding
向文本添加预定义的填充。此设置应用的填充(基于字体字距调整),匹配文本和标准控件(例如,ListBox 或 ListView)边框之间的距离TextFormatFlags
文档中提供了有关(部分:) 的更多信息。
我已将所有绘图部分移至单一方法RenderText()
.
所有的测量和绘图都在这里执行:这样,在绘制项目时应该更容易理解发生了什么。
DrawItem
处理程序中的代码调用此方法,传递一些在满足特定条件时正确的值(如更改FontStyle
、ForeColor
文本部分的替代等)
导致:
? 这里使用的字体是Microsoft YaHei UI, 12pt
. 当然,您可以使用任何其他字体,但是带有附录的 System Font 系列是为此设计的(很好)。UI
? 请记住处理您创建的 Graphics 对象,这非常重要,当这些对象用于为 Controls 提供自定义功能时更重要,因此可能会不断生成。不要指望垃圾收集器,它在这种情况下对您无能为力。
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;
Run Code Online (Sandbox Code Playgroud)
关于更新前问题中使用的方法Graphics.MeasureString()
和Graphics.DrawString()
方法:
Graphics.TextRenderingHint = TextRenderingHint.AntiAlias
当 Text 使用Graphics.DrawString()
:呈现时效果不佳TextRenderingHint.ClearTypeGridFit
。Microsoft Sans Serif
作为字体,使用Segoe UI
或Microsoft YaHei UI
代替(例如):这些字体的权重要好得多,并且明确为此设计(UI
后缀给出了它)。 归档时间: |
|
查看次数: |
186 次 |
最近记录: |