1

Problem:

If the user holds the "enter" keyboard button and opens OPENFILENAME Save As Dialog, it will automatically save the file - dialog only blinks.

Desired result:

The user holds the "enter" keyboard button, opens OPENFILENAME Save As Dialog, nothing happens. He needs to click on the Save button or click again the "enter" keyboard button to save a file.

My current code:

OPENFILENAME ofn;
TCHAR szFile[260] = { 't','e','s','t'}; // example filename

// Initialize OPENFILENAME
ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = hWnd;    
ofn.lpstrFile = szFile;
ofn.nMaxFile = sizeof(szFile);
//Files like: (ALL - *.*), (Text - .TXT)
ofn.lpstrFilter = _T("All\0*.*\0Text\0*.TXT\0");
ofn.nFilterIndex = 1;
ofn.lpstrFileTitle = NULL;
ofn.nMaxFileTitle = 0;
ofn.lpstrInitialDir = NULL;
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;

if (GetSaveFileName(&ofn) == TRUE)
{
    // file saved
}

Actual dialog

Possible solution:

  • When ofn.lpstrFile is empty do nothing; Can't save file when there is no filename
  • When ofn.lpstrFile has suggested filename then turn off focus on "Save" button or somehow ignore button enter holding.

I was trying to do that but failed, I am a beginner in CPP :(

Thanks for help

Maciej Pulikowski
  • 2,457
  • 3
  • 15
  • 34
  • 1
    The first bullet seems sufficient: just don't set `lpstrFile`, the "File name" textbox will not be pre-filled with a default option, the "Save" button won't be enabled, and thus the user will not be able to blindly their way through the dialog box. To do anything else, you'll need to hook the dialog box, and remove the default button style from the Save button. This is possible, but more complex than a beginner will want to take on. – Cody Gray - on strike Nov 13 '21 at 15:33
  • 2
    Maybe you could use IFileDialog instead (GetSaveFileName is legacy API and in fact points to IFileDialog internally), with the IFileDialog::SetOptions(FOS_OKBUTTONNEEDSINTERACTION) (untested). https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/ne-shobjidl_core-_fileopendialogoptions – Simon Mourier Nov 13 '21 at 15:44
  • 1
    @SimonMourier that document specifically says this for `FOS_OKBUTTONNEEDSINTERACTION`: "*Note: Disabling of the OK button does not prevent the dialog from being submitted by the Enter key.*" – Remy Lebeau Nov 13 '21 at 17:38
  • 2
    @RemyLebeau - I know, but it's not 100% clear to me what it means, that's why it needs some testing – Simon Mourier Nov 13 '21 at 18:44
  • 3
    "If the user holds the "enter"..." - what will happen if the user holds power button? Can you protect it? BTW, `ofn.nMaxFile` must be `260` not `sizeof szFile`. – i486 Nov 13 '21 at 23:25
  • @CodyGray Thanks, the first bullet is done by default, without filename user can't save a file, so holding enter does nothing. – Maciej Pulikowski Nov 14 '21 at 00:00
  • @SimonMourier and RemyLebeau It's a good idea but I cannot use IFileDialog, I do this to the super legacy application, switching API is too huge change. :( – Maciej Pulikowski Nov 14 '21 at 00:05
  • @i486 You are right, in 99% of cases is overprotecting, but in my case, I do have to protect that. Thanks for your opinion :) – Maciej Pulikowski Nov 14 '21 at 00:09
  • @MaciejPulikowski - I understand, but your code is already running on IFileDialog, it's just not aware :-) – Simon Mourier Nov 14 '21 at 06:35
  • @SimonMourier I have tested FOS_OKBUTTONNEEDSINTERACTION for IFileSaveDialog. It removes focus from the save button, however holding enter bypass this o.O .... Even if the save button is not focused we still can save the file by holding ENTER. So it doesn't work :( – Maciej Pulikowski Nov 15 '21 at 22:30

1 Answers1

2

The easy solution to prevent data loss is to add the OFN_OVERWRITEPROMPT flag. This does not prevent the issue from happening if the suggested name does not already exist as a file.

To actually interact with the dialog you need OFN_ENABLEHOOK and a hook function. When you receive WM_NOTIFY, you can handle CDN_FILEOK to block the suggested name if not enough time has passed or maybe it is possible to change the focus in CDN_INITDONE.

Either way, you have to be mindful of the fact that you are changing how a common dialog works and this might anger some users.

Here is one way to do it. The actual delay to return the dialog to normal is something you have to decide for yourself.

const int btnid = 1337;

void CALLBACK resetsavedlgdefpush(HWND hWnd, UINT Msg, UINT_PTR idEvent, DWORD Time)
{
    KillTimer(hWnd, idEvent);
    HWND hDlg = GetParent(hWnd);
    UINT id = LOWORD(SendMessage(hDlg, DM_GETDEFID, 0, 0));
    if (id == btnid)
    {
        SendMessage(hDlg, DM_SETDEFID, IDOK, 0);
    }
}

UINT_PTR CALLBACK mysavehook(HWND hWndInner, UINT Msg, WPARAM wParam, LPARAM lParam)
{
    if (Msg == WM_NOTIFY)
    {
        OFNOTIFY*pOFN = (OFNOTIFY*) lParam;
        if (pOFN->hdr.code == CDN_INITDONE)
        {
            HWND hDlg = GetParent(hWndInner);
            CreateWindowEx(0, TEXT("BUTTON"), 0, BS_DEFPUSHBUTTON|BS_TEXT|WS_CHILD|WS_VISIBLE, 0, 0, 0, 0, hWndInner, (HMENU) btnid, 0, 0);
            SendMessage(hDlg, DM_SETDEFID, btnid, 0);
            PostMessage(hDlg, DM_SETDEFID, btnid, 0);
            int keydelay = 0;
            SystemParametersInfo(SPI_GETKEYBOARDDELAY, 0, &keydelay, 0);
            SetTimer(hWndInner, 0, (250 * ++keydelay) * 5, resetsavedlgdefpush);
        }
    }
    return 0;
}

...
ofn.Flags = OFN_PATHMUSTEXIST|OFN_EXPLORER|OFN_OVERWRITEPROMPT|OFN_ENABLESIZING|OFN_ENABLEHOOK;
ofn.lpfnHook = mysavehook;
MessageBox(ofn.hwndOwner, TEXT("Hold enter to test..."), 0, 0);
if (GetSaveFileName(&ofn) == TRUE) ...
Anders
  • 97,548
  • 12
  • 110
  • 164
  • 1
    Seems like what they want is to remove the `BS_DEFPUSHBUTTON` style from the "Save" button in the hook procedure in response to the dialog's creation. This will stop the key from automatically dismissing the dialog box. You don't want to fiddle with the focus; that won't solve the problem anyway, as the "Save" button doesn't need to have the focus for to invoke it. – Cody Gray - on strike Nov 13 '21 at 15:35
  • 1
    @CodyGray that does depend on where you force the focus. If you focus another button, [Enter] will not save. That being said, removing save as the default for x * keyboard-repeat-time is perhaps not a bad idea. – Anders Nov 13 '21 at 15:36
  • 1
    *"If you focus another button..."* Yes, of course, but what other button are you going to focus? The only option you really have is "Cancel", and pressing when that is active will have undesirable consequences, too. If the scenario described in the question, where a user holds down the key, repeats itself with the focus on the "Cancel" button, the dialog box will only blip, closing as soon as it is opened. Somewhat like the old joke of the user who, when confronted with a "Do you want to exit?" dialog, chooses "Cancel" just to make it go away. – Cody Gray - on strike Nov 13 '21 at 15:43
  • 1
    @Cody There is also a toolbar although I don't remember if toolbars eat the default pushbutton. You can also add your own 0x0 pixel "do nothing" button. – Anders Nov 13 '21 at 16:16
  • @Anders thank you so much for the help :) It does what I want! Incredible work! :) But... I don't understand why it changed the layout to the old one? https://imgur.com/a/zsncF2c – Maciej Pulikowski Nov 14 '21 at 00:17
  • @CodyGray Good point, focusing on "Cancel" could be confusing for the user. – Maciej Pulikowski Nov 14 '21 at 00:20
  • @Anders https://stackoverflow.com/questions/4731218/ofn-enablehook-modifies-the-look-of-getopenfilename here is my answer "it is impossible to get the new look when you use a hook."... Damn, I cannot use your method. It was soo damn good. – Maciej Pulikowski Nov 14 '21 at 00:28
  • 1
    Yes, @Maciej, this type of hooking disables theming. That is why you need to use `IFileSaveDialog` instead, which provides all the same features and would thus be a drop-in replacement, without even affecting the user interface. (Alternatively, you could install a CBT hook or something, but... eeek, that's even uglier.) – Cody Gray - on strike Nov 14 '21 at 05:47
  • 1
    @CodyGray Hooking does not disable theming? When hooked it uses the XP dialog and not the Vista dialog. `IFileDialog` does not provide a init event, you would just have to hope `IFileDialogEvents::OnFolderChange` is called at the start. – Anders Nov 14 '21 at 10:35
  • 1
    It disables visual styles, and it prevents you from getting the improvements to the dialogs that come with new shell releases. – Cody Gray - on strike Nov 14 '21 at 10:48
  • 1
    @CodyGray I just checked, a hook does not disable themes/visual styles (and remember, hooks worked normally on XP with themes as well). – Anders Nov 14 '21 at 21:15
  • 1
    @MaciejPulikowski I just checked and hooking the new IFileDialog does not work, it seems to catch [Enter] even if I disable or remove the OK button. – Anders Nov 14 '21 at 21:16
  • @Anders Thanks Anders for help. What hooking did you check? I am trying with IFileDialogEvents::OnFileOk (https://learn.microsoft.com/nl-nl/windows/win32/api/shobjidl_core/nf-shobjidl_core-ifiledialogevents-onfileok) "The application can use this callback method to perform additional validation before the dialog closes, or to prevent the dialog from closing.". However, something is not working... – Maciej Pulikowski Nov 15 '21 at 23:08
  • 1
    I did not try the COM API, I just used a event hook around GetSaveFileName without OFN_ENABLEHOOK. – Anders Nov 16 '21 at 04:52
  • @Anders I will accept your answer. It is great but it has limitations, so I cannot use it :(. I was trying to run your code in a new API -> https://pastebin.com/a7pcXRMG ( yeah, I know my code is super ugly ), but it is not working. My CPP skills are none; I am from java/c#/js world. ;p – Maciej Pulikowski Nov 17 '21 at 23:01
  • 1
    You don't really need the fake button when using the COM interface. Call GetTickCount when IFileDialogEvents is created and return S_FALSE in OnFileOk until enough ticks have passed... – Anders Nov 17 '21 at 23:29
  • @Anders Thanks! The trick with GetTickCount worked! :) – Maciej Pulikowski Nov 18 '21 at 11:46