Select file OR folder from same dialog on Windows? - windows

I'd like to allow users of my Windows game to use a dialog box to select a folder or a file. I can currently do one or the other using SHBrowseForFolder or GetOpenFileName, but not both options from within the same dialog. Is this possible? (Reason being I'm porting a Mac game where this is possible.)

When you bring up the file dialog, give it a default filename such as "Entire folder" and turn off the flag that requires the file to exist before enabling the OK button. When the dialog returns, check the filename to see if it matches the special string "Entire folder" and treat it accordingly.
Based on my answer to another question: How do you configure an OpenFileDialog to select folders?

If you set OFN_NOVALIDATE in the Flags member of the OPENFILENAME structure, typing a folder name without a '\' at the end, and pressing Enter (or Open), will send the CDN_FILEOK notification to the hook procedure, not the CDN_FOLDERCHANGE notification.
From there, you may do whatever you want with the folder path. To me, this is a bug, but it might help you.

To the very best of my knowledge, the common dialogs cannot be operated in a mode where you can select either folder or file from the same dialog.

I know this thread is old but I recently had the same problem.
I discovered that when clicking the OK button the dialog emits the CDN_FOLDERCHANGE message but the folder actually did not change (is the same as at the last call).
So I came up with the follwing hook procedure:
UINT_PTR CALLBACK openfilename_cb (
HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
NMHDR *nmhdr;
OFNOTIFY *ofnotify;
static char dir_now[PATH_MAX], dir_prev[PATH_MAX];
do_debug (5, "openfilename_cb(): hwnd=%p, msg=%d\n", hwnd, msg);
switch (msg)
{
case WM_INITDIALOG:
do_debug (2, "openfilename_cb(): WM_INITDIALOG\n");
break;
case WM_NOTIFY:
ofnotify = (OFNOTIFY *)lParam;
nmhdr = &ofnotify->hdr;
do_debug (4,"openfilename_cb(): WM_NOTIFY, hwnd=%p, id=%d, code=%d\n",
nmhdr->hwndFrom, nmhdr->idFrom, nmhdr->code);
switch (nmhdr->code)
{
case CDN_INITDONE:
do_debug (3, "openfilename_cb(): CDN_INITDONE\n");
dir_prev[0] = '\0';
break;
case CDN_FILEOK:
do_debug (3, "openfilename_cb(): CDN_FILEOK\n");
break;
case CDN_FOLDERCHANGE:
do_debug (3, "openfilename_cb(): CDN_FOLDERCHANGE\n");
SendMessage (nmhdr->hwndFrom, CDM_GETFOLDERPATH, sizeof(dir_now),
(LPARAM)dir_now);
do_debug (3, " directory=%s\n", dir_now);
if (ofnotify->lpOFN->lCustData & FN_OPENDIR)
{
if (stricmp(dir_now, dir_prev) == 0)
{
/* user clicked the OK button */
strncpy (ofnotify->lpOFN->lpstrFile, dir_now, PATH_MAX);
ofnotify->lpOFN->lCustData = MAGIC;
PostMessage (nmhdr->hwndFrom, WM_COMMAND, IDCANCEL, 0);
do_debug (3, " closing dialog\n");
break;
}
strncpy (dir_prev, dir_now, sizeof(dir_prev));
}
break;
case CDN_HELP:
do_debug (3, "openfilename_cb(): CDN_HELP\n");
break;
case CDN_SELCHANGE:
do_debug (3, "openfilename_cb(): CDN_SELCHANGE\n");
break;
case CDN_SHAREVIOLATION:
do_debug (3, "openfilename_cb(): CDN_SHAREVIOLATION\n");
break;
case CDN_TYPECHANGE:
do_debug (3, "openfilename_cb(): CDN_TYPECHANGE\n");
break;
} /* switch (nmhdr->code) */
break; /* WM_NOTIFY */
} /* switch (msg) */
return 0;
}
Be sure to set the OFN_ENABLEHOOK and OFN_EXPLORER bits in the OPENFILENAME struct Flags member in order to get the hook procedure called. And, of course, set the lpfnHook member to the address of your hook function.
I discovered that the dialog looks quite different whether using the hook procedure or not (elements are sorted differently, there is a tree view in the left pane vs. large icons, maybe more).
You have to communicate back and forth to your calling function that you want to / have selected a directory. I use the lCustData member of the OPENFILENAME struct for that.
I tested with Windows 10, Version 19042.1052, 64bit and Windows XP, 32bit.
Johannes

Related

Different behaviour between dialogbox and createdialog is not understood

I am creating a dialog in a DLL and while DialogBox creates it - but seems hard to be stopped programmatically, CreateDialog only shows the borders (and title) of the dialog and "does not respond" after creating
I am adding some functionality to an existing program - not under my control. Control of this added functionality - for some external device - requires the user to do some settings particular to settings for this device, not covered by controls in the main program. For this I add a DLL with a small dialog, one with three control elements. The dialog is started with the DialogBox function in a separate thread and funtions well, apart from termination: the use of the main program may want to switch from one device to another without having to explicity quit the dialog by touching some button or control on the dialog.
Therefore I figured it would be better to use CreateDialog since that gives a handle.
However, when doing that - of course adding ShowWindow (handle, WS_SHOW) to make the dialog visible, only the raw widget appears, no controles visible and suffering from "does not respond"
` // DialogBox (hInstance,`
` widgetHandle = CreateDialog (hInstance,`
` MAKEINTRESOURCE(IDD_DIALOG1), NULL, ialog1Proc);`
` err = GetLastError ();`
` fprintf (stderr, "Last Error = %d\n", err);`
` if (err == 0) {`
` ShowWindow (widgetHandle, SW_SHOW);`
` while (running)`
` Sleep (100);`
` }`
Since - when using CreateBox, the contours of the widget - and the title of the dialog - are visible, I assume that the resource is seen correctly,
but it is as such unusable.
Therefore two questions
a. Is my assumption that CreateBox can be used in a thread in a DLL wrong
b. I'm perfectly willing to use DialogBox here, but then I need some advice on how to terminate the dialog (box) programmatically, i.e. from elsewhere in the DLL.
Any help would be appreciated here
DialogBox creates its own message loop and does not return until the dialog is closed. It is essentially CreateDialog + a message loop.
CreateDialog returns after creating the window and it expects you to process messages for it.
HWND hDlg = CreateDialog(...);
ShowWindow(hDlg, SW_SHOW);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
if (!IsWindow(hDlg)) break;
if (IsDialogMessage(hDlg, &msg)) continue;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
If your only requirement is to be able to close the dialog from a different thread you can pull it off with DialogBox as well:
HWND g_hDlg = 0; // Assumes you only have one dialog instance open at the time
.. MyDlgProc(HWND hDlg, UINT Msg, ..)
{
switch(Msg)
{
case WM_INITDIALOG:
g_hDlg = hDlg;
return TRUE;
case WM_DESTROY:
g_hDlg = 0;
break;
case WM_CLOSE:
EndDialog(hDlg, 0);
break;
}
return FALSE;
}
EXTERN_C void WINAPI CloseDialog()
{
if (g_hDlg) SendMessage(g_hDlg, WM_CLOSE, 0, 0);
}

How do I tell what messages to handle when subclassing a control?

I want to subclass the edit control into a specific case of a masked edit - something that accepts five characters of user input, and displays colons after the first and third characters. I can imagine two basic approaches to this.
I could have the text that the edit control stores be the text I want displayed. In this case, I would need to set the text to L" : : " to begin with, and override the messages that detect user input so I could copy it into the correct slots in that string. However, I don't know how to be sure which messages those are. I assume WM_KEYDOWN is one, but if there are others, and I don't think of them all, input that triggers the messages I missed would incorrectly defer to the edit control's default handling.
Alternatively, I could have the text that the edit control stores be the text the user enters - no colons. In that case, I would need to override the way the control is displayed so I could to generate a string that includes colons based on the stored text, and draw that when drawing the control. I assume this would mean replacing the WM_PAINT handling. The problem with that is that it would seem to require redefining everything about how the control looks myself, when the text is the only part I want to change. I'm not confident I could do that perfectly, and I would certainly rather not.
How should I approach this?
Edit: I've tried overriding WM_PAINT like this:
INT_PTR CALLBACK MaskedEditProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam,
UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
if (message == WM_PAINT)
{
WCHAR userInput[6];
Edit_GetText(hwndDlg, userInput, 6);
WCHAR displayString[]{L" : : "};
int userInputLength{ Edit_GetTextLength(hwndDlg) };
switch (userInputLength)
{
case 5:
displayString[6] = userInput[4];
case 4:
displayString[5] = userInput[3];
case 3:
displayString[3] = userInput[2];
case 2:
displayString[2] = userInput[1];
case 1:
displayString[0] = userInput[0];
}
Edit_SetText(hwndDlg, displayString);
DefSubclassProc(hwndDlg, message, wParam, lParam);
Edit_SetText(hwndDlg, userInput);
return TRUE;
}
return DefSubclassProc(hwndDlg, message, wParam, lParam);
}
This seems to basically work, except for some reason it causes the displayed text to flicker.
Edit 2: I set the control's text to L"0:00:00" from its parent window, and gave it the following window procedure:
INT_PTR CALLBACK MaskedEditProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam,
UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
switch (message)
{
case WM_CUT:
return 0;
case WM_PASTE:
return 0;
case WM_KEYDOWN:
if (wParam == VK_DELETE)
{
WORD caretPosition{ LOWORD(SendMessage(hwndDlg,EM_GETSEL,0,0)) };
switch (caretPosition)
{
case 7:
return 0;
case 1:
case 4:
SendMessage(hwndDlg, EM_SETSEL, caretPosition + 1, caretPosition + 2);
break;
default:
SendMessage(hwndDlg, EM_SETSEL, caretPosition, caretPosition + 1);
}
return DefSubclassProc(hwndDlg, WM_CHAR, '0', 0);
}
case WM_CHAR:
if (wParam == '\b')
{
WORD caretPosition{ LOWORD(SendMessage(hwndDlg,EM_GETSEL,0,0)) };
switch (caretPosition)
{
case 0:
return 0;
case 2:
case 5:
SendMessage(hwndDlg, EM_SETSEL, caretPosition - 2, caretPosition - 1);
break;
default:
SendMessage(hwndDlg, EM_SETSEL, caretPosition - 1, caretPosition);
}
return DefSubclassProc(hwndDlg, WM_CHAR, '0', 0);
}
else if (iswdigit(wParam))
{
WORD caretPosition{ LOWORD(SendMessage(hwndDlg,EM_GETSEL,0,0)) };
switch (caretPosition)
{
case 1:
case 4:
SendMessage(hwndDlg, EM_SETSEL, caretPosition + 1, caretPosition + 2);
break;
default:
SendMessage(hwndDlg, EM_SETSEL, caretPosition, caretPosition + 1);
}
}
}
return DefSubclassProc(hwndDlg, message, wParam, lParam);
}
This seems to work as intended, though my backspace key has always been broken, so I haven't tested that part. The control is also set not to accept non-digit input, so I don't think the fact that I don't handle those here should break anything.
I suggest you handle only WM_CHAR (and possibly custom messages related to your masking). Leave the arrow keys alone, let the edit control handle them and in your WM_CHAR handler query for the caret placement. In your WM_CHAR when the placement would normally reach a colon send a EM_SETSEL that skips to the next character location.

"Caps Lock is on"-tooltip doesn't go away (Citrix? Codejock?)

I'm working with an MFC application. In some of our forms, we have password fields - ordinary CEdit controls with the ES_PASSWORD style set. When focus is moved to a password field, Windows displays a tooltip-warning if the user has Caps Lock on. (See screendump here) Which is OK - the password is case sensitive, so it's good that the user gets informed he should turn Caps Lock off.
But sometimes the warning doesn't go away. Even after the CEdit control (and the whole view window) has been deleted/destroyed. The "tooltip balloon" remains on screen, on top of everything else. (Hides new controls.) The only way to get rid of it is to close the application and restart it.
The problem doesn't appear so often, but is very annoying for the user when it does. As far as we know, this has only happend to users running our application under Citrix - not on a standalone PC. Our application is using Codejock's XTP, but the tooltip is localized so it seems to be generated by Windows.
(1) Does anybody know what can caus e this? We haven't been able to find anything about it online! Could it be a Citrix problem? Or are we wrong about this beeing a Windows thing - could this be a Codejock bug?
(2) Is there a way to fix this? (CEdit::HideBalloonTip/EM_HIDEBALLOONTIP does not help.)
(3) If there isn't a solution to the problem, can we disable the "Caps Lock is on"-tooltip completely for the CEdit control? Better not to show the tooltip at all, than having this problem.
To suppress the Balloon tip you can filter the EM_SHOWBALLOONTIP message as described here:
WNDPROC g_wpEdit;
LRESULT CALLBACK NoBalloonWndProc(HWND hwnd, UINT uMsg,
WPARAM wParam, LPARAM lParam)
{
switch (uMsg) {
case EM_SHOWBALLOONTIP: return FALSE;
}
return CallWindowProc(g_wpEdit, hwnd, uMsg, wParam, lParam);
}
BOOL
OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
{
g_hwndChild = CreateWindow(TEXT("edit"), NULL,
ES_PASSWORD | WS_CHILD | WS_VISIBLE, 0, 0,
0, 0, hwnd, NULL, g_hinst, 0);
if (!g_hwndChild) return FALSE;
g_wpEdit = SubclassWindow(g_hwndChild, NoBalloonWndProc);
return TRUE;
}

Find all windows beneath a point

I want to find all the top-level windows (children of the desktop) beneath a given point on the desktop. I can't find an API for this.
My scenario is that I'm dragging a window across the screen and want to drop it into another (known) window. I can hit test the bounds of the target window ok, but that doesn't tell me whether it's occluded by another (unknown) window. Using WindowFromPoint and friends won't work, because the window being dragged is necessarily directly under the mouse. So I'm wondering if I can obtain all windows at the mouse position, and review them to see whether one of the windows I'm tracking is directly beneath the window I'm dragging.
Is there a way to do this without resorting to EnumDesktopWindows/GetWindowRect on every mouse drag? Or perhaps there's another solution I'm missing.
If you ask kindly, WindowFromPoint will ignore your window (the one currently being dragged) and return the next window. This is what Internet Explorer does when you drag a tab.
To do that:
Handle WM_NCHITTEST in window being dragged
Return HTTRANSPARENT during dragging. Call default window proc otherwise.
WindowFromPoint will ignore HTTRANSPARENT windows, but only those belonging to the calling thread. This shouldn't be a problem for you, because you should be calling WindowFromPoint from the window owner thread anyway.
Make sure there're no child windows at point passed to WindowFromPoint, or handle WM_NCHITTEST for these child windows as well.
Troubleshooting (if you still get your window from WindowFromPoint)
Test GetCurrentThreadID() == GetWindowThreadProcessId(WindowFromPoint(), 0) to ensure you're calling from correct thread
In WM_NCHITTEST, test that hwnd parameter equals what you get from WindowFromPoint()
Example (the area within rectangle returns the underlying window from WindowFromPoint):
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static const RECT s_TransparentRect = {100, 100, 200, 200};
switch (message)
{
case WM_NCCREATE:
SetTimer(hWnd, 1, 100, 0);
break;
case WM_TIMER:
{
POINT cursorPos;
GetCursorPos(&cursorPos);
TCHAR buffer[256];
_snwprintf_s(buffer, _countof(buffer), _TRUNCATE, _T("WindowFromPoint: %08X\n"), (int)WindowFromPoint(cursorPos));
SetWindowText(hWnd, buffer);
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
BeginPaint(hWnd, &ps);
Rectangle(ps.hdc, s_TransparentRect.left, s_TransparentRect.top, s_TransparentRect.right, s_TransparentRect.bottom);
EndPaint(hWnd, &ps);
}
break;
case WM_NCHITTEST:
{
POINT cursorPos;
GetCursorPos(&cursorPos);
MapWindowPoints(HWND_DESKTOP, hWnd, &cursorPos, 1);
if (PtInRect(&s_TransparentRect, cursorPos))
return HTTRANSPARENT;
}
break;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
Right, you already know what WindowFromPoint() is going to return, should be the one you are dragging. Then use GetWindow() with uCmd = GW_HWNDNEXT to get the one below it in the Z-order. GetWindowRect() to get its bounds, IntersectRect() to compute the overlap.
Keep calling GetWindow() to find more windows that might be overlapped. Until it returns NULL or the overlap is good enough. If not then you'll normally favor the one that has the largest result rectangle from IntersectRect().

Win32 Edit Control Caret Placement Offset

I gave an English explanation of my problem below but it is a visual issue so if you don't want to read it all just look at the picture at the bottom).
I'm working on building a reverse polish notation calculator for my class and I just completed having the button controls on my GUI be able to append their values to the edit control which works fine, but the caret is doing something weird and I can't find any information on it.
I send a custom message to the edit control in which it finds the length of the current text in the control and then places the caret at the end of the text so I can then add what text needs to be added (it is right aligned with ES_RIGHT), which again works just fine, but when the caret is in the right most place it can be, it is placed practically right through the middle of most any number.
This only seems to happen in the right most place the caret can be (i.e. anywhere else the caret sits directly to the right of the preceding char, as it should) and I have tried replacing the caret all the way to the right using code, placing it using my keyboard/mouse, and tried adjusting the dimensions of the window in hopes that it was just an offset of the width I defined for it that caused the last place to be off slightly, but the problem persists and it makes it hard to read the last char in the text field.
Relevant Code:
LRESULT CALLBACK EditBoxClass::WinProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
switch( msg )
{
case WM_COMMAND:
break;
case WM_APPEND_EDIT:
/* Get current length of text in the box */
index = new int( GetWindowTextLength (hWnd) );
SetFocus( hWnd );
/* Set the caret to the end of the text in the box */
SendMessage( hWnd, EM_SETSEL, (WPARAM)index, (LPARAM)index );
/* "Replace" the selection (the selection is actually targeting
nothing and just sits at the end of the text in the box)
with the passed in TCHAR* from the button control that
sent the WM_APPEND_EDIT message */
SendMessage( hWnd, EM_REPLACESEL, 0, lParam );
break;
}
return CallWindowProc( EditClassStruct.GetOldProc(), hWnd, msg, wParam, lParam );
}
Picture of problem:
After facing the same problem and presenting my first approach in this answer, I'll now provide two well working solutions. I think there is no other way to fix this glitch properly (unless you're a Microsoft programmer who is responsible for this part of the WinAPI).
I was wondering how to fix this problem on edit controls created with ES_MULTILINE but this glitch seems to be only a problem on single-line edit controls (tested on Windows 7 64-bit). Enabling Visual Styles is also helpful but the problem still remains (the offset is at least not so obvious).
Explanation
Normally, when the caret is at the farthest right position it's x value (provided by GetCaretPos ()) should be equal to the rect.right value provided by EM_GETRECT (when the edit control was created with ES_RIGHT). Due to unknown reasons this is not the case. So you have to check if the caret position is at least in the near of the rect.right value (but not farther away than the last letter is wide). So you have two possibilities to fulfill this task:
You must calculate the width of the outer right character using GetTextExtentPoint32 (), subtract it from the rect.right value provided by calling SendMessage () with EM_GETRECT and check whether the x position of the caret is bigger than the result or not OR
You must calculate the margin between the rect.right value and the outer right caret position (3 in my case) and use this value as a hardcoded offset to do a simple check.
After those steps (regardless which one you have chosen) you have to reposition the caret when necessary.
1. Approach (recommended)
case WM_LBUTTONDOWN: {
TRACKMOUSEEVENT tme = {sizeof (tme), TME_LEAVE, hwnd, HOVER_DEFAULT};
TrackMouseEvent (&tme);
}
break;
case WM_KEYDOWN:
case WM_MOUSELEAVE:
case WM_SETCURSOR: {
DefSubclassProc (hwnd, message, wParam, lParam);
DWORD end;
SendMessage (hwnd, EM_GETSEL, (WPARAM) NULL, (LPARAM) &end);
int len = GetWindowTextLength (hwnd);
if (end < len || len <= 0)
return TRUE;
wchar_t *buffer = new wchar_t[len + 1];
GetWindowText (hwnd, buffer, len + 1);
wchar_t lastChar[] = {buffer[len - 1], '\0'};
delete[] buffer;
SIZE size;
HDC hdc = GetDC (hwnd);
if (hdc == NULL)
return TRUE;
GetTextExtentPoint32 (hdc, lastChar, 1, &size);
ReleaseDC (hwnd, hdc);
POINT pt;
RECT rect;
GetCaretPos (&pt);
SendMessage (hwnd, EM_GETRECT, (WPARAM) 0, (LPARAM) &rect);
if ((rect.right - size.cx) <= pt.x)
SetCaretPos (rect.right, pt.y);
return TRUE;
}
break;
2. Approach (improved original version)
case WM_LBUTTONDOWN: {
TRACKMOUSEEVENT tme = {sizeof (tme), TME_LEAVE, hwnd, HOVER_DEFAULT};
TrackMouseEvent (&tme);
}
break;
case WM_KEYDOWN:
case WM_MOUSELEAVE:
case WM_SETCURSOR: {
DefSubclassProc (hwnd, message, wParam, lParam);
POINT pt;
RECT rect;
GetCaretPos (&pt);
SendMessage (hwnd, EM_GETRECT, (WPARAM) 0, (LPARAM) &rect);
if ((rect.right - pt.x) <= 3)
SetCaretPos (rect.right, pt.y);
return TRUE;
}
break;
You have to subclass the edit controls. Then use this code in their window procedures and enjoy.
In both cases, tracking the mouse event is not absolutely necessary but recommended to completly avoid this glitch. Calling DefSubclassProc () will ensure that the cursor is changed on mouse over.
This may or may not be the cause, but you are misusing EM_SETSEL. You are dynamically allocating (and leaking) an int on the heap and passing a pointer to it as the message parameters, but EM_SETSEL does not expect or use pointers to begin with. So get rid of the dynamic allocation.
Also, the default window proc is not going to know how to handle your WM_APPEND_EDIT message, so there is no point in passing the message to CallWindowProc().
Try this instead:
case WM_APPEND_EDIT:
{
/* Get current length of text in the box */
int index = GetWindowTextLength( hWnd );
SetFocus( hWnd );
/* Set the caret to the end of the text in the box */
SendMessage( hWnd, EM_SETSEL, (WPARAM)index, (LPARAM)index );
/* "Replace" the selection (the selection is actually targeting
nothing and just sits at the end of the text in the box)
with the passed in TCHAR* from the button control that
sent the WM_APPEND_EDIT message */
SendMessage( hWnd, EM_REPLACESEL, 0, lParam );
return 0;
}
That being said, try using EM_GETRECT/EM_SETRECT to expand the right edge of the edit control's formatting rectangle by a few pixels. That should give the caret some extra room to work with.

Resources