SysTutorials

Preventing Window Closure in MFC Applications

Blocking window closure in MFC applications is necessary for critical operations, multi-step dialogs, and workflows that require user confirmation before exit. This requires handling both the close button and the Alt+F4 keyboard shortcut.

Removing the Close Button from the System Menu

The simplest approach is to remove SC_CLOSE from the system menu in your dialog’s WM_INITDIALOG or WM_CREATE handler:

CMenu *pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL) {
    pSysMenu->RemoveMenu(SC_CLOSE, MF_BYCOMMAND);
}

This grays out and disables the X button in the title bar, but it doesn’t prevent Alt+F4—users can still close the window with that keyboard shortcut.

Why Removing WS_SYSMENU Doesn’t Work

You might think completely removing the WS_SYSMENU style would help:

// DON'T do this
ModifyStyle(WS_SYSMENU, 0);

This removes the entire system menu, including minimize, maximize, and restore buttons. You can’t selectively restore individual buttons afterward—Windows doesn’t provide granular control over title bar buttons through style flags alone.

Intercepting Alt+F4 and Close Events

Override OnSysCommand() in your dialog class to catch SC_CLOSE:

void MyDialog::OnSysCommand(UINT nID, LPARAM lParam)
{
    if ((nID & 0xFFF0) == SC_CLOSE) {
        MessageBox(_T("This window cannot be closed at this time."), _T("Action Not Allowed"));
        return;
    }
    else if ((nID & 0xFFF0) == IDM_ABOUTBOX) {
        CAboutDlg dlgAbout;
        dlgAbout.DoModal();
    }
    else {
        CDialog::OnSysCommand(nID, lParam);
    }
}

The 0xFFF0 mask filters out the low 4 bits, which contain state information—this compares only the command ID itself.

Also intercept WM_CLOSE for complete coverage, since some closure paths (taskbar close, DestroyWindow() calls) don’t go through OnSysCommand():

void MyDialog::OnClose()
{
    MessageBox(_T("Cannot close this window."), _T("Action Not Allowed"));
    return;  // Don't call CDialog::OnClose()
}

Add the message map entry:

BEGIN_MESSAGE_MAP(MyDialog, CDialogEx)
    ON_WM_CLOSE()
END_MESSAGE_MAP()

In practice, permanently blocking closure is poor UX. Instead, block closing only during specific operations:

void MyDialog::OnSysCommand(UINT nID, LPARAM lParam)
{
    if ((nID & 0xFFF0) == SC_CLOSE) {
        if (m_isOperationInProgress) {
            MessageBox(_T("Please wait for the operation to complete."), _T("Busy"));
            return;
        }
        CDialog::OnSysCommand(nID, lParam);
        return;
    }
    CDialog::OnSysCommand(nID, lParam);
}

void MyDialog::OnClose()
{
    if (m_isOperationInProgress) {
        MessageBox(_T("Cannot close while operation is in progress."), _T("Busy"));
        return;
    }
    CDialog::OnClose();
}

Add a member variable and toggle it during operations:

// In your dialog header
bool m_isOperationInProgress = false;

// When starting an operation
m_isOperationInProgress = true;

// When the operation completes or errors
m_isOperationInProgress = false;

Disabling Minimize and Maximize

If you need to lock down all title bar interactions during critical operations:

void MyDialog::OnSysCommand(UINT nID, LPARAM lParam)
{
    UINT command = nID & 0xFFF0;

    if ((command == SC_CLOSE || command == SC_MINIMIZE || command == SC_MAXIMIZE) 
        && m_isOperationInProgress) {
        MessageBox(_T("Window is locked during this operation."), _T("Locked"));
        return;
    }

    CDialog::OnSysCommand(nID, lParam);
}

Optionally remove these from the system menu preemptively:

CMenu *pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL) {
    pSysMenu->RemoveMenu(SC_CLOSE, MF_BYCOMMAND);
    pSysMenu->RemoveMenu(SC_MINIMIZE, MF_BYCOMMAND);
    pSysMenu->RemoveMenu(SC_MAXIMIZE, MF_BYCOMMAND);
}

Best Practices

Be temporary and user-friendly. Blocking closure should be brief and conditional:

← All posts