From 81ff24990c8edd573b2cb3d8c447404f61cbfdb3 Mon Sep 17 00:00:00 2001 From: dahall Date: Thu, 3 Dec 2020 22:22:38 -0700 Subject: [PATCH] Fixes to ShellUtil based on testing --- PInvoke/Shell32/ShObjIdl.ShellUtil.cs | 142 +++++++++++++++++-------- UnitTests/PInvoke/Shell32/ShellUtilTests.cs | 158 ++++++++++++++++++++++++++++ 2 files changed, 255 insertions(+), 45 deletions(-) create mode 100644 UnitTests/PInvoke/Shell32/ShellUtilTests.cs diff --git a/PInvoke/Shell32/ShObjIdl.ShellUtil.cs b/PInvoke/Shell32/ShObjIdl.ShellUtil.cs index bd92b503..510c4baa 100644 --- a/PInvoke/Shell32/ShObjIdl.ShellUtil.cs +++ b/PInvoke/Shell32/ShObjIdl.ShellUtil.cs @@ -69,6 +69,11 @@ namespace Vanara.PInvoke return GetKnownFolderFromGuid(ikfm.FindFolderFromPath(path, FFFP_MODE.FFFP_EXACTMATCH).GetId()); } + /// Gets the parent and item for the supplied IShellItem regardless of support by IShellItem. + /// The IShellItem instance. + /// An IParentAndItem reference for the shell item. + public static IParentAndItem GetParentAndItem(IShellItem psi) => psi is IParentAndItem pi ? pi : new ManualParentAndItem(psi); + /// Gets the path for a KNOWNFOLDERID Guid. /// The KNOWNFOLDERID Guid. /// The file system path. @@ -126,8 +131,8 @@ namespace Vanara.PInvoke /// Gets the icon for the item using the specified characteristics. /// The IShellFolder from which to request the IExtractIcon instance. /// The PIDL of the item within . - /// The width, in pixels, of the icon. - /// The resulting icon handle, on success, or null on failure. + /// The width, in pixels, of the icon. + /// The resulting icon handle, on success, or null on failure. /// The result of function. public static HRESULT LoadIconFromExtractIcon(IShellFolder psf, PIDL pidl, ref uint imgSz, out SafeHICON hico) { @@ -160,13 +165,13 @@ namespace Vanara.PInvoke /// Gets the icon for the item using the specified characteristics. /// The IExtractIconW from which to retrieve the icon. - /// The width, in pixels, of the icon. - /// The resulting icon handle, on success, or null on failure. + /// The width, in pixels, of the icon. + /// The resulting icon handle, on success, or null on failure. /// The result of function. public static HRESULT LoadIconFromExtractIcon(IExtractIconW iei, ref uint imgSz, out SafeHICON hico) { var szIconFile = new StringBuilder(Kernel32.MAX_PATH); - var hr = iei.GetIconLocation(GetIconLocationFlags.GIL_FORSHELL, szIconFile, szIconFile.Capacity, out var iIdx, out var flags); + var hr = iei.GetIconLocation(GetIconLocationFlags.GIL_FORSHELL, szIconFile, szIconFile.Capacity, out var iIdx, out _); if (hr.Succeeded) { if (szIconFile.ToString() != "*") @@ -187,7 +192,7 @@ namespace Vanara.PInvoke public static HRESULT LoadIconFromExtractIcon(IExtractIconA iei, ref uint imgSz, out SafeHICON hico) { var szIconFile = new StringBuilder(Kernel32.MAX_PATH); - var hr = iei.GetIconLocation(GetIconLocationFlags.GIL_FORSHELL, szIconFile, szIconFile.Capacity, out var iIdx, out var flags); + var hr = iei.GetIconLocation(GetIconLocationFlags.GIL_FORSHELL, szIconFile, szIconFile.Capacity, out var iIdx, out _); if (hr.Succeeded) { if (szIconFile.ToString() != "*") @@ -217,7 +222,7 @@ namespace Vanara.PInvoke if (GetIconInfo(hico, icoInfo)) imgSz = (uint)GetObject(icoInfo.hbmColor).bmWidth; } - catch (System.Runtime.InteropServices.COMException e) + catch (COMException e) { hico = null; return (HRESULT)e.ErrorCode; @@ -232,6 +237,35 @@ namespace Vanara.PInvoke return hr; } + /// Gets the image for the item using the specified characteristics. + /// The IShellFolder from which to request the IExtractImage instance. + /// The PIDL of the item within . + /// The width, in pixels, of the Bitmap. + /// The resulting Bitmap, on success, or null on failure. + /// The result of function. + public static HRESULT LoadImageFromExtractImage(IShellFolder psf, PIDL pidl, ref uint imgSz, out SafeHBITMAP hbmp) + { + HRESULT hr = psf.GetUIObjectOf((IntPtr)pidl, out var iei); + hbmp = default; + if (hr.Succeeded) + { + try + { + var szIconFile = new StringBuilder(Kernel32.MAX_PATH); + var sz = new SIZE((int)imgSz, (int)imgSz); + IEIFLAG flags = 0; + if ((hr = iei.GetLocation(szIconFile, (uint)szIconFile.Capacity, default, ref sz, 0, ref flags)).Succeeded && (hr = iei.Extract(out hbmp)).Succeeded) + imgSz = (uint)sz.cx; + return hr; + } + finally + { + Marshal.ReleaseComObject(iei); + } + } + return hr; + } + /// Gets the thumbnail image for the item using the specified characteristics. /// The IShellItem from which to request the IThumbnailProvider instance. /// The width, in pixels, of the Bitmap. @@ -241,13 +275,13 @@ namespace Vanara.PInvoke { try { - var itp = psi.BindToHandler(ShellUtil.CreateBindCtx(), BHID.BHID_ThumbnailHandler); + var itp = psi.BindToHandler(null, BHID.BHID_ThumbnailHandler); return LoadImageFromThumbnailProvider(itp, ref imgSz, out hbmp); } - catch (System.Runtime.InteropServices.COMException e) + catch (COMException e) { hbmp = null; - return (HRESULT)e.ErrorCode; + return e.ErrorCode; } } @@ -287,35 +321,6 @@ namespace Vanara.PInvoke } } - /// Gets the image for the item using the specified characteristics. - /// The IShellFolder from which to request the IExtractImage instance. - /// The PIDL of the item within . - /// The width, in pixels, of the Bitmap. - /// The resulting Bitmap, on success, or null on failure. - /// The result of function. - public static HRESULT LoadImageFromExtractImage(IShellFolder psf, PIDL pidl, ref uint imgSz, out SafeHBITMAP hbmp) - { - HRESULT hr = psf.GetUIObjectOf((IntPtr)pidl, out var iei); - hbmp = default; - if (hr.Succeeded) - { - try - { - var szIconFile = new StringBuilder(Kernel32.MAX_PATH); - var sz = new SIZE((int)imgSz, (int)imgSz); - IEIFLAG flags = 0; - if ((hr = iei.GetLocation(szIconFile, (uint)szIconFile.Capacity, default, ref sz, 0, ref flags)).Succeeded && (hr = iei.Extract(out hbmp)).Succeeded) - imgSz = (uint)sz.cx; - return hr; - } - finally - { - Marshal.ReleaseComObject(iei); - } - } - return hr; - } - /// Given a pixel size, return the ShellImageSize value with the closest size. /// Size, in pixels, of the image list size to search for. /// An image list size. @@ -358,11 +363,19 @@ namespace Vanara.PInvoke private WIN32_FIND_DATA fd; private long fileId; - public IntFileSysBindData() { } + public IntFileSysBindData() + { + } - public HRESULT GetFileID(out long pliFileID) { pliFileID = fileId; return HRESULT.S_OK; } + public HRESULT GetFileID(out long pliFileID) + { + pliFileID = fileId; return HRESULT.S_OK; + } - public HRESULT GetFindData(out WIN32_FIND_DATA pfd) { pfd = fd; return HRESULT.S_OK; } + public HRESULT GetFindData(out WIN32_FIND_DATA pfd) + { + pfd = fd; return HRESULT.S_OK; + } public HRESULT GetJunctionCLSID(out Guid pclsid) { @@ -375,11 +388,50 @@ namespace Vanara.PInvoke return HRESULT.E_FAIL; } - public HRESULT SetFileID(long liFileID) { fileId = liFileID; return HRESULT.S_OK; } + public HRESULT SetFileID(long liFileID) + { + fileId = liFileID; return HRESULT.S_OK; + } - public HRESULT SetFindData(in WIN32_FIND_DATA pfd) { fd = pfd; return HRESULT.S_OK; } + public HRESULT SetFindData(in WIN32_FIND_DATA pfd) + { + fd = pfd; return HRESULT.S_OK; + } - public HRESULT SetJunctionCLSID(in Guid clsid) { clsidJunction = clsid; return HRESULT.S_OK; } + public HRESULT SetJunctionCLSID(in Guid clsid) + { + clsidJunction = clsid; return HRESULT.S_OK; + } + } + + private class ManualParentAndItem : IParentAndItem, IDisposable + { + private readonly PIDL pChild; + private readonly IShellFolder psf; + + public ManualParentAndItem(IShellItem psi) + { + psf = psi.BindToHandler(null, BHID.BHID_SFObject); + SHGetIDListFromObject(psi, out var pItem).ThrowIfFailed(); + pChild = pItem.LastId; + pItem.Dispose(); + } + + void IDisposable.Dispose() + { + Marshal.ReleaseComObject(psf); + pChild.Dispose(); + } + + HRESULT IParentAndItem.GetParentAndItem(out PIDL ppidlParent, out IShellFolder ppsf, out PIDL ppidlChild) + { + SHGetIDListFromObject(psf, out ppidlParent).ThrowIfFailed(); + ppsf = psf; + ppidlChild = new PIDL(pChild.GetBytes()); + return HRESULT.S_OK; + } + + HRESULT IParentAndItem.SetParentAndItem([In] PIDL pidlParent, [In] IShellFolder psf, [In] PIDL pidlChild) => HRESULT.E_NOTIMPL; } } } diff --git a/UnitTests/PInvoke/Shell32/ShellUtilTests.cs b/UnitTests/PInvoke/Shell32/ShellUtilTests.cs new file mode 100644 index 00000000..fcb38b85 --- /dev/null +++ b/UnitTests/PInvoke/Shell32/ShellUtilTests.cs @@ -0,0 +1,158 @@ +using NUnit.Framework; +using System; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using Vanara.InteropServices; +using static Vanara.PInvoke.Shell32; +using static Vanara.PInvoke.Shell32.ShellUtil; + +namespace Vanara.PInvoke.Tests +{ + [TestFixture()] + public class ShellUtilTests + { + [Test] + public void GetKnownFolderFromGuidTest() + { + const KNOWNFOLDERID id = KNOWNFOLDERID.FOLDERID_Desktop; + Assert.That(GetKnownFolderFromGuid(id.Guid()), Is.EqualTo(id)); + } + + [Test] + public void GetKnownFolderFromPathTest() + { + const KNOWNFOLDERID id = KNOWNFOLDERID.FOLDERID_Desktop; + Assert.That(GetKnownFolderFromPath(id.FullPath()), Is.EqualTo(id)); + } + + [Test] + public void GetPathForKnownFolderTest() + { + const KNOWNFOLDERID id = KNOWNFOLDERID.FOLDERID_Desktop; + Assert.That(GetPathForKnownFolder(id.Guid()), Is.EqualTo(id.FullPath())); + } + + [Test] + public void GetPathFromShellItemTest() + { + IShellItem shi = null; + Assert.That(() => shi = GetShellItemForPath(TestCaseSources.SmallFile), Throws.Nothing); + try + { + Assert.NotNull(shi); + Assert.AreEqual(GetPathFromShellItem(shi), TestCaseSources.SmallFile); + } + finally + { + Marshal.ReleaseComObject(shi); + } + } + + [Test] + public void SHILPixelConvTest() + { + Assert.That(SHILToPixels(SHIL.SHIL_SMALL), Is.EqualTo(16)); + Assert.That(SHILToPixels(SHIL.SHIL_LARGE), Is.EqualTo(32)); + Assert.That(SHILToPixels(SHIL.SHIL_EXTRALARGE), Is.EqualTo(48)); + Assert.That(SHILToPixels(SHIL.SHIL_JUMBO), Is.EqualTo(256)); + + Assert.That(PixelsToSHIL(16), Is.EqualTo(SHIL.SHIL_SMALL).Or.EqualTo(SHIL.SHIL_SYSSMALL)); + Assert.That(PixelsToSHIL(32), Is.EqualTo(SHIL.SHIL_LARGE)); + Assert.That(PixelsToSHIL(48), Is.EqualTo(SHIL.SHIL_EXTRALARGE)); + Assert.That(PixelsToSHIL(256), Is.EqualTo(SHIL.SHIL_JUMBO)); + } + + [Test] + public void LoadIconFromExtractIconTest() + { + IShellItem shi = null; + Assert.That(() => shi = GetShellItemForPath(TestCaseSources.WordDoc), Throws.Nothing); + try + { + var pi = GetParentAndItem(shi); + pi.GetParentAndItem(out _, out var psf, out var pChild); + uint sz = 32; + Assert.That(LoadIconFromExtractIcon(psf, pChild, ref sz, out var hIcon), ResultIs.Successful); + Assert.IsFalse(hIcon.IsInvalid); + Assert.That(sz, Is.EqualTo(32)); + hIcon.Dispose(); + } + finally + { + Marshal.ReleaseComObject(shi); + } + } + + [Test] + public void LoadImageFromExtractImageTest() + { + IShellItem shi = null; + Assert.That(() => shi = GetShellItemForPath(TestCaseSources.ImageFile), Throws.Nothing); + try + { + var pi = GetParentAndItem(shi); + pi.GetParentAndItem(out _, out var psf, out var pChild); + uint sz = 32; + Assert.That(LoadImageFromExtractImage(psf, pChild, ref sz, out var hBmp), ResultIs.Successful); + Assert.IsFalse(hBmp.IsInvalid); + Assert.That(sz, Is.EqualTo(32)); + hBmp.Dispose(); + } + finally + { + Marshal.ReleaseComObject(shi); + } + } + + [Test] + public void LoadImageFromThumbnailProviderTest() + { + IShellItem shi = null; + Assert.That(() => shi = GetShellItemForPath(TestCaseSources.ImageFile), Throws.Nothing); + try + { + uint sz = 32; + Assert.That(LoadImageFromThumbnailProvider(shi, ref sz, out var hBmp), ResultIs.Successful); + Assert.IsFalse(hBmp.IsInvalid); + Assert.That(sz, Is.EqualTo(32)); + hBmp.Dispose(); + } + finally + { + Marshal.ReleaseComObject(shi); + } + } + + [Test] + public void LoadImageFromThumbnailProviderTest2() + { + IShellItem shi = null; + Assert.That(() => shi = GetShellItemForPath(TestCaseSources.ImageFile), Throws.Nothing); + try + { + var pi = GetParentAndItem(shi); + pi.GetParentAndItem(out _, out var psf, out var pChild); + uint sz = 32; + Assert.That(LoadImageFromThumbnailProvider(psf, pChild, ref sz, out var hBmp), ResultIs.Successful); + Assert.IsFalse(hBmp.IsInvalid); + Assert.That(sz, Is.EqualTo(32)); + hBmp.Dispose(); + } + finally + { + Marshal.ReleaseComObject(shi); + } + } + + [Test] + public void LoadIconFromSystemImageListTest() + { + uint sz = 32; + Assert.That(LoadIconFromSystemImageList(2, ref sz, out var hIcon), ResultIs.Successful); + Assert.IsFalse(hIcon.IsInvalid); + Assert.That(sz, Is.EqualTo(32)); + hIcon.Dispose(); + } + } +} \ No newline at end of file