C++ Windows Credential Provider Progress Screen - c++

I am developing a custom credential provider and I have to show a progress screen with a cancel button. I have seen in some credentials providers and pgina plugins that a screen is displayed with a Cancel button when credential provider is working. I have attached a screenshot of it. I have managed to show the error screen with an Ok button using the following code:
*pcpgsr = CPGSR_NO_CREDENTIAL_NOT_FINISHED;
SHStrDupW(L"Authentication Failed", ppwszOptionalStatusText);
*pcpsiOptionalStatusIcon = CPSI_ERROR;
Now I need to show this progress screen with a cancel button. Any advice how can it be achieved? Also, how to handle the event that fires when this button is pressed?

As I understand your scenario you want to do something in background presenting to the user "wait screen".
You must run a separate thread for background work and change the layout of your credential tile to leave visible only one text element with "Wait..." content and no submit button.
Once your background thread complete its work you may reveal submit button and let user to continue to logon.
For example, have a look at embedded Smartcard Credential Porvider and its behaviour on insertion and removal of the card.

#js.hrt You can run your main thread as dialog, while your background thread does the job. The cancel button would be the control in the dialog, allowing to cancel it. If you need more info, let me know, I can provide some details, as this is the way we do it.
#js.hrt Briefly, you need two classes: dialog and thread.
When you create a dialog, it will create a thread, which will run what you need, and show cancel button. Clicking on it will terminate your thread. Some code below. Hope it helps.
class Thread {
public:
Thread(GUI* object);
virtual ~Thread();
bool start( bool ) {
::CreateThread( NULL, 0, threadRun, lpParameter, dwCreationFlags,
&m_dwThreadId );
}
static DWORD WINAPI threadRun( void* lpVoid ) {
DWORD dwReturn( 0 );
dwReturn = m_object->yourProcessToRun();
return dwReturn;
}
protected:
GUI* m_object;
Runnable* m_lpRunnable;
};
Then, class for your UI, similar to this
#include "atlwin.h"
class GUI: public CDialogImpl<GUI> {
public:
enum { IDD = IDD_FOR_YOUR_DIALOG };
GUI();
~GUI();
BEGIN_MSG_MAP(GUI)
MESSAGE_HANDLER(WM_INITDIALOG,OnInitDialog)
COMMAND_ID_HANDLER(ID_CANCEL,OnCancel)
MESSAGE_HANDLER(WM_TIMER,OnTimer)
MESSAGE_HANDLER(WM_DESTROY,OnDestroy)
END_MSG_MAP()
LRESULT OnInitDialog(UINT,WPARAM,LPARAM, BOOL&) {
myThread = new Thread(this);
m_nTimerID = SetTimer(1,3000,NULL);
myThread->start();
}
LRESULT OnCancel(WORD,WORD,HWND,BOOL& ) {
if(NULL != myThread) {
DWORD exitCode = 0;
myThread->getExitCode(exitCode);
if(exitCode == STILL_ACTIVE)
myThread->terminate();
delete myThread;
myThread = NULL;
}
EndDialog(IDCANCEL);
return true;
}
LRESULT OnTimer(UINT,WPARAM wParam,LPARAM,BOOL&) {
if(wParam != m_nTimerID)
return FALSE;
m_timerticks++;
return FALSE;
}
LRESULT OnDestroy(UINT,WPARAM,LPARAM,BOOL&) {
KillTimer(m_nTimerID);
return FALSE;
}
virtual int yourProcessToRun() {};
void onFinishProgress(int retCode = IDOK) {
if (retCode != IDCANCEL) {
delete myThread;
myThread = NULL;
KillTimer(m_nTimerID);
EndDialog(retCode);
}
}
private:
Thread* myThread;
UINT m_nTimerID;
UINT m_timerticks;
};
The resource for dialog could be like this:
IDD_FOR_YOUR_DIALOG DIALOGEX 0, 0, 309, 80
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP
| WS_CAPTION
CAPTION "Whatever"
FONT 8, "MS Shell Dlg", 400, 0, 0x0
BEGIN
PUSHBUTTON "Cancel",ID_CANCEL,113,50,84,14
CTEXT "Static",IDC_FOR_SOMETHING,7,7,295,20
END
#js.hrt If you don't mind to post your code, I'll make it run.
Can't comment to your's message directly, as I limit by site
requirements

#js.hrt Per your request.
class Thread {
public:
Thread(GUI* object);
virtual ~Thread();
bool start( bool ) {
::CreateThread( NULL, 0, threadRun, lpParameter, dwCreationFlags,
&m_dwThreadId );
}
static DWORD WINAPI threadRun( void* lpVoid ) {
DWORD dwReturn( 0 );
dwReturn = m_object->yourProcessToRun();
return dwReturn;
}
protected:
GUI* m_object;
Runnable* m_lpRunnable;
};
Then, class for your UI, similar to this
#include "atlwin.h"
class GUI: public CDialogImpl<GUI> {
public:
enum { IDD = IDD_FOR_YOUR_DIALOG };
GUI();
~GUI();
BEGIN_MSG_MAP(GUI)
MESSAGE_HANDLER(WM_INITDIALOG,OnInitDialog)
COMMAND_ID_HANDLER(ID_CANCEL,OnCancel)
MESSAGE_HANDLER(WM_TIMER,OnTimer)
MESSAGE_HANDLER(WM_DESTROY,OnDestroy)
END_MSG_MAP()
LRESULT OnInitDialog(UINT,WPARAM,LPARAM, BOOL&) {
myThread = new Thread(this);
m_nTimerID = SetTimer(1,3000,NULL);
myThread->start();
}
LRESULT OnCancel(WORD,WORD,HWND,BOOL& ) {
if(NULL != myThread) {
DWORD exitCode = 0;
myThread->getExitCode(exitCode);
if(exitCode == STILL_ACTIVE)
myThread->terminate();
delete myThread;
myThread = NULL;
}
EndDialog(IDCANCEL);
return true;
}
LRESULT OnTimer(UINT,WPARAM wParam,LPARAM,BOOL&) {
if(wParam != m_nTimerID)
return FALSE;
m_timerticks++;
return FALSE;
}
LRESULT OnDestroy(UINT,WPARAM,LPARAM,BOOL&) {
KillTimer(m_nTimerID);
return FALSE;
}
virtual int yourProcessToRun() {};
void onFinishProgress(int retCode = IDOK) {
if (retCode != IDCANCEL) {
delete myThread;
myThread = NULL;
KillTimer(m_nTimerID);
EndDialog(retCode);
}
}
private:
Thread* myThread;
UINT m_nTimerID;
UINT m_timerticks;
};
The resource for dialog could be like this:
IDD_FOR_YOUR_DIALOG DIALOGEX 0, 0, 309, 80
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP
| WS_CAPTION
CAPTION "Whatever"
FONT 8, "MS Shell Dlg", 400, 0, 0x0
BEGIN
PUSHBUTTON "Cancel",ID_CANCEL,113,50,84,14
CTEXT "Static",IDC_FOR_SOMETHING,7,7,295,20
END

I assume you are looking for IConnectableCredentialProviderCredential::Connect().
You need to implement the IConnectableCredentialProviderCredentialinterface and put your logic to the Connect() function. It is called right after Submit button pressed.
The Connect() function will give you the IQueryContinueWithStatus interface. In this interface you need to call the QueryContinue() function periodically, to handle Cancel button or some system events.
For more information look at this article: https://learn.microsoft.com/en-us/windows/win32/api/credentialprovider/nf-credentialprovider-iconnectablecredentialprovidercredential-connect

Related

How can I change the style of a control (CListBox) initialized with DDX_Control, before DDX_Control is called

I'm modifying an existing project and a dialog has controls I am subclassing to theme differently in some cases (in other cases I will leave it entirely alone). By the time DDX_Control() is called during DoDataExchange(), the hwnd for the ListBox already has styles applied. Specifically, at this time even if I do SetWindowLongPtr(), the LBS_OWNERDRAWFIXED does not work. By "does not work," I mean that although the style is applied, owner draw messages are not received by the CListBox.
Conversely, if I avoid the DDX_Control() and simply do a create, the ListBox does receive the messages and can be ownerdrawn. But if I do this there are now two HWND, only one of which is returned by GetDlgItem(). I believe I can make this work if necessary, but I wondered if there is a secret to intercept the HWND creation of the controls in the dialog (actually a CPropertyPage).
Below is code that doesn't work, with more commented code that "works" but isn't the way I wanted it to work, if possible.
void CMyPropertySheet::DoDataExchange(CDataExchange* pDX)
{
HWND hWndCtrl;
pDX->m_pDlgWnd->GetDlgItem(IDC_LIST1, &hWndCtrl);
if (themed) {
DWORD style = GetWindowLongPtr(hWndCtrl, GWL_STYLE) | LBS_OWNERDRAWFIXED;
SetWindowLongPtr(hWndCtrl, GWL_STYLE, style);
DDX_Control(pDX, IDC_LIST1, m_listbox);
//RECT wr;
//::GetWindowRect(hWndCtrl, &wr);
//m_listbox.Create(style, wr, this, IDC_LIST1);
} else {
DDX_Control(pDX, IDC_LIST1, m_listbox);
}
I should probably add I tried subclassing the window, but it didn't help, and CMyPropertySheet::PreSubclassWindow wasn't soon enough, either.
Some creation flags like LBS_OWNERDRAWFIXED and LBS_SORT are cached and modifying them afterwards have no effect. You have to change the template, or just make a duplicate of the listbox. Copy the style of the old listbox, then hide that listbox, change its ID, and create a new listbox based on the old one. You then have to remove DDX_Control(pDX, IDC_LIST1, m_listbox)
The example below starts with a standard list which has its sort flag set. It duplicates the listbox and disables the sort option.
For simplicity this example avoids LBS_OWNERDRAWFIXED, it uses LBS_SORT instead.
class CMyPropertyPage : public CPropertyPage
{
public:
CListBox m_listbox;
int m_listbox_index;
CMyPropertyPage(int idd) : CPropertyPage(idd)
{
m_listbox_index = 1;
}
void DoDataExchange(CDataExchange* pDX)
{
//This function is automatically called before
//CPropertyPage::OnInitDialog is complete
//On the first call, IDC_LIST1 will point to the template listbox
if(m_listbox.m_hWnd)
{
//m_listbox is ready,
//IDC_LIST1 will refer to the new listbox
DDX_LBIndex(pDX, IDC_LIST1, m_listbox_index);
}
}
BOOL OnInitDialog()
{
CPropertyPage::OnInitDialog();
CListBox* old_listbox = (CListBox*)GetDlgItem(IDC_LIST1);
if(old_listbox)
{
DWORD style = ~LBS_SORT & GetWindowLongPtr(old_listbox->m_hWnd, GWL_STYLE);
CRect rc;
old_listbox->GetWindowRect(&rc);
ScreenToClient(&rc);
old_listbox->SetDlgCtrlID(0);//change the old ID to something unused
old_listbox->ShowWindow(SW_HIDE); //hide the old listbox
m_listbox.Create(style | WS_BORDER, rc, this, IDC_LIST1);
m_listbox.SetFont(GetFont());
}
ASSERT(m_listbox.GetDlgCtrlID() == IDC_LIST1);
m_listbox.AddString(L"2");
m_listbox.AddString(L"1");
m_listbox.AddString(L"0");
UpdateData(FALSE);
return TRUE;
}
};
class CMyWinApp : public CWinApp
{
BOOL InitInstance()
{
CWinApp::InitInstance();
CPropertySheet sh;
CMyPropertyPage page(IDD_PAGE1);
sh.AddPage(&page);
sh.DoModal();
return TRUE;
}
} myapp;
OK, I'm not sure I recommend this to anyone, but I did find a way to modify the template, finally. I had to use VirtualProtect to unlock the memory of the template.
for (int i = 0; i < m_pages.GetSize(); i++) {
CPropertyPage* pPage = GetPage(i);
PROPSHEETPAGE* tpsp = &pPage->m_psp;
const DLGTEMPLATE* pTemplate;
if (tpsp->dwFlags & PSP_DLGINDIRECT) {
pTemplate = tpsp->pResource;
} else {
HRSRC hResource = ::FindResource(tpsp->hInstance, tpsp->pszTemplate, RT_DIALOG);
if (hResource == NULL) return false;
HGLOBAL hTemplate = LoadResource(tpsp->hInstance, hResource);
if (hTemplate == NULL) return false;
pTemplate = (LPCDLGTEMPLATE)LockResource(hTemplate);
if (pTemplate == NULL) return false;
}
if (afxOccManager != NULL) {
DLGITEMTEMPLATE *pItem = _AfxFindFirstDlgItem(pTemplate);
DLGITEMTEMPLATE *pNextItem;
BOOL bDialogEx = IsDialogEx(pTemplate);
int iItem, iItems = DlgTemplateItemCount(pTemplate);
for (iItem = 0; iItem < iItems; iItem++) {
pNextItem = _AfxFindNextDlgItem(pItem, bDialogEx);
DWORD dwOldProtect, tp;
if (bDialogEx) {
_DialogSplitHelper::DLGITEMTEMPLATEEX *pItemEx = (_DialogSplitHelper::DLGITEMTEMPLATEEX *)pItem;
if (pItemEx->id == IDC_LIST1) {
if (VirtualProtect(&pItemEx->style, sizeof(pItemEx->style), PAGE_READWRITE, &dwOldProtect)) {
pItemEx->style |= LBS_OWNERDRAWFIXED;
VirtualProtect(&pItemEx->style, sizeof(pItemEx->style), dwOldProtect, &tp);
}
}
} else {
if (pItem->id == IDC_LIST1) {
if (VirtualProtect(&pItem->style, sizeof(pItem->style), PAGE_READWRITE, &dwOldProtect)) {
pItem->style |= LBS_OWNERDRAWFIXED;
VirtualProtect(&pItem->style, sizeof(pItem->style), dwOldProtect, &tp);
}
}
}
pItem = pNextItem;
}
}
}
return true;

Open a MFC dialog in a std::thread

I would like to show a dialog to inform the user that the application is busy. To avoid blocking of the main thread, I was thinking to use a std::thread to show the dialog. Consider the following code:
InProcDlg inProcess;
std::thread t([ &inProcess ] {
inProcess.DoModal();
delete inProcess;
});
// wait till process has finished
::PostMessage(inProcess.m_hWnd, WM_USER + 1, 0, 0);
if (t.joinable()){
t.join();
}
InProcDlg.cpp
BEGIN_MESSAGE_MAP(InProcDlg, CDialogEx)
...
ON_MESSAGE(WM_USER + 1, &InProcDlg::close)
END_MESSAGE_MAP()
LRESULT InProcDlg::close(WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(wParam, lParam);
EndDialog(1);
return 0;
}
Running this code the dialog is shown properly. The dialog is also closed, but the main dialog is not shown, the application hangs in CreateRunDlgIndirect(). Trying to step in, while setting some breakpoints the main dialog is shown properly back again. Very strange. I would be very happy for any advices where I have to dive deeper in.
In the next step I would also like to show the process to the user, by sending an integer indicating the current state of process.
int *percent;
::PostMessage(inProcess.m_hWnd, WM_USER + 2, 0, reinterpret_cast<LPARAM>(percent));
How I can gain evidence that the dialog is already existing, before sending or posting a message?
I'm using Visual Studio 2013.
I can think of two ways to do that:
Modeless dialog
https://www.codeproject.com/Articles/1651/Tutorial-Modeless-Dialogs-with-MFC
User thread (UI thread)
Creating a brother to the main UI thread (CWinApp) by using CWinThread. Most important is to assign CWinThread::m_pMainWnd member, with a pointer to a Dialog. If the dialog is Modal you return FALSE right after the call to DoModal, and return TRUE for Modeless.
class CMainFrame : public CFrameWnd {
// pointer to thread
CWinThread* m_pUserThread;
}
start thread
m_pUserThread = AfxBeginThread(RUNTIME_CLASS(CUserThread), THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED );
m_pUserThread->m_bAutoDelete = TRUE;
m_pUserThread->ResumeThread();
headr file**
class CUserThread : public CWinThread
{
DECLARE_DYNCREATE(CUserThread)
public:
CUserThread();
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CUserThread)
public:
virtual BOOL InitInstance();
virtual int ExitInstance();
//}}AFX_VIRTUAL
protected:
virtual ~CUserThread();
// Generated message map functions
//{{AFX_MSG(CUserThread)
// NOTE - the ClassWizard will add and remove member functions here.
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
private:
CUserMsg* m_pDlg;
}
source file
#define DO_MODAL
BOOL CUserThread::InitInstance()
{
#ifdef DO_MODAL
// create and assign Modeless dialog
CUserMsg dlg;
m_pMainWnd = &m_pDlg;
dlg.DoModal();
return FALSE;
#endif
#ifdnef DO_MODAL
// create and assign Modal dialog
m_pDlg = new CUserMsg();
m_pDlg->Create( IDD_USER_DLG, AfxGetMainWnd() );
m_pMainWnd = m_pDlg;
return TRUE;
#endif
}
int CUserThread::ExitInstance()
{
// check for null
m_pDlg->SendMessage( WM_CLOSE ) ;
delete m_pDlg;
return CWinThread::ExitInstance();
}
to terminate the thread
// post quit message to thread
m_pUserThread->PostThreadMessage(WM_QUIT,0,0);
// wait until thread termineates
::WaitForSingleObject(m_pUserThread->m_hThread,INFINITE);
For both ways I would highly recommend making the dialog as a top most window:
BOOL CLicenseGenDlg::OnInitDialog() {
CDialog::OnInitDialog();
// TODO: Add extra initialization here
SetWindowPos( &wndTopMost, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | WS_EX_TOPMOST );
return TRUE;
}

C++ Win32 Listen for global keybinds

I am trying to implement global hotkeys on Windows in my C++/Qt application. I used RegisterHotKey, which surprisingly worked and I can see that pressing the combination triggers my event, but since I did not know any other way, I just used a loop, which now blocks my windows and stops it from ever showing up.
You can find my code below. How do I listen for this combination? There certainly has to be another way.
void set_win32_keys(MainWindow *mwin) {
HWND main_hwnd = (HWND)mwin->winId();
RegisterHotKey(main_hwnd, 2, MOD_CONTROL | MOD_SHIFT, 0x32 /*2 key*/);
MSG *msg;
msg = new MSG();
BOOL b_ret;
while ((b_ret = GetMessage(msg, main_hwnd, 0, 0)) != 0) {
if (b_ret == -1) {
qDebug() << "Error";
} else {
if (msg->message == WM_HOTKEY) {
mwin->new_screenshot();
qDebug() << msg;
}
}
}
}
Shameless plug: I have written a library for Qt that provides global hotkeys in a cross-platform manner - and allows the usage of for example QKeySequence to create the hotkey. It allows you to use a QKeySequenceEdit to let the user enter his own shortcut:
https://github.com/Skycoder42/QHotkey
Example:
//MainWindow constructor:
QHotkey *hotkey = new QHotkey(Qt::Key_2, Qt::ControlModifier | Qt::ShiftModifier, true, this);
connect(hotkey, &QHotkey::activated, this, &MainWindow::new_screenshot);
And that's it! Hope it helps
How can I listen to the system hot key bound to the app main window?
Many system events can be caught at main window native event handler. And the original author post actually binds to main window. The below should process the requested message:
class MainWindow : public QMainWindow
{
// ... snip ...
bool nativeEvent(const QByteArray& eventType, void* message, long* result) override;
// ... snip ...
};
bool MainWindow::nativeEvent(const QByteArray& eventType, void* message, long* result)
{
MSG* pMsg = reinterpret_cast<MSG*>(message);
if (pMsg->message == WM_HOTKEY)
{
// process pMsg->wParam / lParam etc.
// the intended action
this->new_screenshot();
}
// call the parent, could be QWidget::nativeEvent
return QMainWidow::nativeEvent(eventType, message, result);
}
Because of assumption that mwin has QMainWindow*:
HWND main_hwnd = (HWND)mwin->winId();
RegisterHotKey(main_hwnd, 2, MOD_CONTROL | MOD_SHIFT, 0x32 /*2 key*/);

Add tooltip to a CStatic

I haven't been able to find a concise chunk of code that allows me to add/display tooltips to a CStatic (and CLed) control. Obviously, the standard code to do so does not apply for this type of control. Can someone post code snippets?
I hope this code will solve your problem .One important thing make NOTIFY property of CStatic =TRUE.
if( !m_ToolTip.Create(this))
{
TRACE0("Unable to create the ToolTip!");
}
else
{
CWnd* pWnd = GetDlgItem(IDC_STATIC_MASTER_PWD);
m_ToolTip.AddTool(pWnd,"Ok");
m_ToolTip.Activate(TRUE);
}
Let me know if any problem.
I don't know if this is still needed, but here's what I used to solve the problem:
just add SS_NOTIFY to dwStyle when creating the static label. (or simply set "Nofity" "True" in the properties). That worked fine for me.
When I add CStatic on Dialog based autocreated mfc application, tooltips don't show until I add RelayEvent in pretranslate dialog message
BOOL CTooltipStaticDlg::PreTranslateMessage(MSG* pMsg)
{
m_ToolTip.RelayEvent(pMsg);
return CDialog::PreTranslateMessage(pMsg);
}
I've had success with multiline tooltips using this simple class:
Create a class for ToolTips:
class ToolTip
{
public:
static HWND CreateToolTip(int toolID, HWND hDlg, UINT id);
};
Next, implement a tooltip creation function:
HWND ToolTip::CreateToolTip(int toolID, HWND hDlg, UINT id)
{
if (!toolID || !hDlg || !id)
{
return FALSE;
}
CString strTTText;
strTTText.LoadString( id );
// Get the window handle of the control to attach the TT to.
HWND hwndTool = ::GetDlgItem(hDlg, toolID);
// Create the tooltip window
HWND hwndTip = CreateWindowEx(NULL, TOOLTIPS_CLASS, NULL,
WS_POPUP |TTS_ALWAYSTIP,// | TTS_BALLOON,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
hDlg, NULL,
AfxGetInstanceHandle() , NULL);
if (!hwndTool || !hwndTip)
{
return (HWND)NULL;
}
// Associate the tooltip with the tool.
TOOLINFO toolInfo = { 0 };
toolInfo.cbSize = sizeof(toolInfo);
toolInfo.hwnd = hDlg;
toolInfo.uFlags = TTF_IDISHWND | TTF_SUBCLASS;
toolInfo.uId = (UINT_PTR)hwndTool;
toolInfo.lpszText = (char*)(LPCTSTR)strTTText;
::SendMessage(hwndTip, TTM_ADDTOOL, 0, (LPARAM)&toolInfo);
::SendMessageA(hwndTip, TTM_SETMAXTIPWIDTH, 0, 40); // force multi-line
return hwndTip;
}
Call it somewhere in your InitDialog:
CMyDialog::InitDialog()
{
ToolTip::CreateToolTip( PickAUniqueNumber, m_hWnd, IDS_MY_RESOURCE_STRING );
}
I've had on my dialog label with assigned custom ID IDC_PATH. I needed to turn on Notify flag (SS_NOTIFY) of the label and I needed to overload CWnd method OnToolHitTest and handle tooltip hit test like this:
INT_PTR CPath::OnToolHitTest(CPoint point, TOOLINFO* pTI) const
{
INT_PTR r = CWnd::OnToolHitTest(point,pTI);
this->ClientToScreen(&point);
CRect rcLbl;
GetDlgItem(IDC_PATH)->GetWindowRect(&rcLbl);
if( rcLbl.PtInRect(point) )
{
pTI->uFlags |= TTF_IDISHWND;
pTI->uFlags &= ~TTF_NOTBUTTON;
pTI->uId = (UINT_PTR)GetDlgItem(IDC_PATH)->m_hWnd;
return IDC_PATH;
}
return r;
}
Then my dialog started to receive TTN_NEEDTEXT notification, which I handled and dynamicaly set text for tooltip.
BOOL CPath::OnTtnNeedText(UINT id, NMHDR *pNMHDR, LRESULT *pResult)
{
UNREFERENCED_PARAMETER(id);
TOOLTIPTEXT *pTTT = (TOOLTIPTEXT *)pNMHDR;
UINT_PTR nID = pNMHDR->idFrom;
BOOL bRet = FALSE;
if (pTTT->uFlags & TTF_IDISHWND)
{
// idFrom is actually the HWND of the tool
nID = ::GetDlgCtrlID((HWND)nID);
if(nID == IDC_PATH)
{
pTTT->lpszText = (LPSTR)(LPCTSTR)m_FullDestPath;
bRet = TRUE;
}
}
*pResult = 0;
return bRet;
}

Detecting modal dialogs in MFC

How can I programmatically detect if my MFC application currently is displaying a modal dialog or property sheet? Currently I'm using the following, but I feel that the code also triggers for modeless dialogs.
bool HasModalDialog(const CWnd* pWnd)
{
const CWnd* pChildWnd = pWnd ? pWnd->GetNextWindow(GW_HWNDPREV) : NULL;
while (pChildWnd)
{
if (pWnd == pChildWnd->GetTopLevelParent() &&
(pChildWnd->IsKindOf(RUNTIME_CLASS(CDialog)) ||
pChildWnd->IsKindOf(RUNTIME_CLASS(CPropertySheet))))
{
return true;
}
pChildWnd = pChildWnd->GetNextWindow(GW_HWNDPREV);
}
return false;
}
Usage:
HasModalDialog(AfxGetMainWnd())
Anyone got a alternative way of detecting modal dialogs?
Have you tried CWnd::GetLastActivePopup?
I haven't tested this to see if it'll work for modal dialogs only.
Edit 1: According to Raymond Chen, GetLastActivePopup should return the current active modal dialog.
Edit 2: Perhaps another method to retrieve the current modal window would be to modify your code to check for a disabled parent/owner - modal dialogs should always disable their owner before displaying.
I've tried many ways to solve that, why i needed that because i'm dealing with code that declare all the dialog as pointers to allocated in the heapmemory (TDialog* d = new TDialog) this was OWL code I converted it to MFC I want to delete those pointers automatically only if the dialog is modal it is not allocated in the heap, so i need to check for it my solution was easy to override the DoModal in my inherited class and set a flag isModal to true if it is not shown using DoModal the flag isModal will still null_ptr as it was initialized in the constructor
class : public CDialog
{
private:
bool isModal
public:
CMyDlg(int id, CWnd* parent = NULL) : CDialog(id, parent), isModal(false)
{
}
virtual INT_PTR DoModal()
{
isModal = true;
return CDialog::DoModal();//return __super::DoModal();
}
bool IsModal()
{
return isModal;
}
virtual void OnCancel()
{
if(isModal)
{
CDialog::OnCancel();
}
else
{
DestroyWindow();
}
}
virtual void OnOk()
{
if(isModal)
{
CDialog::OnCancel();
}
else
{
DestroyWindow();
}
}
virtual void PostNcDestroy()
{
delete this;
}
}
If you are only detecting windows within your application then you could derive your own CDialog and CPropertySheet and put a simple bool in there that keeps track of whether it is modal or not.
bool HasModalDialog(const CWnd* pWnd)
{
const CWnd* pChildWnd = pWnd ? pWnd->GetNextWindow(GW_HWNDPREV) : NULL;
while (pChildWnd)
{
if (pWnd == pChildWnd->GetTopLevelParent() )
{
if ( pChildWnd->IsKindOf(RUNTIME_CLASS(CMyDialog) )
{
return ((CMyDialog*)pChildWnd)->IsModal();
}
if ( pChildWnd->IsKindOf(RUNTIME_CLASS(CMyPropertySheet) )
{
return ((CMyPropertySheet*)pChildWnd)->IsModal();
}
}
pChildWnd = pChildWnd->GetNextWindow(GW_HWNDPREV);
}
return false;
}
There must be another way to do it but thats the only way I can think of off the top of my head.
I can't believe windows doesn't offer a function to do this; as calling EndDialog for a modeless dialog is undefined behaviour. But modal dialog box must use EndDialog. So if I don't want to create two seperate dialog procedures, how the heck will I know the right way to close the dialog.
Anyhow my simple solution is to to create a custom DialogBox style. Following is list of all dialog box styles
DS_FIXEDSYS = 0x0008
DS_SETFONT = 0x0040
DS_ABSALIGN = 0x0001
DS_CENTER = 0x0800
DS_CENTERMOUSE = 0x1000
DS_CONTEXTHELP = 0x2000
DS_CONTROL = 0x0400
DS_LOCALEDIT = 0x0020
DS_MODALFRAME = 0x0080
DS_NOFAILCREATE = 0x0010
DS_SETFOREGROUND = 0x0200
DS_SYSMODAL = 0x0002
DS_3DLOOK = 0x0004
DS_NOIDLEMSG = 0x0100
Close inspection shows that all dialog box styles reside on there own bits. Note that I ignored DS_SHELLFONT as it is simply a compound style of DS_SETFONT and DS_FIXEDSYS.
Thus our custom style just simply falls on its own bit.
JAV_DS_IS_MODELESS = 0x4000
Then when creating a modeless dialog box we set that style into it. Note that using DS_SYSMODAL style instead might be undefined according to MSDN documentation.
struct WindowContent
{
virtual void onClose(HWND hwnd) { closeWindow(hwnd,0); }
void closeWindow(HWND hwnd,int result);
}
BOOL CALLBACK dlgProc(HWND hwnd,UINT msg,WPARAM,LPARAM lparam)
{
WindowContent *content = (WindowContent*)GetWindowLongPtr(hwnd,GWL_USERDATA);
if(msg == WM_INITDIALOG
{
content = (WindowContent*)lparam;
SetWindowLongPtr(hwnd,GWL_USERDATA,content);
return 0;
}
if(msg == WM_CLOSE)
{
content->onClose(hwnd);
return 0;
}
return 0;
}
HWND createDialog(const char *dlg_template_name,WindowContent *content,HWND owner=NULL)
{
HWND dlg = CreateDialogParamA(NULL,dlg_template_name,owner,dlgProc,(LPARAM)content);
UINT old_style = GetWindowLong(dlg,GWL_STYLE);
SetWindowLong(dlg,GWL_STYLE,old_style|JAV_IS_MODELESS);
return 0;
}
void WindowContent::closeWindow(HWND hwnd,int result)
{
if( GetClassLong(hwnd,GCW_ATOM) == (int)WC_DIALOG )
{
if(GetWindowLong(hwnd,GWL_STYLE) & JAV_DS_IS_MODELESS) DestroyWindow(hwnd);
else EndDialog(hwnd,result);
}
else DestroyWindow(hwnd);
}