日期:2014-05-17  浏览次数:21059 次

关于C#绘制qq好友列表控件

上一个效果图

貌似图片被和谐了左边那张图貌似忘了展示一个功能模式图被和谐了

源码下载

http://download.csdn.net/detail/crystal_lz/4755251   个人感觉注释还是比较详细

貌似这是第一次 直接继承Control写控件 以前都是UserControl    对于写控件真没啥经验 途中遇到各种问题 各种蛋疼   所以代码有啥不对的地方 多包涵

- -!、、其实  这里面有一个问题 就是TypeConverter哪里 不知道咋搞 不不用转换器的话 在窗体设计时候 向控件添加项的时候 调用默认构造器 然后其余属性挨个赋值 所以自动生成的代码 非常臃肿  最开始写了TypeConverter但是没有 果断窗体设计时自己都不生成代码了  经测试  转换器内的代码压根就没有执行  后来不知道咋搞的貌似有点反应了 不过编译就报错 说 属性 代码生成失败 无法将XXXX转换为 XXXX.InstanceDescriptor 我就郁闷了  然后百度说 把程序集版本改成 1 0 0 * 就可以  试了一下 确实没有报那个错了 不过又有其他问题 ......

XXXXXXXXXX........!@@¥%……&U总之 p话不多说 由于没有这方面经验 希望哪位会的人士 给予一下指点 到底要怎么才能让那个自己写的 TypeConverter 类正确执行 在窗体设计时候自动生成的代码 看上去和谐    虽然说 这东西一般都是 程序运行出来动态加载列表的   但是在窗体设计的时候 手动添加项的时候 自动生成的代码真的很不和谐 虽然一般情况下也不去看 但感觉已经不爽了...


好吧 上面的都是p话  只是希望有人能解决一下我的问题  现在来说说 我做这个控件的时候的感想吧  在做之前我在网上参考过一些 也下载过两个 不过感觉真不咋滴(我指的是 我下载的那两个 不代表全部)    运行出来 亲一色的 全是企鹅头 这也就算了 还亲一色的只有在线状态   XXXX...!@#¥ 我总结出来的就是 只以做的像为目的 压根就没有考虑过投入使用的问题  所以我在制作的时候考虑了要投入使用的问题(- -!、其实是压根本来就要用所以才考虑到的)  比如图片上 每一个子项拥有一个 布尔 值来决定是否闪烁  还有不同的在线状态 而且还是按照状态进行排序的......


现在来说思路 我在里面 分Item 和 SubItem    Item就是看到的分组而SubItem就是分组下的子项  所以我就搞了两个类 ChatListItem 和 ChatListSubItem 这两个类

ChatListItem被控件包含 而ChatListSubItem 被 ChatListItem 包含 所以大概的代码就是

public class ChatListBox : Control{
	XXXXXXX
	public ChatListItem[] Items {get; set;}
	XXXXXXX
	.......
}

public class ChatListItem{
	XXXXXXX
	public ChatListSubItem[] SubItems {get; set;}
	XXXXXXX
	.......
}

public class ChatListSubItem{
	public int ID {get; set;}
	public string NicName {get; set;}
	.........
	.........
}
当然 真实情况下不是用的数组 这里 只是为了方便   这样 就有了一个层次关系 然后就是绘制的问题

绘制的时候在 OnPaint 中循环Items

protected override void OnPaint(PaintEventArgs e){
	for(int i = 0;i < Items.Count;i++){				//循环绘制每一个Item
		DrawItem(items[i]);		//绘制Item
		if(items[i].IsOpen){	//如果Item的列表是展开的 那么还得绘制他的子项
			for(int j = 0;j < Items[i].SubItem.Count;j++){
				DrawSubItems(Items[i].SubItems[j]);	//绘制子项
			}
		}
	}
}
大概也就这样 不过里面还要计算区域 不能每个Item或者SubItem绘制的时候都重叠到一起啊 所以代码改一个就成这个样子

protected override void OnPaint(PaintEventArgs e){
	Rectangle rectItem = new Rectangle(0,0,this.Width,20);	//假设每个item的高度是20 这是第一个的
	Rectangle rectSubItem = new Rectangle(0,21,this.Width,50)	//假设每个SubItem高50 这个是第一个的 21 是在Item标题下移动一格像素的
	for(int i = 0;i < Items.Count;i++){				//循环绘制每一个Item
		DrawItem(items[i],rectItem);				//绘制Item	增加一个参数
		if(items[i].IsOpen){	//如果Item的列表是展开的 那么还得绘制他的子项
			rectSubItem.Y = rectItem.Bottom + 1;	//这个是展开的第一个的子项 那么他的位置 就该是Item标题的下面一个像素
			for(int j = 0;j < Items[i].SubItem.Count;j++){
				DrawSubItems(Items[i].SubItems[j],rectSubItem);	//绘制子项  增加一个参数
				rectSubItem.Y = rectSubItem.Bottom + 1;			//计算下一个子项的区域
			}
			rectItem.Y = rectSubItem.Bottom + 1;				//子项绘制完  那么该计算下一个Item的坐标
		}else{
			rectItem.Y = rectItem.Bottom;						//如果没有展开 那么下一个item的坐标就是上一个Item的下面一个像素
		}
	}
}
这样绘制上 基本没有啥问题了不过 这也只是绘制出来了而已 你还得在上面操作 比如鼠标点击一个子项  你用什么来判断鼠标点击了一个子项?

所以还得把每个Item和SubItem绘制在什么地方记录下来 到时候用鼠标坐标去比对

所以不管是ChatListItem 还是 ChatListSubItem 都要在他们里面加一个 Rectangle 类型的属性 每绘制一个Item或者SubItem 的时候就把那块区域赋值过去

到时候就用Rectangle.Contains(Point)来判断鼠标到底在那一块区域内

所以就有了我代码中的

for (int i = 0, lenItem = items.Count; i < lenItem; i++) {
	DrawItem(g, items[i], rectItem, sb);        //绘制列表项
	if (items[i].IsOpen) {                      //如果列表项展开绘制子项
		rectSubItem.Y = rectItem.Bottom + 1;
		for (int j = 0, lenSubItem = items[i].SubItems.Count; j < lenSubItem; j++) {
			DrawSubI