comparison win/XBrowseForFolder.cpp @ 632:bf3a6d596cd4

Use better directory browser widget.
author mhessling@81767d24-ef19-dc11-ae90-00e081727c95
date Thu, 30 Oct 2008 10:46:03 +0000
parents
children 412af8059331
comparison
equal deleted inserted replaced
631:fa6c46796883 632:bf3a6d596cd4
1 // XBrowseForFolder.cpp Version 1.2
2 //
3 // Author: Hans Dietrich
4 // hdietrich@gmail.com
5 //
6 // Description:
7 // XBrowseForFolder.cpp implements XBrowseForFolder(), a function that
8 // wraps SHBrowseForFolder().
9 //
10 // History
11 // Version 1.2 - 2008 February 29
12 // - Changed API to allow for initial CSIDL.
13 // - Added option to set dialog caption, suggested by SimpleDivX.
14 // - Added option to set root, suggested by Jean-Michel Reghem.
15 //
16 // Version 1.1 - 2003 September 29 (not released)
17 // - Added support for edit box
18 //
19 // Version 1.0 - 2003 September 25
20 // - Initial public release
21 //
22 // License:
23 // This software is released into the public domain. You are free to use
24 // it in any way you like, except that you may not sell this source code.
25 //
26 // This software is provided "as is" with no expressed or implied warranty.
27 // I accept no liability for any damage or loss of business that this
28 // software may cause.
29 //
30 ///////////////////////////////////////////////////////////////////////////////
31
32 // if you don't want to use MFC, comment out the following line:
33 //#include "stdafx.h"
34
35 #ifndef __AFX_H__
36 #include "windows.h"
37 #include "crtdbg.h"
38 #include "tchar.h"
39 #endif
40
41 #include "Shlobj.h"
42 #include "io.h"
43 #include "XBrowseForFolder.h"
44
45 #pragma warning(disable: 4127) // conditional expression is constant (_ASSERTE)
46 #pragma warning(disable : 4996) // disable bogus deprecation warning
47
48 #ifndef __noop
49 #if _MSC_VER < 1300
50 #define __noop ((void)0)
51 #endif
52 #endif
53
54 #undef TRACE
55 #define TRACE __noop
56
57 //=============================================================================
58 // if you want to see the TRACE output, uncomment this line:
59 //#include "XTrace.h"
60 //=============================================================================
61
62 //=============================================================================
63 // struct to pass to callback function
64 //=============================================================================
65 struct FOLDER_PROPS
66 {
67 LPCTSTR lpszTitle;
68 LPCTSTR lpszInitialFolder;
69 UINT ulFlags;
70 };
71
72 #ifndef __AFX_H__
73
74 ///////////////////////////////////////////////////////////////////////////////
75 // CRect - a minimal CRect class
76
77 class CRect : public tagRECT
78 {
79 public:
80 //CRect() { }
81 CRect(int l = 0, int t = 0, int r = 0, int b = 0)
82 {
83 left = l;
84 top = t;
85 right = r;
86 bottom = b;
87 }
88 int Width() const { return right - left; }
89 int Height() const { return bottom - top; }
90 void SwapLeftRight() { SwapLeftRight(LPRECT(this)); }
91 static void SwapLeftRight(LPRECT lpRect) { LONG temp = lpRect->left;
92 lpRect->left = lpRect->right;
93 lpRect->right = temp; }
94 operator LPRECT() { return this; }
95 };
96 #endif
97
98 ///////////////////////////////////////////////////////////////////////////////
99 // ScreenToClientX - helper function in case non-MFC
100 static void ScreenToClientX(HWND hWnd, LPRECT lpRect)
101 {
102 _ASSERTE(::IsWindow(hWnd));
103 ::ScreenToClient(hWnd, (LPPOINT)lpRect);
104 ::ScreenToClient(hWnd, ((LPPOINT)lpRect)+1);
105 }
106
107 ///////////////////////////////////////////////////////////////////////////////
108 // MoveWindowX - helper function in case non-MFC
109 static void MoveWindowX(HWND hWnd, CRect& rect, BOOL bRepaint)
110 {
111 _ASSERTE(::IsWindow(hWnd));
112 ::MoveWindow(hWnd, rect.left, rect.top,
113 rect.Width(), rect.Height(), bRepaint);
114 }
115
116 ///////////////////////////////////////////////////////////////////////////////
117 // SizeBrowseDialog - resize dialog, move controls
118 static void SizeBrowseDialog(HWND hWnd, FOLDER_PROPS *fp)
119 {
120 TRACE(_T("in void SizeBrowseDialog\n"));
121
122 // find the folder tree and make dialog larger
123 HWND hwndTree = FindWindowEx(hWnd, NULL, _T("SysTreeView32"), NULL);
124
125 if (!hwndTree)
126 {
127 // ... this usually means that BIF_NEWDIALOGSTYLE is enabled.
128 // Then the class name is as used in the code below.
129 hwndTree = FindWindowEx(hWnd, NULL, _T("SHBrowseForFolder ShellNameSpace Control"), NULL);
130 TRACE(_T("SHBrowseForFolder ShellNameSpace Control: hwndTree=%X\n"), hwndTree);
131 }
132
133 CRect rectDlg;
134
135 _ASSERTE(IsWindow(hwndTree));
136
137 if (hwndTree)
138 {
139 // check if edit box
140 int nEditHeight = 0;
141 HWND hwndEdit = FindWindowEx(hWnd, NULL, _T("Edit"), NULL);
142 TRACE(_T("hwndEdit=%x\n"), hwndEdit);
143 CRect rectEdit;
144 if (hwndEdit && (fp->ulFlags & BIF_EDITBOX))
145 {
146 ::GetWindowRect(hwndEdit, &rectEdit);
147 ScreenToClientX(hWnd, &rectEdit);
148 nEditHeight = rectEdit.Height();
149 }
150 else if (hwndEdit)
151 {
152 ::MoveWindow(hwndEdit, 20000, 20000, 10, 10, FALSE);
153 ::ShowWindow(hwndEdit, SW_HIDE);
154 hwndEdit = 0;
155 }
156
157 // make the dialog larger
158 ::GetWindowRect(hWnd, &rectDlg);
159 rectDlg.right += 40;
160 rectDlg.bottom += 30;
161 if (hwndEdit)
162 rectDlg.bottom += nEditHeight + 5;
163 MoveWindowX(hWnd, rectDlg, TRUE);
164 ::GetClientRect(hWnd, &rectDlg);
165
166 int hMargin = 10;
167 int vMargin = 10;
168
169 // check if new dialog style - this means that there will be a resizing
170 // grabber in lower right corner
171 if (fp->ulFlags & BIF_NEWDIALOGSTYLE)
172 hMargin = ::GetSystemMetrics(SM_CXVSCROLL);
173
174 // move the Cancel button
175 CRect rectCancel;
176 HWND hwndCancel = ::GetDlgItem(hWnd, IDCANCEL);
177 if (hwndCancel)
178 ::GetWindowRect(hwndCancel, &rectCancel);
179 ScreenToClientX(hWnd, &rectCancel);
180 int h = rectCancel.Height();
181 int w = rectCancel.Width();
182 rectCancel.bottom = rectDlg.bottom - vMargin;//nMargin;
183 rectCancel.top = rectCancel.bottom - h;
184 rectCancel.right = rectDlg.right - hMargin; //(scrollWidth + 2*borderWidth);
185 rectCancel.left = rectCancel.right - w;
186 if (hwndCancel)
187 {
188 //TRACERECT(rectCancel);
189 MoveWindowX(hwndCancel, rectCancel, FALSE);
190 }
191
192 // move the OK button
193 CRect rectOK(0, 0, 0, 0);
194 HWND hwndOK = ::GetDlgItem(hWnd, IDOK);
195 if (hwndOK)
196 ::GetWindowRect(hwndOK, &rectOK);
197 ScreenToClientX(hWnd, &rectOK);
198 rectOK.bottom = rectCancel.bottom;
199 rectOK.top = rectCancel.top;
200 rectOK.right = rectCancel.left - 10;
201 rectOK.left = rectOK.right - w;
202 if (hwndOK)
203 {
204 MoveWindowX(hwndOK, rectOK, FALSE);
205 }
206
207 // expand the folder tree to fill the dialog
208 CRect rectTree;
209 ::GetWindowRect(hwndTree, &rectTree);
210 ScreenToClientX(hWnd, &rectTree);
211 if (hwndEdit)
212 {
213 rectEdit.left = hMargin;
214 rectEdit.right = rectDlg.right - hMargin;
215 rectEdit.top = vMargin;
216 rectEdit.bottom = rectEdit.top + nEditHeight;
217 MoveWindowX(hwndEdit, rectEdit, FALSE);
218 rectTree.top = rectEdit.bottom + 5;
219 }
220 else
221 {
222 rectTree.top = vMargin;
223 }
224 rectTree.left = hMargin;
225 rectTree.bottom = rectOK.top - 10;//nMargin;
226 rectTree.right = rectDlg.right - hMargin;
227 //TRACERECT(rectTree);
228 MoveWindowX(hwndTree, rectTree, FALSE);
229 }
230 else
231 {
232 TRACE(_T("ERROR - tree control not found.\n"));
233 //_ASSERTE(hwndTree);
234 }
235 }
236
237 ///////////////////////////////////////////////////////////////////////////////
238 // BrowseCallbackProc - SHBrowseForFolder callback function
239 static int CALLBACK BrowseCallbackProc(HWND hWnd, // Window handle to the browse dialog box
240 UINT uMsg, // Value identifying the event
241 LPARAM lParam, // Value dependent upon the message
242 LPARAM lpData) // Application-defined value that was
243 // specified in the lParam member of the
244 // BROWSEINFO structure
245 {
246 switch (uMsg)
247 {
248 case BFFM_INITIALIZED: // sent when the browse dialog box has finished initializing.
249 {
250 TRACE(_T("hWnd=%X\n"), hWnd);
251
252 // remove context help button from dialog caption
253 LONG lStyle = ::GetWindowLong(hWnd, GWL_STYLE);
254 lStyle &= ~DS_CONTEXTHELP;
255 ::SetWindowLong(hWnd, GWL_STYLE, lStyle);
256 lStyle = ::GetWindowLong(hWnd, GWL_EXSTYLE);
257 lStyle &= ~WS_EX_CONTEXTHELP;
258 ::SetWindowLong(hWnd, GWL_EXSTYLE, lStyle);
259
260 FOLDER_PROPS *fp = (FOLDER_PROPS *) lpData;
261 if (fp)
262 {
263 if (fp->lpszInitialFolder && (fp->lpszInitialFolder[0] != _T('\0')))
264 {
265 // set initial directory
266 ::SendMessage(hWnd, BFFM_SETSELECTION, TRUE, (LPARAM)fp->lpszInitialFolder);
267 }
268
269 if (fp->lpszTitle && (fp->lpszTitle[0] != _T('\0')))
270 {
271 // set window caption
272 ::SetWindowText(hWnd, fp->lpszTitle);
273 }
274 }
275
276 SizeBrowseDialog(hWnd, fp);
277 }
278 break;
279
280 case BFFM_SELCHANGED: // sent when the selection has changed
281 {
282 TCHAR szDir[MAX_PATH*2] = { 0 };
283
284 // fail if non-filesystem
285 BOOL bRet = SHGetPathFromIDList((LPITEMIDLIST) lParam, szDir);
286 if (bRet)
287 {
288 // fail if folder not accessible
289 if (_taccess(szDir, 00) != 0)
290 {
291 bRet = FALSE;
292 }
293 else
294 {
295 SHFILEINFO sfi;
296 ::SHGetFileInfo((LPCTSTR)lParam, 0, &sfi, sizeof(sfi),
297 SHGFI_PIDL | SHGFI_ATTRIBUTES);
298 TRACE(_T("dwAttributes=0x%08X\n"), sfi.dwAttributes);
299
300 // fail if pidl is a link
301 if (sfi.dwAttributes & SFGAO_LINK)
302 {
303 TRACE(_T("SFGAO_LINK\n"));
304 bRet = FALSE;
305 }
306 }
307 }
308
309 // if invalid selection, disable the OK button
310 if (!bRet)
311 {
312 ::EnableWindow(GetDlgItem(hWnd, IDOK), FALSE);
313 }
314
315 TRACE(_T("szDir=%s\n"), szDir);
316 }
317 break;
318 }
319
320 return 0;
321 }
322
323 ///////////////////////////////////////////////////////////////////////////////
324 //
325 // XBrowseForFolder()
326 //
327 // Purpose: Invoke the SHBrowseForFolder API. If lpszInitialFolder is
328 // supplied, it will be the folder initially selected in the tree
329 // folder list. Otherwise, the initial folder will be set to the
330 // current directory. The selected folder will be returned in
331 // lpszBuf.
332 //
333 // Parameters: hWnd - handle to the owner window for the dialog
334 // lpszInitialFolder - initial folder in tree; if NULL, the initial
335 // folder will be the current directory; if
336 // if this is a CSIDL, must be a real folder.
337 // nRootFolder - optional CSIDL of root folder for tree;
338 // -1 = use default.
339 // lpszCaption - optional caption for folder dialog
340 // lpszBuf - buffer for the returned folder path
341 // dwBufSize - size of lpszBuf in TCHARs
342 // bEditBox - TRUE = include edit box in dialog
343 //
344 // Returns: BOOL - TRUE = success; FALSE = user hit Cancel
345 //
346 BOOL _cdecl XBrowseForFolder(HWND hWnd,
347 LPCTSTR lpszInitialFolder,
348 int nRootFolder,
349 LPCTSTR lpszCaption,
350 LPTSTR lpszBuf,
351 DWORD dwBufSize,
352 BOOL bEditBox /*= FALSE*/)
353 {
354 _ASSERTE(lpszBuf);
355 _ASSERTE(dwBufSize >= MAX_PATH);
356
357 if (lpszBuf == NULL || dwBufSize < MAX_PATH)
358 return FALSE;
359
360 ZeroMemory(lpszBuf, dwBufSize*sizeof(TCHAR));
361
362 BROWSEINFO bi = { 0 };
363
364 // check if there is a special root folder
365 LPITEMIDLIST pidlRoot = NULL;
366 if (nRootFolder != -1)
367 {
368 if (SUCCEEDED(SHGetSpecialFolderLocation(hWnd, nRootFolder, &pidlRoot)))
369 bi.pidlRoot = pidlRoot;
370 }
371
372 TCHAR szInitialPath[MAX_PATH*2] = { _T('\0') };
373 if (lpszInitialFolder)
374 {
375 // is this a folder path string or a csidl?
376 if (HIWORD(lpszInitialFolder) == 0)
377 {
378 // csidl
379 int nFolder = LOWORD((UINT)(UINT_PTR)lpszInitialFolder);
380 TRACE(_T("csidl: nFolder=0x%X\n"), nFolder);
381 SHGetSpecialFolderPath(hWnd, szInitialPath, nFolder, FALSE);
382 }
383 else
384 {
385 // string
386 _tcsncpy(szInitialPath, lpszInitialFolder,
387 sizeof(szInitialPath)/sizeof(TCHAR)-2);
388 }
389 TRACE(_T("szInitialPath=<%s>\n"), szInitialPath);
390 }
391
392 if ((szInitialPath[0] == _T('\0')) && (bi.pidlRoot == NULL))
393 {
394 // no initial folder and no root, set to current directory
395 ::GetCurrentDirectory(sizeof(szInitialPath)/sizeof(TCHAR)-2,
396 szInitialPath);
397 }
398
399 FOLDER_PROPS fp;
400
401 bi.hwndOwner = hWnd;
402 bi.ulFlags = BIF_RETURNONLYFSDIRS; // do NOT use BIF_NEWDIALOGSTYLE,
403 // or BIF_STATUSTEXT
404 if (bEditBox)
405 bi.ulFlags |= BIF_EDITBOX;
406 bi.ulFlags |= BIF_NONEWFOLDERBUTTON;
407 bi.lpfn = BrowseCallbackProc;
408 bi.lParam = (LPARAM) &fp;
409
410 fp.lpszInitialFolder = szInitialPath;
411 fp.lpszTitle = lpszCaption;
412 fp.ulFlags = bi.ulFlags;
413
414 BOOL bRet = FALSE;
415
416 LPITEMIDLIST pidlFolder = SHBrowseForFolder(&bi);
417
418 if (pidlFolder)
419 {
420 TCHAR szBuffer[MAX_PATH*2] = { _T('\0') };
421
422 if (SHGetPathFromIDList(pidlFolder, szBuffer))
423 {
424 _tcsncpy(lpszBuf, szBuffer, dwBufSize-1);
425 bRet = TRUE;
426 }
427 else
428 {
429 TRACE(_T("SHGetPathFromIDList failed\n"));
430 }
431 }
432
433 // free up pidls
434 IMalloc *pMalloc = NULL;
435 if (SUCCEEDED(SHGetMalloc(&pMalloc)) && pMalloc)
436 {
437 if (pidlFolder)
438 pMalloc->Free(pidlFolder);
439 if (pidlRoot)
440 pMalloc->Free(pidlRoot);
441 pMalloc->Release();
442 }
443
444 return bRet;
445 }