今天在百度知道上看到一个提问,要做个简单的文件浏览器,于是拿出以前写的半成品修改了一下,中间碰到了一点小麻烦。主要是如何在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;
- }
- }
我开始写的代码是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控件,节省了原来重复复制图标的开销,代码也更简洁了。