I am writing a C++ MFC application to control a machine in a manufacturing setting. This app also needs to analyze a lot of information in a very short cycle time.
For testing purposes and long term maintenance, I need to be able to graph data coming from a sensor on the console. I may have totally overlooked an option (feel free to propose other options) but my research has taken me to using a picture control.
I am successfully drawing in this control by use of OnPaint(). My issue is that I need to redraw a new image every few seconds and I cannot call OnPaint() repetitively or pass data to it.
How can I create a new function that can be used to draw on the picture control repetitively? Also, this is my first foray into an MFC app so please explain on an appropriate level. Thanks!
class CPicture : public CStatic
{
DECLARE_MESSAGE_MAP()
public:
afx_msg void OnPaint();
};
BEGIN_MESSAGE_MAP(CPicture, CStatic)
ON_WM_PAINT()
END_MESSAGE_MAP()
void CPicture::OnPaint()
{
CPaintDC dc(this); // device context for painting
dc.SelectStockObject(BLACK_BRUSH);
dc.Rectangle(5, 50, 1000, 51);
}
I guess the question is how and where to access this
//Picture
class CPicture : public CStatic
{
DECLARE_MESSAGE_MAP()
public:
afx_msg void OnPaint();
vector<Coordinates> GraphData;
};
void CPicture::OnPaint()
{
// device context for painting
CPaintDC dc(this);
// save current brush
CBrush *pOldBrush = (CBrush*)dc.SelectStockObject(BLACK_BRUSH);
int NumPoints = GraphData.size() - 1;
for (int N = 0; N <= NumPoints; N++) {
dc.Rectangle(GraphData[N].x, GraphData[N].y, GraphData[N].x, GraphData[N].y);
}
// select original brush into device contect
dc.SelectObject(pOldBrush);
}
You can call Invalidate() on your control when new data arrives, or use RedrawWindow() to force an immediate redraw:
CPicture myPicture;
myPicture.Invalidate();
or
myPicture.RedrawWindow();
I cannot call OnPaint() repetitively or pass data to it.
To pass data, a structure containg the data can be declared inside your CPicture class (or some place else in your program), and that data can then be accessed from within OnPaint():
struct myData {
int value1;
int value2; // or an array, or some other data structure
}
class CPicture : public CStatic
{
DECLARE_MESSAGE_MAP()
public:
myData m_data;
afx_msg void OnPaint();
};
In OnPaint() (you should also select the original brush back into the device context to avoid resource leaks):
void CPicture::OnPaint()
{
CPaintDC dc(this); // device context for painting
// save current brush
CBrush *pOldBrush = (CBrush*)dc.SelectStockObject(BLACK_BRUSH);
// check pOldBrush - could be NULL
// dc.Rectangle(5, 50, 1000, 51);
// access m_data here, for example
dc.Rectangle(m_data.value1, m_data.value2, 1000, 51);
// select original brush into device contect
dc.SelectObject(pOldBrush);
}
Update (working with threads):
Assuming the following (from the comments):
for the main thread you have a dialog CLongbowDlg.
for the graph, you have a PicControl derived from CStatic, and that control is placed on the dialog.
from the main thread, a worker thread is started to read the data.
PicControl and CLongbowDlg are defined in the same header, but are
independent of each other. I need to be able to call Invalidate() or
RedrawWindow() from inside CLongbowDlg's functions because they
represent the primary thread.
I'll try to give a short description of one of the possibilities here, because this should actually be a seperate question.
Firstly, an object of PicControl has to be a member of CLongbowDlg, which I assume is the case (let's call it m_PicControl) - So, in class CLongbowDlg:
PicControl m_PicControl;
For the data (I'll be using the above myData as example data): in your main thread (the Dialog), create a variable of type myData: m_data (for larger data you could allocate space on the heap, or use CArray or some other container):
myData m_data;
In PicControl create a member variable of type myData* and set it to NULL in the PicControl constructor.
myData *m_pData;
In OnInitDialog() (main dialog), provide m_picControl with a pointer to the data (or better create a function to do that in PicControl):
m_picControl.m_pData = &m_data;
When starting the worker thread, also provide it a pointer to m_data and/or a pointer to the dialog itself (this).
Make sure to protect the data with a critical section.
When data comes in, the worker thread can write to it via the provided pointer.
In PicControl::OnPaint(), the same data can be accessed through m_pData.
To initiate a redraw, there are several ways:
use a timer inside PicControl or in the main dialog, and call Invalidate() every time the timer fires.
to control the redrawing from the worker thread (when a certain amount of new data has arrived for example) a message can be posted, using PostMessage(), to the main dialog (using the pointer that was provided when starting the thread - the this pointer).
To receive the message you'll have to create a message handler in the main dialog, and from there call Invalidate() on m_picControl (you could also post a message directly to PicControl, but I prefer to do it via the main window).
Related
I create a new class extend CEdit to override some of the message handles.
My ultimate goal is when edit control is on focus, some of the toolbar buttons become available.
I created a bool variable in doc. then the pCmdUI->enable() set to this bool. The onfocus is overridden in new edit control class. I'm having trouble to update this bool vairbale from the onfocus message handle.
void CMFCDoc::OnUpdateTextColor(CCmdUI *pCmdUI)
{
// TODO: Add your command update UI handler code here
pCmdUI->Enable(shape_onfocus_);
}
class CMFCDoc : public COleServerDoc
{
...
bool shape_onfocus_;
}
//edit control
#include <afxwin.h>
class CEditControl :
public CEdit
{
public:
CEditControl();
~CEditControl();
DECLARE_MESSAGE_MAP()
afx_msg void OnEnSetfocus();
};
void CEditControl::OnEnSetfocus()
{
//----- I want to update shape_onfocus_ here. -----
this->SetWindowTextA(_T("Hello world"));
}
Assuming your CEditControl instance is a child of some sort of CView, you could go about it like this:
void CEditControl::OnEnSetfocus()
{
CView *view = static_cast<CView *>(GetParent());
CMFCDoc *doc = static_cast<CMFCDoc *>(view->GetDocument());
doc->shape_onfocus_ = true;
...
}
Assuming the edit-control is a child of a CView-derived class, you should better put the OnUpdateUI() handler in the view class, not the document one.
For example, if the view-class is CFormView-derived (dialog), you could simply write:
void CMyView::OnUpdateTextColor(CCmdUI *pCmdUI)
{
pCmdUI->Enable(GetFocus()==GetDlgItem(IDC_MYEDIT));
}
This piece of code works for both SDI and MDI applications.
If the view class is not CFormView-derived (the edit-box was created programmatically), the code above could be modified slightly, and instead of calling GetDlgItem() you should enumerate the view's children list (search your edit-box there).
If the only reason to override the edit-control was to capture the EN_SET/KILLFOCUS messages, sorry this wasn't worth the effort, as you could simply capture these in the view's code. Then the view's message-map would contain:
ON_EN_SETFOCUS(IDC_MYEDIT, &CMyView::OnEnSetfocusMyEdit)
ON_EN_KILLFOCUS(IDC_MYEDIT, &CMyView::OnEnKillfocusMyEdit)
and the view-class code:
void CMyView::OnEnSetfocusMyEdit()
{
// TODO: Add your control notification handler code here
}
void CMyView::OnEnKillfocusMyEdit()
{
// TODO: Add your control notification handler code here
}
These are generated by the wizard. Go to the Class View tab, select your class and then go to the Events page; in the Controls subtree you can find your control and add handlers for its events. But all this is not needed, as you can just use GetFocus()/GetDlgItem() as suggested above.
And as other members said, you can access the document class from any of its views by calling the GetDocument() function.
I Set an int Variable for IDC_EDIT1 Control.
now i Want Change it With a Function, But when clicking on Button, Show an Error!
void test()
{
CthDlg d;
d.m_text1 = 5;
d.UpdateData(FALSE);
}
void CthDlg::OnBnClickedOk()
{
// TODO: Add your control notification handler code here
// pThread = AfxBeginThread(ThreadFunction, THREAD_PRIORITY_NORMAL);
test();
}
In the test function you define a completely new instance of the CthDlg class, and try to modify it. That will not work as it's not properly created, and also have no relation with the actual dialog being displayed.
Instead, if test is a stand-alone (not member) function then you should pass the actual dialog instance as an argument, and use that.
For example
void tesy(CthDlg& dlg)
{
dlg.m_text1 = ...;
dlg.UpdateData(FALSE);
}
void CthDlg::OnBnClickedOk()
{
test(*this);
}
The controls are created when you call DoModal or Create.
And therefore calling UpdateData will only succeed when the Dialog is created.
This is the usual sequence: The value members may be set before you Launch the Control. The data is transferred when the Dialog is created and transfered back from the controls into the data members when the Dialog is closed with OnOK.
I am trying to dynamically change the text of a CStatic control. My member variable is called mStatic of the type CStatic. I have changed the ID to IDC_MYSTATIC instead of IDC_STATIC.
I am calling mStatic.SetWindowText("asdfasdf") when I want to change the text of the control. I do this periodically in a timer.
Now I have the problem that the previous text is not erased after I call the SetWindowText(). It just keeps piling up until I get a mess on the screen.
The parent window has the layered property with a bitmap background. I have also set the color_key property so a certain color of the bitmap is viewed as transparent (I.e. It will not be drawn and will let mouse messages through). The mStatic control is drawn on the parts not transparent, that have a bitmap background.
Why isn't the window invalidating?
Had the same issue. The following code fixed it:
mStatic.SetWindowText("New text");
CRect rect;
mStatic.GetWindowRect(&rect);
ScreenToClient(&rect);
InvalidateRect(&rect);
UpdateWindow();
Perhaps your static text control have a SS_SIMPLE style enabled. You can check style flags on resource file or using GetStyle().
Static control with SS_SIMPLE style displays text faster, but also - as MSDN describes -
"SS_SIMPLE static controls do not clear the control's display area when displaying text. If a shorter string is displayed, the part of the original string that is longer than the new shorter string is displayed."
Clear SS_SIMPLE from style flags and CStatic will behave 'normally'.
This knowledge base support article describes the same problem when the SetWindowText() call is made from another thread. Is that what your timer is doing?
If so the solution could simply be to:
mStatic.SetWindowText("asdfasdf");
CRect clientRect;
mStatic.GetClientRect(clientRect);
mStatic.InvalidateRect(clientRect);
As mentioned by others already, a static control doesn't necessarily erase its background prior to drawing the text.
I find it a much better solution to subclass the static control and force the invalidation of the control from there. This enables one to easily implement it on all static texts with transparent background, without having to do extra calls to invalidate the control from its parent class.
One way to catch a change of the control's text from within the control itself is to react to the WM_SETTEXT message and force the invalidation from there:
int CStaticT::OnSetText(LPCTSTR text)
{
LRESULT res = Default();
Invalidate();
UpdateWindow();
return res;
}
Here is a brief example, extracted from one of my classes, of how such a subclassed control could look like:
//////////////////////////////////////////////////////////////////////////
// Header
//////////////////////////////////////////////////////////////////////////
class CStaticT : public CStatic
{
DECLARE_DYNAMIC(CStaticT)
public:
CStaticT();
virtual ~CStaticT();
protected:
afx_msg int OnSetText(LPCTSTR text);
DECLARE_MESSAGE_MAP()
private:
BOOL m_InitialSet;
};
//////////////////////////////////////////////////////////////////////////
// Implementation
//////////////////////////////////////////////////////////////////////////
IMPLEMENT_DYNAMIC(CStaticT, CStatic)
CStaticT::CStaticT()
{
m_InitialSet = FALSE;
}
CStaticT::~CStaticT()
{
}
BEGIN_MESSAGE_MAP(CStaticT, CStatic)
ON_WM_SETTEXT()
END_MESSAGE_MAP()
int CStaticT::OnSetText(LPCTSTR text)
{
LRESULT res = Default();
// I've noticed issues when this forces the invalidation
// of the static control before the parent's background
// is painted completely.
// This is a cheap workaround, skipping the initial setting
// of the text by the subclassing call.
// You have to test if this works out for your environment.
if (!m_InitialSet)
{
m_InitialSet = TRUE;
return res;
}
// Force of the invalidation
Invalidate();
UpdateWindow();
return res;
}
I've been trying to program in C++ a sort of simple "window" system for use in a game, which draws windows that can have buttons, etc. in them in the game area (internal to the game's own graphics, i.e. not the OS's GUI windows). The window objects (call it "class Window" for here) have some methods for events like key press, and the ability to hook on a handler to be called upon receipt of that event.
Windows are (or will be) collected in a "window manager", and the window object will have "close()" member that would call the parent window manager's window-deletion routine to delete itself. An event handler hooked to, say, a button on the window might invoke this routine to close the window (think an "OK" box).
The trouble is this sounds like a "delete *this;" statement, which I've heard is a no-no. True, it doesn't do that directly, but the effect is the same: an object has a member function that brings about its own destruction (e.g. the "close()" function, or the event function that triggers the handler leading to the "close()" function being called.). If this is bad, then what is a better way to design this?
There is nothing wrong with an object deleting itself. You must simply tell the window manager to remove the window from it's collection and then delete. If you have the window manager delete the window object, that's even better.
If you really want to avoid this behavior, you can add a bool dead; to each window that initializes to false. When the window is to be closed, set this->dead = true;. Every frame, have the window manager iterate through it's windows and delete the ones that are dead.
Note that this solution still does not fix errors that arise from external systems that have a reference to the deleted window, but it does have the advantage of centralizing the deletion of windows.
I have designed many games' window systems, and in my experience, allowing windows to delete themselves is a very elegant solution, even if it is more error-prone.
A minimal example:
class Window
{
public:
void keyPressCallback(int c)
{
if (c == KEY_ESC)
{
manager.destroy(this);
return;
}
}
WindowManager& manager;
};
class WindowManager
{
public:
void destroy(Window* target)
{
delete target;
windows.erase(std::find(windows.begin(), windows.end(), target));
}
std::vector<Window*> windows;
};
As long as there are no remaining pointers to that window, this method is perfectly safe and semantically sane. When the window receives a signal to close, it closes itself.
The same example with the dead flag:
class Window
{
public:
Window() : dead(false) {}
void keyPressCallback(int c)
{
if (c == KEY_ESC)
{
dead = true;
return;
}
}
bool dead;
};
class WindowManager
{
public:
void cleanup()
{
for (auto iter = windows.begin(); iter != windows.end(); ++iter)
{
if (iter->dead) windows.erase(iter);
}
}
std::vector<Window*> windows;
};
I have an RPC thread that is calling back to me from that thread. I need to somehow inform Qt that it needs to make a function call from the main thread. In straight Windows I could do this by using a custom message and then posting that message to the message queue, e.g., I could create a WM_CALLFUNCTION message and pass the function pointer through wParam and the parameter (class pointer) through lParam.
Has anyone an idea how I could do this with Qt? I've come across QCustomEvent but I have no idea how to use it or how to process it. Any help would be hugely appreciated!
Edit:
In the end I went with QMetaObject::invokeMethod which works perfectly.
Using custom events generally involves creating your own QEvent subclass, overriding customEvent() in the QObject class that will receive the event (often the main window class) and some code that "posts" the event from your thread to the receiver.
I like to implement the event posting code as a method of the receiver class. That way, the caller only has to know about the recevier object and not any of the "Qt" specifics. The caller will invoke this method which will then essentially post a message to itself. Hopefully the code below will make it clearer.
// MainWindow.h
...
// Define your custom event identifier
const QEvent::Type MY_CUSTOM_EVENT = static_cast<QEvent::Type>(QEvent::User + 1);
// Define your custom event subclass
class MyCustomEvent : public QEvent
{
public:
MyCustomEvent(const int customData1, const int customData2):
QEvent(MY_CUSTOM_EVENT),
m_customData1(customData1),
m_customData2(customData2)
{
}
int getCustomData1() const
{
return m_customData1;
}
int getCustomData2() const
{
return m_customData2;
}
private:
int m_customData1;
int m_customData2;
};
public:
void postMyCustomEvent(const int customData1, const int customData2);
....
protected:
void customEvent(QEvent *event); // This overrides QObject::customEvent()
...
private:
void handleMyCustomEvent(const MyCustomEvent *event);
The customData1 and customData2 are there to demonstrate how you might pass some data along in your event. They don't have to be ints.
// MainWindow.cpp
...
void MainWindow::postMyCustomEvent(const int customData1, const int customData2)
{
// This method (postMyCustomEvent) can be called from any thread
QApplication::postEvent(this, new MyCustomEvent(customData1, customData2));
}
void MainWindow::customEvent(QEvent * event)
{
// When we get here, we've crossed the thread boundary and are now
// executing in the Qt object's thread
if(event->type() == MY_CUSTOM_EVENT)
{
handleMyCustomEvent(static_cast<MyCustomEvent *>(event));
}
// use more else ifs to handle other custom events
}
void MainWindow::handleMyCustomEvent(const MyCustomEvent *event)
{
// Now you can safely do something with your Qt objects.
// Access your custom data using event->getCustomData1() etc.
}
I hope I didn't leave anything out. With this in place, code in some other thread just needs to get a pointer to a MainWindow object (let's call it mainWindow) and call
mainWindow->postMyCustomEvent(1,2);
where, just for our example, 1 and 2 can be any integer data.
In Qt 3, the usual way to communicate
with the GUI thread from a non-GUI
thread was by posting a custom event
to a QObject in the GUI thread. In Qt
4, this still works and can be
generalized to the case where one
thread needs to communicate with any
other thread that has an event loop.
To ease programming, Qt 4 also allows
you to establish signal--slot
connections across threads. Behind the
scenes, these connections are
implemented using an event. If the
signal has any parameters, these are
also stored in the event. Like
previously, if the sender and receiver
live in the same thread, Qt makes a
direct function call.
--
http://doc.qt.nokia.com/qq/qq14-threading.html#signalslotconnectionsacrossthreads