I have created a small MFC Document View App in C++ and I am having some trouble receiving messages in a class that inherits from CStatic. I have managed to create the CStatic derivative and it is visible on my View however my message handlers are not being fired.
When using Spy++ it seems that window is only receiving WM_NCHITTEST and it is returning HTTRANSPARENT, which according to MSDN means:
"In a window currently covered by another window in the same thread (the message will be sent to underlying windows in the same thread until one of them returns a code that is not HTTRANSPARENT)."
Here is an exert from Spy++:
<000001> 001D1350 S WM_NCHITTEST xPos:128 yPos:167
<000002> 001D1350 R WM_NCHITTEST nHittest:HTTRANSPARENT
<000003> 001D1350 S WM_NCHITTEST xPos:128 yPos:166
<000004> 001D1350 R WM_NCHITTEST nHittest:HTTRANSPARENT
<000005> 001D1350 S WM_NCHITTEST xPos:128 yPos:165
<000006> 001D1350 R WM_NCHITTEST nHittest:HTTRANSPARENT
<000007> 001D1350 S WM_NCHITTEST xPos:128 yPos:164
<000008> 001D1350 R WM_NCHITTEST nHittest:HTTRANSPARENT
This seems strange because the CStatic derivative is the only child window of my view. I created it like this:
Create(pItem->Value->GetBuffer(), WS_CHILD | WS_VISIBLE | SS_CENTER, Rect, Parent);
ShowWindow(SW_SHOW);
where Parent is a pointer to the CView.
Any help would be really appreciated.
EDIT:
Foo.h
class Foo: public CStatic
{
DECLARE_DYNAMIC(Foo)
public:
Foo();
virtual ~Foo();
virtual void CreateCtrl(CWnd * Parent, POINT TopLeft, SIZE sz);
protected:
DECLARE_MESSAGE_MAP()
public:
afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
};
Foo.cpp
void Foo::CreateCtrl(CWnd * Parent, POINT TopLeft, SIZE sz)
{
CRect Rect(TopLeft, sz);
Create(pItem->Value->GetBuffer(), WS_CHILD | WS_VISIBLE | SS_CENTER, Rect, Parent);
ShowWindow(SW_SHOW);
}
BEGIN_MESSAGE_MAP(Foo, CStatic)
ON_WM_LBUTTONUP()
END_MESSAGE_MAP()
void Foo::OnLButtonUp(UINT nFlags, CPoint point)
{
AfxMessageBox("Hello World!");
__super::OnLButtonUp(nFlags, point);
}
See Microsoft's article "About Static Controls", and in particular this part:
WM_NCHITTEST: Returns HTCLIENT if the control style is SS_NOTIFY; otherwise, returns HTTRANSPARENT.
Once the window returns HTTRANSPARENT from WM_NCHITTEST, all further mouse messages go to the window underneath it in Z-order; in your case, the parent view. The window is "transparent" as far as mouse handling is concerned.
After a bit of experimenting, it looks like setting and additional SS_NOTIFY style in Foo::CreateCtrl() gets MFC to call Foo::OnLButtonUp().
I am a bit confused with this style setting, specially after reading this SO post; the MSDN page for SS_NOTIFY just says "Sends the parent window STN_CLICKED, STN_DBLCLK, STN_DISABLE, and STN_ENABLE notification codes when the user clicks or double-clicks the control."
Maybe without the SS_NOTIFY style, it doesn't have to receive messages because they are not relayed to the parent?
Anyway, adding the SS_NOTIFY style seems to make it work!
Related
I am a newcomer to programming. I am writing a dialog based application which has a static control on it. Using
Using
void CMy1stDlg::OnMouseMove(UINT nFlags, CPoint point)
{
if (this == GetCapture())
{
CClientDC aDC(this);
aDC.SetPixel(point, RGB(255,0,0));
}
}
I can create results like
However what I want is that the locus of mouse is only drawn within the static window. I can't find the reference to this in MSDN and I don't know why the following method fails.
void CMy1stDlg::OnMouseMove(UINT nFlags, CPoint point)
{
CWnd* pMYSTATIC = GetDlgItem (IDC_MYSTATIC); //IDC_MYSTATIC is the ID of the static control
if (pMYSTATIC == GetCapture())
{
CClientDC aDC(pMYSTATIC);
aDC.SetPixel(point, RGB(255,0,0));
}
}
How can I get what I want? Are there any methods to get something for the static window analogous to this? I will appreciate any help with these.
OK, try this:
void CMy1stDlg::OnMouseMove(UINT nFlags, CPoint point)
{
CRect rect;
// Get static control's rectangle
GetDlgItem(IDC_MYSTATIC)->GetWindowRect(&rect);
// Convert it to client coordinates
ScreenToClient(&rect);
// Check if mouse pointer is inside the static window, if so draw the pixel
if (rect.PtInRect(point))
{
CClientDC dc(this);
dc.SetPixel(point.x, point.y, RGB(255,0,0));
}
}
This code may need some fixes too, eg shrink the rectangle (to its client-only area), before checking whether to draw the pixel.
Please note that you don't need to check GetCapture(); if your dialog hasn't captured the mouse it won't be receiving this message anyway.
Also, all these functions are wrappers of Windows SDK ones, eg the ClientDC() class, basically wraps GetDC()/ReleaseDC().
I have an MFC application which is composed of multiple threads, but the problem is with a specific two.
The first thread (CGuiThread) is responsible for GUI (it's not the main thread) and contains a window object (CMainWindow), which contains an inner window object (CInnerWindow), which displays multiple progress displays and has a scroll bar.
The second thread (CStatusDispatcherThread) is responsible for sending to the gui thread messages, which contain progress status information related to some calculation processes.
Once the calculations begin, the status dispatcher sends messages with the status to the GUI thread. The gui thread updates progress bars in the inner window accordingly.
The problem starts when I move or hold the thumb of the inner window's scroll bar - it seems that GUI thread stops processing the status messages from the status dispatcher thread, since the progress bars are no longer updated. Not only that, I'd expect the status messages to be stopped somewhere and processed once I release the tumb, but it is not happening. New messages arrive but the messages that while clicking are lost.
If anyone has an idea what could be the cause, I would be very grateful.
I tried "catching" the status messages in the CGuiThread::PreaTranslateMessage function, but it seems that after holding the scroll thumb, they no longer get there, even though PostThreadMessage of CStatusDispatcherThread indicates they were sent successfully.
#define MY_MESSAGE 1
class CStatusDispatcherThread : public CWinThread
{
//...
// This class sends progress status percentaget to gui thread via PostThreadMessage
OnTimer(UINT nIDEvent)
{
PostThreadMessage(iThreadID,MY_MESSAGE,100,0);
}
};
class CGuiThread : public CWinThread
{
//...
BEGIN_MESSAGE_MAP(CGuiThread, CWinThread)
ON_THREAD_MESSAGE(MY_MESSAGE,OnStatusMessage)
END_MESSAGE_MAP()
private:
CMyMainWindow m_mainWindow;
void OnStatusMessage(WPARAM iStatus, LPARAM dummy);
{
m_mainWindow.updateStatus((int)iStatus)
}
};
class CMyMainWindow : public CWnd
{
//...
void updateStatus(int iStatus)
{
m_sbarWindow.updateStatusBar(iStatus);
}
private:
CInnerWindow m_sbarWindow;
};
class CInnerWindow : public CWnd
{
//...
void updateStatusBar(int iStatus)
{
//...
}
private:
BOOL Create(...)
{
CWnd::Create(strClassName, strWindowTitle, WS_DLGFRAME | WS_CHILD| WS_VISIBLE | WS_VSCROLL,
rectRectOfWnd, pParentWnd, iID, NULL);
}
void OnVScroll(nSBCode, nPos, pScrollBar)
{
//...
}
};
Thank in advance,
Gal
It is documented to fail. From the MSDN page on PostThreadMessage:
if the recipient thread is in a modal loop (as used by MessageBox or
DialogBox), the messages will be lost.
Holding down the scroll slider creates such a modal loop. You can eliminate the problem by posting to an HWND, not to a thread ID.
The only thread that is allowed to update the GUI is the main thread. Otherwise you'll end up with unpredicted behavior.
I created an application in which I would like to add a loading screen when the application is opening a project because the loading of the project can be long and sometimes, the gui blocks so the user can think there is a crash.
So I tried with QThread, reading doc and "solved" examples on this forum but nothing to do, I can't make it work.
I have a MainWindow class which deals with GUI and this class is the one I create in the main function :
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
Then I have :
mainwindow.h
class MyThread;
class MainWindow : public QMainWindow
{
Q_OBJECT
...
private :
Controller *controller;//inherits from QObject and loads the project
QList<MyThread*> threads;
public slots :
void animateLoadingScreen(int inValue);
}
mainwindow.cpp
MainWindow::MainWindow(...)
{
controller=new Controller(...);
threads.append(new MyThread(30, this));
connect(threads[0], SIGNAL(valueChanged(int)), this, SLOT(animateLoadingScreen(int)));
}
void MainWindow::animateLoadingScreen(int inValue)
{
cout<<"MainWindow::animateLoadingScreen"<<endl;
widgetLoadingScreen->updateValue(inValue);//to update the animation
}
void MainWindow::openProject()
{
widgetLoadingScreen->show()://a widget containing a spinner for example
threads[0]->start();//I want to launch the other thread here
controller->openProject();
threads[0]->exit(0);//end of thread so end of loading screen
}
MyThread.h
class MyThread : public QThread
{
Q_OBJECT;
public:
explicit MyThread(int interval, QObject* parent = 0);
~MyThread();
signals:
void valueChanged(int);
private slots:
void count(void);
protected:
void run(void);
private:
int i;
int inc;
int intvl;
QTimer* timer;
};
MyThread.cpp
MyThread::MyThread(int interval, QObject* parent): QThread(parent), i(0), inc(-1), intvl(interval), timer(0)
{
}
void MyThread::run(void)
{
if(timer == 0)
{
timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(count()));
}
timer->start(intvl);
exec();
}
void MyThread::count(void)
{
if(i >= 100 || i <= 0)
inc = -inc;
i += inc;
emit valueChanged(i);
}
When I execute the app, and click on open button which launches MainWindow::openProject(), I get :
QObject: Cannot create children for a parent that is in a different thread.
(Parent is MyThread(0x5463930), parent's thread is QThread(0x3cd1f80), current thread is MyThread(0x5463930)
MainWindow::animateLoadingScreen
MainWindow::animateLoadingScreen
....
MainWindow::animateLoadingScreen
MainWindow::animateLoadingScreen
(and here the controller outputs. and no MainWindow::animateLoadingScreen anymore so the widget loading screen is never animated during the opening of the project)
So what do I have to do, what do I have to put in MyThread class, how to link its signal to MainWindow to update the loading screen. And I think that there may be a problem with widgetLoadingScreen which is created in MainWindow so if MainWindow is blocked beacause of the opening, the widgetLoadingScreen can't be updated since it is in the thread of MainWindow which handles the GUI ?!
I read :
http://www.qtcentre.org/wiki/index.php?title=Updating_GUI_from_QThread
but with that one, I got error message at runtime, it's the one I use in the code I give above
QObject: Cannot create children for a parent that is in a different thread.
(Parent is MyThread(0x41938e0), parent's thread is QThread(0x1221f80), current thread is MyThread(0x41938e0)
I tried that too :
How to emit cross-thread signal in Qt?
but, even if I don't have the error message at runtime, it's the same for the animation which is not updated.
I am completely lost, and I don't think it's something difficult to do to have a loading screen in a thread while the main thread is opening a project?!
Qt has a QSplashScreen class which you could use for a loading screen.
Besides this, one of the problems you have is due to thread affinity (the thread in which an object is running) and the fact that you've decided to inherit from QThread.
In my opinion, QThread has a misleading name. It's more of a thread controller and unless you want to change how Qt handles threads, you really shouldn't inherit from it and many will tell you that "You're doing it Wrong!"
By inheriting QThread, your instance of MyThread is running on the main thread. However, when a call to exec() is made, it starts the event loop for the new thread and all its children, which includes the QTimer, and tries to move it to the new thread.
The correct way to solve this is not to inherit from QThread, but to create a worker object, derived from QObject and move it to the new thread. You can read about how to 'Really Truly Use QThread' here.
1st problem - runtime error with QTimer
Problem is in void MyThread::run(void), line timer = new QTimer(this);. The thread instance is created in (and so owned by) different (main) thread then it represents. For this reason, you cannot use thread as parent for object created inside the thread. In your case, solution is simple - create the timer on stack.
Example:
void MyThread::run(void)
{
QTimer timer;
connect(&timer, SIGNAL(timeout()), this, SLOT(count()));
timer.start(intvl);
// Following method start event loop which makes sure that timer
// will exists. When this method ends, timer will be automatically
// destroyed and thread ends.
exec();
}
2nd problem - GUI not updating
Qt requires the GUI to be created and displayed from main application thread. This means that blocking main thread with big operation blocks whole GUI. There are only 2 possible solutions:
Move the heavy work to other (loading) thread. This is best solution and I believe it's worth the effort (I'm aware you wrote that this would be problematic).
During the heavy operation, call regularly QCoreApplication::processEvents() - each time you call this, events will be processed, which includes calling slots, key and mouse events, GUI redraw etc. The more often you call this, the more responsive GUI will be. This is simpler to implement (just put one line all over the code), but it's quite ugly and if the operation blocks on loading a file, the GUI will freeze for some time. That means that responsiveness of your GUI depends on interval between calls. (But if you call it too often, you will slow down the loading progress.) This technique is discouraged and it's best to avoid it...
The way I will do it based on the default example on Qthread Page:
Break the big controller->openProject(); such that it updates a variable
provide access to that variable using a signal ( like statusChanged(int)).
Do what you need to do once the signal is triggered.
The code looks like
class Worker : public QObject
{
Q_OBJECT
QThread workerThread;
private:
int status; //
bool hasworktoDo();
void doSmallWork();
public slots:
void doWork() {
while(hasworktoDo())
{
doSmallWork(); // fragments of controller->openProject();
++status; //previously set to 0
emit statusChanged(status);
}
emit resultReady(result);
}
signals:
void resultReady(const QString &result);
void statusChanged(int value);
};
class Controller : public QObject
{
Q_OBJECT
QThread workerThread;
public:
Controller() {
Worker *worker = new Worker;
worker->moveToThread(&workerThread);
connect(workerThread, SIGNAL(statusChanged(int)), this, SLOT(updateStatus(int)));
connect(workerThread, SIGNAL(finished()), worker, SLOT(deleteLater()));
connect(this, SIGNAL(operate()), worker, SLOT(doWork()));
connect(worker, SIGNAL(resultReady(QString)), this, SLOT(handleResults(QString)));
workerThread.start();
}
~Controller() {
workerThread.quit();
workerThread.wait();
}
public slots:
void handleResults(const QString &);
void updateStatus(int value)
{
int status = value;
//now you have what to use to update the ui
}
signals:
void operate(); //
};
If you have trouble breaking the openProject operations in small parts, it just mean it is pointless to say on the UI that you are at 50%... Just say Loading... and make the code simpler. Note that you don't need a timer in my case, as the worker sends regular updates
I have been learning MFC these days.I want to draw lines with MoveTo() and LineTo() functions both in VC++6.0 and VS2010,but it seems that it does not work in vs2010.I only add two windows message handler,WM_LBUTTONDOWN and WM_LBUTTONUP,in the single document project.
Here is the code in VC++6.0:
CPoint m_ptOrign;
void CStyleView::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
m_ptOrign=point;
CView::OnLButtonDown(nFlags, point);
}
void CStyleView::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
CClientDC dc(this);
dc.MoveTo(m_ptOrign);
dc.LineTo(point);
CView::OnLButtonUp(nFlags, point);
}
Here is the code in vs2010:
CPoint m_ptOrign;
void CStyleView::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
m_ptOrign=point;
CView::OnLButtonDown(nFlags, point);
}
void CStyleView::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
CClientDC dc(this);
dc.MoveTo(m_ptOrign);
dc.LineTo(point);
CView::OnLButtonUp(nFlags, point);
}
The codes I add to the two projects are the same.When I release the left button ,the line appears immediately in the vc++6.0 project,but it doesn't appear in the vs 2010 mfc project.
If the size or location of the window of the vs 2010 project changes,the line apprears.
But when I use dc.Rectangle(CRect(m_ptOrign,point)) in the vs 2010 project ,it works well.
I do not know why.....
What's more,if I use
CBrush *pBbrush=CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH));
dc.SelectObject(pBbrush);
dc.Rectangle(CRect(m_ptOrign,point))
in vs2010,it does not work again,like the drawing line case
LineTo is going to use the pen that is currently selected into the DC. Since you haven't selected a pen, it will use whatever is the default. I don't know why that would be different between VC6 and VC2010, perhaps it has something to do with the differences in MFC between the two versions.
In general it's a bad idea to grab a DC and start drawing on it. Better is to do all your drawing in the OnPaint or OnDraw methods. You can call InvalidateRect to cause a paint message to be sent to the window.
I'm trying to add a menu item such that it acts like a check mark where the user can check/uncheck, and the other classes can see that menu item's check mark status. I received a suggestion of creating a class for the menu option (with a popup option), however, I can't create a class for the menu option when I'm in the resource layout editor in Visual Studio 2005. It would be great to hear suggestions on the easiest way to create menu items that can do what I have described.
You should use the CCmdUI::SetCheck function to add a checkbox to a menu item, via an ON_UPDATE_COMMAND_UI handler function, and the ON_COMMAND handler to change the state of the checkbox. This method works for both for your application's main menu and for any popup menus you might create.
Assuming you have an MDI or SDI MFC application, you should first decide where you want to add the handler functions, for example in the application, main frame, document, or view class. This depends on what the flag will be used for: if it controls application-wide behaviour, put it in the application class; if it controls view-specific behaviour, put it in your view class, etc.
(Also, I'd recommend leaving the menu item's Checked property in the resource editor set to False.)
Here's an example using a view class to control the checkbox state of the ID_MY_COMMAND menu item:
// MyView.h
class CMyView : public CView
{
private:
BOOL m_Flag;
afx_msg void OnMyCommand();
afx_msg void OnUpdateMyCommand(CCmdUI* pCmdUI);
DECLARE_MESSAGE_MAP()
};
// MyView.cpp
BEGIN_MESSAGE_MAP(CMyView, CView)
ON_COMMAND(ID_MY_COMMAND, OnMyCommand)
ON_UPDATE_COMMAND_UI(ID_MY_COMMAND, OnUpdateMyCommand)
END_MESSAGE_MAP()
void CMyView::OnMyCommand()
{
m_Flag = !m_Flag; // Toggle the flag
// Use the new flag value.
}
void CMyView::OnUpdateMyCommand(CCmdUI* pCmdUI)
{
pCmdUI->SetCheck(m_Flag);
}
You should ensure the m_Flag member variable is initialised, for example, in the CMyView constructor or OnInitialUpdate function.
I hope this helps!
#ChrisN's approach doesn't quite work for MFC Dialog applications (the pCmdUI->SetCheck(m_Flag); has no effect). Here is a solution for Dialog apps:
// MyView.h
class CMyView : public CView
{
private:
BOOL m_Flag;
CMenu * m_menu;
virtual BOOL OnInitDialog();
afx_msg void OnMyCommand();
DECLARE_MESSAGE_MAP()
};
// MyView.cpp
BEGIN_MESSAGE_MAP(CMyView, CView)
ON_COMMAND(ID_MY_COMMAND, OnMyCommand)
END_MESSAGE_MAP()
BOOL CMyView::OnInitDialog()
{
m_menu = GetMenu();
}
void CMyView::OnMyCommand()
{
m_Flag = !m_Flag; // Toggle the flag
if (m_flag) {
m_menu->CheckMenuItem(ID_MENUITEM, MF_CHECKED | MF_BYCOMMAND);
} else {
m_menu->CheckMenuItem(ID_MENUITEM, MF_UNCHECKED | MF_BYCOMMAND);
}
}
References:
http://www.codeguru.com/forum/showthread.php?t=322261
I ended up retrieving the menu from the mainframe using GetMenu() method, and then used that menu object and ID numbers to call CheckMenuItem() with the right flags, as well as GetMenuState() function.