如何使用图形突出显示控件中的换行文本? [英] How to highlight wrapped text in a control using the graphics?
问题描述
我需要使用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()
andGraphics.DrawString()
do the exact same thing here.
SettingApplication.SetCompatibleTextRenderingDefault()
totrue
orfalse
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 ifOwnerDrawVariable
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 theListItem
text where the pattern (string HighLightString
) appears.
2) Define an array ofCharacterRange
structures with the position and length of the pattern.
3) Fill aStringFormat
with all theCharacterRange
structs using.SetMeasurableCharacterRanges()
4) Define an array of Regions usingGraphics.MeasureCharacterRanges()
passing the initializedStringFormat
.
5) Define an array of Rectangles sized usingRegion.GetBounds()
6) Fill all the Rectangles with the highlight color usingGraphics.FillRectangles()
7) Draw theListItem
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 theListBox.DrawMode
, it may become necessary to subscribe theListBox.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屋!