代码分享,WinForm中LinkLabel实现关键字高亮

threenewbee 2013-09-12 02:10:23
加精
效果:


使用说明:

IgnoreCase 是否匹配大小写,默认false,严格匹配大小写

Keyword 关键字

KeywordColor 关键字的颜色,默认红色

BUG列表:

部分颜色属性不工作,比如访问后颜色,自定义背景色等等

不支持多行文本

代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;

namespace WindowsFormsApplication1
{
class SuperLinkLabel : LinkLabel
{
public bool IgnoreCase { get; set; }

private string keyWord = "";
public string Keyword
{
get { return keyWord; }
set { keyWord = value; this.Invalidate(); }
}

public Color KeywordColor { get; set; }

public SuperLinkLabel()
{
Keyword = "";
KeywordColor = Color.Red;
}

protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.FillRectangle(new SolidBrush(SystemColors.Control), e.ClipRectangle);

StringFormat stringFormat = new StringFormat();
stringFormat.SetMeasurableCharacterRanges(GetRanges());
stringFormat.FormatFlags = StringFormatFlags.MeasureTrailingSpaces;
Region[] regions = e.Graphics.MeasureCharacterRanges(Text, Font, new RectangleF(e.ClipRectangle.X, e.ClipRectangle.Y, e.ClipRectangle.Width + 200, e.ClipRectangle.Height), stringFormat);

int i = 0;
foreach (var item in GetStringRgn().ToList())
{
bool isKeyword = GetStringRgn().Single(x => x.Item1 == item.Item1).Item3;
e.Graphics.DrawString(Text.Substring(item.Item1, item.Item2 - item.Item1 + 1), Font, isKeyword ? new SolidBrush(KeywordColor) : new SolidBrush(LinkColor), new PointF(regions[i].GetBounds(e.Graphics).X, regions[i].GetBounds(e.Graphics).Y), stringFormat);
i++;
}
}

private CharacterRange[] GetRanges()
{
return GetStringRgn().ToArray().Select(x => new CharacterRange(x.Item1, x.Item2 - x.Item1 + 1)).ToArray();
}

private IEnumerable<Tuple<int, int, bool>> GetStringRgn()
{
if (Keyword == "" || Text == "")
{
if (Text == "") yield break;
yield return new Tuple<int, int, bool>(0, Text.Length - 1, false);
yield break;
}
int pre = 0;
int i = 0;
while (i <= Text.Length - Keyword.Length)
{
if (Text.Substring(i, Keyword.Length).ToUpper() == Keyword.ToUpper())
{
if (IgnoreCase || Text.Substring(i, Keyword.Length) == Keyword)
{
if (pre != i) yield return new Tuple<int, int, bool>(pre, i - 1, false);
yield return new Tuple<int, int, bool>(i, i + Keyword.Length - 1, true);
i += Keyword.Length;
pre = i;
}
else
{
i++;
}
}
else
{
i++;
}
}
if (pre <= Text.Length - 1) yield return new Tuple<int, int, bool>(pre, Text.Length - 1, false);
}
}
}



最后show下VS2013
...全文
2997 66 打赏 收藏 转发到动态 举报
写回复
用AI写文章
66 条回复
切换为时间正序
请发表友善的回复…
发表回复
猴头 2013-09-28
  • 打赏
  • 举报
回复
引用 45 楼 jshi123 的回复:
试了下用TextRenderer好像定位要准一些:

protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.FillRectangle(new SolidBrush(SystemColors.Control), e.ClipRectangle);

var pattern = string.Format("({0}|\n)", Keyword);
string[] strings = Regex.Split(Text, pattern, IgnoreCase ? RegexOptions.IgnoreCase : RegexOptions.None);
int lineHeight = TextRenderer.MeasureText(e.Graphics, "A", Font).Height * 3 / 2;
var pt = new Point();
Size proposedSize = new Size(int.MaxValue, int.MaxValue);
var flags = TextFormatFlags.NoPadding;
foreach (var str in strings)
{
if (str != "\n")
{
var sz = TextRenderer.MeasureText(e.Graphics, str, Font, proposedSize, flags);
var color = str.Equals(keyWord, IgnoreCase ? StringComparison.InvariantCultureIgnoreCase : StringComparison.InvariantCulture) ?
KeywordColor : LinkColor;
TextRenderer.DrawText(e.Graphics, str, Font, pt, color, flags);
pt.X += sz.Width;
}
else
{
pt = new Point(0, pt.Y + lineHeight);
}
}
}




GDI+获取到的字符串的size比实际的字符串的size范围大一点,TextRenderer是用GDI的,计算得到的size更准确。

其实我来是想问问

private CharacterRange[] GetRanges()

private IEnumerable<Tuple<int, int, bool>> GetStringRgn()

的过程是做什么的,我想 改造下,把单个字符串改为字符串列表的.

wangbin12314789 2013-09-23
  • 打赏
  • 举报
回复
不错不错。。。
sqd123123 2013-09-20
  • 打赏
  • 举报
回复
msdn上说的是32个“character ranges”,不是32个字符,
智商余额不足 2013-09-13
  • 打赏
  • 举报
回复
引用 38 楼 caozhy 的回复:
为了重现这个bug,将e或者某个大量出现的文本作为keyword,运行我的程序。
msdn说不允许设置超过32个字符的范围,这个规定我也不清楚为什么~~
threenewbee 2013-09-13
  • 打赏
  • 举报
回复
为了重现这个bug,将e或者某个大量出现的文本作为keyword,运行我的程序。
threenewbee 2013-09-13
  • 打赏
  • 举报
回复
引用 34 楼 caozhy 的回复:
[quote=引用 32 楼 hwenycocodq520 的回复:] 无语,好像上面那regions[]图分析错了,大家先无视楼上先@我再看看。。
我之前的代码,高亮using 结果前面几行都是对的,唯独最后一个不对。简直见鬼。。。我再琢磨下。[/quote] 这个问题顿时就像明白了,因为最后一个using之后的文本才有换行。 另外还有一个问题,想问问你是否知道。 就是regions[]的区域不能太多,一多就溢出错误,这是什么原因。
threenewbee 2013-09-13
  • 打赏
  • 举报
回复
引用 35 楼 hwenycocodq520 的回复:
更改下上面的截图: 由regions[i].GetBounds(e.Graphics)返回的矩形如下,可以看到regions[4]返回的矩形包含了非关键字 “ {get;set;} public SuperLinkLabel() ” 它的坐标与regions[0]重叠了,所以用regions[4].GetBounds(e.Graphics)获取到的X,Y绘制regions[4]的非关键字将在regions[0].GetBounds(e.Graphics).X,...Y处绘制,所以就看到字体重叠的bug:
我明白了,GetBounds返回的是整个文本所在的范围,而如果遇到换行,那么它的x是所有行文本中最左边的位置,而不是第一行最左边的位置。
智商余额不足 2013-09-13
  • 打赏
  • 举报
回复
更改下上面的截图:
由regions[i].GetBounds(e.Graphics)返回的矩形如下,可以看到regions[4]返回的矩形包含了非关键字

{get;set;}
public SuperLinkLabel()


它的坐标与regions[0]重叠了,所以用regions[4].GetBounds(e.Graphics)获取到的X,Y绘制regions[4]的非关键字将在regions[0].GetBounds(e.Graphics).X,...Y处绘制,所以就看到字体重叠的bug:
threenewbee 2013-09-13
  • 打赏
  • 举报
回复
引用 32 楼 hwenycocodq520 的回复:
无语,好像上面那regions[]图分析错了,大家先无视楼上先@我再看看。。
我之前的代码,高亮using 结果前面几行都是对的,唯独最后一个不对。简直见鬼。。。我再琢磨下。
threenewbee 2013-09-13
  • 打赏
  • 举报
回复
妙哉。我调试的时候就发现红色之后的文本的坐标不符合预期。但是百思不得其解。
jshi123 2013-09-13
  • 打赏
  • 举报
回复
试了下用TextRenderer好像定位要准一些:

	protected override void OnPaint(PaintEventArgs e)
	{
		e.Graphics.FillRectangle(new SolidBrush(SystemColors.Control), e.ClipRectangle);

		var pattern = string.Format("({0}|\n)", Keyword);
		string[] strings = Regex.Split(Text, pattern, IgnoreCase ? RegexOptions.IgnoreCase : RegexOptions.None);
		int lineHeight = TextRenderer.MeasureText(e.Graphics, "A", Font).Height * 3 / 2;
		var pt = new Point();
		Size proposedSize = new Size(int.MaxValue, int.MaxValue);
		var flags = TextFormatFlags.NoPadding;
		foreach (var str in strings)
		{
			if (str != "\n")
			{
				var sz = TextRenderer.MeasureText(e.Graphics, str, Font, proposedSize, flags);
				var color = str.Equals(keyWord, IgnoreCase ? StringComparison.InvariantCultureIgnoreCase : StringComparison.InvariantCulture) ?
						KeywordColor : LinkColor;
				TextRenderer.DrawText(e.Graphics, str, Font, pt, color, flags);
				pt.X += sz.Width;
			}
			else
			{
				pt = new Point(0, pt.Y + lineHeight);
			}
		}
	}
jshi123 2013-09-13
  • 打赏
  • 举报
回复
msdn上说的是32个“character ranges”,不是32个字符,我试了下确实是这样的。
threenewbee 2013-09-13
  • 打赏
  • 举报
回复
引用 40 楼 hwenycocodq520 的回复:
[quote=引用 38 楼 caozhy 的回复:] 为了重现这个bug,将e或者某个大量出现的文本作为keyword,运行我的程序。
msdn说不允许设置超过32个字符的范围,这个规定我也不清楚为什么~~[/quote] 你说的我也看到了。但是显然不是。首先,我大段的文本早就超过32个字符了。其次msdn说的是堆栈溢出,而不是溢出。
智商余额不足 2013-09-12
  • 打赏
  • 举报
回复
引用 13 楼 hwenycocodq520 的回复:
顶楼主: BUG列表: +1 窗体部分移出屏幕再移回来画面乱了
下面是我的解决方案,ClipRectangle是获取到无效区域部分,移出一部分到屏幕之外这个无效区域就不是整个控件区域了,用它的ClientRectangle替代它,每次需要重绘时都绘制它整个画面

//Region[] regions = 
//e.Graphics.MeasureCharacterRanges(Text, Font, 
//new RectangleF(
//    e.ClipRectangle.X, 
//    e.ClipRectangle.Y, 
//    e.ClipRectangle.Width + 200,
//    e.ClipRectangle.Height),
// stringFormat);

Region[] regions = 
e.Graphics.MeasureCharacterRanges(Text, Font, 
new RectangleF(
    this.ClientRectangle.X, 
    this.ClientRectangle.Y, 
    this.ClientRectangle.Width + 200,
    this.ClientRectangle.Height),
 stringFormat);
智商余额不足 2013-09-12
  • 打赏
  • 举报
回复
无语,好像上面那regions[]图分析错了,大家先无视楼上先@我再看看。。
智商余额不足 2013-09-12
  • 打赏
  • 举报
回复
回25#的bug,
主要是这个regions[i].GetBounds(e.Graphics)返回的是包含Region内部的矩形,所以如果有换行的情况它获取到的矩形是这样的:

所以这样获取到各段文字范围(关键字,非关键字)的坐标是不正确的,比如以上图的“KeyWord”坐标被设置到它边界矩形的左上角了,所以出现的bug会看到与关键字重叠了

PointF ptf = new PointF(regions[i].GetBounds(e.Graphics).X,
regions[i].GetBounds(e.Graphics).Y);

我的解决方案先绘制完整的字符串,再绘制关键字,绘制关键字之前用FillRegion清楚原来的内容

protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.FillRectangle(new SolidBrush(SystemColors.Control), e.ClipRectangle);
StringFormat stringFormat = new StringFormat();
stringFormat.SetMeasurableCharacterRanges(GetRanges());
stringFormat.FormatFlags = StringFormatFlags.MeasureTrailingSpaces;
Region[] regions = e.Graphics.MeasureCharacterRanges(Text,
Font,
new RectangleF(this.ClientRectangle.X,
this.ClientRectangle.Y,
4000,
4000),
stringFormat);

e.Graphics.DrawString(Text,
Font,
new SolidBrush(LinkColor),
new Point(this.ClientRectangle.X, this.ClientRectangle.Y),
stringFormat);

int i = 0;
foreach (var item in GetStringRgn().ToList())
{
bool isKeyword = GetStringRgn().Single(x => x.Item1 == item.Item1).Item3;
if (isKeyword)
{
PointF ptf = new PointF(regions[i].GetBounds(e.Graphics).X-3,
regions[i].GetBounds(e.Graphics).Y);
e.Graphics.FillRegion(new SolidBrush(BackColor), regions[i]);
e.Graphics.DrawString(Text.Substring(item.Item1, item.Item2 - item.Item1 + 1),
Font,
new SolidBrush(KeywordColor),
ptf,
stringFormat);
}
i++;
}
}

private IEnumerable<Tuple<int, int, bool>> GetStringRgn()
{
if (Keyword == "" || Text == "")
{
if (Text == "") yield break;
yield return new Tuple<int, int, bool>(0, Text.Length - 1, false);
yield break;
}
int i = 0;
while (i <= Text.Length - Keyword.Length)
{
if (Text.Substring(i, Keyword.Length).ToUpper() == Keyword.ToUpper())
{
if (IgnoreCase || Text.Substring(i, Keyword.Length) == Keyword)
{
yield return new Tuple<int, int, bool>(i, i + Keyword.Length - 1, true);
i += Keyword.Length;
continue;
}
}
i++;
}
}



获取到字体的精确定位这个没搞得,对那方面我不熟悉,所以只能提供这点小更改了
智商余额不足 2013-09-12
  • 打赏
  • 举报
回复
顶楼主: BUG列表: +1 窗体部分移出屏幕再移回来画面乱了
threenewbee 2013-09-12
  • 打赏
  • 举报
回复
引用 16 楼 hwenycocodq520 的回复:
[quote=引用 13 楼 hwenycocodq520 的回复:] 顶楼主: BUG列表: +1 窗体部分移出屏幕再移回来画面乱了
下面是我的解决方案,ClipRectangle是获取到无效区域部分,移出一部分到屏幕之外这个无效区域就不是整个控件区域了,用它的ClientRectangle替代它,每次需要重绘时都绘制它整个画面

//Region[] regions = 
//e.Graphics.MeasureCharacterRanges(Text, Font, 
//new RectangleF(
//    e.ClipRectangle.X, 
//    e.ClipRectangle.Y, 
//    e.ClipRectangle.Width + 200,
//    e.ClipRectangle.Height),
// stringFormat);

Region[] regions = 
e.Graphics.MeasureCharacterRanges(Text, Font, 
new RectangleF(
    this.ClientRectangle.X, 
    this.ClientRectangle.Y, 
    this.ClientRectangle.Width + 200,
    this.ClientRectangle.Height),
 stringFormat);
[/quote] 谢谢你的修改。果然是这样。 不过还有一个多行的bug,你有兴趣试试么?我搞不定了。 这是我的提问帖。 http://social.msdn.microsoft.com/Forums/vstudio/en-US/0c4cda67-8832-450a-9242-7621a10d7b60/a-problem-about-create-a-keyword-highlighted-linkedlabel#0c4cda67-8832-450a-9242-7621a10d7b60
十八道胡同 2013-09-12
  • 打赏
  • 举报
回复
加载更多回复(1)

110,534

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧