Skip to content

Commit 6568509

Browse files
author
dahall
committed
Added to and adjusted unit tests for all new changes
1 parent a2a68b7 commit 6568509

File tree

12 files changed

+699
-189
lines changed

12 files changed

+699
-189
lines changed
Lines changed: 201 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using NUnit.Framework;
2+
using System.Collections.Generic;
23
using System.Linq;
34
using static Vanara.PInvoke.Shell32;
45
using static Vanara.PInvoke.User32;
@@ -8,74 +9,241 @@ namespace Vanara.PInvoke.Tests;
89
[TestFixture]
910
public class ContextMenuTests
1011
{
12+
const uint m_CmdFirst = 1;
13+
14+
private static IEnumerable<TestCaseData> CreateSources()
15+
{
16+
var shi = TestCaseSources.ImageFile;
17+
(string? f, string[] i)[] items =
18+
[
19+
(null, []), // Desktop
20+
(null, [TestCaseSources.TempDir]), // Folder
21+
(null, [shi]), // Single file
22+
(null, [shi, TestCaseSources.Image2File]), // Multiple files, same parent
23+
(null, [shi, System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "notepad.exe")]), // Multiple files, different parents
24+
(TestCaseSources.TempDir, []), // Folder as parent
25+
(TestCaseSources.TempDir, [TestCaseSources.TempDir]), // Folder
26+
(TestCaseSources.TempDir, [shi]), // Single file
27+
(TestCaseSources.TempDir, [shi, TestCaseSources.Image2File]), // Multiple files, same parent
28+
(@"C:\", [shi, System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "notepad.exe")]), // Multiple files, different parents
29+
];
30+
foreach ((string? f, string[] i) s in items)
31+
{
32+
foreach (var e in Enum.GetValues<CMF>())
33+
{
34+
yield return new TestCaseData(e, s.f is null ? null : MakeFolder(s.f!), Array.ConvertAll(s.i, i => SHCreateItemFromParsingName<IShellItem>(i)))
35+
.SetArgDisplayNames(e.ToString(), s.f is null ? "null" : System.IO.Path.GetFileName(s.f), $"[{string.Join(",", s.i.Select(i => System.IO.Path.GetFileName(i)))}]");
36+
}
37+
}
38+
39+
static IShellFolder MakeFolder(string path)
40+
{
41+
SHCreateItemHandlerFromParsingName(path, out IShellFolder? ppv, BHID.BHID_SFObject).ThrowIfFailed();
42+
return ppv!;
43+
}
44+
}
45+
46+
[Test]
47+
public void SHCreateDefaultContextMenuTest([Values] CMF cmf)
48+
{
49+
Assert.That(SHParseDisplayName(TestCaseSources.ImageFile, default, out var pidlChild, 0, out _), ResultIs.Successful);
50+
Assert.That(SHParseDisplayName(System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "notepad.exe"), default, out var pidlChild2, 0, out _), ResultIs.Successful);
51+
Assert.That(SHParseDisplayName(System.IO.Path.GetPathRoot(TestCaseSources.TempDir)!, default, out var pidlFolder, 0, out _), ResultIs.Successful);
52+
Assert.That(SHBindToObject(null, pidlFolder, null, out IShellFolder? pshf), ResultIs.Successful);
53+
//Assert.That(SHGetDesktopFolder(out IShellFolder? pContainingFolder), ResultIs.Successful);
54+
55+
Assert.That(SHCreateDefaultContextMenu(new DEFCONTEXTMENU(pshf!, [pidlChild, pidlChild2], null, out _), out IContextMenu3? pcm), ResultIs.Successful);
56+
Assert.That(pcm, Is.Not.Null);
57+
58+
using var hmenu = CreatePopupMenu();
59+
HRESULT hr;
60+
Assert.That(hr = pcm!.QueryContextMenu(hmenu, 0, m_CmdFirst, 0x7FFF, cmf), ResultIs.Successful);
61+
TestContext.WriteLine($"Menu items: {hr.Code}");
62+
Assert.That(hr.Code, Is.GreaterThan(0));
63+
var miis = MenuItemInfo.GetMenuItems(hmenu, pcm);
64+
for (int i = 0; i < miis.Length; i++)
65+
ShowMII(miis[i], i);
66+
}
67+
68+
[TestCaseSource(nameof(CreateSources))]
69+
public void SHCreateDefaultContextMenuTest2(CMF cmf, IShellFolder? folder, IShellItem[] items)
70+
{
71+
var pcm = SHCreateDefaultContextMenuEx(folder, out _, items);
72+
Assert.That(pcm, Is.Not.Null);
73+
74+
using var hmenu = CreatePopupMenu();
75+
HRESULT hr;
76+
Assert.That(hr = pcm!.QueryContextMenu(hmenu, 0, m_CmdFirst, 0x7FFF, cmf), ResultIs.Successful);
77+
TestContext.WriteLine($"Menu items: {hr.Code}");
78+
Assert.That(hr.Code, Is.GreaterThan(0));
79+
var miis = MenuItemInfo.GetMenuItems(hmenu, pcm);
80+
for (int i = 0; i < miis.Length; i++)
81+
ShowMII(miis[i], i);
82+
}
83+
1184
[Test]
12-
public void QueryTest([Values] CMF cmf)
85+
public void QueryFolderTest([Values] CMF cmf)
1386
{
14-
var pshi = SHCreateItemFromParsingName<IShellItem>(TestCaseSources.WordDoc);
87+
var pshi = SHCreateItemFromParsingName<IShellItem>(TestCaseSources.TempDir);
1588
Assert.That(pshi, Is.Not.Null);
16-
var pcm = pshi!.BindToHandler<IContextMenu>(null, BHID.BHID_SFUIObject.Guid());
89+
var pshf = pshi!.BindToHandler<IShellFolder>(null, BHID.BHID_SFObject);
90+
Assert.That(pshi, Is.Not.Null);
91+
var pcm = pshf.CreateViewObject<IContextMenu>(HWND.NULL)!;
1792
using var hmenu = CreatePopupMenu();
18-
Assert.That(pcm.QueryContextMenu(hmenu, 0, 1, int.MaxValue, cmf), ResultIs.Successful);
19-
var miis = MenuItemInfo.GetMenuItems(hmenu);
20-
using var memstr = new SafeCoTaskMemString(1024, CharSet.Ansi);
93+
HRESULT hr;
94+
Assert.That(hr = pcm.QueryContextMenu(hmenu, 0, m_CmdFirst, 0x7FFF, cmf), ResultIs.Successful);
95+
TestContext.WriteLine($"Menu items: {hr.Code}");
96+
Assert.That(hr.Code, Is.GreaterThan(0));
97+
var miis = MenuItemInfo.GetMenuItems(hmenu, pcm);
2198
for (int i = 0; i < miis.Length; i++)
2299
ShowMII(miis[i], i);
23100
if (cmf == CMF.CMF_NORMAL)
24101
{
25-
var oid = miis.First(m => m.Verb == "properties").Id;
26-
var cix = new CMINVOKECOMMANDINFOEX((int)oid - 1);
27-
pcm.InvokeCommand(cix);
102+
CMINVOKECOMMANDINFOEX cix = new(new SafeResourceId("properties"), useUnicode: true);
103+
Assert.That(pcm.InvokeCommand(cix), ResultIs.Successful);
28104
}
105+
}
29106

30-
void ShowMII(MenuItemInfo mii, int c, int indent = 0)
107+
[Test]
108+
public void QueryItemTest([Values] CMF cmf)
109+
{
110+
const uint mStartId = 1;
111+
var pshi = SHCreateItemFromParsingName<IShellItem>(TestCaseSources.ImageFile);
112+
Assert.That(pshi, Is.Not.Null);
113+
var pcm = pshi!.BindToHandler<IContextMenu>(null, BHID.BHID_SFUIObject);
114+
using var hmenu = CreatePopupMenu();
115+
HRESULT hr;
116+
Assert.That(hr = pcm.QueryContextMenu(hmenu, 0, mStartId, 0x7FFF, cmf), ResultIs.Successful);
117+
TestContext.WriteLine($"Menu items: {hr.Code}");
118+
Assert.That(hr.Code, Is.GreaterThan(0));
119+
var miis = MenuItemInfo.GetMenuItems(hmenu, pcm);
120+
for (int i = 0; i < miis.Length; i++)
121+
ShowMII(miis[i], i);
122+
if (cmf == CMF.CMF_NORMAL)
31123
{
32-
mii.Verb = mii.Type == MenuItemType.MFT_STRING && pcm.GetCommandString((IntPtr)(int)(mii.Id - 1), GCS.GCS_VERBA, default, memstr, memstr.Size) == HRESULT.S_OK ? memstr.ToString() ?? "" : "";
33-
TestContext.WriteLine($"{new string(' ', indent * 3)}{c + 1}) {mii.Text} (#{mii.Id}) - Type={mii.Type}; State={mii.State}; Verb={mii.Verb}");
34-
for (int j = 0; j < mii.SubMenus.Length; j++)
35-
ShowMII(mii.SubMenus[j], j, indent + 1);
124+
CMINVOKECOMMANDINFOEX cix = new(new SafeResourceId("properties"), useUnicode: true);
125+
Assert.That(pcm.InvokeCommand(cix), ResultIs.Successful);
36126
}
37127
}
38128

129+
[Test]
130+
public void QueryItemsTest([Values] CMF cmf)
131+
{
132+
const uint mStartId = 1;
133+
IShellItem pshi = SHCreateItemFromParsingName<IShellItem>(TestCaseSources.ImageFile)!;
134+
Assert.That(pshi, Is.Not.Null);
135+
IShellItem pshi2 = SHCreateItemFromParsingName<IShellItem>(TestCaseSources.Image2File)!;
136+
Assert.That(pshi2, Is.Not.Null);
137+
138+
var pidls = Array.ConvertAll([pshi, pshi2], si => { SHGetIDListFromObject(si!, out var pidl).ThrowIfFailed(); return pidl; });
139+
var parent = PIDL.FindCommonParent(pidls);
140+
var relPidls = Array.ConvertAll(pidls, p => p.GetRelativeTo(parent));
141+
var parentFolder = SHBindToObject<IShellFolder>(null, parent, null);
142+
var pcm = parentFolder!.GetUIObjectOf<IContextMenu>(HWND.NULL, relPidls);
143+
144+
using var hmenu = CreatePopupMenu();
145+
HRESULT hr;
146+
Assert.That(hr = pcm.QueryContextMenu(hmenu, 0, mStartId, 0x7FFF, cmf), ResultIs.Successful);
147+
TestContext.WriteLine($"Menu items: {hr.Code}");
148+
Assert.That(hr.Code, Is.GreaterThan(0));
149+
var miis = MenuItemInfo.GetMenuItems(hmenu, pcm);
150+
for (int i = 0; i < miis.Length; i++)
151+
ShowMII(miis[i], i);
152+
if (cmf == CMF.CMF_NORMAL)
153+
{
154+
CMINVOKECOMMANDINFOEX cix = new(new SafeResourceId("properties"), useUnicode: true);
155+
Assert.That(pcm.InvokeCommand(cix), ResultIs.Successful);
156+
}
157+
}
158+
159+
[Test]
160+
public void QueryItems2Test([Values] CMF cmf)
161+
{
162+
const uint mStartId = 1;
163+
IShellItem pshi = SHCreateItemFromParsingName<IShellItem>(TestCaseSources.ImageFile)!;
164+
Assert.That(pshi, Is.Not.Null);
165+
IShellItem pshi2 = SHCreateItemFromParsingName<IShellItem>("C:\\Windows\\notepad.exe")!;
166+
Assert.That(pshi2, Is.Not.Null);
167+
168+
var pidls = Array.ConvertAll([pshi, pshi2], si => { SHGetIDListFromObject(si!, out var pidl).ThrowIfFailed(); return pidl; });
169+
var parent = PIDL.FindCommonParent(pidls);
170+
var relPidls = Array.ConvertAll(pidls, p => p.GetRelativeTo(parent));
171+
var parentFolder = SHBindToObject<IShellFolder>(null, parent, null);
172+
var pcm = parentFolder!.GetUIObjectOf<IContextMenu>(HWND.NULL, relPidls);
173+
174+
using var hmenu = CreatePopupMenu();
175+
HRESULT hr;
176+
Assert.That(hr = pcm.QueryContextMenu(hmenu, 0, mStartId, 0x7FFF, cmf), ResultIs.Successful);
177+
TestContext.WriteLine($"Menu items: {hr.Code}");
178+
Assert.That(hr.Code, Is.GreaterThan(0));
179+
var miis = MenuItemInfo.GetMenuItems(hmenu, pcm);
180+
for (int i = 0; i < miis.Length; i++)
181+
ShowMII(miis[i], i);
182+
if (cmf == CMF.CMF_NORMAL)
183+
{
184+
CMINVOKECOMMANDINFOEX cix = new(new SafeResourceId("properties"), useUnicode: true);
185+
Assert.That(pcm.InvokeCommand(cix), ResultIs.Successful);
186+
}
187+
}
188+
189+
static void ShowMII(MenuItemInfo mii, int c, int indent = 0)
190+
{
191+
if (mii.Text is "" or "-")
192+
TestContext.WriteLine($"{new string(' ', indent * 3)}{c + 1}) \"{mii.Text}\" (#{mii.Id}) - Type={mii.Type}; State={mii.State}");
193+
else
194+
TestContext.WriteLine($"{new string(' ', indent * 3)}{c + 1}) \"{mii.Text}\" (#{mii.Id}) - Type={mii.Type}; State={mii.State}; Verb={mii.Verb}; Tooltip={mii.HelpText}; IconLoc={mii.VerbIconLocation}");
195+
for (int j = 0; j < mii.SubMenus.Length; j++)
196+
ShowMII(mii.SubMenus[j], j, indent + 1);
197+
}
198+
39199
public class MenuItemInfo
40200
{
41-
internal MenuItemInfo(HMENU hMenu, uint idx)
201+
internal MenuItemInfo(HMENU hMenu, int idx, IContextMenu? cm)
42202
{
43-
using var strmem = new SafeHGlobalHandle(512);
44-
var mii = new MENUITEMINFO
203+
// Get the string length
204+
MENUITEMINFO miis = new(MenuItemInfoMask.MIIM_STRING);
205+
GetMenuItemInfo(hMenu, (uint)Math.Abs(idx), idx >= 0, ref miis);
206+
using SafeCoTaskMemString strmem = new((int)miis.cch + 1, CharSet.Auto);
207+
208+
// Get all the details
209+
MENUITEMINFO mii = new(MenuItemInfoMask.MIIM_ID | MenuItemInfoMask.MIIM_SUBMENU | MenuItemInfoMask.MIIM_FTYPE | (miis.cch == 0 ? 0 : MenuItemInfoMask.MIIM_STRING) | MenuItemInfoMask.MIIM_STATE | MenuItemInfoMask.MIIM_BITMAP)
45210
{
46-
cbSize = (uint)Marshal.SizeOf(typeof(MENUITEMINFO)),
47-
fMask = MenuItemInfoMask.MIIM_ID | MenuItemInfoMask.MIIM_SUBMENU | MenuItemInfoMask.MIIM_FTYPE | MenuItemInfoMask.MIIM_STRING | MenuItemInfoMask.MIIM_STATE | MenuItemInfoMask.MIIM_BITMAP,
48-
fType = MenuItemType.MFT_STRING,
49211
dwTypeData = (IntPtr)strmem,
50-
cch = strmem.Size / (uint)StringHelper.GetCharSize()
212+
cch = (uint)strmem.Capacity
51213
};
52-
Win32Error.ThrowLastErrorIfFalse(GetMenuItemInfo(hMenu, idx, true, ref mii));
53-
Id = mii.wID;
54-
Text = mii.fType.IsFlagSet(MenuItemType.MFT_SEPARATOR) ? "-" : mii.fType.IsFlagSet(MenuItemType.MFT_STRING) ? strmem.ToString(-1, CharSet.Auto) ?? "" : "";
214+
Win32Error.ThrowLastErrorIfFalse(GetMenuItemInfo(hMenu, (uint)Math.Abs(idx), idx >= 0, ref mii));
215+
Id = unchecked((int)mii.wID);
216+
Text = mii.fType.IsFlagSet(MenuItemType.MFT_SEPARATOR) ? "-" : mii.fType.IsFlagSet(MenuItemType.MFT_STRING) ? mii.dwTypeData.ToString() ?? "" : "";
55217
Type = mii.fType;
56218
State = mii.fState;
57219
BitmapHandle = mii.hbmpItem;
58-
SubMenus = GetMenuItems(mii.hSubMenu);
220+
if (cm is not null && !mii.fType.IsFlagSet(MenuItemType.MFT_SEPARATOR))
221+
{
222+
uint id = mii.wID - m_CmdFirst;
223+
Verb = cm.GetCommandString(id, GCS.GCS_VERBW, out var mStr).Succeeded ? mStr : null;
224+
HelpText = cm.GetCommandString(id, GCS.GCS_HELPTEXTW, out mStr).Succeeded ? mStr : null;
225+
VerbIconLocation = cm.GetCommandString(id, GCS.GCS_VERBICONW, out mStr).Succeeded ? mStr : null;
226+
}
227+
SubMenus = GetMenuItems(mii.hSubMenu, cm);
59228
}
60229

61-
public static MenuItemInfo[] GetMenuItems(HMENU hMenu)
230+
public static MenuItemInfo[] GetMenuItems(HMENU hMenu, IContextMenu? cm)
62231
{
63-
if (hMenu.IsNull)
64-
return new MenuItemInfo[0];
65-
66-
var SubMenus = new MenuItemInfo[GetMenuItemCount(hMenu)];
67-
for (uint i = 0; i < SubMenus.Length; i++)
68-
SubMenus[i] = new MenuItemInfo(hMenu, i);
232+
var SubMenus = new MenuItemInfo[hMenu.IsNull ? 0 : GetMenuItemCount(hMenu)];
233+
for (int i = 0; i < SubMenus.Length; i++)
234+
SubMenus[i] = new(hMenu, i, cm);
69235
return SubMenus;
70236
}
71237

72-
public uint Id { get; }
238+
public int Id { get; }
73239
public string Text { get; }
74240
public MenuItemType Type { get; }
75241
public MenuItemState State { get; }
76242
public MenuItemInfo[] SubMenus { get; }
77243
public HBITMAP BitmapHandle { get; }
78244
public string? Verb { get; internal set; }
245+
public string? HelpText { get; internal set; }
246+
public string? VerbIconLocation { get; internal set; }
79247
}
80248

81249
}

0 commit comments

Comments
 (0)