Skip to content

Commit 3e8d218

Browse files
committed
Added custom Folder Browser Dialog allowing the user to paste.
1 parent c6a2eda commit 3e8d218

3 files changed

Lines changed: 417 additions & 18 deletions

File tree

Lines changed: 393 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,393 @@
1+
using System;
2+
using System.ComponentModel;
3+
using System.Runtime.InteropServices;
4+
using System.Security;
5+
using System.Security.Permissions;
6+
using System.Threading;
7+
using System.Windows.Forms;
8+
9+
namespace WindowsPathEditor
10+
{
11+
/// <summary>
12+
/// Similar to <see cref="FolderBrowserDialog"/>, but provides a more
13+
/// fine grained access to the creation flags of the dialog (<see cref="CreationFlags"/>
14+
/// property).
15+
/// </summary>
16+
public sealed class FolderBrowserDialogEx : CommonDialog
17+
{
18+
/// <summary>
19+
/// Various creation flags modifying the appearance
20+
/// and the behavior of the dialog.
21+
/// </summary>
22+
[Flags]
23+
public enum Flags : int
24+
{
25+
/// <summary>
26+
/// Only return file system directories. If the user selects
27+
/// folders that are not part of the file system, the OK button is grayed.
28+
/// </summary>
29+
/// <remarks>
30+
/// The OK button remains enabled for "\\server" items, as well as
31+
/// "\\server\share" and directory items. However, if the user selects
32+
/// a "\\server" item, passing the PIDL returned by SHBrowseForFolder
33+
/// to SHGetPathFromIDList fails.
34+
/// </remarks>
35+
BIF_RETURNONLYFSDIRS = 0x0001,
36+
37+
/// <summary>
38+
/// Do not include network folders below the domain level in
39+
/// the dialog box's tree view control.
40+
/// </summary>
41+
BIF_DONTGOBELOWDOMAIN = 0x0002,
42+
43+
/// <summary>
44+
/// Include a status area in the dialog box. The callback function can set
45+
/// the status text by sending messages to the dialog box. This flag is not
46+
/// supported when BIF_NEWDIALOGSTYLE is specified.
47+
/// </summary>
48+
BIF_STATUSTEXT = 0x0004,
49+
50+
/// <summary>
51+
/// Only return file system ancestors. An ancestor is a subfolder that
52+
/// is beneath the root folder in the namespace hierarchy. If the user
53+
/// selects an ancestor of the root folder that is not part of the file
54+
/// system, the OK button is grayed.
55+
/// </summary>
56+
BIF_RETURNANCESTORS = 0x0008,
57+
58+
/// <summary>
59+
/// <b>Version 4.71.</b> Include an edit control in the browse dialog box
60+
/// that allows the user to type the name of an item.
61+
/// </summary>
62+
BIF_EDITBOX = 0x0010,
63+
64+
// TODO: continue filling documentation from MSDN...
65+
66+
/// <summary>
67+
///
68+
/// </summary>
69+
BIF_VALIDATE = 0x0020,
70+
71+
/// <summary>
72+
///
73+
/// </summary>
74+
BIF_NEWDIALOGSTYLE = 0x0040,
75+
76+
/// <summary>
77+
///
78+
/// </summary>
79+
BIF_USENEWUI = 0x0050,
80+
81+
/// <summary>
82+
///
83+
/// </summary>
84+
BIF_BROWSEINCLUDEURLS = 0x0080,
85+
86+
/// <summary>
87+
///
88+
/// </summary>
89+
BIF_UAHINT = 0x0100,
90+
91+
/// <summary>
92+
///
93+
/// </summary>
94+
BIF_NONEWFOLDERBUTTON = 0x0200,
95+
96+
/// <summary>
97+
///
98+
/// </summary>
99+
BIF_NOTRANSLATETARGETS = 0x0400,
100+
101+
/// <summary>
102+
///
103+
/// </summary>
104+
BIF_BROWSEFORCOMPUTER = 0x1000,
105+
106+
/// <summary>
107+
///
108+
/// </summary>
109+
BIF_BROWSEFORPRINTER = 0x2000,
110+
111+
/// <summary>
112+
///
113+
/// </summary>
114+
BIF_BROWSEINCLUDEFILES = 0x4000,
115+
116+
/// <summary>
117+
///
118+
/// </summary>
119+
BIF_SHAREABLE = 0x8000
120+
}
121+
122+
#region Interop
123+
124+
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
125+
private class BROWSEINFO
126+
{
127+
public IntPtr hwndOwner;
128+
public IntPtr pidlRoot;
129+
public IntPtr pszDisplayName;
130+
public string lpszTitle;
131+
public int ulFlags;
132+
public BrowseCallbackProc lpfn;
133+
public IntPtr lParam;
134+
public int iImage;
135+
}
136+
137+
[ComImport, Guid("00000002-0000-0000-c000-000000000046"),
138+
SuppressUnmanagedCodeSecurity,
139+
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
140+
private interface IMalloc
141+
{
142+
[PreserveSig]
143+
IntPtr Alloc(int cb);
144+
[PreserveSig]
145+
IntPtr Realloc(IntPtr pv, int cb);
146+
[PreserveSig]
147+
void Free(IntPtr pv);
148+
[PreserveSig]
149+
int GetSize(IntPtr pv);
150+
[PreserveSig]
151+
int DidAlloc(IntPtr pv);
152+
[PreserveSig]
153+
void HeapMinimize();
154+
}
155+
156+
private const int BFFM_INITIALIZED = 1;
157+
private const int BFFM_SELCHANGED = 2;
158+
private const int BFFM_SETSELECTION = 0x466;
159+
private const int BFFM_SETSELECTIONW = 0x467;
160+
private const int BFFM_ENABLEOK = 0x465;
161+
162+
private delegate int BrowseCallbackProc(IntPtr hwnd, int msg, IntPtr lParam, IntPtr lpData);
163+
164+
[DllImport("user32.dll", CharSet = CharSet.Auto)]
165+
private static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, string lParam);
166+
167+
[DllImport("user32.dll", CharSet = CharSet.Auto)]
168+
private static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, int lParam);
169+
170+
[DllImport("shell32.dll", CharSet = CharSet.Auto)]
171+
private static extern bool SHGetPathFromIDList(IntPtr pidl, IntPtr pszPath);
172+
173+
[DllImport("shell32.dll")]
174+
private static extern int SHGetMalloc([Out, MarshalAs(UnmanagedType.LPArray)] IMalloc[] ppMalloc);
175+
176+
[DllImport("shell32.dll")]
177+
private static extern int SHGetSpecialFolderLocation(IntPtr hwnd, int csidl, ref IntPtr ppidl);
178+
179+
[DllImport("shell32.dll", CharSet = CharSet.Auto)]
180+
private static extern IntPtr SHBrowseForFolder([In] BROWSEINFO lpbi);
181+
182+
#endregion
183+
184+
private BrowseCallbackProc callback;
185+
private string descriptionText;
186+
private Environment.SpecialFolder rootFolder;
187+
private string selectedPath;
188+
private bool selectedPathNeedsCheck;
189+
190+
private Flags flags;
191+
192+
/// <summary>
193+
/// Initializes a new instance of the <see cref="FolderBrowserDialogEx"/> class.
194+
/// </summary>
195+
public FolderBrowserDialogEx() : base() { Reset(); }
196+
197+
#region Properties
198+
199+
/// <summary>
200+
/// Gets or sets the description.
201+
/// </summary>
202+
/// <value>The description.</value>
203+
[Description("Folder Browser Dialog Description"),
204+
Category("Folder browsing"), DefaultValue(""),
205+
Browsable(true), Localizable(true)]
206+
public string Description
207+
{
208+
get { return descriptionText; }
209+
set { descriptionText = (value == null) ? string.Empty : value; }
210+
}
211+
212+
/// <summary>
213+
/// Gets or sets the root folder.
214+
/// </summary>
215+
/// <value>The root folder.</value>
216+
[Description("Folder Browser Dialog Root Folder"), Localizable(false), DefaultValue(0),
217+
Category("Folder Browsing"), Browsable(true)]
218+
public Environment.SpecialFolder RootFolder
219+
{
220+
get { return rootFolder; }
221+
set
222+
{
223+
if (!Enum.IsDefined(typeof(Environment.SpecialFolder), value))
224+
throw new InvalidEnumArgumentException("value",
225+
(int)value, typeof(Environment.SpecialFolder));
226+
rootFolder = value;
227+
}
228+
}
229+
230+
/// <summary>
231+
/// Gets or sets the selected path.
232+
/// </summary>
233+
/// <value>The selected path.</value>
234+
[Description("Folder Browser Dialog Selected Path"), Category("Folder Browsing"),
235+
Browsable(true), DefaultValue(""), Localizable(true)]
236+
public string SelectedPath
237+
{
238+
get
239+
{
240+
if ((selectedPath != null) && (selectedPath.Length != 0) && selectedPathNeedsCheck)
241+
new FileIOPermission(FileIOPermissionAccess.PathDiscovery, selectedPath).Demand();
242+
return selectedPath;
243+
}
244+
set
245+
{
246+
selectedPath = (value == null) ? string.Empty : value;
247+
selectedPathNeedsCheck = false;
248+
}
249+
}
250+
251+
/// <summary>
252+
/// Gets or sets a value indicating whether to show the new folder button.
253+
/// </summary>
254+
/// <value>
255+
/// <c>true</c> if the new folder button should be shown; otherwise, <c>false</c>.
256+
/// </value>
257+
[Category("Folder Browsing"), Localizable(false),
258+
Description("Folder Browser Dialog Show New Folder Button"),
259+
DefaultValue(true), Browsable(true)]
260+
public bool ShowNewFolderButton
261+
{
262+
get
263+
{
264+
return ((flags & Flags.BIF_NONEWFOLDERBUTTON) !=
265+
Flags.BIF_NONEWFOLDERBUTTON);
266+
}
267+
set
268+
{
269+
if (value)
270+
flags &= ~Flags.BIF_NONEWFOLDERBUTTON;
271+
else flags |= Flags.BIF_NONEWFOLDERBUTTON;
272+
}
273+
}
274+
275+
/// <summary>
276+
/// Gets or sets the creation flags.
277+
/// </summary>
278+
/// <value>The creation flags.</value>
279+
[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
280+
public Flags CreationFlags
281+
{
282+
get { return flags; }
283+
set { flags = value; }
284+
}
285+
286+
#endregion
287+
288+
/// <summary>
289+
/// Resets the properties of this common dialog box to their default values.
290+
/// </summary>
291+
public override void Reset()
292+
{
293+
rootFolder = Environment.SpecialFolder.Desktop;
294+
descriptionText = string.Empty;
295+
selectedPath = string.Empty;
296+
selectedPathNeedsCheck = false;
297+
flags = Flags.BIF_USENEWUI;
298+
}
299+
300+
/// <summary>
301+
/// When overridden in a derived class, specifies a common dialog box.
302+
/// </summary>
303+
/// <param name="hwndOwner">A value that represents the window handle of the owner window for the common dialog box.</param>
304+
/// <returns>
305+
/// true if the dialog box was successfully run; otherwise, false.
306+
/// </returns>
307+
protected override bool RunDialog(IntPtr hwndOwner)
308+
{
309+
IntPtr zero = IntPtr.Zero;
310+
bool flag = false;
311+
SHGetSpecialFolderLocation(hwndOwner, (int)rootFolder, ref zero);
312+
if (zero == IntPtr.Zero)
313+
{
314+
SHGetSpecialFolderLocation(hwndOwner, 0, ref zero);
315+
if (zero == IntPtr.Zero) throw new InvalidOperationException(
316+
"Folder Browser Dialog: no root folder");
317+
}
318+
319+
//int flags = 0x40;
320+
//if (!showNewFolderButton) flags += 0x200;
321+
322+
if (Control.CheckForIllegalCrossThreadCalls && (Application.OleRequired() != ApartmentState.STA))
323+
throw new ThreadStateException("Debugging exception only: Thread must be STA");
324+
325+
IntPtr pidl = IntPtr.Zero;
326+
IntPtr hglobal = IntPtr.Zero;
327+
IntPtr pszPath = IntPtr.Zero;
328+
try
329+
{
330+
BROWSEINFO lpbi = new BROWSEINFO();
331+
hglobal = Marshal.AllocHGlobal((int)(260 * Marshal.SystemDefaultCharSize));
332+
pszPath = Marshal.AllocHGlobal((int)(260 * Marshal.SystemDefaultCharSize));
333+
callback = new BrowseCallbackProc(FolderBrowserDialog_BrowseCallbackProc);
334+
lpbi.pidlRoot = zero;
335+
lpbi.hwndOwner = hwndOwner;
336+
lpbi.pszDisplayName = hglobal;
337+
lpbi.lpszTitle = descriptionText;
338+
lpbi.ulFlags = (int)flags;
339+
lpbi.lpfn = callback;
340+
lpbi.lParam = IntPtr.Zero;
341+
lpbi.iImage = 0;
342+
pidl = SHBrowseForFolder(lpbi);
343+
if (pidl != IntPtr.Zero)
344+
{
345+
SHGetPathFromIDList(pidl, pszPath);
346+
selectedPathNeedsCheck = true;
347+
selectedPath = Marshal.PtrToStringAuto(pszPath);
348+
flag = true;
349+
}
350+
}
351+
finally
352+
{
353+
IMalloc sHMalloc = GetSHMalloc();
354+
sHMalloc.Free(zero);
355+
if (pidl != IntPtr.Zero) sHMalloc.Free(pidl);
356+
if (pszPath != IntPtr.Zero) Marshal.FreeHGlobal(pszPath);
357+
if (hglobal != IntPtr.Zero) Marshal.FreeHGlobal(hglobal);
358+
359+
callback = null;
360+
}
361+
return flag;
362+
}
363+
364+
private static IMalloc GetSHMalloc()
365+
{
366+
IMalloc[] ppMalloc = new IMalloc[1];
367+
SHGetMalloc(ppMalloc);
368+
return ppMalloc[0];
369+
}
370+
private int FolderBrowserDialog_BrowseCallbackProc(IntPtr hwnd, int msg, IntPtr lParam, IntPtr lpData)
371+
{
372+
switch (msg)
373+
{
374+
case BFFM_INITIALIZED:
375+
if (selectedPath.Length != 0)
376+
SendMessage(new HandleRef(null, hwnd), BFFM_SETSELECTIONW, 1, selectedPath);
377+
break;
378+
379+
case BFFM_SELCHANGED:
380+
IntPtr pidl = lParam;
381+
if (pidl != IntPtr.Zero)
382+
{
383+
IntPtr pszPath = Marshal.AllocHGlobal((int)(260 * Marshal.SystemDefaultCharSize));
384+
bool flag = SHGetPathFromIDList(pidl, pszPath);
385+
Marshal.FreeHGlobal(pszPath);
386+
SendMessage(new HandleRef(null, hwnd), BFFM_ENABLEOK, 0, flag ? 1 : 0);
387+
}
388+
break;
389+
}
390+
return 0;
391+
}
392+
}
393+
}

0 commit comments

Comments
 (0)