如何使用图形突出显示控件中的换行文本?

Adh*_*dhi 4 .net c# gdi+ highlight winforms

我需要使用填充矩形突出显示控件中的特定字符。我可以使用Graphics.MeasureString()下面的方法获取文本未换行时的位置,

var size = g.MeasureString(tempSearchText, style.Font, 0, StringFormat.GenericTypographic);
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

如果文本被换行,那么我无法找到字符的确切边界来突出显示文本。

在此处输入图片说明

我需要在被包装的文本中获得给定字符的确切边界。提供您的建议以实现此场景。

Jim*_*imi 6

没有明确指定要针对哪些控件,所以我正在测试 3 个不同的:
TextBoxRichTextboxListBox

TextBox 和 RichTextbox 具有相同的行为并共享相同的工具,因此无需定义两种不同的方法来实现相同的结果。
当然 RichTextbox 提供了更多选项,包括RTF

另外,我正在测试Graphics.DrawString()TextRenderer.DrawText()

这是本次测试的结果,因此代码的作用更加清晰。

在此处输入图片说明

警告
对于本示例,我使用的是Control.CreateGraphics(),因为TextBoxRichTextBox控件不提供Paint()事件。对于真实世界的应用程序,您应该创建一个派生自TextBoxor RichTextBox、覆盖WndPrc和 handle的自定义控件WM_PAINT

1)突出显示多行 TextBox 控件中的所有t

TextRenderer->DrawText():

//Define some useful flags for TextRenderer
TextFormatFlags flags = TextFormatFlags.Left | TextFormatFlags.Top | 
                        TextFormatFlags.NoPadding | TextFormatFlags.WordBreak | 
                        TextFormatFlags.TextBoxControl;
//The char to look for
char TheChar = 't';

//Find all 't' chars indexes in the text
List<int> TheIndexList = textBox1.Text.Select((chr, idx) => chr == TheChar ? idx : -1)
                                      .Where(idx => idx != -1).ToList();

//Or with Regex - same thing, pick the one you prefer
List<int> TheIndexList = Regex.Matches(textBox1.Text, TheChar.ToString())
                              .Cast<Match>()
                              .Select(chr => chr.Index).ToList();

//Using .GetPositionFromCharIndex(), define the Point [p] where the highlighted text is drawn
if (TheIndexList.Count > 0)
{
    foreach (int Position in TheIndexList)
    {
        Point p = textBox1.GetPositionFromCharIndex(Position);
        using (Graphics g = textBox1.CreateGraphics())
               TextRenderer.DrawText(g, TheChar.ToString(), textBox1.Font, p,
                                     textBox1.ForeColor, Color.LightGreen, flags);
    }
}
Run Code Online (Sandbox Code Playgroud)

使用Graphics.FillRectangle()和的相同操作Graphics.DrawString()

if (TheIndexList.Count > 0)
{
    using (Graphics g = textBox1.CreateGraphics())
    {
        foreach (int Position in TheIndexList)
        {
            PointF pF = textBox1.GetPositionFromCharIndex(Position);
            SizeF sF = g.MeasureString(TheChar.ToString(), textBox1.Font, 0,
                                       StringFormat.GenericTypographic);

            g.FillRectangle(Brushes.LightGreen, new RectangleF(pF, sF));
            using (SolidBrush brush = new SolidBrush(textBox1.ForeColor))
            {
                g.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;
                g.DrawString(TheChar.ToString(), textBox1.Font, brush, pF, StringFormat.GenericTypographic);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

有行为没有显着的区别:TextRenderer.DrawText()Graphics.DrawString()这里做同样的事情。
设置Application.SetCompatibleTextRenderingDefault()truefalse似乎没有任何影响(至少在当前上下文中)。

2)在 TextBox 控件和多行 RichTextbox 控件中突出显示一些字符串模式(“Words”)。

TextRenderer仅使用,因为在行为上没有区别。

我只是让IndexOf()找到第一次出现的字符串,但之前使用的相同搜索模式可以取而代之。正则表达式效果更好。

string[] TheStrings = {"for", "s"};
foreach (string pattern in TheStrings)
{
    Point p = TextBox2.GetPositionFromCharIndex(TextBox2.Text.IndexOf(pattern));
    using (var g = TextBox2.CreateGraphics()) { 
        TextRenderer.DrawText(g, pattern, TextBox2.Font, p, 
                              TextBox2.ForeColor, Color.LightSkyBlue, flags);
    }
}

TheStrings = new string []{"m", "more"};
foreach (string pattern in TheStrings)
{
    Point p = richTextBox1.GetPositionFromCharIndex(richTextBox1.Text.IndexOf(pattern));
    using (Graphics g = richTextBox1.CreateGraphics())
        TextRenderer.DrawText(g, pattern, richTextBox1.Font, p,
                              richTextBox1.ForeColor, Color.LightSteelBlue, flags);
}
Run Code Online (Sandbox Code Playgroud)

3)高亮显示所有小号中的所有ListItems列表框控制(当然也可以是任何其他字符串的:)

ListBox.DrawMode设置为Normal,改变“对飞”,以OwnerDrawVariable评估是否TextRendererGraphics不同的表现在这里。

有一个小的区别:与标准实现相比,相对于 ListBox 的左边距有不同的偏移量。TextRenderer,TextFormatFlags.NoPadding向左渲染 2 个像素(相反,没有标志)。图形向右渲染 1 个像素。
当然如果OwnerDrawVariable是在设计模式下设置,这个就不会被注意到了。

string HighLightString = "s";
int GraphicsPaddingOffset = 1;
int TextRendererPaddingOffset = 2;

private void button1_Click(object sender, EventArgs e)
{
    listBox1.DrawMode = DrawMode.OwnerDrawVariable;
}
Run Code Online (Sandbox Code Playgroud)

以下代码的工作原理:

  1. 获取ListItem文本中string HighLightString出现模式 ( ) 的所有位置。
  2. CharacterRange使用模式的位置和长度定义结构数组。
  3. StringFormat使用所有CharacterRange结构填充 a.SetMeasurableCharacterRanges()
  4. 使用Graphics.MeasureCharacterRanges()传递初始化的StringFormat.
  5. 定义大小的矩形数组 Region.GetBounds()
  6. 使用高亮颜色填充所有矩形 Graphics.FillRectangles()
  7. 绘制ListItem文本。

TextRenderer.DrawText() 执行:

private void listBox1_DrawItem(object sender, DrawItemEventArgs e)
{
    e.DrawBackground();

    TextFormatFlags flags = TextFormatFlags.Left | TextFormatFlags.Top | TextFormatFlags.NoPadding |
                            TextFormatFlags.WordBreak | TextFormatFlags.TextBoxControl;
    Rectangle bounds = new Rectangle(e.Bounds.X + TextRendererPaddingOffset, 
                                     e.Bounds.Y, e.Bounds.Width, e.Bounds.Height);

    string ItemString = listBox1.GetItemText(listBox1.Items[e.Index]);
    List<int> TheIndexList = Regex.Matches(ItemString, HighLightString)
                                  .Cast<Match>()
                                  .Select(s => s.Index).ToList();

    if (TheIndexList.Count > 0)
    {
        CharacterRange[] CharRanges = new CharacterRange[TheIndexList.Count];
        for (int CharX = 0; CharX < TheIndexList.Count; CharX++)
            CharRanges[CharX] = new CharacterRange(TheIndexList[CharX], HighLightString.Length);

        StringFormat format = new StringFormat(StringFormat.GenericDefault);
        format.SetMeasurableCharacterRanges(CharRanges);

        Region[] regions = e.Graphics.MeasureCharacterRanges(ItemString, e.Font, e.Bounds, format);

        RectangleF[] rectsF = new RectangleF[regions.Length];
        for (int RFx = 0; RFx < regions.Length; RFx++)
            rectsF[RFx] = regions[RFx].GetBounds(e.Graphics);

        e.Graphics.FillRectangles(Brushes.LightGreen, rectsF);
    }
    TextRenderer.DrawText(e.Graphics, ItemString, e.Font, bounds, e.ForeColor, flags);
}
Run Code Online (Sandbox Code Playgroud)
`Graphics.DrawString()` 实现
private void listBox1_DrawItem(object sender, DrawItemEventArgs e)
{
    e.DrawBackground();
    Rectangle bounds = new Rectangle(e.Bounds.X - GraphicsPaddingOffset,
                                     e.Bounds.Y, e.Bounds.Width, e.Bounds.Height);

    string ItemString = listBox1.GetItemText(listBox1.Items[e.Index]);
    List<int> TheIndexList = Regex.Matches(ItemString, HighLightString)
                                  .Cast<Match>()
                                  .Select(s => s.Index).ToList();

    StringFormat format = new StringFormat(StringFormat.GenericDefault);
    if (TheIndexList.Count > 0)
    {
        CharacterRange[] CharRanges = new CharacterRange[TheIndexList.Count];
        for (int CharX = 0; CharX < TheIndexList.Count; CharX++)
            CharRanges[CharX] = new CharacterRange(TheIndexList[CharX], HighLightString.Length);

        format.SetMeasurableCharacterRanges(CharRanges);
        Region[] regions = e.Graphics.MeasureCharacterRanges(ItemString, e.Font, e.Bounds, format);

        RectangleF[] rectsF = new RectangleF[regions.Length];
        for (int RFx = 0; RFx < regions.Length; RFx++)
            rectsF[RFx] = regions[RFx].GetBounds(e.Graphics);

        e.Graphics.FillRectangles(Brushes.LightGreen, rectsF);
    }
    using (SolidBrush brush = new SolidBrush(e.ForeColor))
        e.Graphics.DrawString(ItemString, e.Font, brush, bounds, format);
}
Run Code Online (Sandbox Code Playgroud)

注意:
根据ListBox.DrawMode,可能需要订阅ListBox.MeasureItem()事件或将.ItemHeight 属性设置为正确的值。

private void listBox1_MeasureItem(object sender, MeasureItemEventArgs e)
{
      e.ItemHeight = listBox1.Font.Height;
}
Run Code Online (Sandbox Code Playgroud)