I'm trying to write a little MFC app just for myself, to test some AI's I'm training.
So I added a picture control and a static control where I can paint stuff freely in the OnPaint() method of my main Window.
It seems to work when just drawing my app once, but I now added a loop that performs OnPaint() multiple times before stopping.
When in this loop, some other controls don't show up, for example all my buttons are gone, and some sliders even are missing some times, but other times, they're there.
My code goes like this:
void CKiUebung1Dlg::OnBnClickedButtongo()
{
m_bisGoing = true;
OnPaint();
if(m_fDiagramData.size() <= 0)
{
m_fDiagramData.push_back((float)rand() / RAND_MAX);
InvalidateRect(NULL, TRUE);
}
OnPaint();
for(int i(9); i >= 0; --i)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
m_fDiagramData.push_back((float)rand() / RAND_MAX);
InvalidateRect(NULL, TRUE);
OnPaint();
}
m_bisGoing = false;
OnPaint();
}
void CKiUebung1Dlg::OnPaint()
{
if(IsIconic())
{
CPaintDC dc(this); // Gerätekontext zum Zeichnen
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
// Symbol in Clientrechteck zentrieren
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// Symbol zeichnen
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialogEx::OnPaint();
}
{
constexpr const int border = 5;
CPaintDC dc(&m_cDiagram);
CRect l_cPos;
m_cDiagram.GetClientRect(&l_cPos);
const int width(l_cPos.Width() - border * 2 - 2), height(l_cPos.Height() - border * 2 - 12);
const int numPoints(m_fDiagramData.size());
POINT* points(new POINT[numPoints]);
for(int i(numPoints - 1); i >= 0; --i)
{
const int
x((float)i / (numPoints - 1) * width + border + 1),
y(height - m_fDiagramData[i] * height + border + 9);
points[i] = { x,y };
}
dc.Polyline(points, numPoints);
static CString going(_T(" "));
if(m_bisGoing) { going += _T("."); if(going.GetLength() > 300) going = _T(" ."); }
else going = _T(" ");
float fprog(0); if(m_fDiagramData.size() > 0) fprog = m_fDiagramData.back();
CString prog; prog.Format(_T("Progress %03.2f%%"), fprog * 100); if(m_bisGoing) prog += going;
m_cDiagram.SetWindowTextW(prog);
m_cDiagram.RedrawWindow();
delete[] points;
}
}
This is how it looks when the loop isn't running:
This is how it looks when the loop is running:
You seem to have trouble understanding how invalidating/painting works.
The documentation you should read first is:
Painting and Drawing
While many developers recommend painting only in WM_PAINT processing (OnPaint() in MFC), this is not always the best solution, because this message is low-priority, painting may not be immediate (have a "choppy" feel), and you may get a "flickering" effect.
Instead, I sometimes recommend a mix of drawing and painting:
Employ painting in WM_PAINT processing. This should paint the whole client area (or only the invalidated part of it, if you want a more "optimized" implementation). Please note that WM_PAINT message may be received as a result of invalidating a part or all of the client area, due to moving, resizing, unhiding etc the window, in addition to programmatically invalidating it. So in response to a WM_PAINT message you should perform a full repaint, ie all the items you want to be displayed.
Employ drawing for the changes you want to be shown immediately, while the application is busy (not waiting for the "asynchronous" WM_PAINT message to be received). Please note that these should be in WM_PAINT processing as well, so you rather have to write some drawing/painting routines, taking a HDC (or CDC*) as a parameter (along any other parameter needed), and call them from both the OnPaint() function (passing the ClientDC there) and from your additional drawing actions needed (passing a CDC* acquired by calling GetDC()).
So, let me share my experience with an application I wrote some (long) time ago. It's an image-display/manipulation (among others) application, processing images in a custom format, and using a special library, which was rather "slow", as it only provided a function to display the image in the device context (this includes possible cropping, adjustments, resizing etc which are CPU-costly operations). Here is an image:
You can see the user performing a selection. The application has to display the image, and possibly the selection rectangle on top of it, and of course that's what OnPaint() does. An "easy" (albeit technically "correct") implementation would be to call Invalidate() or InvalidateRect() in response each mouse move message (while selecting). This would cause a full repaint (which is "OK"), but also suffer from performance problems, due to the slow image-library: if you also call UpdateWindow() after invalidating (requesting an immediate refresh) performance would be sluggish (having to reprocess/redisplay the image), if not, the refresh would just take place some (noticeable) time later. This was solved by employing drawign (not painting) in response to the WM_MOUSEMOVE message: no invalidating there, instead drawing just the selection rectangle (after restoring the part modified by the previous selection message - I only backup/restore the four sides of the frame, not the whole rectangle). As a result, the application is responsive and the operation smooth, despite the slow library, and shows the image and the selection correctly, even if you switch to another application and then back to it, while the selection is being tracked (dashed line).
Some notes and suggestion about your implementation (it has quite a few issues):
As other members have noted, you may not call OnPaint() yourself. Especially those calls after Invalidate() make absolutely no sense. Instead, call UpdateWindow(), if you want an immediate update.
Imo it is NOT OK to perform calculations within OnPaint(), and I mean those points calculations (although in your case the calculation is rather trivial). OnPaint() should just display the data calculated in another part of your code.
Also, setting the m_cDiagram text and repainting from within OnPaint() is not OK either (may cause additional paint requests). Better move these into OnBnClickedButtongo().
You don't need to invalidate (and particularly erase) the whole client area to cause some controls to be repainted, instead invalidate only those controls. Remember, the sleep_for() function is blocking, and the WM_PAINT message won't be sent and processed while your loop is running.
Btw, consider a non-blocking approach, eg using a timer, as #Barmak Shemirani suggested. Alternatively, it may be possible to write a "non-blocing sleep()" by running the message-loop yourself (take parts of the code in CWinApp::Run() and modify it).
Since you have a dialog and created separate controls to display your data, using OnPaint() is not a good implementation, as it affects (paints) the whole client area. It is mostly useful for classes like CView or CScrollView (or custom-painting CWnds in general). You paint the graph on the dialog's surface, and have to perform calculations to get the coordinates in m_cDiagram (btw you can use GetWindowRect() and then ScreenToClient() instead) but it would be best to use an owner-drawn control (to paint/draw the graph on), and it's not really difficult, you just have to respond to paint requests (just as in OnPaint()), and the device context you get can paint on the control only, not on the dialog; coordinates are relative to the control's client area, starting from (0,0).
Hope this helps
CWnd::OnPaint is a response to WM_PAINT message and should not be called directly.
WM_PAINT calls CWnd::OnPaint, which calls CPaintDC dc(this), which in turns calls BeginPaint/EndPaint APIs. This sequence of message+response should be left as is.
Therefore CPaintDC dc(this) must appear once - and only once - inside OnPaint, and not anywhere else. Override OnPaint as follows:
void CMyDialog::OnPaint()
{
CDialogEx::OnPaint(); //this will call CPaintDC dc(this);
//optional:
CClientDC dc(this); //CClientDC can be used anywhere in a valid window
//use dc for drawing
}
//or
void CMyDialog::OnPaint()
{
CPaintDC dc(this);
//use dc for drawing
}
You also don't need the outdated if (IsIconic()) {...} condition.
To force the window to repaint itself, call Invalidate() (same thing as InvalidateRect(NULL, TRUE))
InvalidateRect(NULL, TRUE) is a request to repaint the window. The system will look at this request, and will send WM_PAINT message to that window when there is a chance. Therefore a call to InvalidateRect may not process the way you expect it to work in a sequential program. For example, a second consecutive call to InvalidateRect will not have any effect. Window was already marked to be updated.
for(int i(9); i >= 0; --i)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
m_fDiagramData.push_back((float)rand() / RAND_MAX);
InvalidateRect(NULL, TRUE);
OnPaint();
}
OnPaint() should be removed from above code. Still, animation is not possible in a single thread (at least not in this manner). The program is busy going through the loop, it cannot deal with WM_PAINT and other messages.
So you need an additional thread, or simply use SetTimer, and respond to ON_WM_TIMER()/OnTimer for animation. Example:
int counter = 0;
BEGIN_MESSAGE_MAP(CMyDialog, CDialogEx)
ON_WM_PAINT()
ON_WM_TIMER()
...
END_MESSAGE_MAP()
void CMyDialog::OnPaint()
{
CPaintDC dc(this);
CString s;
s.Format(L"%02d", counter);
dc.TextOut(0, 0, s);
}
void CMyDialog::animate()
{
counter = 0;
SetTimer(1, 1000, NULL);
}
void CMyDialog::OnTimer(UINT_PTR n)
{
if(n == 1)
{
Invalidate(); //force repaint
counter++;
if(counter == 10)
KillTimer(1);
}
}
Related
I want to use the value output by this slider to change the image which is loaded into the img variable. The paint function is called whenever the slider is moved.
The slider's value is 0 - 2. The second if statement tests as correct when the slider is > 1, however, the image does not change. Without the if statements I can switch the image manually, otherwise it isn't doing what I expect.
void ModelGUI::sliderValueChanged(Slider* slider)
{
rotate.getValue();
DBG(rotate.getValue());
}
void ModelGUI::paint (Graphics& g)
{
DBG("PAINT");
g.setColour(Colours::red);
if(rotate.getValue() < 1)
{
img = ImageFileFormat::loadFrom(f);
}
if(rotate.getValue() > 1)
{
img = ImageFileFormat::loadFrom(f2);
}
g.drawImage(img, model);
g.drawEllipse(225, 230, 2, 2, 2);
g.drawEllipse(240, 240, 2, 2, 2);
g.drawEllipse(245, 275, 2, 2, 2);
}
Why is this happening? Cheers
just a wild guess but shouldn't you force repainting in the sliderValueChanged event somehow? I do not know juce but I would expect one of these:
g.Repaint();
g.Refresh();
g.Update();
as changing slider is probably just repainting the slider itself and not updating the rest of your GUI. The g should be your Graphics object of coarse.
If the repaint is really time consuming then I usually do something like this:
bool _redraw=false; // some global variable (or local but accessible to your GUI/Form/Canvas whatever)
void any_event()
{
// here event stuff code
if (need_to_repaint) _redraw=true; // for example you change something hit a special key that changes something etc ...
}
void timer() // any timer with target max fps interval
{
if (_redraw())
{
_redraw=false;
// here force repaint or call the repaint directly
}
}
This way the repaint will not slow down other events too much. For example If I use zoom on wheel and I am rendering a lot of stuff the wheel could feel slagish if repainted on each change of wheel. This will repaint only with timer period which should be fine if set right.
The usual events that need such handling are zoom, mouse move/edit and window/view resize as they can create a lot of event calls per second ...
As #Spektre surmises -- in JUCE, your component needs to request that it be repainted when it knows that's necessary. In this case:
void ModelGUI::sliderValueChanged(Slider* slider)
{
rotate.getValue();
DBG(rotate.getValue());
repaint();
}
...will cause your ModelGUI component (and all of its child components) to repaint at the next reasonable point in time.
See: https://docs.juce.com/master/classComponent.html#af3ab3eabec93dd60faf983370f3a1206
I have a CListCtrl which is dynamically resized with the dialogue. I used a WM_SIZE message handler in the derived CListCtrl to resize the columns such that the total is the width of the control - 4, where the - 4 is to indicate the width of the border.
When I make the dialogue bigger, the control resizes correctly and I don't get the bottom scrollbar. However when I shrink the control, I sometimes get the horizontal scrollbar showing up.
void CMyListCtrl::OnSize(UINT nType, int cx, int cy)
{
CListCtrl::OnSize(nType, cx, cy);
ResizeLastColumn();
}
void CMyListCtrl::ResizeLastColumn()
{
LVCOLUMN column;
column.mask = LVCF_WIDTH;
LONG maxWidth = 0;
for (int i = 0; i < lastColumnIndex; ++i)
{
GetColumn(i, &column);
maxWidth += column.cx;
}
CRect wndRect;
GetWindowRect(&wndRect);
SetColumnWidth(lastColumnIndex, wndRect.Width() - maxWidth - 4);
}
It is like the WM_SIZE message is getting to the control before the control is finally resized.
This is related to How to determine if a scrollbar for a CListCtrl is displaying?. However, this question is not dealing with the right scrollbar, and is assuming that it is not being displayed.
Resizing the window generates a message to test for horizontal scroll. SetColumnWidth will also generate the same message. It depends how ListView handles this internally, but a vertical scroll could also come in and go, this will change the client area, so the code may have to make recursive calls to figure out if the scroll should be visible or not. You can see this can easily run in to problems.
Try resizing the columns in WM_WINDOWPOSCHANGED, before calling the default procedure. Use SetRedraw to stop redundant paint messages.
ON_WM_WINDOWPOSCHANGED()
...
void CMyListCtrl::OnWindowPosChanged(WINDOWPOS *wpos)
{
SetRedraw(FALSE);
ResizeLastColumn();
SetRedraw(TRUE);
CListCtrl::OnWindowPosChanged(wpos);
}
You can use GetClientRect for the client area, then you don't need to subtract the border thickness (which is not always 4).
void ResizeLastColumn()
{
int maxwidth = 0;
int index = GetHeaderCtrl()->GetItemCount() - 1;
for(int i = 0; i < index; ++i)
maxwidth += GetColumnWidth(i);
CRect rc;
GetClientRect(&rc);
SetColumnWidth(index, rc.Width() - maxwidth);
}
Also GetHeaderCtrl()->GetItemCount() returns the number of columns.
I'm facing the exact same issue, with the exact same Use Case.
To disable the horizontal scroll bar altogether, you add a message handler for WM_NCCALCSIZE
Using the Class Wizard this adds the following to your message map:
ON_WM_NCCALCSIZE()
In the implementation of this handler, you modify the style to disable the horizontal scroll bar.
void CMyListCtrl::OnNcCalcSize(BOOL bCalcValidRects, NCCALCSIZE_PARAMS* lpncsp)
{
ModifyStyle(WS_HSCROLL, 0); // disable the horizontal scroll bar
__super::OnNcCalcSize(bCalcValidRects, lpncsp);
}
In my own implementation of ResizeLastColumn(), which is similar to yours, I subtract ::GetSystemMetrics(SM_CXVSCROLL) from the width to account for the vertical scroll bar.
I realise this reply is rather late, but hopefully it will help someone.
(Edited to remove mentions of some problems I am having because a) they are off-topic and b) because I think they stem from me not using the Doc-View architecture and things not being wired up correctly as a result. I'm going to re-think my approach.)
I am currently writing a gtk program that uses a custom title bar (i.e., it is not being decorated by the window manager). But since using a custom title bar also disables support of dragging the window around, I wrote my custom drag function which moves the window by calling window.move(x, y):
bool on_titlebar_drag(GdkEvent* event)
{
static int drag_x_offset = 0;
static int drag_y_offset = 0;
int x, y;
if (event->type == GDK_BUTTON_PRESS)
{
drag_x_offset = event->button.x;
drag_y_offset = event->button.y;
} else if(event->type == GDK_BUTTON_RELEASE) {
drag_x_offset = 0;
drag_y_offset = 0;
} else if(event->type == GDK_MOTION_NOTIFY) {
x = event->motion.x_root - drag_x_offset;
y = event->motion.y_root - drag_y_offset;
mainWindow.move(x, y);
}
return true;
}
This works just fine except of the fact that it cannot move the window beyond the screen limits, like the normal behaviour for other windows, so you can drag it "out of sight" to make place for others.
I am trying to resize the window smaller as soon as it touches the screen by calling window.resize(width, height) but this is not what I intend to do, because resizing also resizes the window contents to the smaller scale, while I would just like to make its physical size smaller.
I have also tried using set_allocation and size_allocate, these two didnt make any change at all.
My question is, do you know a way to either be able to move the window beyond the screen borders (not totally, but in a way that the window is not fully on-screen), or to change the size of the window without resizing its contents?
If you are using GTK 3.10 or newer you can use gtk_window_set_titlebar
gtk_window_set_titlebar (GtkWindow *window, GtkWidget *titlebar) the only arguments you need are the window that you want to customise and the GtkWidget that will serve as the titlebar. Using GtkHeaderBar is suggested but in your case you can use any custom GtkWidget and get draggable bar which will tug the whole window as would the one from the window manager.
I'm writing a dialog that plays video on a dialog frame by frame, and displays some status info around it. The dialog resource contains just a few CStatic controls that have bitmaps drawn on them.
The loop looks like this
DWORD milliPerFrame = std::lround(1000.0 / videoFile.get(CV_CAP_PROP_FPS));
unsigned int videoFrameCount = (unsigned int)videoFile.get(CV_CAP_PROP_FRAME_COUNT),
videoFrameWidth = (unsigned int)videoFile.get(CV_CAP_PROP_FRAME_WIDTH),
videoFrameHeight = (unsigned int)videoFile.get(CV_CAP_PROP_FRAME_HEIGHT);
uint64_t startTime, endTime;
CVideoDlg* canvas = new CVideoDlg();
canvas->Create(IDD_VIDEO_DIALOG, this);
canvas->ShowWindow(SW_SHOW);
CRect dialogSize = canvas->getDrawingRect();
int winFrameWidth = dialogSize.right - dialogSize.left,
winFrameHeight = dialogSize.bottom - dialogSize.top;
cv::Mat vFrame, dFrame;
for (unsigned frameNo = 1; frameNo <= videoFrameCount; frameNo++)
{
startTime = getCurrTime();
videoFile.read(vFrame);
cv::resize(vFrame, dFrame, cv::Size(winFrameWidth, winFrameHeight));
canvas->setFrame(dFrame);
bool status = (frameNo % 2 == 0);
canvas->setStitchingStatus(status);
endTime = getCurrTime();
Sleep(std::max(0, (int)(milliPerFrame - (endTime - startTime))));
}
The real code calls a routine that returns a status, which I've commented out for debug purposes.
The CVideoDlg has an OnInitDialog routine which calls ModifyStyle(0xF, SS_BITMAP, 0) for each of the CStatic controls, the two set methods, and getDrawingRect.
The stitching status is simple
void CVideoDlg::setStitchingStatus(bool status)
{
m_Stitching.ShowWindow(status ? SW_SHOW : SW_HIDE);
}
setFrame is a rather long routine, that draws the frame on another CStatic using StretchDIBits.
Problem is, stepping through with the debug I can see each CStatic drawn correctly, but causing the other one to be erased.
Why is that happening, and how can I fix it?
Edit: the top right rectangle is the stitching status CStaticm, the large one is the frame.
Edit: I've overridden the dialog's PreTranslateMessage method and put a break on it to see what messages it gets that might erase it's background. Nothing.
I've added a call to the dialog's RedrawWindow, and all the bitmaps correctly appear, but I still wonder why is it deleted in the first place.
In our project, we use GDI to draw waveform and any other graphics, however, we meet a special problem, the situations are:
The waveform stop drawing, just like not response.
The buttons in toolbar also stop updating its background while mouse hots it or clicks it, however, it is very mystical, the button could respond event and open the relevant dialog.
The dialog opened from toolbar could draw normally, any figures could work well.
While resize the window, double click the title of application, anything works normally again.
So, I hope to know whether there is any bug or driven problem in Win7 64bits Pro version which have been reported.
The similar problems in stack-overflow see this link:
What could cause redraw issues on 64-bit vista but not in 32-bit in .NET WInForms?
The code of drawing waveform and graphics, and
void CZMultiPatientView::UpdateDisplay(int ThisSecondCount, int ThisMillisecondCount)
{
CClientDC dc(this);
CDC *pDC = &dc;
//
// Draw waveforms
//
DrawPatientViewWaveforms(pDC);
//
// Update parameters
//
if ((GetElapsedMilliseconds(mLastAlarmMillisecondCount, ThisMillisecondCount) >= 500) || (mForceRedraw))
{
//
// !!! SHOULD NOT DO THIS IN HERE !!!
//
if (mSetupParameterDialogDisplayed)
{
//mParameterDataDialog.UpdateCurrentParameterValues() ;
m_pParamDlg->SendMessage(WM_UPDATE_VALUE); // Kevin
}
}
if ((GetElapsedSeconds(mLastAlarmsecondCount, ThisSecondCount) >= 1) || (mForceRedraw))
{
//Update messages (once a second)
UpdateMessages(pDC);
mLastAlarmsecondCount=ThisSecondCount;
}
if (GetElapsedMilliseconds(mLastDrawParametersCount, ThisMillisecondCount) >= 200 || (mForceRedraw))
{
PostMessage(APP_UPDATE_VIEWS, (WPARAM)ThisMillisecondCount, (LPARAM)mForceRedraw);
mLastDrawParametersCount = ThisMillisecondCount;
}
//
// Update alarms
//
if (GetElapsedMilliseconds(mLastAlarmMillisecondCount, ThisMillisecondCount) >= 500)
{
mLastAlarmMillisecondCount = ThisMillisecondCount ;
DoSoundAlarmTone();
}
mForceRedraw = 0 ;
}