在开发协议分析器的过程中,被要求编写一个图形用户界面(GUI),该界面能够将字符串中的某些部分用不同的背景颜色标记出来,以指示字符串中的搜索结果。由于字符串需要在自定义控件上绘制,因此必须自己进行绘制。
本可以将字符串分割成几个子字符串,并分别用不同的颜色绘制,但这将非常慢且不准确。幸运的是,.NET提供了一些复杂的(尽管存在bug)API:
这对API能够提供绘制所需的信息,使能够在不需要进行复杂计算的情况下,精确地在请求的字符串部分下方绘制背景颜色。
首先,在搜索功能中,通过插入开始和结束标签来标记高亮部分,类似于HTML中的
"This is the highlighted portion of a string"
现在,在绘制函数中,首先调用GetCharacterRanges方法,该方法创建一个CharacterRange结构的列表。这个结构将包含每个高亮部分的字符串的起始位置和长度。然后,调用.NETAPI StringFormat.SetMeasurableCharacterRanges,它接受一个这些结构的数组,以便在下一次API调用中使用。
调用Graphics.MeasureCharacterRanges现在将返回一个Region数组。每个Region包含基于先前传递的CharacterRegion数组的确切绘制信息。现在只剩下为每个这样的Region绘制FillRectangle来创建背景。最后,绘制字符串。
以下是C#代码实现:
private static string startTag = "";
private static string endTag = " ";
private void Form1_Paint(object sender, PaintEventArgs e)
{
RectangleF f = new RectangleF(0, 0, 300, 30);
DrawTaggedString(e.Graphics, "This is the highlighted portion of a string", Font, Brushes.Magenta, f, new StringFormat());
}
private String GetCleanText(String text)
{
if (text == null)
{
return null;
}
string cleanString = text.Replace(startTag, "");
cleanString = cleanString.Replace(endTag, "");
return cleanString;
}
private static List GetCharacterRanges(string text)
{
int start = 0;
List characterRanges = new List();
int charsRemoved = 0;
while (start < text.Length)
{
start = text.IndexOf(startTag, start);
if (start >= 0)
{
int end = text.IndexOf(endTag, start);
int ofs = start - charsRemoved;
int length = end - charsRemoved - startTag.Length - ofs;
characterRanges.Add(new CharacterRange(ofs, length));
start = end + startTag.Length;
charsRemoved += (startTag.Length + endTag.Length);
}
else
{
break;
}
}
return characterRanges;
}
public void DrawTaggedString(Graphics g, String text, Font font, Brush brush, RectangleF layoutRect, StringFormat stringFormat)
{
List characterRanges = GetCharacterRanges(text);
int offset = 0;
int countLeft = characterRanges.Count;
do
{
int count = Math.Min(countLeft, 32);
List subRange = characterRanges.GetRange(offset, count);
int ranges = subRange.Count;
string cleanString = GetCleanText(text);
stringFormat.SetMeasurableCharacterRanges(subRange.ToArray());
Region[] stringRegions;
stringRegions = g.MeasureCharacterRanges(cleanString, font, layoutRect, stringFormat);
for (int i = 0; i < ranges; i++)
{
RectangleF measureRect = stringRegions[i].GetBounds(g);
g.FillRectangle(Brushes.Blue, Rectangle.Round(measureRect));
}
g.DrawString(cleanString, font, brush, layoutRect, stringFormat);
offset += count;
countLeft -= count;
}
while (countLeft > 0);
}