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



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

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

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

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

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

所以就有了我代码中的

[csharp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. for (int i = 0, lenItem = items.Count; i < lenItem; i++) {  
  2.     DrawItem(g, items[i], rectItem, sb);        //绘制列表项  
  3.     if (items[i].IsOpen) {                      //如果列表项展开绘制子项  
  4.         rectSubItem.Y = rectItem.Bottom + 1;  
  5.         for (int j = 0, lenSubItem = items[i].SubItems.Count; j < lenSubItem; j++) {  
  6.             DrawSubItem(g, items[i].SubItems[j], ref rectSubItem, sb);  //绘制子项  
  7.             rectSubItem.Y = rectSubItem.Bottom + 1;             //计算下一个子项的区域  
  8.             rectSubItem.Height = (int)iconSizeMode;             //大图标小图标模式的高度  
  9.         }  
  10.         rectItem.Height = rectSubItem.Bottom - rectItem.Top - (int)iconSizeMode - 1;  
  11.         //这里之所以给rectItem高度重新复制 是因为 SubItem 是包含在 Item 内部的  
  12.         //所以rectItem的高度就是 他标题的高度 加上他内部所有子项的高度 把他所有的子项都圈起来  
  13.     }  
  14.     items[i].Bounds = new Rectangle(rectItem.Location, rectItem.Size);  
  15.     rectItem.Y = rectItem.Bottom + 1;           //计算下一个列表项区域  
  16.     rectItem.Height = 25;  
  17. }  
每个item和subitem有了一个自己的区域后 那么要判断鼠标是否落在他们某一个区域上就方便了

[csharp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. for(int i = 0;i < Items.Count;i++){    
  2.     if(items[i].Bounds.Contains(MousePoint)){   //如果鼠标位置在 某一个item上  
  3.         if(items[i].IsOpen){                    //判断该Item是否为展开的  
  4.             for(int j = 0;j < Items[i].SubItem.Count;j++){  
  5.                 if(Items[i].SubItems[j].Bounds.Contains(MousePoint)){  
  6.                     XXXXXXXX....;  
  7.                     return;     //鼠标位置只可能在某一个Item 或者 SubItem 上所以找到一个后处理完事情直接return  
  8.                 }     
  9.             }//如果该项又是展开的 循环完子项却又没有找到符合条件的子项 那么就极有可能鼠标位置在标题上(因为每个Item或者SubItem有一像素间隔)  
  10.             if(new Rectangle(0,Items[i].Bounds.Top,this.Height,20).Contains(MousePoint)){  
  11.                 ........;  
  12.                 return;  
  13.             }  
  14.         }  
  15.     }  
  16. }  
主要思路 也就差不多是这些 怎么绘制 绘制后取得相应区域  剩下的就是一些细节上的功能 还有滚动条 

对对对 滚动条 所以在绘制的时候 要根据滚动条进行y坐标的一个偏移

g.TranslateTransform(0, -chatVScroll.Value);        //根据滚动条的值设置坐标偏移

这个是我控件中 完整的OnPaint

[csharp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. protected override void OnPaint(PaintEventArgs e) {  
  2.             Graphics g = e.Graphics;  
  3.             g.TranslateTransform(0, -chatVScroll.Value);        //根据滚动条的值设置坐标偏移  
  4.             Rectangle rectItem = new Rectangle(0, 1, this.Width, 25);                       //列表项区域  
  5.             Rectangle rectSubItem = new Rectangle(0, 26, this.Width, (int)iconSizeMode);    //子项区域  
  6.             SolidBrush sb = new SolidBrush(this.itemColor);  
  7.             try {  
  8.                 for (int i = 0, lenItem = items.Count; i < lenItem; i++) {  
  9.                     DrawItem(g, items[i], rectItem, sb);        //绘制列表项  
  10.                     if (items[i].IsOpen) {                      //如果列表项展开绘制子项  
  11.                         rectSubItem.Y = rectItem.Bottom + 1;  
  12.                         for (int j = 0, lenSubItem = items[i].SubItems.Count; j < lenSubItem; j++) {  
  13.                             DrawSubItem(g, items[i].SubItems[j], ref rectSubItem, sb);  //绘制子项  
  14.                             rectSubItem.Y = rectSubItem.Bottom + 1;             //计算下一个子项的区域  
  15.                             rectSubItem.Height = (int)iconSizeMode;  
  16.                         }  
  17.                         rectItem.Height = rectSubItem.Bottom - rectItem.Top - (int)iconSizeMode - 1;  
  18.                     }  
  19.                     items[i].Bounds = new Rectangle(rectItem.Location, rectItem.Size);  
  20.                     rectItem.Y = rectItem.Bottom + 1;           //计算下一个列表项区域  
  21.                     rectItem.Height = 25;  
  22.                 }  
  23.                 g.ResetTransform();             //重置坐标系  
  24.                 chatVScroll.VirtualHeight = rectItem.Bottom - 26;   //绘制完成计算虚拟高度决定是否绘制滚动条  
  25.                 if (chatVScroll.ShouldBeDraw)   //是否绘制滚动条  
  26.                     chatVScroll.ReDrawScroll(g);  
  27.             } finally { sb.Dispose(); }  
  28.             base.OnPaint(e);  
  29.         }  
也就是 在绘制控件的时候 坐标就不是 0 0 左上角位置开始绘制了 而是一个 0 Y 的一个虚拟的坐标了 Y 的值是由滚动条决定的  

所以在判断鼠标落的区域的时候 也有点变化了 Rectangle.Contains(MousePont.Y + 滚动条的值);

还有就是 离线 离开状态什么的 这些都是枚举值 然后ChatListSubItem实现一个排序接口 按照他们的顺序排序就可以了

其实 枚举值 也就相当于 int 值所以在 定义枚举类的时候 把顺序搞定  对SubItem排序的时候 按照一个升序排序就搞定了 这个和普通的排序没啥区别

还有就是 头像闪动的问题  在SubItem里面有一个 布尔值 来决定是否闪动 不仅是它 它所属的Item也的有关联  (在定义ChatListSubItem类的时候里面有个ChatListItem类型的一个Owner属性来表示该SubItem是属于哪个Item的  Item 同理有一个表示属于哪一个ChatListBox控件的 因为 在这些类中的一些操作要引发控件的重绘 所以要把他们一级一级关联)

因为 列表关闭的时候 头像没有办法闪动 所以只有列表标题闪动 所以在ChatListItem类里面 还定义了一个 计数器 保存在它列表下的子项闪动的个数 重绘的时候 如果列表关闭 计数器不为0 那么闪动列表标题 如果列表打开 那么闪动头像       当然在给ChatListSubItem的那个决定是否闪动的属性赋值的时候 响应的也要给所属的Item的计数器 进行操作

对于这个控件 我暂时能想到的就这些控件自身功能 然后就是给用户的接口了

我里面只定义了上个自定义事件 因为想了一下 在实际应用中  也就只有这三个有用

双击一个列表中的子项的时候   (QQ的话就弹出聊天对话框了)     【DoubleClickSubItem】

鼠标移动到一个子项的头像上面  (QQ这个时候 坐标出现一个小窗体 来加载该用户的一些资料)   【MouseEnterHead】

鼠标离开该子项的头像  (QQ的画 那个小窗体就消失了) 【MouseLeaveHead】


不过 我还是希望有谁能 解决一下那个TypeConverter的问题 虽然感觉没啥用处    不过 心理上却感觉不爽

修改bug

正如下面评论中 adrianEvin 提到的

“点击闪速,当闪速到没有时候再点闪速停止之后
图标都变成没有的了 鼠标移上去又好了 哈哈”

解决办法:

图片貌似被和谐了

在ChatListSubItem中的 IsTwinkle 属性中加上最后一句


from:

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

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

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

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

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

所以就有了我代码中的

[csharp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. for (int i = 0, lenItem = items.Count; i < lenItem; i++) {  
  2.     DrawItem(g, items[i], rectItem, sb);        //绘制列表项  
  3.     if (items[i].IsOpen) {                      //如果列表项展开绘制子项  
  4.         rectSubItem.Y = rectItem.Bottom + 1;  
  5.         for (int j = 0, lenSubItem = items[i].SubItems.Count; j < lenSubItem; j++) {  
  6.             DrawSubItem(g, items[i].SubItems[j], ref rectSubItem, sb);  //绘制子项  
  7.             rectSubItem.Y = rectSubItem.Bottom + 1;             //计算下一个子项的区域  
  8.             rectSubItem.Height = (int)iconSizeMode;             //大图标小图标模式的高度  
  9.         }  
  10.         rectItem.Height = rectSubItem.Bottom - rectItem.Top - (int)iconSizeMode - 1;  
  11.         //这里之所以给rectItem高度重新复制 是因为 SubItem 是包含在 Item 内部的  
  12.         //所以rectItem的高度就是 他标题的高度 加上他内部所有子项的高度 把他所有的子项都圈起来  
  13.     }  
  14.     items[i].Bounds = new Rectangle(rectItem.Location, rectItem.Size);  
  15.     rectItem.Y = rectItem.Bottom + 1;           //计算下一个列表项区域  
  16.     rectItem.Height = 25;  
  17. }  
每个item和subitem有了一个自己的区域后 那么要判断鼠标是否落在他们某一个区域上就方便了

[csharp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. for(int i = 0;i < Items.Count;i++){    
  2.     if(items[i].Bounds.Contains(MousePoint)){   //如果鼠标位置在 某一个item上  
  3.         if(items[i].IsOpen){                    //判断该Item是否为展开的  
  4.             for(int j = 0;j < Items[i].SubItem.Count;j++){  
  5.                 if(Items[i].SubItems[j].Bounds.Contains(MousePoint)){  
  6.                     XXXXXXXX....;  
  7.                     return;     //鼠标位置只可能在某一个Item 或者 SubItem 上所以找到一个后处理完事情直接return  
  8.                 }     
  9.             }//如果该项又是展开的 循环完子项却又没有找到符合条件的子项 那么就极有可能鼠标位置在标题上(因为每个Item或者SubItem有一像素间隔)  
  10.             if(new Rectangle(0,Items[i].Bounds.Top,this.Height,20).Contains(MousePoint)){  
  11.                 ........;  
  12.                 return;  
  13.             }  
  14.         }  
  15.     }  
  16. }  
主要思路 也就差不多是这些 怎么绘制 绘制后取得相应区域  剩下的就是一些细节上的功能 还有滚动条 

对对对 滚动条 所以在绘制的时候 要根据滚动条进行y坐标的一个偏移

g.TranslateTransform(0, -chatVScroll.Value);        //根据滚动条的值设置坐标偏移

这个是我控件中 完整的OnPaint

[csharp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. protected override void OnPaint(PaintEventArgs e) {  
  2.             Graphics g = e.Graphics;  
  3.             g.TranslateTransform(0, -chatVScroll.Value);        //根据滚动条的值设置坐标偏移  
  4.             Rectangle rectItem = new Rectangle(0, 1, this.Width, 25);                       //列表项区域  
  5.             Rectangle rectSubItem = new Rectangle(0, 26, this.Width, (int)iconSizeMode);    //子项区域  
  6.             SolidBrush sb = new SolidBrush(this.itemColor);  
  7.             try {  
  8.                 for (int i = 0, lenItem = items.Count; i < lenItem; i++) {  
  9.                     DrawItem(g, items[i], rectItem, sb);        //绘制列表项  
  10.                     if (items[i].IsOpen) {                      //如果列表项展开绘制子项  
  11.                         rectSubItem.Y = rectItem.Bottom + 1;  
  12.                         for (int j = 0, lenSubItem = items[i].SubItems.Count; j < lenSubItem; j++) {  
  13.                             DrawSubItem(g, items[i].SubItems[j], ref rectSubItem, sb);  //绘制子项  
  14.                             rectSubItem.Y = rectSubItem.Bottom + 1;             //计算下一个子项的区域  
  15.                             rectSubItem.Height = (int)iconSizeMode;  
  16.                         }  
  17.                         rectItem.Height = rectSubItem.Bottom - rectItem.Top - (int)iconSizeMode - 1;  
  18.                     }  
  19.                     items[i].Bounds = new Rectangle(rectItem.Location, rectItem.Size);  
  20.                     rectItem.Y = rectItem.Bottom + 1;           //计算下一个列表项区域  
  21.                     rectItem.Height = 25;  
  22.                 }  
  23.                 g.ResetTransform();             //重置坐标系  
  24.                 chatVScroll.VirtualHeight = rectItem.Bottom - 26;   //绘制完成计算虚拟高度决定是否绘制滚动条  
  25.                 if (chatVScroll.ShouldBeDraw)   //是否绘制滚动条  
  26.                     chatVScroll.ReDrawScroll(g);  
  27.             } finally { sb.Dispose(); }  
  28.             base.OnPaint(e);  
  29.         }  
也就是 在绘制控件的时候 坐标就不是 0 0 左上角位置开始绘制了 而是一个 0 Y 的一个虚拟的坐标了 Y 的值是由滚动条决定的  

所以在判断鼠标落的区域的时候 也有点变化了 Rectangle.Contains(MousePont.Y + 滚动条的值);

还有就是 离线 离开状态什么的 这些都是枚举值 然后ChatListSubItem实现一个排序接口 按照他们的顺序排序就可以了

其实 枚举值 也就相当于 int 值所以在 定义枚举类的时候 把顺序搞定  对SubItem排序的时候 按照一个升序排序就搞定了 这个和普通的排序没啥区别

还有就是 头像闪动的问题  在SubItem里面有一个 布尔值 来决定是否闪动 不仅是它 它所属的Item也的有关联  (在定义ChatListSubItem类的时候里面有个ChatListItem类型的一个Owner属性来表示该SubItem是属于哪个Item的  Item 同理有一个表示属于哪一个ChatListBox控件的 因为 在这些类中的一些操作要引发控件的重绘 所以要把他们一级一级关联)

因为 列表关闭的时候 头像没有办法闪动 所以只有列表标题闪动 所以在ChatListItem类里面 还定义了一个 计数器 保存在它列表下的子项闪动的个数 重绘的时候 如果列表关闭 计数器不为0 那么闪动列表标题 如果列表打开 那么闪动头像       当然在给ChatListSubItem的那个决定是否闪动的属性赋值的时候 响应的也要给所属的Item的计数器 进行操作

对于这个控件 我暂时能想到的就这些控件自身功能 然后就是给用户的接口了

我里面只定义了上个自定义事件 因为想了一下 在实际应用中  也就只有这三个有用

双击一个列表中的子项的时候   (QQ的话就弹出聊天对话框了)     【DoubleClickSubItem】

鼠标移动到一个子项的头像上面  (QQ这个时候 坐标出现一个小窗体 来加载该用户的一些资料)   【MouseEnterHead】

鼠标离开该子项的头像  (QQ的画 那个小窗体就消失了) 【MouseLeaveHead】


不过 我还是希望有谁能 解决一下那个TypeConverter的问题 虽然感觉没啥用处    不过 心理上却感觉不爽

修改bug

正如下面评论中 adrianEvin 提到的

“点击闪速,当闪速到没有时候再点闪速停止之后
图标都变成没有的了 鼠标移上去又好了 哈哈”

解决办法:

图片貌似被和谐了

在ChatListSubItem中的 IsTwinkle 属性中加上最后一句


https://blog.csdn.net/zjwen2007/article/details/43731991



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

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

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

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

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

所以就有了我代码中的

[csharp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. for (int i = 0, lenItem = items.Count; i < lenItem; i++) {  
  2.     DrawItem(g, items[i], rectItem, sb);        //绘制列表项  
  3.     if (items[i].IsOpen) {                      //如果列表项展开绘制子项  
  4.         rectSubItem.Y = rectItem.Bottom + 1;  
  5.         for (int j = 0, lenSubItem = items[i].SubItems.Count; j < lenSubItem; j++) {  
  6.             DrawSubItem(g, items[i].SubItems[j], ref rectSubItem, sb);  //绘制子项  
  7.             rectSubItem.Y = rectSubItem.Bottom + 1;             //计算下一个子项的区域  
  8.             rectSubItem.Height = (int)iconSizeMode;             //大图标小图标模式的高度  
  9.         }  
  10.         rectItem.Height = rectSubItem.Bottom - rectItem.Top - (int)iconSizeMode - 1;  
  11.         //这里之所以给rectItem高度重新复制 是因为 SubItem 是包含在 Item 内部的  
  12.         //所以rectItem的高度就是 他标题的高度 加上他内部所有子项的高度 把他所有的子项都圈起来  
  13.     }  
  14.     items[i].Bounds = new Rectangle(rectItem.Location, rectItem.Size);  
  15.     rectItem.Y = rectItem.Bottom + 1;           //计算下一个列表项区域  
  16.     rectItem.Height = 25;  
  17. }  
每个item和subitem有了一个自己的区域后 那么要判断鼠标是否落在他们某一个区域上就方便了

[csharp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. for(int i = 0;i < Items.Count;i++){    
  2.     if(items[i].Bounds.Contains(MousePoint)){   //如果鼠标位置在 某一个item上  
  3.         if(items[i].IsOpen){                    //判断该Item是否为展开的  
  4.             for(int j = 0;j < Items[i].SubItem.Count;j++){  
  5.                 if(Items[i].SubItems[j].Bounds.Contains(MousePoint)){  
  6.                     XXXXXXXX....;  
  7.                     return;     //鼠标位置只可能在某一个Item 或者 SubItem 上所以找到一个后处理完事情直接return  
  8.                 }     
  9.             }//如果该项又是展开的 循环完子项却又没有找到符合条件的子项 那么就极有可能鼠标位置在标题上(因为每个Item或者SubItem有一像素间隔)  
  10.             if(new Rectangle(0,Items[i].Bounds.Top,this.Height,20).Contains(MousePoint)){  
  11.                 ........;  
  12.                 return;  
  13.             }  
  14.         }  
  15.     }  
  16. }  
主要思路 也就差不多是这些 怎么绘制 绘制后取得相应区域  剩下的就是一些细节上的功能 还有滚动条 

对对对 滚动条 所以在绘制的时候 要根据滚动条进行y坐标的一个偏移

g.TranslateTransform(0, -chatVScroll.Value);        //根据滚动条的值设置坐标偏移

这个是我控件中 完整的OnPaint

[csharp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. protected override void OnPaint(PaintEventArgs e) {  
  2.             Graphics g = e.Graphics;  
  3.             g.TranslateTransform(0, -chatVScroll.Value);        //根据滚动条的值设置坐标偏移  
  4.             Rectangle rectItem = new Rectangle(0, 1, this.Width, 25);                       //列表项区域  
  5.             Rectangle rectSubItem = new Rectangle(0, 26, this.Width, (int)iconSizeMode);    //子项区域  
  6.             SolidBrush sb = new SolidBrush(this.itemColor);  
  7.             try {  
  8.                 for (int i = 0, lenItem = items.Count; i < lenItem; i++) {  
  9.                     DrawItem(g, items[i], rectItem, sb);        //绘制列表项  
  10.                     if (items[i].IsOpen) {                      //如果列表项展开绘制子项  
  11.                         rectSubItem.Y = rectItem.Bottom + 1;  
  12.                         for (int j = 0, lenSubItem = items[i].SubItems.Count; j < lenSubItem; j++) {  
  13.                             DrawSubItem(g, items[i].SubItems[j], ref rectSubItem, sb);  //绘制子项  
  14.                             rectSubItem.Y = rectSubItem.Bottom + 1;             //计算下一个子项的区域  
  15.                             rectSubItem.Height = (int)iconSizeMode;  
  16.                         }  
  17.                         rectItem.Height = rectSubItem.Bottom - rectItem.Top - (int)iconSizeMode - 1;  
  18.                     }  
  19.                     items[i].Bounds = new Rectangle(rectItem.Location, rectItem.Size);  
  20.                     rectItem.Y = rectItem.Bottom + 1;           //计算下一个列表项区域  
  21.                     rectItem.Height = 25;  
  22.                 }  
  23.                 g.ResetTransform();             //重置坐标系  
  24.                 chatVScroll.VirtualHeight = rectItem.Bottom - 26;   //绘制完成计算虚拟高度决定是否绘制滚动条  
  25.                 if (chatVScroll.ShouldBeDraw)   //是否绘制滚动条  
  26.                     chatVScroll.ReDrawScroll(g);  
  27.             } finally { sb.Dispose(); }  
  28.             base.OnPaint(e);  
  29.         }  
也就是 在绘制控件的时候 坐标就不是 0 0 左上角位置开始绘制了 而是一个 0 Y 的一个虚拟的坐标了 Y 的值是由滚动条决定的  

所以在判断鼠标落的区域的时候 也有点变化了 Rectangle.Contains(MousePont.Y + 滚动条的值);

还有就是 离线 离开状态什么的 这些都是枚举值 然后ChatListSubItem实现一个排序接口 按照他们的顺序排序就可以了

其实 枚举值 也就相当于 int 值所以在 定义枚举类的时候 把顺序搞定  对SubItem排序的时候 按照一个升序排序就搞定了 这个和普通的排序没啥区别

还有就是 头像闪动的问题  在SubItem里面有一个 布尔值 来决定是否闪动 不仅是它 它所属的Item也的有关联  (在定义ChatListSubItem类的时候里面有个ChatListItem类型的一个Owner属性来表示该SubItem是属于哪个Item的  Item 同理有一个表示属于哪一个ChatListBox控件的 因为 在这些类中的一些操作要引发控件的重绘 所以要把他们一级一级关联)

因为 列表关闭的时候 头像没有办法闪动 所以只有列表标题闪动 所以在ChatListItem类里面 还定义了一个 计数器 保存在它列表下的子项闪动的个数 重绘的时候 如果列表关闭 计数器不为0 那么闪动列表标题 如果列表打开 那么闪动头像       当然在给ChatListSubItem的那个决定是否闪动的属性赋值的时候 响应的也要给所属的Item的计数器 进行操作

对于这个控件 我暂时能想到的就这些控件自身功能 然后就是给用户的接口了

我里面只定义了上个自定义事件 因为想了一下 在实际应用中  也就只有这三个有用

双击一个列表中的子项的时候   (QQ的话就弹出聊天对话框了)     【DoubleClickSubItem】

鼠标移动到一个子项的头像上面  (QQ这个时候 坐标出现一个小窗体 来加载该用户的一些资料)   【MouseEnterHead】

鼠标离开该子项的头像  (QQ的画 那个小窗体就消失了) 【MouseLeaveHead】


不过 我还是希望有谁能 解决一下那个TypeConverter的问题 虽然感觉没啥用处    不过 心理上却感觉不爽

修改bug

正如下面评论中 adrianEvin 提到的

“点击闪速,当闪速到没有时候再点闪速停止之后
图标都变成没有的了 鼠标移上去又好了 哈哈”

解决办法:

图片貌似被和谐了

在ChatListSubItem中的 IsTwinkle 属性中加上最后一句

相关文章
相关标签/搜索