在.NET 4.0的应用程序(WPF),我们正在使用SHGetFileInfo
,以获得一个目录树壳图标。 因为这需要在某些情况下,相当长的一段时间(即一个网络驱动器无法访问或软盘驱动器),我们想要做这一个线程,然后当它在被读取更新的图标。
呼叫基本上是相同的,它现在只是一个线程中执行。 因为有人说,线程必须STA
这个工作,我们使用了线程,而不是线程池进行测试,以相同的结果。 使用ThreadPool
也没有工作。
SHGetFileInfo
成功(返回1),但在该结构中HICON构件是零。
IntPtr GetIcon(string name)
{
Shell32.SHFILEINFO shfi = new Shell32.SHFILEINFO();
uint flags = Shell32.SHGFI_ICON | Shell32.SHGFI_USEFILEATTRIBUTES | Shell32.SHGFI_SMALLICON;
Shell32.SHGetFileInfo(
name, System.IO.Directory.Exists(name) ? Shell32.FILE_ATTRIBUTE_DIRECTORY : Shell32.FILE_ATTRIBUTE_NORMAL,
ref shfi,
(uint) System.Runtime.InteropServices.Marshal.SizeOf(shfi),
flags );
return shfi.hIcon;
}
非常相同的代码从GUI线程工作正常。 什么必须做,从一个单独的线程使函数的工作,或者,然而,使其工作,而不会阻塞GUI线程?
更新:解决这个问题的代码基本上是这样的:
var thread = new System.Threading.Thread(() => {
var result = GetIcon("C:\\");
// ... do something with the result
});
thread.SetApartmentState(System.Threading.ApartmentState.STA);
thread.Start();
如果只有线程委托中的线留在,它工作正常(但GUI线程,当然)。
更新:现在,我们只需调用调用SHGetFileInfo
,使其工作。 这有(直到所有的图标都被加载并没有显示与文件视图页面)最初的问题已经解决了优势,但它意味着页面挂起每个图标。 但至少现在用户看到的东西是怎么回事。 我们仍然在寻找一个实际的解决问题的办法。
我不认为有任何问题。 你并不需要使用SetApartmentState。 根据该文件 ,你确实需要呼吁的CoInitialize或OleInitialize,但我认为这应该是由WPF反正叫你。
我创建了下面一个简单的WPF应用程序。 这工作得很好。 运行的SHGetFileInfo在不同的线程到UI线程和shfi.hIcon不为零。
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);
Task<IntPtr> task = Task.Factory.StartNew(() => GetIcon("C:\\"));
}
private IntPtr GetIcon(string name)
{
Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);
var shfi = new Shell32.SHFILEINFO();
uint flags = Shell32.SHGFI_ICON | Shell32.SHGFI_USEFILEATTRIBUTES | Shell32.SHGFI_SMALLICON;
Shell32.SHGetFileInfo(
name,
Directory.Exists(name) ? Shell32.FILE_ATTRIBUTE_DIRECTORY : Shell32.FILE_ATTRIBUTE_NORMAL,
ref shfi,
(uint) Marshal.SizeOf(shfi),
flags);
Debug.WriteLine(shfi.hIcon);
return shfi.hIcon;
}
}
public class Shell32
{
public const int MAX_PATH = 256;
// Browsing for directory.
public const uint BIF_RETURNONLYFSDIRS = 0x0001;
public const uint BIF_DONTGOBELOWDOMAIN = 0x0002;
public const uint BIF_STATUSTEXT = 0x0004;
public const uint BIF_RETURNFSANCESTORS = 0x0008;
public const uint BIF_EDITBOX = 0x0010;
public const uint BIF_VALIDATE = 0x0020;
public const uint BIF_NEWDIALOGSTYLE = 0x0040;
public const uint BIF_USENEWUI = (BIF_NEWDIALOGSTYLE | BIF_EDITBOX);
public const uint BIF_BROWSEINCLUDEURLS = 0x0080;
public const uint BIF_BROWSEFORCOMPUTER = 0x1000;
public const uint BIF_BROWSEFORPRINTER = 0x2000;
public const uint BIF_BROWSEINCLUDEFILES = 0x4000;
public const uint BIF_SHAREABLE = 0x8000;
public const uint SHGFI_ICON = 0x000000100; // get icon
public const uint SHGFI_DISPLAYNAME = 0x000000200; // get display name
public const uint SHGFI_TYPENAME = 0x000000400; // get type name
public const uint SHGFI_ATTRIBUTES = 0x000000800; // get attributes
public const uint SHGFI_ICONLOCATION = 0x000001000; // get icon location
public const uint SHGFI_EXETYPE = 0x000002000; // return exe type
public const uint SHGFI_SYSICONINDEX = 0x000004000; // get system icon index
public const uint SHGFI_LINKOVERLAY = 0x000008000; // put a link overlay on icon
public const uint SHGFI_SELECTED = 0x000010000; // show icon in selected state
public const uint SHGFI_ATTR_SPECIFIED = 0x000020000; // get only specified attributes
public const uint SHGFI_LARGEICON = 0x000000000; // get large icon
public const uint SHGFI_SMALLICON = 0x000000001; // get small icon
public const uint SHGFI_OPENICON = 0x000000002; // get open icon
public const uint SHGFI_SHELLICONSIZE = 0x000000004; // get shell size icon
public const uint SHGFI_PIDL = 0x000000008; // pszPath is a pidl
public const uint SHGFI_USEFILEATTRIBUTES = 0x000000010; // use passed dwFileAttribute
public const uint SHGFI_ADDOVERLAYS = 0x000000020; // apply the appropriate overlays
public const uint SHGFI_OVERLAYINDEX = 0x000000040; // Get the index of the overlay
public const uint FILE_ATTRIBUTE_DIRECTORY = 0x00000010;
public const uint FILE_ATTRIBUTE_NORMAL = 0x00000080;
[DllImport("Shell32.dll")]
public static extern IntPtr SHGetFileInfo(
string pszPath,
uint dwFileAttributes,
ref SHFILEINFO psfi,
uint cbFileInfo,
uint uFlags
);
#region Nested type: BROWSEINFO
[StructLayout(LayoutKind.Sequential)]
public struct BROWSEINFO
{
public IntPtr hwndOwner;
public IntPtr pidlRoot;
public IntPtr pszDisplayName;
[MarshalAs(UnmanagedType.LPTStr)] public string lpszTitle;
public uint ulFlags;
public IntPtr lpfn;
public int lParam;
public IntPtr iImage;
}
#endregion
#region Nested type: ITEMIDLIST
[StructLayout(LayoutKind.Sequential)]
public struct ITEMIDLIST
{
public SHITEMID mkid;
}
#endregion
#region Nested type: SHFILEINFO
[StructLayout(LayoutKind.Sequential)]
public struct SHFILEINFO
{
public const int NAMESIZE = 80;
public IntPtr hIcon;
public int iIcon;
public uint dwAttributes;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_PATH)] public string szDisplayName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = NAMESIZE)] public string szTypeName;
};
#endregion
#region Nested type: SHITEMID
[StructLayout(LayoutKind.Sequential)]
public struct SHITEMID
{
public ushort cb;
[MarshalAs(UnmanagedType.LPArray)] public byte[] abID;
}
#endregion
}
/// <summary>
/// Wraps necessary functions imported from User32.dll. Code courtesy of MSDN Cold Rooster Consulting example.
/// </summary>
public class User32
{
/// <summary>
/// Provides access to function required to delete handle. This method is used internally
/// and is not required to be called separately.
/// </summary>
/// <param name="hIcon">Pointer to icon handle.</param>
/// <returns>N/A</returns>
[DllImport("User32.dll")]
public static extern int DestroyIcon(IntPtr hIcon);
}
刚刚成功地得到的东西就是这样的工作。 当Visual Studio之外运行它此前被撞毁严重。 在此之前,我们会经常被找回的默认图标,而不是正确的文件类型(因为我们的文件图标后,未在目录图标)。
的因素汇总考虑。
- 如已经讨论的,使用Shell相互作用需要使用STA螺纹,其泵的消息。 一个BackgroundWorker不会足够在这里。
- 当你SHFILEINFO初始化, 设置这两个字符串属性(显示名称和类型名)的String.Empty。 这不是大多数样品中显示,但未能做到这一点是导致飞机坠毁的美国。 (在调试模式下,它意味着,我们得到的第一个图标后面是错误的,这可能是同你的问题。)
- 检查互操作的声明是正确的。 例如,
SHFILEINFO
类应该大概有[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 4)]
属性。
一的TaskScheduler后台任务在STA线程
我们使用任务并行库,所以想要一个的TaskScheduler,将安排在合适的后台线程的工作。 下面的代码示例是用于暴露可以用于此一的TaskScheduler属性的类。
请注意,在我们的,我们有这个类,持续应用的整个生命周期的一个实例,所以我们没有实现IDisposable的。 如果你想创建/销毁这些你需要处理的。
namespace MyNamespace
{
using System;
using System.ComponentModel.Composition;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
/// <summary>
/// Exposes a <see cref="TaskScheduler"/> that schedules its work on a STA background thread.
/// </summary>
[Export]
public class StaTaskSchedulerSource
{
/// <summary>
/// A window that is used for message pumping.
/// </summary>
private Window window;
/// <summary>
/// Thread on which work is scheduled.
/// </summary>
private Thread thread;
/// <summary>
/// The <see cref="TaskScheduler"/> exposed by this class.
/// </summary>
private TaskScheduler taskScheduler;
/// <summary>
/// Initializes a new instance of the <see cref="StaTaskSchedulerSource"/> class.
/// </summary>
public StaTaskSchedulerSource()
{
using (ManualResetEvent re = new ManualResetEvent(false))
{
this.thread = new Thread(
() =>
{
this.window = new Window();
re.Set();
Dispatcher.Run();
});
this.thread.IsBackground = true;
this.thread.SetApartmentState(ApartmentState.STA);
this.thread.Start();
re.WaitOne();
}
this.window.Dispatcher.Invoke(
new Action(
() =>
{
this.taskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
}));
}
/// <summary>
/// Gets a <see cref="TaskScheduler"/> that schedules work on a background STA
/// thread.
/// </summary>
public TaskScheduler TaskScheduler
{
get
{
return this.taskScheduler;
}
}
}
}
当然,这一切都只是包装用调度调用另一个线程的调用。 这可能是一个有点慢。 因此,如果处理许多图标,这将是更好批量起来。 此外,我们缓存我们已经获取的,所以我们并不需要周围使用赢壳牌在所有第二次的图标。
SafeIconHandle
您也可以找到一个图标下面的包装处理非常有用。 它派生自SafeHandle的 ,并确保您图标在所有情况下正确销毁。
namespace UnmanagedResourceLib
{
using System;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
using Microsoft.Win32.SafeHandles;
/// <summary>
/// A <see cref="SafeHandle"/> implementation for HICON icon handles.
/// </summary>
[SecurityPermission(SecurityAction.InheritanceDemand, UnmanagedCode = true)]
[SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
internal class SafeIconHandle : SafeHandleZeroOrMinusOneIsInvalid
{
/// <summary>
/// Prevents a default instance of the <see cref="SafeIconHandle"/> class from being created.
/// </summary>
private SafeIconHandle()
: base(true)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="SafeIconHandle"/> class.
/// </summary>
/// <param name="nativeHandle">The HICON to wrap.</param>
/// <param name="ownsHandle"><c>true</c> if finalization of this object should cause the icon to be destroyed.</param>
public SafeIconHandle(IntPtr nativeHandle, bool ownsHandle)
: base(ownsHandle)
{
this.handle = nativeHandle;
}
/// <inheritdoc />
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
override protected bool ReleaseHandle()
{
return NativeMethods.DestroyIcon(this.handle);
}
/// <summary>
/// Exposes Windows API call to destroy an icon.
/// </summary>
[SuppressUnmanagedCodeSecurity]
internal static class NativeMethods
{
/// <summary>
/// Destroys an icon and frees any memory the icon occupied.
/// </summary>
/// <param name="hIcon">A handle to the icon to be destroyed.</param>
/// <returns><c>true</c> if the function succeeds.</returns>
[System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)]
public static extern bool DestroyIcon(IntPtr hIcon);
}
}
}