今天在百度知道上看到一个提问,要做个简单的文件浏览器,于是拿出以前写的半成品修改了一下,中间碰到了一点小麻烦。主要是如何在c#里面给ListView控件设置图标时使用系统的ImageList,而不是自己创建ImageList对象。
先把封装了提取图标等API的FileIcon类贴出来。 class FileIcon
{
[StructLayout(LayoutKind.Sequential)]
public struct SHFILEINFO
{
public IntPtr hIcon;
public int iIcon;
public uint dwAttributes;
public string szDisplayName;
public string szTypeName;
};
const uint SHGFI_ICON = 0x00000100;
const uint SHGFI_LARGEICON = 0x00000000;
const uint SHGFI_SMALLICON = 0x00000001;
const uint SHGFI_SYSICONINDEX = 0x4000;
[DllImport("shell32.dll")]
public static extern IntPtr SHGetFileInfo(string filename,uint fileattributes,ref SHFILEINFO shfi,uint cbfi,uint flag);
[DllImport("user32.dll")]
public static extern bool DestroyIcon(IntPtr hIcon);
public static uint LVM_FIRST = 0x1000;
public static uint LVM_SETIMAGELIST = LVM_FIRST + 3;
public static uint LVSIL_NORMAL = 0;
public static uint LVSIL_SMALL = 1;
[DllImport("User32.DLL")]
public static extern int SendMessage(IntPtr hWnd, uint Msg, uint wParam, IntPtr lParam);
public static void ListViewSetImageList(System.Windows.Forms.ListView lv)
{
SHFILEINFO shfi = new SHFILEINFO();
IntPtr hImageList;
hImageList = SHGetFileInfo("", 0, ref shfi, (uint)Marshal.SizeOf(shfi), SHGFI_LARGEICON | SHGFI_SYSICONINDEX);
SendMessage(lv.Handle, LVM_SETIMAGELIST, LVSIL_NORMAL, hImageList);
hImageList = SHGetFileInfo("", 0, ref shfi, (uint)Marshal.SizeOf(shfi), SHGFI_SMALLICON | SHGFI_SYSICONINDEX);
SendMessage(lv.Handle, LVM_SETIMAGELIST, LVSIL_SMALL, hImageList);
}
public static int GetIconIndex(string filename)
{
SHFILEINFO shfi = new SHFILEINFO();
SHGetFileInfo(filename, 0, ref shfi, (uint)Marshal.SizeOf(shfi), SHGFI_ICON | SHGFI_SMALLICON);
return (int)shfi.iIcon;
}
public static Icon GetSmallIconFromFile(string filename)
{
SHFILEINFO shfi=new SHFILEINFO();
SHGetFileInfo(filename, 0,ref shfi,(uint) Marshal.SizeOf(shfi), SHGFI_ICON | SHGFI_SMALLICON);
Icon myicon =(Icon) Icon.FromHandle(shfi.hIcon).Clone();
DestroyIcon(shfi.hIcon);
return myicon;
}
public static Icon GetLargeIconFromFile(string filename)
{
SHFILEINFO shfi = new SHFILEINFO();
SHGetFileInfo(filename, 0, ref shfi, (uint)Marshal.SizeOf(shfi), SHGFI_ICON | SHGFI_LARGEICON);
Icon myicon = (Icon)Icon.FromHandle(shfi.hIcon).Clone();
DestroyIcon(shfi.hIcon);
return myicon;
}
}
这里要注意的是,使用SHGetFileInfo函数,如果参数中有SHGFI_ICON,也就是获得了icon的句柄的话,要记得使用DestroyIcon释放句柄,msdn明确说明了这个,我在网上看到的代码都没有这一句。
我开始写的代码是ListView.Items每添加一项,对应的SmallImageList和LargeImageList都用SHGetFileInfo获取一个图标并添加到ImageList,下面贴出这部分的代码。
public Form1()
{
InitializeComponent();
this.listView1.View = View.LargeIcon;
this.listView1.SmallImageList = this.imageListSmall;
this.listView1.LargeImageList = this.imageListLarge;
}
void RefreshFileList()
{
this.listView1.Items.Clear();
this.imageListLarge.Images.Clear();
this.imageListSmall.Images.Clear();
int count = 0;
DirectoryInfo dirInfo = new DirectoryInfo(this.textBox1.Text);
if (dirInfo.Exists)
{
DirectoryInfo[] infos = dirInfo.GetDirectories();
foreach (DirectoryInfo d in infos)
{
ListViewItem lv = new ListViewItem(d.Name);
lv.ImageIndex = count;
listView1.Items.Add(lv);
}
if (infos.Length > 0)
{
this.imageListSmall.Images.Add(FileIcon.GetSmallIconFromFile(""));
this.imageListLarge.Images.Add(FileIcon.GetLargeIconFromFile(""));
count++;
}
}
string[] files = Directory.GetFiles(this.textBox1.Text);
foreach (string str in files)
{
this.imageListLarge.Images.Add(FileIcon.GetLargeIconFromFile(str));
this.imageListSmall.Images.Add(FileIcon.GetSmallIconFromFile(str));
ListViewItem lv = new ListViewItem(Path.GetFileName(str));
FileInfo fi = new FileInfo(str);
string num = fi.Length.ToString();
for (int i = num.Length - 3; i > 0; i -= 3)
{
num = num.Insert(i, ",");
}
lv.SubItems.Add(num);
lv.ImageIndex = count++;
this.listView1.Items.Add(lv);
}
}
使用这种方法,小规模的文件显示是没问题的,但是如果一次显示很多文件肯定会卡,甚至内存不足。在网上搜了一下发现SHGetFileInfo函数使用SHGFI_SYSICONINDEX参数可以返回系统的ImageList的句柄,在vc里面可以直接使用,但是c#的ListView控件的Handle属性是只读的,而且也没有提供类似FromHandle之类的方法。苦恼之余继续上网搜索,终于找到一篇文章, (链接:http://www.cnblogs.com/parry/archive/2007/10/23/935026.html) 作者使用SendMessage直接向ListView控件发送设置图像列表的消息,参考了这种方法我很快改进了我的代码。
public Form1()
{
InitializeComponent();
this.listView1.View = View.LargeIcon;
FileIcon.ListViewSetImageList(listView1);
}
void RefreshFileList()
{
this.listView1.Items.Clear();
DirectoryInfo dirInfo = new DirectoryInfo(this.textBox1.Text);
if (!dirInfo.Exists)
return;
DirectoryInfo[] infos = dirInfo.GetDirectories();
int iDirIconIndex=FileIcon.GetIconIndex("");
this.listView1.BeginUpdate();
foreach (DirectoryInfo d in infos)
{
ListViewItem lv = new ListViewItem(d.Name);
lv.ImageIndex = iDirIconIndex;
listView1.Items.Add(lv);
}
string[] files = Directory.GetFiles(this.textBox1.Text);
foreach (string str in files)
{
ListViewItem lv = new ListViewItem(Path.GetFileName(str));
FileInfo fi = new FileInfo(str);
string num = fi.Length.ToString();
for (int i = num.Length - 3; i > 0; i -= 3)
{
num = num.Insert(i, ",");
}
lv.SubItems.Add(num);
lv.ImageIndex = FileIcon.GetIconIndex(str);
this.listView1.Items.Add(lv);
}
this.listView1.EndUpdate();
}
使用这种方法比原来少使用了两个ImageList控件,节省了原来重复复制图标的开销,代码也更简洁了。