如何使用图形突出显示控件中的换行文本? [英] How to highlight wrapped text in a control using the graphics?

查看:90
本文介绍了如何使用图形突出显示控件中的换行文本?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要使用fill rect突出显示控件中的特定字符。
使用如下所示的 Graphics.MeasureString()方法,可以得到未包装文本的位置,如下所示,

  var size = g.MeasureString(tempSearchText,style.Font,0,StringFormat.GenericTypographic); 



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





我需要获得文本中的给定字符将被换行。提供您的建议以实现这种情况。

解决方案

目前尚不清楚要针对的控件,因此我正在测试3不同:

TextBox RichTextbox ListBox



TextBox和RichTextbox具有相同的行为并共享相同的工具,因此无需定义两种不同的方法即可获得相同的结果。

当然,RichTextbox提供了更多选项,包括



警告

在此示例中,我使用的是 Control.CreateGraphics(),因为 TextBox RichTextBox 控件不提供 Paint()事件。对于真实的应用程序,您应该创建一个从 TextBox RichTextBox 派生的自定义控件,并覆盖 OnPaint()方法



1)高亮显示所有 t 在多行TextBox控件中。



TextRenderer-> DrawText():

  //为TextRenderer定义一些有用的标志
TextFormatFlags标志= TextFormatFlags.Left | TextFormatFlags.Top |
TextFormatFlags.NoPadding | TextFormatFlags.WordBreak |
TextFormatFlags.TextBoxControl;
//要查找
的字符char TheChar ='t';

//在文本
List< int>中查找所有't'字符索引TheIndexList = textBox1.Text.Select((chr,idx)=> chr == TheChar?idx:-1)
.Where(idx => idx!= -1).ToList();

//或使用Regex-同样,选择最喜欢的一个
List< int> TheIndexList = Regex.Matches(textBox1.Text,TheChar.ToString())
.Cast< Match>()
.Select(chr => chr.Index).ToList();

//使用.GetPositionFromCharIndex(),定义点[p],如果(TheIndexList.Count> 0)
{
foreach(TheIndexList中的int位置)
{
点p = textBox1.GetPositionFromCharIndex(Position);
使用(图形g = textBox1.CreateGraphics())
TextRenderer.DrawText(g,TheChar.ToString(),textBox1.Font,p,
textBox1.ForeColor,Color.LightGreen,标志);
}
}

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

  if(TheIndexList.Count> 0)
{
使用(Graphics g = textBox1.CreateGraphics())
{
foreach(TheIndexList中的int位置)
{
PointF pF = textBox1.GetPositionFromCharIndex(Position);
SizeF sF = g.MeasureString(TheChar.ToString(),textBox1.Font,0,
StringFormat.GenericTypographic);

g.FillRectangle(Brushes.LightGreen,new RectangleF(pF,sF));
使用(SolidBrush笔刷= new SolidBrush(textBox1.ForeColor))
{
g.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;
g.DrawString(TheChar.ToString(),textBox1.Font,brush,pF,StringFormat.GenericTypographic);
}
}
}
}




行为上没有显着差异: TextRenderer.DrawText()
Graphics.DrawString()在此处执行完全相同的操作。

设置 Application.SetCompatibleTextRenderingDefault() true
false 似乎没有任何影响(至少在当前情况下如此)。





2)高亮显示一些字符串模式(单词)一个文本框控件和一个多行RichTextbox控件。



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


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




  string [] TheStrings = { for, s} ; 
foreach(TheStrings中的字符串模式)
{
点p = TextBox2.GetPositionFromCharIndex(TextBox2.Text.IndexOf(pattern));
TextRenderer.DrawText(TextBox2.CreateGraphics(),pattern,TextBox2.Font,p,
TextBox2.ForeColor,Color.LightSkyBlue,标志);
}

TheStrings =新字符串[] { m, more};
foreach(TheStrings中的字符串模式)
{
点p = richTextBox1.GetPositionFromCharIndex(richTextBox1.Text.IndexOf(pattern));
使用(图形g = richTextBox1.CreateGraphics())
TextRenderer.DrawText(g,pattern,richTextBox1.Font,p,
richTextBox1.ForeColor,Color.LightSteelBlue,标志);
}




3)高亮显示ListBox控件的所有 ListItems 中的所有 s (当然也可以是其他任何字符串:)



ListBox.DrawMode 设置为 Normal 并即时更改为 OwnerDrawVariable 来评估 TextRenderer 图形在这里的行为有所不同。


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




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

private void button1_Click(对象发送者,EventArgs e)
{
listBox1.DrawMode = DrawMode.OwnerDrawVariable;
}




以下代码的工作方式:

1)获取 ListItem 文本中模式( string HighLightString )出现。

2)定义 CharacterRange 结构以及模式的位置和长度。

3)填充 使用所有 CharacterRange 结构的StringFormat 使用 .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标志= 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);

字符串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格式= new StringFormat(StringFormat.GenericDefault);
format.SetMeasurableCharacterRanges(CharRanges);

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

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

e.Graphics.FillRectangles(Brushes.LightGreen,rectsF);
}
TextRenderer.DrawText(e.Graphics,ItemString,e.Font,bounds,e.ForeColor,flags);
}



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

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

StringFormat格式= 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);

格式。SetMeasurableCharacterRanges(CharRanges);
Region []地区= e.Graphics.MeasureCharacterRanges(ItemString,e.Font,e.Bounds,format);

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

e.Graphics.FillRectangles(Brushes.LightGreen,rectsF);
}
使用(SolidBrush笔刷= new SolidBrush(e.ForeColor))
e.Graphics.DrawString(ItemString,e.Font,brush,bounds,format);
}




注意:
取决于 ListBox.DrawMode ,可能需要
订阅 ListBox.MeasureItem()事件或设置 .ItemHeight
属性的当前值。




  private void listBox1_MeasureItem(对象发送者,MeasureItemEventArgs e)
{
e.ItemHeight = listBox1.Font.Height;
}


I need to highlight the particular character in a control using the fill rect. I can get the location of the text when it's not wrapped by using the Graphics.MeasureString() method like below,

var size = g.MeasureString(tempSearchText, style.Font, 0, StringFormat.GenericTypographic);

If the text is wrapped then I'm not able to find the exact bounds of the character to highlight the text.

I need to get the exact bounds of the given character in the text wich is wrapped. Provide your suggestion to achive this scenario.

解决方案

There is no clear specification of which controls to target, so I'm testing 3 different:
TextBox, RichTextbox and ListBox.

TextBox and RichTextbox have the same behavior and share the same tools, so there's no need to define two different methods to achieve the same result.
Of course RichTextbox offers many more options, including RTF.

Also, I'm testing both Graphics.DrawString() and TextRenderer.DrawText().

This is the result of this test, so it's more clear what the code does.

Warning:
For this example I'm using Control.CreateGraphics(), because TextBox and RichTextBox controls don't provide a Paint() event. For a real world application, you should create a Custom Control derived from TextBox or RichTextBox and override the OnPaint() method.

1) Highlight all t in a multiline TextBox control.

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 like best
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);
    }
}

The same operation using Graphics.FillRectangle() and 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);
            }
        }
    }
}

There is no notable difference in behavior: TextRenderer.DrawText() and Graphics.DrawString() do the exact same thing here.
Setting Application.SetCompatibleTextRenderingDefault() to true or false does not seem to have any affect (in the current context, at least).


2) Highlight some string patterns ("Words") in a TextBox control and a multiline RichTextbox control.

Using TextRenderer only, since there's no difference in behavior.

I'm simply letting IndexOf() find the the first occurrence of the strings, but the same search pattern used before can take it's place. Regex works better.

string[] TheStrings = {"for", "s"};
foreach (string pattern in TheStrings)
{
    Point p = TextBox2.GetPositionFromCharIndex(TextBox2.Text.IndexOf(pattern));
    TextRenderer.DrawText(TextBox2.CreateGraphics(), 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);
}


3) Highlight all s in all the ListItems of a ListBox control (of course it can be any other string :)

The ListBox.DrawMode is set to Normal and changed "on the fly" to OwnerDrawVariable to evaluate whether TextRenderer and Graphics behave differently here.

There is a small difference: a different offset, relative to the left margin of the ListBox, compared to the standard implementation. TextRenderer, with TextFormatFlags.NoPadding renders 2 pixels to the left (the opposite without the flag). Graphics renders 1 pixel to the right.
Of course if OwnerDrawVariable is set in design mode, this will not be noticed.

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

private void button1_Click(object sender, EventArgs e)
{
    listBox1.DrawMode = DrawMode.OwnerDrawVariable;
}

How the following code works:
1) Get all the positions in the ListItem text where the pattern (string HighLightString) appears.
2) Define an array of CharacterRange structures with the position and length of the pattern.
3) Fill a StringFormat with all the CharacterRange structs using .SetMeasurableCharacterRanges()
4) Define an array of Regions using Graphics.MeasureCharacterRanges() passing the initialized StringFormat.
5) Define an array of Rectangles sized using Region.GetBounds()
6) Fill all the Rectangles with the highlight color using Graphics.FillRectangles()
7) Draw the ListItem text.

TextRenderer.DrawText() implementation:

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


Graphics.DrawString() implementation

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

Note:
Depending on the ListBox.DrawMode, it may become necessary to subscribe the ListBox.MeasureItem() event or set the .ItemHeight property to the corrent value.

private void listBox1_MeasureItem(object sender, MeasureItemEventArgs e)
{
      e.ItemHeight = listBox1.Font.Height;
}

这篇关于如何使用图形突出显示控件中的换行文本?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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