使用任务获取目录图标
Getting directory icons using tasks
我的任务是使用任务获取目录图标并将它们显示在 DataGridView 中(我正在通过文件夹执行搜索)。为此,我使用 SHGetImageList WinAPI 函数。我有一个助手 class 如下:
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace WindowsFormsApplication6 {
public class Helper {
private const uint ILD_TRANSPARENT = 0x00000001;
private const uint SHGFI_SYSICONINDEX = 0x000004000;
private const uint SHGFI_ICON = 0x000000100;
public static readonly int MaxEntitiesCount = 80;
public static void GetDirectories(string path, List<Image> col, IconSizeType sizeType, Size itemSize) {
DirectoryInfo dirInfo = new DirectoryInfo(path);
DirectoryInfo[] dirs = dirInfo.GetDirectories("*", SearchOption.TopDirectoryOnly);
for (int i = 0; i < dirs.Length && i < MaxEntitiesCount; i++) {
DirectoryInfo subDirInfo = dirs[i];
if (!CheckAccess(subDirInfo) || !MatchFilter(subDirInfo.Attributes)) {
continue;
}
col.Add(GetFileImage(subDirInfo.FullName, sizeType, itemSize));
}
}
public static bool CheckAccess(DirectoryInfo info) {
bool isOk = false;
try {
var secInfo = info.GetAccessControl();
isOk = true;
}
catch {
}
return isOk;
}
public static bool MatchFilter(FileAttributes attributes) {
return (attributes & (FileAttributes.Hidden | FileAttributes.System)) == 0;
}
public static Image GetFileImage(string path, IconSizeType sizeType, Size itemSize) {
return IconToBitmap(GetFileIcon(path, sizeType, itemSize), sizeType, itemSize);
}
public static Image IconToBitmap(Icon ico, IconSizeType sizeType, Size itemSize) {
if (ico == null) {
return new Bitmap(itemSize.Width, itemSize.Height);
}
return ico.ToBitmap();
}
public static Icon GetFileIcon(string path, IconSizeType sizeType, Size itemSize) {
SHFILEINFO shinfo = new SHFILEINFO();
IntPtr retVal = SHGetFileInfo(path, 0, ref shinfo, (uint)Marshal.SizeOf(shinfo), (int)(SHGFI_SYSICONINDEX | SHGFI_ICON));
int iconIndex = shinfo.iIcon;
IImageList iImageList = (IImageList)GetSystemImageListHandle(sizeType);
IntPtr hIcon = IntPtr.Zero;
if (iImageList != null) {
iImageList.GetIcon(iconIndex, (int)ILD_TRANSPARENT, ref hIcon);
}
Icon icon = null;
if (hIcon != IntPtr.Zero) {
icon = Icon.FromHandle(hIcon).Clone() as Icon;
DestroyIcon(shinfo.hIcon);
}
return icon;
}
private static IImageList GetSystemImageListHandle(IconSizeType sizeType) {
IImageList iImageList = null;
Guid imageListGuid = new Guid("46EB5926-582E-4017-9FDF-E8998DAA0950");
int ret = SHGetImageList((int)sizeType, ref imageListGuid, ref iImageList);
return iImageList;
}
[DllImport("shell32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, uint cbFileInfo, uint uFlags);
[DllImport("shell32.dll", EntryPoint = "#727")]
private static extern int SHGetImageList(int iImageList, ref Guid riid, ref IImageList ppv);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool DestroyIcon(IntPtr hIcon);
public enum IconSizeType {
Medium = 0x0,
Small = 0x1,
Large = 0x2,
ExtraLarge = 0x4
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
private struct SHFILEINFO {
public IntPtr hIcon;
public int iIcon;
public uint dwAttributes;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string szDisplayName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
public string szTypeName;
}
[ComImport,
Guid("46EB5926-582E-4017-9FDF-E8998DAA0950"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IImageList {
[PreserveSig]
int Add(IntPtr hbmImage, IntPtr hbmMask, ref int pi);
[PreserveSig]
int ReplaceIcon(int i, IntPtr hicon, ref int pi);
[PreserveSig]
int SetOverlayImage(int iImage, int iOverlay);
[PreserveSig]
int Replace(int i, IntPtr hbmImage, IntPtr hbmMask);
[PreserveSig]
int AddMasked(IntPtr hbmImage, int crMask, ref int pi);
[PreserveSig]
int Draw(ref IMAGELISTDRAWPARAMS pimldp);
[PreserveSig]
int Remove(int i);
[PreserveSig]
int GetIcon(int i, int flags, ref IntPtr picon);
[PreserveSig]
int GetImageInfo(int i, ref IMAGEINFO pImageInfo);
[PreserveSig]
int Copy(int iDst, IImageList punkSrc, int iSrc, int uFlags);
[PreserveSig]
int Merge(int i1, IImageList punk2, int i2, int dx, int dy, ref Guid riid, ref IntPtr ppv);
[PreserveSig]
int Clone(ref Guid riid, ref IntPtr ppv);
[PreserveSig]
int GetImageRect(int i, ref RECT prc);
[PreserveSig]
int GetIconSize(ref int cx, ref int cy);
[PreserveSig]
int SetIconSize(int cx, int cy);
[PreserveSig]
int GetImageCount(ref int pi);
[PreserveSig]
int SetImageCount(int uNewCount);
[PreserveSig]
int SetBkColor(int clrBk, ref int pclr);
[PreserveSig]
int GetBkColor(ref int pclr);
[PreserveSig]
int BeginDrag(int iTrack, int dxHotspot, int dyHotspot);
[PreserveSig]
int EndDrag();
[PreserveSig]
int DragEnter(IntPtr hwndLock, int x, int y);
[PreserveSig]
int DragLeave(IntPtr hwndLock);
[PreserveSig]
int DragMove(int x, int y);
[PreserveSig]
int SetDragCursorImage(ref IImageList punk, int iDrag, int dxHotspot, int dyHotspot);
[PreserveSig]
int DragShowNolock(int fShow);
[PreserveSig]
int GetDragImage(ref POINT ppt, ref POINT pptHotspot, ref Guid riid, ref IntPtr ppv);
[PreserveSig]
int GetItemFlags(int i, ref int dwFlags);
[PreserveSig]
int GetOverlayImage(int iOverlay, ref int piIndex);
}
;
[StructLayout(LayoutKind.Sequential)]
private struct IMAGELISTDRAWPARAMS {
public int cbSize;
public IntPtr himl;
public int i;
public IntPtr hdcDst;
public int x;
public int y;
public int cx;
public int cy;
public int xBitmap;
public int yBitmap;
public int rgbBk;
public int rgbFg;
public int fStyle;
public int dwRop;
public int fState;
public int Frame;
public int crEffect;
}
[StructLayout(LayoutKind.Sequential)]
private struct IMAGEINFO {
public IntPtr hbmImage;
public IntPtr hbmMask;
public int Unused1;
public int Unused2;
public RECT rcImage;
}
[StructLayout(LayoutKind.Sequential)]
public struct RECT {
public int Left, Top, Right, Bottom;
public RECT(int l, int t, int r, int b) {
Left = l;
Top = t;
Right = r;
Bottom = b;
}
public RECT(Rectangle r) {
Left = r.Left;
Top = r.Top;
Right = r.Right;
Bottom = r.Bottom;
}
public Rectangle ToRectangle() {
return Rectangle.FromLTRB(Left, Top, Right, Bottom);
}
public void Inflate(int width, int height) {
Left -= width;
Top -= height;
Right += width;
Bottom += height;
}
public override string ToString() {
return string.Format("x:{0},y:{1},width:{2},height:{3}", Left, Top, Right - Left, Bottom - Top);
}
}
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
public struct POINT {
public int X, Y;
public POINT(int x, int y) {
this.X = x;
this.Y = y;
}
public POINT(Point pt) {
this.X = pt.X;
this.Y = pt.Y;
}
public Point ToPoint() {
return new Point(X, Y);
}
}
}
}
在一个窗体上,我有两个 DataGridView 和两个按钮。单击第一个按钮时,我在 UI 线程中加载图标:
private void button1_Click(object sender, EventArgs e) {
List<Image> list = new List<Image>();
Helper.GetDirectories(fPath, list, Helper.IconSizeType.Small, new Size(16, 16));
dataGridView1.DataSource = list;
}
在点击第二个按钮时,我做了:
private void button2_Click(object sender, EventArgs e) {
Func<object, List<Image>> a = null;
a = (p) => {
string path = (string)p;
List<Image> list = new List<Image>();
Helper.GetDirectories(path, list, Helper.IconSizeType.Small, new Size(16, 16));
return list;
};
Task.Factory.StartNew(a, fPath).ContinueWith(t => { dataGridView2.DataSource = t.Result;},
TaskScheduler.FromCurrentSynchronizationContext());
}
所以,我也这样做,但是在一个任务中。
当我点击第一个按钮然后点击第二个按钮时,我得到以下内容 System.InvalidCastException:
Unable to cast COM object of type 'System.__ComObject' to interface
type 'IImageList'. This operation failed because the QueryInterface
call on the COM component for the interface with IID
'{46EB5926-582E-4017-9FDF-E8998DAA0950}' failed due to the following
error: No such interface supported (Exception from HRESULT: 0x80004002
(E_NOINTERFACE)).
在
中引发异常
int ret = SHGetImageList((int)sizeType, ref imageListGuid, ref iImageList);
GetSystemImageListHandle 方法的行。
我不知道自己做错了什么。感谢任何帮助。
Windows shell 函数只能从 UI 线程调用。您有与此问题相同的根本问题:Program.Main()
.
上的 Unable to cast COM object of type 'System.__ComObject' to interface type 'IImageList'. Or more formally they can only be created in a single thread apartment. The UI thread in WPF and WinForms is be default in a single threaded apartment (that's the meaning of the [STAThread]
属性
Task.Run()
使用的线程池线程不会是单线程套间(会是多线程套间)。当您尝试访问线程单元类型中的 COM 对象时,您可能会收到 E_NOINTERFACE
错误。*
可以在单线程单元中手动创建新线程。然而,在这种情况下,这似乎确实可靠地工作。它仍然会偶尔抛出 E_NOINTERFACE
exce[topms.只需在 UI 线程上调用 SHGetImageList
。感谢@vendettamit 实际测试它并发现它不起作用。
可以创建 new thread manually that is in a single threaded apartment:
Thread thread = new Thread(ThreadStartMethod);
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
这可能适用于您的情况,但如果您尝试访问 IImageList
或不同线程上的任何 COM 对象,您将 运行 遇到问题**。由于您似乎不会进行跨线程调用,因此以下内容应该有效:
private void button2_Click(object sender, EventArgs e) {
ParameterizedThreadStart a = null;
a = (p) => {
string path = (string)p;
List<Image> list = new List<Image>();
Helper.GetDirectories(path, list, Helper.IconSizeType.Small, new Size(16, 16));
this.Invoke(new Action(() => dataGridView2.DataSource = list));
};
Thread thread = new Thread(a);
thread.SetApartmentState(ApartmentState.STA);
thread.Start(fPath);
}
* 一些 COM 接口支持跨单元编组,但 COM 编组是一个完全不同的主题。
** 因为 COM STA 封送处理的工作原理,您需要 Windows 消息泵 运行 在您的后台线程上运行。
Update: Chaning the [STAThread] to [MTAThread] on Main method, with the given code @Gosha_Fighten's problem everything seems to be working.
以下解决方案仅适用于 运行 在 [STAThread] 下的应用程序。
@shf301 解释的很好!但是答案中提供的示例代码无法解决问题。通过 SHGetImageList
调用跨线程构造函数导致了这个问题。因此,如果在 Worker 线程上调用了 Helper.GetDirectories
,则不会出现该问题,而不是调用 DataGrid 绑定语句。
以下代码运行良好:
private void button2_Click_1(object sender, EventArgs e)
{
Action a = null;
a = () =>
{
List<Image> list = null;
this.BeginInvoke(new Action(() =>
{
list = new List<Image>();
Helper.GetDirectories(fPath, list, Helper.IconSizeType.Small, new Size(16, 16));
dataGridView2.DataSource = list;
}));
};
Task.Factory.StartNew(a);
}
这里我删除了 ContinueWith()
并传递了 Current synchronization
参数。现在控件的调用方法将在工作线程上执行任务。
注意 - 由于所有工作都在 WorkerThread 上完成,因此无需保留 Task
。
.NET有SynchronizationContext的概念:
Provides the basic functionality for propagating a synchronization
context in various synchronization models.
此上下文的主要方法名为 Post
,它主要是向上下文发送异步消息。在这里,根据底层 UI 技术(Winforms、WPF 等)或非 UI 技术,可以调整事物并在这些技术的限制下优雅地工作。
默认情况下,Tasks 的任务调度程序不使用当前同步上下文,而是使用您无法真正控制的 ThreadPool
(顺便说一下,它不使用 Winforms,也不玩 WPF),所以你必须指定你想要来自 SynchronizationContext 的 TaskScheduler,你只是部分地做了。
由于您是 运行 Winforms 应用程序,因此当前的同步上下文(Synchronization.Current) should be of WindowsFormsSynchronizationContext 类型。如果您可以查看其 Post 的实现,您将看到此:
public override void Post(SendOrPostCallback callback, object state)
{
if (controlToSendTo != null)
{
controlToSendTo.BeginInvoke(callback, new object[] { state });
}
}
这个上下文的实现应该可以很好地与 Winforms UI 线程一起工作......只要你使用它。事实上你几乎做对了,你只是忘了在 StartNew
方法中使用它。
所以,只需将您的代码更改为:
Task.Factory.StartNew(a, fPath,
CancellationToken.None, TaskCreationOptions.None, // unfortunately, there is no easier overload with just the context...
TaskScheduler.FromCurrentSynchronizationContext()).ContinueWith(
t => { dataGridView2.DataSource = t.Result; },
TaskScheduler.FromCurrentSynchronizationContext());
它应该可以工作。
只需插入
Marshal.FinalReleaseComObject(iImageList);
之后
iImageList.GetIcon(iconIndex, (int)ILD_TRANSPARENT, ref hIcon);
行。
此外,您可能感兴趣的是,当您传递 SHGFI_SYSICONINDEX 时,SHGetFileInfo 实际上是 returns IImageList。所以,这样的事情会起作用:
[DllImport("shell32.dll", EntryPoint = "SHGetFileInfo", CharSet = CharSet.Auto)]
private static extern IImageList SHGetFileInfoAsImageList(string pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, uint cbFileInfo, uint uFlags);
整个图像提取可以很简单:
public static Image GetFileImage(string path, IconSizeType sizeType, Size itemSize)
{
var shfi = new SHFILEINFO();
var imageList = SHGetFileInfoAsImageList(path, 0, ref shfi, (uint)Marshal.SizeOf(shfi), (int)SHGFI_SYSICONINDEX);
if (imageList != null)
{
var hIcon = IntPtr.Zero;
imageList.GetIcon(shfi.iIcon, (int)ILD_TRANSPARENT, ref hIcon);
Marshal.FinalReleaseComObject(imageList);
if (hIcon != IntPtr.Zero)
{
var image = Bitmap.FromHicon(hIcon);
DestroyIcon(hIcon);
return image;
}
}
return new Bitmap(itemSize.Width, itemSize.Height);
}
我的任务是使用任务获取目录图标并将它们显示在 DataGridView 中(我正在通过文件夹执行搜索)。为此,我使用 SHGetImageList WinAPI 函数。我有一个助手 class 如下:
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace WindowsFormsApplication6 {
public class Helper {
private const uint ILD_TRANSPARENT = 0x00000001;
private const uint SHGFI_SYSICONINDEX = 0x000004000;
private const uint SHGFI_ICON = 0x000000100;
public static readonly int MaxEntitiesCount = 80;
public static void GetDirectories(string path, List<Image> col, IconSizeType sizeType, Size itemSize) {
DirectoryInfo dirInfo = new DirectoryInfo(path);
DirectoryInfo[] dirs = dirInfo.GetDirectories("*", SearchOption.TopDirectoryOnly);
for (int i = 0; i < dirs.Length && i < MaxEntitiesCount; i++) {
DirectoryInfo subDirInfo = dirs[i];
if (!CheckAccess(subDirInfo) || !MatchFilter(subDirInfo.Attributes)) {
continue;
}
col.Add(GetFileImage(subDirInfo.FullName, sizeType, itemSize));
}
}
public static bool CheckAccess(DirectoryInfo info) {
bool isOk = false;
try {
var secInfo = info.GetAccessControl();
isOk = true;
}
catch {
}
return isOk;
}
public static bool MatchFilter(FileAttributes attributes) {
return (attributes & (FileAttributes.Hidden | FileAttributes.System)) == 0;
}
public static Image GetFileImage(string path, IconSizeType sizeType, Size itemSize) {
return IconToBitmap(GetFileIcon(path, sizeType, itemSize), sizeType, itemSize);
}
public static Image IconToBitmap(Icon ico, IconSizeType sizeType, Size itemSize) {
if (ico == null) {
return new Bitmap(itemSize.Width, itemSize.Height);
}
return ico.ToBitmap();
}
public static Icon GetFileIcon(string path, IconSizeType sizeType, Size itemSize) {
SHFILEINFO shinfo = new SHFILEINFO();
IntPtr retVal = SHGetFileInfo(path, 0, ref shinfo, (uint)Marshal.SizeOf(shinfo), (int)(SHGFI_SYSICONINDEX | SHGFI_ICON));
int iconIndex = shinfo.iIcon;
IImageList iImageList = (IImageList)GetSystemImageListHandle(sizeType);
IntPtr hIcon = IntPtr.Zero;
if (iImageList != null) {
iImageList.GetIcon(iconIndex, (int)ILD_TRANSPARENT, ref hIcon);
}
Icon icon = null;
if (hIcon != IntPtr.Zero) {
icon = Icon.FromHandle(hIcon).Clone() as Icon;
DestroyIcon(shinfo.hIcon);
}
return icon;
}
private static IImageList GetSystemImageListHandle(IconSizeType sizeType) {
IImageList iImageList = null;
Guid imageListGuid = new Guid("46EB5926-582E-4017-9FDF-E8998DAA0950");
int ret = SHGetImageList((int)sizeType, ref imageListGuid, ref iImageList);
return iImageList;
}
[DllImport("shell32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, uint cbFileInfo, uint uFlags);
[DllImport("shell32.dll", EntryPoint = "#727")]
private static extern int SHGetImageList(int iImageList, ref Guid riid, ref IImageList ppv);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool DestroyIcon(IntPtr hIcon);
public enum IconSizeType {
Medium = 0x0,
Small = 0x1,
Large = 0x2,
ExtraLarge = 0x4
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
private struct SHFILEINFO {
public IntPtr hIcon;
public int iIcon;
public uint dwAttributes;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string szDisplayName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
public string szTypeName;
}
[ComImport,
Guid("46EB5926-582E-4017-9FDF-E8998DAA0950"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IImageList {
[PreserveSig]
int Add(IntPtr hbmImage, IntPtr hbmMask, ref int pi);
[PreserveSig]
int ReplaceIcon(int i, IntPtr hicon, ref int pi);
[PreserveSig]
int SetOverlayImage(int iImage, int iOverlay);
[PreserveSig]
int Replace(int i, IntPtr hbmImage, IntPtr hbmMask);
[PreserveSig]
int AddMasked(IntPtr hbmImage, int crMask, ref int pi);
[PreserveSig]
int Draw(ref IMAGELISTDRAWPARAMS pimldp);
[PreserveSig]
int Remove(int i);
[PreserveSig]
int GetIcon(int i, int flags, ref IntPtr picon);
[PreserveSig]
int GetImageInfo(int i, ref IMAGEINFO pImageInfo);
[PreserveSig]
int Copy(int iDst, IImageList punkSrc, int iSrc, int uFlags);
[PreserveSig]
int Merge(int i1, IImageList punk2, int i2, int dx, int dy, ref Guid riid, ref IntPtr ppv);
[PreserveSig]
int Clone(ref Guid riid, ref IntPtr ppv);
[PreserveSig]
int GetImageRect(int i, ref RECT prc);
[PreserveSig]
int GetIconSize(ref int cx, ref int cy);
[PreserveSig]
int SetIconSize(int cx, int cy);
[PreserveSig]
int GetImageCount(ref int pi);
[PreserveSig]
int SetImageCount(int uNewCount);
[PreserveSig]
int SetBkColor(int clrBk, ref int pclr);
[PreserveSig]
int GetBkColor(ref int pclr);
[PreserveSig]
int BeginDrag(int iTrack, int dxHotspot, int dyHotspot);
[PreserveSig]
int EndDrag();
[PreserveSig]
int DragEnter(IntPtr hwndLock, int x, int y);
[PreserveSig]
int DragLeave(IntPtr hwndLock);
[PreserveSig]
int DragMove(int x, int y);
[PreserveSig]
int SetDragCursorImage(ref IImageList punk, int iDrag, int dxHotspot, int dyHotspot);
[PreserveSig]
int DragShowNolock(int fShow);
[PreserveSig]
int GetDragImage(ref POINT ppt, ref POINT pptHotspot, ref Guid riid, ref IntPtr ppv);
[PreserveSig]
int GetItemFlags(int i, ref int dwFlags);
[PreserveSig]
int GetOverlayImage(int iOverlay, ref int piIndex);
}
;
[StructLayout(LayoutKind.Sequential)]
private struct IMAGELISTDRAWPARAMS {
public int cbSize;
public IntPtr himl;
public int i;
public IntPtr hdcDst;
public int x;
public int y;
public int cx;
public int cy;
public int xBitmap;
public int yBitmap;
public int rgbBk;
public int rgbFg;
public int fStyle;
public int dwRop;
public int fState;
public int Frame;
public int crEffect;
}
[StructLayout(LayoutKind.Sequential)]
private struct IMAGEINFO {
public IntPtr hbmImage;
public IntPtr hbmMask;
public int Unused1;
public int Unused2;
public RECT rcImage;
}
[StructLayout(LayoutKind.Sequential)]
public struct RECT {
public int Left, Top, Right, Bottom;
public RECT(int l, int t, int r, int b) {
Left = l;
Top = t;
Right = r;
Bottom = b;
}
public RECT(Rectangle r) {
Left = r.Left;
Top = r.Top;
Right = r.Right;
Bottom = r.Bottom;
}
public Rectangle ToRectangle() {
return Rectangle.FromLTRB(Left, Top, Right, Bottom);
}
public void Inflate(int width, int height) {
Left -= width;
Top -= height;
Right += width;
Bottom += height;
}
public override string ToString() {
return string.Format("x:{0},y:{1},width:{2},height:{3}", Left, Top, Right - Left, Bottom - Top);
}
}
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
public struct POINT {
public int X, Y;
public POINT(int x, int y) {
this.X = x;
this.Y = y;
}
public POINT(Point pt) {
this.X = pt.X;
this.Y = pt.Y;
}
public Point ToPoint() {
return new Point(X, Y);
}
}
}
}
在一个窗体上,我有两个 DataGridView 和两个按钮。单击第一个按钮时,我在 UI 线程中加载图标:
private void button1_Click(object sender, EventArgs e) {
List<Image> list = new List<Image>();
Helper.GetDirectories(fPath, list, Helper.IconSizeType.Small, new Size(16, 16));
dataGridView1.DataSource = list;
}
在点击第二个按钮时,我做了:
private void button2_Click(object sender, EventArgs e) {
Func<object, List<Image>> a = null;
a = (p) => {
string path = (string)p;
List<Image> list = new List<Image>();
Helper.GetDirectories(path, list, Helper.IconSizeType.Small, new Size(16, 16));
return list;
};
Task.Factory.StartNew(a, fPath).ContinueWith(t => { dataGridView2.DataSource = t.Result;},
TaskScheduler.FromCurrentSynchronizationContext());
}
所以,我也这样做,但是在一个任务中。
当我点击第一个按钮然后点击第二个按钮时,我得到以下内容 System.InvalidCastException:
Unable to cast COM object of type 'System.__ComObject' to interface type 'IImageList'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{46EB5926-582E-4017-9FDF-E8998DAA0950}' failed due to the following error: No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE)).
在
中引发异常int ret = SHGetImageList((int)sizeType, ref imageListGuid, ref iImageList);
GetSystemImageListHandle 方法的行。
我不知道自己做错了什么。感谢任何帮助。
Windows shell 函数只能从 UI 线程调用。您有与此问题相同的根本问题:Program.Main()
.
[STAThread]
属性
Task.Run()
使用的线程池线程不会是单线程套间(会是多线程套间)。当您尝试访问线程单元类型中的 COM 对象时,您可能会收到 E_NOINTERFACE
错误。*
可以在单线程单元中手动创建新线程。然而,在这种情况下,这似乎确实可靠地工作。它仍然会偶尔抛出 E_NOINTERFACE
exce[topms.只需在 UI 线程上调用 SHGetImageList
。感谢@vendettamit 实际测试它并发现它不起作用。
可以创建 new thread manually that is in a single threaded apartment:
Thread thread = new Thread(ThreadStartMethod);
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
这可能适用于您的情况,但如果您尝试访问 IImageList
或不同线程上的任何 COM 对象,您将 运行 遇到问题**。由于您似乎不会进行跨线程调用,因此以下内容应该有效:
private void button2_Click(object sender, EventArgs e) {
ParameterizedThreadStart a = null;
a = (p) => {
string path = (string)p;
List<Image> list = new List<Image>();
Helper.GetDirectories(path, list, Helper.IconSizeType.Small, new Size(16, 16));
this.Invoke(new Action(() => dataGridView2.DataSource = list));
};
Thread thread = new Thread(a);
thread.SetApartmentState(ApartmentState.STA);
thread.Start(fPath);
}
* 一些 COM 接口支持跨单元编组,但 COM 编组是一个完全不同的主题。
** 因为 COM STA 封送处理的工作原理,您需要 Windows 消息泵 运行 在您的后台线程上运行。
Update: Chaning the [STAThread] to [MTAThread] on Main method, with the given code @Gosha_Fighten's problem everything seems to be working.
以下解决方案仅适用于 运行 在 [STAThread] 下的应用程序。
@shf301 解释的很好!但是答案中提供的示例代码无法解决问题。通过 SHGetImageList
调用跨线程构造函数导致了这个问题。因此,如果在 Worker 线程上调用了 Helper.GetDirectories
,则不会出现该问题,而不是调用 DataGrid 绑定语句。
以下代码运行良好:
private void button2_Click_1(object sender, EventArgs e)
{
Action a = null;
a = () =>
{
List<Image> list = null;
this.BeginInvoke(new Action(() =>
{
list = new List<Image>();
Helper.GetDirectories(fPath, list, Helper.IconSizeType.Small, new Size(16, 16));
dataGridView2.DataSource = list;
}));
};
Task.Factory.StartNew(a);
}
这里我删除了 ContinueWith()
并传递了 Current synchronization
参数。现在控件的调用方法将在工作线程上执行任务。
注意 - 由于所有工作都在 WorkerThread 上完成,因此无需保留 Task
。
.NET有SynchronizationContext的概念:
Provides the basic functionality for propagating a synchronization context in various synchronization models.
此上下文的主要方法名为 Post
,它主要是向上下文发送异步消息。在这里,根据底层 UI 技术(Winforms、WPF 等)或非 UI 技术,可以调整事物并在这些技术的限制下优雅地工作。
默认情况下,Tasks 的任务调度程序不使用当前同步上下文,而是使用您无法真正控制的 ThreadPool
(顺便说一下,它不使用 Winforms,也不玩 WPF),所以你必须指定你想要来自 SynchronizationContext 的 TaskScheduler,你只是部分地做了。
由于您是 运行 Winforms 应用程序,因此当前的同步上下文(Synchronization.Current) should be of WindowsFormsSynchronizationContext 类型。如果您可以查看其 Post 的实现,您将看到此:
public override void Post(SendOrPostCallback callback, object state)
{
if (controlToSendTo != null)
{
controlToSendTo.BeginInvoke(callback, new object[] { state });
}
}
这个上下文的实现应该可以很好地与 Winforms UI 线程一起工作......只要你使用它。事实上你几乎做对了,你只是忘了在 StartNew
方法中使用它。
所以,只需将您的代码更改为:
Task.Factory.StartNew(a, fPath,
CancellationToken.None, TaskCreationOptions.None, // unfortunately, there is no easier overload with just the context...
TaskScheduler.FromCurrentSynchronizationContext()).ContinueWith(
t => { dataGridView2.DataSource = t.Result; },
TaskScheduler.FromCurrentSynchronizationContext());
它应该可以工作。
只需插入
Marshal.FinalReleaseComObject(iImageList);
之后
iImageList.GetIcon(iconIndex, (int)ILD_TRANSPARENT, ref hIcon);
行。
此外,您可能感兴趣的是,当您传递 SHGFI_SYSICONINDEX 时,SHGetFileInfo 实际上是 returns IImageList。所以,这样的事情会起作用:
[DllImport("shell32.dll", EntryPoint = "SHGetFileInfo", CharSet = CharSet.Auto)]
private static extern IImageList SHGetFileInfoAsImageList(string pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, uint cbFileInfo, uint uFlags);
整个图像提取可以很简单:
public static Image GetFileImage(string path, IconSizeType sizeType, Size itemSize)
{
var shfi = new SHFILEINFO();
var imageList = SHGetFileInfoAsImageList(path, 0, ref shfi, (uint)Marshal.SizeOf(shfi), (int)SHGFI_SYSICONINDEX);
if (imageList != null)
{
var hIcon = IntPtr.Zero;
imageList.GetIcon(shfi.iIcon, (int)ILD_TRANSPARENT, ref hIcon);
Marshal.FinalReleaseComObject(imageList);
if (hIcon != IntPtr.Zero)
{
var image = Bitmap.FromHicon(hIcon);
DestroyIcon(hIcon);
return image;
}
}
return new Bitmap(itemSize.Width, itemSize.Height);
}