为ListView动态组合图标
浙江省丽水市汽车运输集团有限公司信息中心 苟安廷
注:本文代码请到ftp://202.107.251.26/上下载
我们先来看一组大家很熟悉的图标:
很显然,这是表示网络状态的一组图标,第一个表示正常状态,第二个表示禁用状态,后面的四个图标是在前面两个的基础上增加了断开、有防火墙或者既断开又有防火墙的几种附加状态,通常,我们用ListView控件来表示这类状态。
图中可以看出,共有两种基本状态:正常和禁用,两种附加状态,有防火墙和断开,所有的状态都是由基本状态和附加状态组合而成的。如何实现呢?你可以参照上图准备六个图标,并根据不同的状态选择显示不同的图标,但这是比较简单的情况,以宾馆客房为例,通常有空房、维修、入住、预留、脏房等m种基本状态,以及预订、有食品、叫醒等n种附加状态,那么你要准备多少个图标呢?(m+1)×n个?不对,因为许多附加状态是同时出现的,如上图中断开且有防火墙,这时,最理想的办法就是准备m个基本图标,n个附加图标,然后根据实际需要动态组合起来,但ListView本身并不支持动态图标的组合,本文就讨论如何实现这一设想,希望对有类似需求的朋友提供一些思路。
打开VS2005,新建一个C#的windows项目,不妨取名为DyIconForListView吧,在窗口上放一个ListView控件(为简化叙述,我们都使用默认的控件名),设置其View属性为LargIcon,再放两个ImageList控件,imageList1的图象大小属性ImageSize=40×40,表示基本状态,因此图标要大一些,imageList2的图象大小属性ImageSize=12×12,表示基本附加状态,因此,图标小一些,并将listView1的LargImageList设置为imagaList1,SmallImageList不用设置,imageList2是组合用的。通过截取屏幕等方法准备两个40×40的位图,一个表示网络正常状态,一个表示禁用状态,并添加到imageList1中,再准备两个12×12的小位图,一个为表示断开的“×”图标,一个为表示有防火墙的“锁”图标,并添加到imageList2中。
为了显示各种状态,我们还必须增加一些其他控件。在窗口上放一个ListBox 控件,用来选择当前使用哪一种基本状态,放一个CheckListBox控件,用来选择当前使用哪几种附加状态,当然,还要放一个Button控件,用来将设置的状态显示出来,为了不太难看,将Button1的Text属性改为“设置”吧,布置好后,界面如下:
切换到代码窗口中,增加引用:
using System.Collections;
增加两个枚举,用来表示基本状态和附加状态的名称,注意,对于附加状态,每一种状态用字节中的一个位表示,由于附加状态图标通常是放在基本图标的四个角落上的,也就是说通常不会超过四种,最多再加上四个边,共八种附加图标,但这样一来,基本图标就被挡得看不清楚了,实际上也用不到这么多附加状态。
private enum BaseStateNames
{
正常 = 0, 禁用 = 1
}
private enum AttachStateNames
{
断开=1, //二进制的00000001
有防火墙=2 //二进制的00000010
//如果有其他附加状态依此类推
//第三种状态=4 //二进制的00000100
}
编写Load事件,为ListView增加一个Item,用于显示设置的结果,并将基本状态和附加状态的名称显示在ListBox和CheckListBox控件中供选择:
private void Form1_Load(object sender, EventArgs e)
{
ListViewItem item = new ListViewItem("本地连接");
this.listView1.Items.Add(item);
this.listBox1.Items.AddRange(Enum.GetNames(typeof(BaseStateNames)));
this.checkedListBox1.Items.AddRange(Enum.GetNames(typeof(AttachStateNames)));
}
编写一个函数,将一个基本位图(不是图标)和一个附加的位图组合而成一个新的位图,用于动态组合:
/// <summary>
/// 将两个位图组合,得到一个新的位图
/// </summary>
/// <param name="p_BaseBmp">基本位图</param>
/// <param name="p_AttachBmp">附加位图</param>
/// <param name="p_Align">附加位图放在基本位图的位置</param>
/// <returns>生成的新位图</returns>
private Bitmap AttachBmp(Bitmap p_BaseBmp, Bitmap p_AttachBmp, System.Drawing.ContentAlignment p_Align)
{
//附加位图的起始坐标,默认放在左上角,因此为0,0
int nX = 0, nY = 0;
if (p_Align == ContentAlignment.BottomLeft)
nY = p_BaseBmp.Height - p_AttachBmp.Height;
else if (p_Align == ContentAlignment.BottomRight)
{
nX = p_BaseBmp.Width - p_AttachBmp.Width;
nY = p_BaseBmp.Height - p_AttachBmp.Height;
}
else if (p_Align == ContentAlignment.TopRight)
{
nX = p_BaseBmp.Width - p_AttachBmp.Width;
}
//你还可以将附加位图放在其他位置,如四个边上,处理方法类似上面的代码
//将附加位图的逐点取出来,写到基本位图上
for (int i = 0; i < p_AttachBmp.Height; i++)
{
for (int j = 0; j < p_AttachBmp.Width; j++)
{
Color cr = p_AttachBmp.GetPixel(j, i);
//忽略白色背景,当然,你也可以指定为其他颜色,或者不忽略
if (cr.ToArgb() != Color.White.ToArgb())
p_BaseBmp.SetPixel(nX + j, nY + i, p_AttachBmp.GetPixel(j, i));
}
}
return p_BaseBmp;
}
新建一个嵌套的类,表示组合后图标的信息:
internal class AttachIcon
{
/// <summary>
/// 基本图标索引
/// </summary>
public int BaseImagIndex = 0;
/// <summary>
/// 组合状态
/// </summary>
public int Tag = 0;
/// <summary>
/// 组合后图标在ImageList1中的的索引
/// </summary>
public int NewImagIndex = 0;
}
编写一个数组,用于存放原基本图标和组合后产生的图标信息:
ArrayList m_ArrAttachIcon = new ArrayList();
再编写一个函数,根据基本状态的和附加状态的组合产生新的图标,并返回新图标的索引,注意,是附加状态的组合,如果没有附加状态,则组合状态为0,第一种附加状态为二进制的0000 0001,第二种状态为二进制的0000 0010,因此,如果是第一、第二两种附加状态组合,则为0000 0011:
/// <summary>
/// 根据基本状态和附加状态,返回ImageIndex,如果是基本状态,直接返回,
/// 否则,将基本状态图标+附加的图标,生成一个新的图标,添加的ImageList中去,
/// 并返回新的索引
/// </summary>
/// <param name="p_BaseImageIndex">基本状态索引</param>
/// <param name="p_Stat">附加状态的组合</param>
/// <returns>最终图标的索引</returns>
private int GetImageIndex(int p_BaseImageIndex, int p_Stat)
{
if (p_Stat == 0)
return p_BaseImageIndex;
//查看是已经生成了组合图标,如果有,直接返回索引
foreach (AttachIcon att in m_ArrAttachIcon)
{
if ((att.BaseImagIndex == p_BaseImageIndex) && (att.Tag == p_Stat))
return att.NewImagIndex;
}
//重新组合一个出来,首先得到基本图标
Bitmap bmpBase = (Bitmap)this.imageList1.Images[p_BaseImageIndex].Clone();
//再根据附加图状态组合新的图标
if ((p_Stat & ((int)AttachStateNames.断开)) > 0)
{
bmpBase = AttachBmp(bmpBase, (Bitmap)this.imageList2.Images[0].Clone(), System.Drawing.ContentAlignment.TopLeft);
}
if ((p_Stat & ((int)AttachStateNames.有防火墙)) > 0)
{
bmpBase = AttachBmp(bmpBase, (Bitmap)this.imageList2.Images[1].Clone(), System.Drawing.ContentAlignment.TopRight);
}
//将新组合而成的图标加入的ImageList1和数组中去
int nIndex = this.imageList1.Images.Count;
this.imageList1.Images.Add(bmpBase);
AttachIcon newatt = new AttachIcon();
newatt.BaseImagIndex = p_BaseImageIndex;
newatt.Tag = p_Stat;
newatt.NewImagIndex = nIndex;
this.m_ArrAttachIcon.Add(newatt);
return nIndex;
}
到此,准备工作完成了,下面我们通过Button1的Click事件实现:
private void button1_Click(object sender, EventArgs e)
{
//得到基本图标索引
int nBaseIndex = this.listBox1.SelectedIndex;
if (nBaseIndex < 0)
{
MessageBox.Show("请选择基本状态!");
return;
}
//得到附加状态的组合
int nAttachTags = 0;
foreach (string strAttachName in this.checkedListBox1.CheckedItems)
{
if (strAttachName == AttachStateNames.断开.ToString())
nAttachTags |= (int)AttachStateNames.断开;
else if(strAttachName == AttachStateNames.有防火墙.ToString())
nAttachTags |= (int)AttachStateNames.有防火墙;
}
int nImageIndex = GetImageIndex(nBaseIndex, nAttachTags);
if (nImageIndex > -1)
this.listView1.Items[0].ImageIndex = nImageIndex;
},
运行效果如下: