Fix crash when several instances of the file dialog are shown
Pass a pointer to FileDialogEventHandler instance via GWLP_USERDATA instead of using static variable. This way each window can have its own pointer. Fix #10290, close #10303
This commit is contained in:
parent
96e985dea5
commit
121f19d8f9
|
@ -180,6 +180,15 @@ namespace // anonymous
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LRESULT callWindowClassProc(const TCHAR* className, HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
|
||||||
|
{
|
||||||
|
WNDCLASSEX wndclass = {};
|
||||||
|
wndclass.cbSize = sizeof(wndclass);
|
||||||
|
if (GetClassInfoEx(nullptr, className, &wndclass) && wndclass.lpfnWndProc)
|
||||||
|
return CallWindowProc(wndclass.lpfnWndProc, hwnd, msg, wparam, lparam);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
// Backs up the current directory in constructor and restores it in destructor.
|
// Backs up the current directory in constructor and restores it in destructor.
|
||||||
// This is needed in case dialog changes the current directory.
|
// This is needed in case dialog changes the current directory.
|
||||||
class CurrentDirBackup
|
class CurrentDirBackup
|
||||||
|
@ -348,12 +357,10 @@ public:
|
||||||
: _cRef(1), _dialog(dlg), _customize(dlg), _filterSpec(filterSpec), _currentType(fileIndex + 1),
|
: _cRef(1), _dialog(dlg), _customize(dlg), _filterSpec(filterSpec), _currentType(fileIndex + 1),
|
||||||
_lastSelectedType(fileIndex + 1), _wildcardType(wildcardIndex >= 0 ? wildcardIndex + 1 : 0)
|
_lastSelectedType(fileIndex + 1), _wildcardType(wildcardIndex >= 0 ? wildcardIndex + 1 : 0)
|
||||||
{
|
{
|
||||||
_staticThis = this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
~FileDialogEventHandler()
|
~FileDialogEventHandler()
|
||||||
{
|
{
|
||||||
_staticThis = nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const generic_string& getLastUsedFolder() const { return _lastUsedFolder; }
|
const generic_string& getLastUsedFolder() const { return _lastUsedFolder; }
|
||||||
|
@ -376,9 +383,12 @@ private:
|
||||||
HRESULT hr = pOleWnd->GetWindow(&hwndDlg);
|
HRESULT hr = pOleWnd->GetWindow(&hwndDlg);
|
||||||
if (SUCCEEDED(hr) && hwndDlg)
|
if (SUCCEEDED(hr) && hwndDlg)
|
||||||
{
|
{
|
||||||
EnumChildWindows(hwndDlg, &EnumChildProc, 0);
|
EnumChildWindows(hwndDlg, &EnumChildProc, reinterpret_cast<LPARAM>(this));
|
||||||
if (_staticThis->_hwndButton)
|
if (_hwndButton && !GetWindowLongPtr(_hwndButton, GWLP_USERDATA))
|
||||||
_staticThis->_okButtonProc = (WNDPROC)SetWindowLongPtr(_staticThis->_hwndButton, GWLP_WNDPROC, (LPARAM)&OkButtonWndProc);
|
{
|
||||||
|
SetWindowLongPtr(_hwndButton, GWLP_USERDATA, reinterpret_cast<LPARAM>(this));
|
||||||
|
_okButtonProc = (WNDPROC)SetWindowLongPtr(_hwndButton, GWLP_WNDPROC, (LPARAM)&OkButtonWndProc);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -460,22 +470,27 @@ private:
|
||||||
|
|
||||||
// Enumerates the child windows of a dialog.
|
// Enumerates the child windows of a dialog.
|
||||||
// Sets up window procedure overrides for "OK" button and file name edit box.
|
// Sets up window procedure overrides for "OK" button and file name edit box.
|
||||||
static BOOL CALLBACK EnumChildProc(HWND hwnd, LPARAM)
|
static BOOL CALLBACK EnumChildProc(HWND hwnd, LPARAM param)
|
||||||
{
|
{
|
||||||
const int bufferLen = MAX_PATH;
|
const int bufferLen = MAX_PATH;
|
||||||
static TCHAR buffer[bufferLen];
|
static TCHAR buffer[bufferLen];
|
||||||
|
|
||||||
|
auto* inst = reinterpret_cast<FileDialogEventHandler*>(param);
|
||||||
|
if (!inst)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
if (IsWindowEnabled(hwnd) && GetClassName(hwnd, buffer, bufferLen) != 0)
|
if (IsWindowEnabled(hwnd) && GetClassName(hwnd, buffer, bufferLen) != 0)
|
||||||
{
|
{
|
||||||
if (lstrcmpi(buffer, _T("ComboBox")) == 0)
|
if (lstrcmpi(buffer, _T("ComboBox")) == 0)
|
||||||
{
|
{
|
||||||
// The edit box of interest is a child of the combo box and has empty window text.
|
// The edit box of interest is a child of the combo box and has empty window text.
|
||||||
// Note that there might be other combo boxes (file type dropdown, address bar, etc).
|
// We use the first combo box, but there might be the others (file type dropdown, address bar, etc).
|
||||||
HWND hwndChild = FindWindowEx(hwnd, nullptr, _T("Edit"), _T(""));
|
HWND hwndChild = FindWindowEx(hwnd, nullptr, _T("Edit"), _T(""));
|
||||||
if (hwndChild && !_staticThis->_hwndNameEdit)
|
if (hwndChild && !inst->_hwndNameEdit && !GetWindowLongPtr(hwndChild, GWLP_USERDATA))
|
||||||
{
|
{
|
||||||
_staticThis->_fileNameProc = (WNDPROC)SetWindowLongPtr(hwndChild, GWLP_WNDPROC, (LPARAM)&FileNameWndProc);
|
SetWindowLongPtr(hwndChild, GWLP_USERDATA, reinterpret_cast<LPARAM>(inst));
|
||||||
_staticThis->_hwndNameEdit = hwndChild;
|
inst->_fileNameProc = (WNDPROC)SetWindowLongPtr(hwndChild, GWLP_WNDPROC, reinterpret_cast<LPARAM>(&FileNameWndProc));
|
||||||
|
inst->_hwndNameEdit = hwndChild;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (lstrcmpi(buffer, _T("Button")) == 0)
|
else if (lstrcmpi(buffer, _T("Button")) == 0)
|
||||||
|
@ -493,19 +508,19 @@ private:
|
||||||
if ((type == BS_PUSHBUTTON || type == BS_DEFPUSHBUTTON) && (appearance == BS_TEXT))
|
if ((type == BS_PUSHBUTTON || type == BS_DEFPUSHBUTTON) && (appearance == BS_TEXT))
|
||||||
{
|
{
|
||||||
// Get the leftmost button.
|
// Get the leftmost button.
|
||||||
if (_staticThis->_hwndButton)
|
if (inst->_hwndButton)
|
||||||
{
|
{
|
||||||
RECT rc1 = {};
|
RECT rc1 = {};
|
||||||
RECT rc2 = {};
|
RECT rc2 = {};
|
||||||
if (GetWindowRect(hwnd, &rc1) && GetWindowRect(_staticThis->_hwndButton, &rc2))
|
if (GetWindowRect(hwnd, &rc1) && GetWindowRect(inst->_hwndButton, &rc2))
|
||||||
{
|
{
|
||||||
if (rc1.left < rc2.left)
|
if (rc1.left < rc2.left)
|
||||||
_staticThis->_hwndButton = hwnd;
|
inst->_hwndButton = hwnd;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_staticThis->_hwndButton = hwnd;
|
inst->_hwndButton = hwnd;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -532,13 +547,22 @@ private:
|
||||||
pressed = (wparam == VK_RETURN);
|
pressed = (wparam == VK_RETURN);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (pressed)
|
auto* inst = reinterpret_cast<FileDialogEventHandler*>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
|
||||||
_staticThis->onPreFileOk();
|
if (inst)
|
||||||
return CallWindowProc(_staticThis->_okButtonProc, hwnd, msg, wparam, lparam);
|
{
|
||||||
|
if (pressed)
|
||||||
|
inst->onPreFileOk();
|
||||||
|
return CallWindowProc(inst->_okButtonProc, hwnd, msg, wparam, lparam);
|
||||||
|
}
|
||||||
|
return callWindowClassProc(_T("Button"), hwnd, msg, wparam, lparam);
|
||||||
}
|
}
|
||||||
|
|
||||||
static LRESULT CALLBACK FileNameWndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
|
static LRESULT CALLBACK FileNameWndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
|
||||||
{
|
{
|
||||||
|
auto* inst = reinterpret_cast<FileDialogEventHandler*>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
|
||||||
|
if (!inst)
|
||||||
|
return callWindowClassProc(_T("Edit"), hwnd, msg, wparam, lparam);
|
||||||
|
|
||||||
// WM_KEYDOWN with wparam == VK_RETURN isn't delivered here.
|
// WM_KEYDOWN with wparam == VK_RETURN isn't delivered here.
|
||||||
// So watch for the keyboard input while the control has focus.
|
// So watch for the keyboard input while the control has focus.
|
||||||
// Initially, the control has focus.
|
// Initially, the control has focus.
|
||||||
|
@ -547,30 +571,28 @@ private:
|
||||||
switch (msg)
|
switch (msg)
|
||||||
{
|
{
|
||||||
case WM_SETFOCUS:
|
case WM_SETFOCUS:
|
||||||
_staticThis->_monitorKeyboard = true;
|
inst->_monitorKeyboard = true;
|
||||||
break;
|
break;
|
||||||
case WM_KILLFOCUS:
|
case WM_KILLFOCUS:
|
||||||
_staticThis->_monitorKeyboard = false;
|
inst->_monitorKeyboard = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// Avoid unnecessary processing by polling keyboard only on some messages.
|
// Avoid unnecessary processing by polling keyboard only on some messages.
|
||||||
bool checkMsg = msg > WM_USER;
|
bool checkMsg = msg > WM_USER;
|
||||||
if (_staticThis->_monitorKeyboard && !processingReturn && checkMsg)
|
if (inst->_monitorKeyboard && !processingReturn && checkMsg)
|
||||||
{
|
{
|
||||||
SHORT state = GetAsyncKeyState(VK_RETURN);
|
SHORT state = GetAsyncKeyState(VK_RETURN);
|
||||||
if (state & 0x8000)
|
if (state & 0x8000)
|
||||||
{
|
{
|
||||||
// Avoid re-entrance because the call might generate some messages.
|
// Avoid re-entrance because the call might generate some messages.
|
||||||
processingReturn = true;
|
processingReturn = true;
|
||||||
_staticThis->onPreFileOk();
|
inst->onPreFileOk();
|
||||||
processingReturn = false;
|
processingReturn = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return CallWindowProc(_staticThis->_fileNameProc, hwnd, msg, wparam, lparam);
|
return CallWindowProc(inst->_fileNameProc, hwnd, msg, wparam, lparam);
|
||||||
}
|
}
|
||||||
|
|
||||||
static FileDialogEventHandler* _staticThis;
|
|
||||||
|
|
||||||
long _cRef;
|
long _cRef;
|
||||||
com_ptr<IFileDialog> _dialog;
|
com_ptr<IFileDialog> _dialog;
|
||||||
com_ptr<IFileDialogCustomize> _customize;
|
com_ptr<IFileDialogCustomize> _customize;
|
||||||
|
@ -586,8 +608,6 @@ private:
|
||||||
bool _monitorKeyboard = true;
|
bool _monitorKeyboard = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
FileDialogEventHandler* FileDialogEventHandler::_staticThis;
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// Private implementation to avoid pollution with includes and defines in header.
|
// Private implementation to avoid pollution with includes and defines in header.
|
||||||
|
@ -723,10 +743,16 @@ public:
|
||||||
|
|
||||||
bool show()
|
bool show()
|
||||||
{
|
{
|
||||||
|
// Allow only one instance of the dialog to be active at a time.
|
||||||
|
static bool isActive = false;
|
||||||
|
if (isActive)
|
||||||
|
return false;
|
||||||
|
|
||||||
assert(_dialog);
|
assert(_dialog);
|
||||||
if (!_dialog)
|
if (!_dialog)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
isActive = true;
|
||||||
HRESULT hr = S_OK;
|
HRESULT hr = S_OK;
|
||||||
DWORD dwCookie = 0;
|
DWORD dwCookie = 0;
|
||||||
com_ptr<IFileDialogEvents> dialogEvents = _events;
|
com_ptr<IFileDialogEvents> dialogEvents = _events;
|
||||||
|
@ -755,6 +781,8 @@ public:
|
||||||
if (dialogEvents)
|
if (dialogEvents)
|
||||||
_dialog->Unadvise(dwCookie);
|
_dialog->Unadvise(dwCookie);
|
||||||
|
|
||||||
|
isActive = false;
|
||||||
|
|
||||||
return okPressed;
|
return okPressed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue