ComboBox OwnerDrawVariable字体格式大小问题 [英] ComboBox OwnerDrawVariable Font format size problem

查看:108
本文介绍了ComboBox OwnerDrawVariable字体格式大小问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试实现类似于Visual Studio的转到成员搜索的自动完成/搜索框:


< a href = https://i.stack.imgur.com/KciJX.png rel = nofollow noreferrer>


但是,我的 bold 文本格式及其间距不是计算权。我将省略此功能的自动完成功能,只包含通过对搜索词进行硬编码来格式化结果的代码。

e.Graphics.MeasureString <确定的间距/ code>似乎没有返回正确的值。我尝试使用


此外,如果我将鼠标悬停在某个项目上,它将重新绘制我的文本,不加粗。我也想停止该操作。


更新:我将代码更改为使用 TextRenderer

现在我连接的每场比赛前后似乎都有多余的空间。


下面的更新代码:


 私有无效Form1_Load(对象发送者,EventArgs e)
{
var docGenFields = new [] {
new DocGenFieldItem {Display = $ Profile.date-birth.value,值= 5/9/1973,FieldCode = $ Profile.date-birth.value },
新的DocGenFieldItem {显示= $ Profile.date-birth.text,值=出生日期,FieldCode = $ Profile.date-birth.text; },
新的DocGenFieldItem {显示= $ Profile.date-birth.raw-value,值= 1973-05-09,FieldCode = $ Profile.date-birth.raw-value ; },
新的DocGenFieldItem {显示= $ Profile.name-first.value,值= Terry,FieldCode = $ Profile.name-first.value; },
新的DocGenFieldItem {显示= $ Profile.name-first.text,值= First Name,FieldCode = $ Profile.name-first.text; },
新的DocGenFieldItem {显示= $ Profile.name-first.raw-value,值= Terry,FieldCode = $ Profile.name-first.raw-value; },
新的DocGenFieldItem {显示= $ Profile.name-first.value,值=明尼苏达州,FieldCode = $ Profile.state.value; },
新的DocGenFieldItem {显示= $ Profile.name-first.text,值= State,FieldCode = $ Profile.state.text; },
新的DocGenFieldItem {显示= $ Profile.name-first.raw-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 + =新MeasureItemEventHandler(comboBoxItems_MeasureItem);
}

private void comboBoxItems_DrawItem(object sender,DrawItemEventArgs e)
{
//绘制项目的背景。
e.DrawBackground();

var listItem = comboBoxItems.Items [e.Index]作为DocGenFieldItem;

var searchTerm = P;
var matchs = 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;
//浮动currentX = 0;
var currentX = 0;
var currentMatch = 0;
var keyLength = searchTerm.Length;

foreach(匹配中的var m)
{
//如果搜索词的字符是第一个(例如StartsWith)或最后一个(例如EndsWith)字符
//匹配将为空。因此,如果不为空,则需要在常规字符
中渲染字符之间
// 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 ++;

//呈现搜索词字符(需要使用当前文本的子字符串来保持
//文本的原始大小写)*匹配之间使用粗体*。
//字符串。IsNullOrEmpty(m)&& currentMatch == 1-如果搜索词匹配整个值
//,则currentMatch =匹配。长度(1),但匹配的'm'将为空。
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,粗体,Brushes.Black,p,StringFormat.GenericTypographic);

var p = new Point(currentX,e.Bounds.Y);
var mWidth = TextRenderer.MeasureText(e.Graphics,mValue,粗体);
TextRenderer.DrawText(e.Graphics,mValue,粗体,p,System.Drawing.Color.Black);

currentX + = mWidth.Width;
currentCharacter + = keyLength;
}
}

//在下拉菜单
中显示辅助信息行var b = new SolidBrush(ColorTranslator.FromHtml(#636363) );
var valueWidth = e.Graphics.MeasureString(" Value:" ;,粗体);

e.Graphics.DrawString( Value:,粗体,b,
新RectangleF(e.Bounds.X,e.Bounds.Y + 21,e.Bounds。宽度,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)
);

//如果鼠标悬停在某个项目上,则绘制焦点矩形。
e.DrawFocusRectangle();
}

private void comboBoxItems_MeasureItem(object sender,MeasureItemEventArgs e)
{
e.ItemHeight = 44;
}


解决方案


►此处使用的字体为 Microsoft YaHei UI,12pt 。当然,您可以使用任何其他字体,但是设计了带有 UI 附录的系统字体系列(很好)


►记住要处理创建的Graphics对象,这很重要,当这些对象用于为控件提供自定义功能(可能不断生成)时,这一点尤为重要。不要指望垃圾收集器,在这种情况下它对您无能为力。


EDIT :代码优化。

 字符串searchTerm = string.Empty; 
TextFormatFlags format = TextFormatFlags.Top | TextFormatFlags.Left |
TextFormatFlags.NoClipping | TextFormatFlags.NoPadding;

私有大小RenderText(字符串文本,DrawItemEventArgs e,FontStyle样式,颜色altForeColor,点偏移)
{
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,文本,字体,矩形,颜色,e.BackColor,格式);
返回textSize;
}
}

私有IEnumerable<(字符串文本,bool已选定)> BuildDrawingString(string itemContent,string pattern)
{
if(pattern.Length == 0){
收益回报(itemContent,false);
}
else {
var matchs = Regex.Split(itemContent,$((?i){pattern}));
int pos = itemContent.IndexOf(pattern,StringComparison.CurrentCultureIgnoreCase);
for(int i = 0; i< matchs.Length; i ++){
if(matches [i] .Length == 0&< i< matchs.Length-1){
收益回报(itemContent.Substring(pos,pattern.Length),matchs [i] .Length> 0?false:true);
}
else {
收益率回报(matches [i],假);
if(i< matches.Length-1){
收益回报(itemContent.Substring(pos,pattern.Length),true);
}
}
}
}
}

private void comboBoxItems_DrawItem(object sender,DrawItemEventArgs e)
{
var listItem =(发送者为ComboBox)。Items[e.Index]为DocGenFieldItem;
e.DrawBackground();

int drawingPosition = 0;
foreach(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 =(发送方作为控件)。Font.Height* 2 + 4;




Graphics.MeasureString( ) Graphics.DrawString() 方法用于更新前的问题:



  • 当我们使用特定的StringFormat测量文本时,如果我们希望我们的绘图遵守测量的边界,则我们将使用相同的StringFormat绘制文本。
  • $当使用 Graphics.DrawString()渲染文本时,b $ b
  • Graphics.TextRenderingHint = TextRenderingHint.AntiAlias 不能很好地工作code>:改为使用 TextRenderingHint.ClearTypeGridFit

  • 可能,避免使用 Microsoft Sans Serif 。 code>作为字体,使用 Segoe UI Microsoft YaHei UI (例如):按此显式设计,这些字体的权重要好得多( UI 后缀将其放弃)


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 text and its spacing isn't calculating right. I'll omit the auto complete functionality of this and only include code that is formatting the result by hard coding a search term.
The spacing determined by e.Graphics.MeasureString doesn't seem to return correct value. I tried to use StringFormat.GenericTypographic from this question and I got closer but still not correct.

Here is a display of my dropdown where the matched term (in bold) is easily showing that my format position calculation is off (the f is clearly encroaching on the i).

In addition to that, if I hover over an item, it redraws my text without bold. I'd like to stop that as well.

Update: I changed my code to use TextRenderer but now it appears even worse.
There now seems to be extra space before and after each match I concatenate.

Updated code below:

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;
}

解决方案

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 uses 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 measure and/or render the Text.

  • Always declare the type of Alignment
  • Specify the clipping/wrapping behavior (e.g., we want the Text to wrap or we really don't want it to, we want it clipped instead)
  • If the Text should NOT be padded, specify TextFormatFlags.NoPadding, otherwise the Text will be stretched to fill the drawing bounds.
  • If the the drawing bounds are not arranged manually (to draw text in specific positions), specify TextFormatFlags.LeftAndRightPadding to add a pre-defined 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 one 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 the Graphics.MeasureString() and Graphics.DrawString() methods used in the question before the update:

  • When we measure Text with a specific StringFormat, then we draw the Text using the same StringFormat, if we want our drawings to respect the measured bounds.
  • Graphics.TextRenderingHint = TextRenderingHint.AntiAlias doesn't work really well when Text is rendered with Graphics.DrawString(): use TextRenderingHint.ClearTypeGridFit instead.
  • Possibly, avoid Microsoft Sans Serif as Font, use Segoe UI or Microsoft YaHei UI instead (for example): these Fonts are much better weighted as explicitly designed for this (the UI suffix gives it away).

这篇关于ComboBox OwnerDrawVariable字体格式大小问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆