C++ Win32 Listen for global keybinds - c++

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*/);

Related

C++ Windows Credential Provider Progress Screen

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

Get Raw Mouse Movement in Qt

After working on this and QAbstractNativeEventFilter class I finally got native events from HID (both mouse and keyboard).
I've read many similar questions but none solved my problem. I try to get mouse movement based on dpi. I work on Qt 5.5 cause my whole project built there.
I can't separate mouse movement events from other HID events (mouse and keyboard) even with RIM_TYPEMOUSE flag.
Here is some code from my implementation:
bool MouseRawMovement::nativeEventFilter(const QByteArray &eventType, void *message, long *result)
{
if(eventType == "windows_generic_MSG")
{
MSG *msg = reinterpret_cast<MSG*>(message);
qDebug()<<msg->message; // It prints numbers such as 6,26,28,141 on each event
if(msg->message == WM_INPUT) //it never gets in
{
UINT dwSize = 40;
static BYTE lpb[40];
GetRawInputData((HRAWINPUT)msg->lParam, RID_INPUT,
lpb, &dwSize, sizeof(RAWINPUTHEADER));
RAWINPUT* raw = (RAWINPUT*)lpb;
if (raw->header.dwType == RIM_TYPEMOUSE)
{
int xPosRelative = raw->data.mouse.lLastX;
int yPosRelative = raw->data.mouse.lLastY;
qDebug()<<xPosRelative<<yPosRelative ;
}
}
}
return false;
}
Also here is my constructor
MouseRawMovement::MouseRawMovement()
{
Rid[0].usUsagePage = 0x01;
Rid[0].usUsage = 0x02;
Rid[0].dwFlags = RIDEV_INPUTSINK;
Rid[0].hwndTarget = 0;
if(!RegisterRawInputDevices(Rid, 1, sizeof(Rid[0])))
qDebug()<<QString::number(GetLastError()); //I see error msg 6 - Ref. ERROR_INVALID_HANDLE
}
Output shows me zeros (0) all time.
Whats going on with hWnd. I tried to give this:
HWND hWnd =::GetConsoleWindow();
but I had the same result.
In main.cpp I install native Filter
MainWindow w;
a.installNativeEventFilter(&w.mm);
I try for days and I could not find the solution. Is there anyone...(???)
#nnatarr your help was substantial! Thank you!!!
I finally find the solution.
I had to call RegisterRawInputDevices in main.cpp and change lot of things.
Here is main.cpp
#include "mainwindow.h"
#include <QApplication>
#include <windows.h>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
a.installNativeEventFilter(&w.mm);
w.show();
UINT nDevices;
PRAWINPUTDEVICELIST pRawInputDeviceList;
if (!GetRawInputDeviceList(NULL, &nDevices, sizeof(RAWINPUTDEVICELIST)))
{
qDebug() << "ERROR -- GetRawInputDeviceList ...";
return 1;
}
if (!(pRawInputDeviceList = (PRAWINPUTDEVICELIST)malloc(sizeof(RAWINPUTDEVICELIST) * nDevices)))
{
qDebug() << "Initialization failed...";
return 1;
}
RAWINPUTDEVICE Rid[1];
Rid[0].usUsagePage = HID_USAGE_PAGE_GENERIC;
Rid[0].usUsage = HID_USAGE_GENERIC_MOUSE;
Rid[0].dwFlags = RIDEV_INPUTSINK;
Rid[0].hwndTarget = (HWND)w.effectiveWinId();
if(!RegisterRawInputDevices(Rid, 1, sizeof(Rid[0])))
qDebug()<<"Huston Problem.";
qDebug()<<QString::number(GetLastError());
return a.exec();
}
And here is a part from Mouse Handlig Class
bool MouseRawMovement::nativeEventFilter(const QByteArray &eventType, void *message, long *result)
{
if(eventType == "windows_generic_MSG")
{
MSG *msg = reinterpret_cast<MSG*>(message);
if(msg->message == WM_INPUT)
{
UINT dwSize = 40;
static BYTE lpb[40];
if(!GetRawInputData((HRAWINPUT)msg->lParam, RID_INPUT,lpb, &dwSize, sizeof(RAWINPUTHEADER)))
qDebug()<<"Error GetRawInputData";
else
{
RAWINPUT* raw = (RAWINPUT*)lpb;
if (raw->header.dwType == RIM_TYPEMOUSE)
{
int xPosRelative = raw->data.mouse.lLastX;
int yPosRelative = raw->data.mouse.lLastY;
//qDebug()<<xPosRelative<<yPosRelative;
}
}
}
}
return false;
}
As the GetRawInputData MSDN page states, first parameter of this function is
hRawInput [in] Type: HRAWINPUTA handle to the RAWINPUT structure. This comes from the lParam in WM_INPUT.
So, you need to check first if message you are processing is a WM_INPUT message (msg->message == WM_INPUT) and only then try to extract raw input data.
Next, the lParam of WM_INPUT message is
lParam
A handle to the RAWINPUT structure that contains the raw input from the device.
as it says on WM_INPUT MSDN page. You need to use this handle in GetRawInputData function. Right now, you are using incorrect data handle so GetRawInputData do not return any valid information to you (it just doesn't know where to take data to process).
You should read article MSDN: Using Raw Input. There you can find sample code for the keybaord and mouse raw input processing.
Useful links:
MSDN: GetRawInputData
MSDN: WM_INPUT
MSDN: RegisterRawInputDevices – you should associate your application with raw input to receive WM_INPUT messages
MSDN: Using Raw Input – sample code for registering for raw input and for raw input processing.
One more thing. You can use equality operator to compare QByteArray instance to a string, in your case it will be like this: if (eventType == "windows_generic_MSG") {...}. This is because QByteArray has the overloaded equality operator:
bool QByteArray::operator==(const QString & str) const
You can read about it at this page: QByteArray::operator==.
UPDATE
MSDN: RAWINPUTDEVICE page notes that
RIDEV_INPUTSINK 0x00000100 If set, this enables the caller to receive
the input even when the caller is not in the foreground. Note that
hwndTarget must be specified.
You have the INVALID_HANDLE_ERROR error because you need to specify hWnd of your window.
What is MainWindow class? Do you inherit from QMainWindow or QWidget? Every widget in Qt have the winId property (WId QWidget::winId() const) which is the very hWnd you are looking for. So you need to take winId() of your window, cast it to HWND and write into the Rid structure like this:
Rid[0].hwndTarget = (HWND)w->winId();
If it won't help, then you need to provide a Minimal, Complete, and Verifiable example for further investigation.

Worker threads and MFC controls

I'm aware of the fact that MFC GUI controls are not accessible directly from a worker thread, but for example, they getting by passing to this thread a pointer to the object instance that owns the controls. My problem is, that I'm really sure about how it goes when I'm calling functions within the scope of the worker thread, which needs to access MFC controls. Please consider the following code:
//header:
class CMyDlg : public CDialog
{
...
...
...
afx_msg void OnButtonControl();
static UNIT ControlThread(LPVOID pParam);
bool ValidateEditControl();
}
//cpp
void CMyDlg::OnButtonControl()
{
CString Text = "Hello";
GetDlgItem(IDC_EDIT_HELLO)->SetWindowText(Text);
m_hControlThread = AxtBeginThread(ControlThread, this);
}
UINT CMyDlg::ControlThread(LPVOID pParam)
{
CMyDlg *dlg = (CMyDlg*) pParam;
CString Text = "Hello";
while(SomethingIsTrue) {
bool Ret = dlg->ValidateEditControl();
if (!Ret) //Someone changed ControlEntry -> change it back
dlg->GetDlgItem(IDC_EDIT_HELLO)->SetWindowText(Text);
}
AfxEndThread(0);
}
bool CMyDlg::ValidateEditControl()
{
CString Text;
this->GetDlgItem(IDC_EDIT_HELLO)->GetWindowText(Text); // do I need the "this" pointer here, or for general how do I access my MFC control at this point?
if (Text == "Hello")
return true;
else
return false;
}
What is the best way to this?
Thank you in advance
best Greg
Without going into too much details, here is how you should do it. I have't build, judged or modified your basic code, I have just addressed your threading part of question. You should be able to take it from here.
UINT CMyDlg::ControlThread(LPVOID pParam)
{
HWND hWnd = (HWND) pParam;
CString Text = "Hello";
while(SomethingIsTrue) {
bool Ret = SendMessage(HwND, VALIDATE_CONTROL,0,0 );
if (!Ret) //Someone changed ControlEntry -> change it back
SendMessage(CHANGE_EDIT_HELLO, &Text, 0);
}
AfxEndThread(0);
}

Receive WM_COPYDATA messages in a Qt app

I am working on a Windows-only Qt application, and I need to receive data from a Microsoft OneNote plugin. The plugin is written in C#, and can send WM_COPYDATA messages. How do I receive these messages in a C++ Qt app?
I need to:
Be able to specify the "class name" a window registers as when it calls RegisterClassEx, so that I can make sure the plugin sends WM_COPYDATA messages to the correct window.
Have access to the message id to check if it's WM_COPYDATA and lParam, which contains the COPYDATASTRUCT with the actual data. This information is passed in WndProc, but I am unable to find a hook where I can intercept these messages.
This can all be handled within Qt:
Extend QWidget with a class that will capture the WM_COPYDATA messages:
class EventReceiverWindow : public QWidget
{
Q_OBJECT
public:
EventReceiverWindow();
signals:
void eventData(const QString & data);
private:
bool winEvent ( MSG * message, long * result );
};
Generate a GUID to set as the QWidget's windowTitle:
EventReceiverWindow::EventReceiverWindow()
{
setWindowTitle("ProjectName-3F2504E0-4F89-11D3-9A0C-0305E82C3301::EventReceiver");
}
Override winEvent to handle the WM_COPYDATA structure and emit a signal when you get it:
bool EventReceiverWindow::winEvent ( MSG * message, long * result )
{
if( message->message == WM_COPYDATA ) {
// extract the string from lParam
COPYDATASTRUCT * data = (COPYDATASTRUCT *) message->lParam;
emit eventData(QString::fromAscii((const char *)data->lpData, data->cbData));
// keep the event from qt
*result = 0;
return true;
}
// give the event to qt
return false;
}
In another class, you can use this class to receive the message strings:
EventReceiverWindow * eventWindow = new EventReceiverWindow;
QObject::connect(eventWindow, SIGNAL(eventData(const QString &)), this, SLOT(handleEventData(const QString &)));
...
void OneNoteInterface::handleEventData(const QString &data)
{
qDebug() << "message from our secret agent: " << data;
}
And in the program that is sending the messages, simply find the window by the unique window caption. Here's an example in C#:
private struct COPYDATASTRUCT
{
public IntPtr dwData;
public int cbData;
[MarshalAs(UnmanagedType.LPStr)]
public string lpData;
}
private const int WM_COPYDATA = 0x4A;
[DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)]
static extern IntPtr FindWindowByCaption(IntPtr ZeroOnly, string lpWindowName);
[DllImport("User32.dll", EntryPoint = "SendMessage")]
private static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, ref COPYDATASTRUCT lParam);
private void sendMessageTo(IntPtr hWnd, String msg)
{
int wParam = 0;
int result = 0;
if (hWnd != IntPtr.Zero )
{
byte[] sarr = System.Text.Encoding.Default.GetBytes(msg);
int len = sarr.Length;
COPYDATASTRUCT cds;
cds.dwData = IntPtr.Zero;
cds.lpData = msg;
cds.cbData = len + 1;
result = SendMessage(hWnd, WM_COPYDATA, wParam, ref cds);
}
}
then you can:
IntPtr hwnd = FindWindowByCaption(IntPtr.zero, "ProjectName-3F2504E0-4F89-11D3-9A0C-0305E82C3301::EventReceiver");
sendMessageTo(hwnd, "omg hai");
You can also create a dummy window just for receiving that message with the Win32 API. I guess you won't have access to a Qt-Window's window proc, so this should be the easiest way.
You could (I wouldn't) also subclass the window by setting a new WndProc (with SetWindowLong(Ptr), the window's handle can be obtained with QWidget::winId()). In this WndProc, you could just handle your specific WM_COPYDATA and pass all other window messages to the old WndProc.
To handle messages your window receives, override your QCoreApplication::winEventFilter. If that doesn't work you can take a look at QAbstractEventDispatcher.
For the class name you could try using QWidget::winId along with Win32 API. I would try and find it for you but I can't right now, maybe try GetClassName.
You can use QWinHost from Qt solutions to create a dummy window. Following the guide will show you how to specify your class name and check the event loop for your message.

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);
}