我正试图为自己编写一个小MFC应用程序,以测试我正在训练的一些AI。
因此,我添加了一个图片控件和一个静态控件,在其中我可以在主窗口的OnPaint()方法中自由绘制内容。
仅绘制一次我的应用程序似乎可以工作,但是现在我添加了一个循环,该循环在停止之前多次执行OnPaint()。
在此循环中,其他一些控件没有显示,例如我的所有按钮都消失了,有些滑块甚至不见了,但有时却在那儿。
我的代码如下:
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;
}
}
答案 0 :(得分:3)
CWnd::OnPaint
是对WM_PAINT
消息的响应,不应直接调用。
WM_PAINT
调用CWnd::OnPaint
,后者调用CPaintDC dc(this)
,后者依次调用BeginPaint
/ EndPaint
API。消息+响应的顺序应保持不变。
因此,CPaintDC dc(this)
必须在OnPaint
内出现一次,并且只能出现一次,而不能出现在其他任何地方。如下重写OnPaint
:
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
}
您也不需要过时的if (IsIconic()) {...}
条件。
要强制窗口重新绘制自身,请调用Invalidate()
(与InvalidateRect(NULL, TRUE)
相同)
InvalidateRect(NULL, TRUE)
是重新绘制窗口的请求。系统将查看此请求,并在有机会时向该窗口发送WM_PAINT
消息。因此,对InvalidateRect
的调用可能无法处理您期望它在顺序程序中工作的方式。例如,第二次连续调用InvalidateRect
不会产生任何效果。窗口已被标记为要更新。
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()
应该从上面的代码中删除。仍然不能在单个线程中进行动画处理(至少不能以这种方式)。程序忙于循环,它无法处理WM_PAINT
和其他消息。
因此,您需要一个附加线程,或仅使用SetTimer
,然后对ON_WM_TIMER()
/ OnTimer
进行动画处理。示例:
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);
}
}
答案 1 :(得分:2)
您似乎很难理解无效/绘画的工作原理。 您首先应该阅读的文档是: Painting and Drawing
尽管许多开发人员建议仅在WM_PAINT
处理中进行绘制(在MFC中为OnPaint()
),但这并不总是最好的解决方案,因为此消息的优先级较低,因此绘制可能不会立即进行(具有”震荡”的感觉,您可能会获得“闪烁”的效果。
相反,有时我会推荐混合使用绘画和绘画:
WM_PAINT
处理中使用绘画。这应该绘制整个客户区域(或者,如果您想要更“优化”的实现,则仅绘制其中的无效部分)。请注意,除了以编程方式使其无效之外,由于移动,调整窗口大小,取消隐藏窗口等原因,也可能由于使部分或全部客户区域无效而收到WM_PAINT
消息。因此,为了响应WM_PAINT
消息,您应该执行一次完整的重新绘制,即要显示的所有项目。WM_PAINT
消息的情况下,使用绘图来立即显示您想要的更改)。请注意,这些也应在WM_PAINT
处理中,因此,您必须编写一些绘图/绘画例程,并以HDC
(或CDC*
)作为参数(以及其他参数)参数),然后从OnPaint()
函数(在此处传递ClientDC
)和所需的其他绘制动作(传递通过调用CDC*
获得的GetDC()
)中调用它们。所以,让我分享我(很久以前)编写的应用程序的经验。这是一个图像显示/操作(除其他外)应用程序,以自定义格式处理图像,并使用一个相当“慢”的特殊库,因为它仅提供了在设备上下文中显示图像的功能(这包括可能的裁剪,调整,调整大小等操作,这是CPU成本高昂的操作)。这是一张图片:
您可以看到用户正在执行选择。应用程序必须显示图像,并可能显示在其顶部的选择矩形,这当然是OnPaint()
所做的。一种“简单”(尽管从技术上讲是“正确”)实现是响应每个鼠标移动消息(选择时)来调用Invalidate()
或InvalidateRect()
。这将导致完全重画(“确定”),但由于图像库速度慢而导致性能问题:如果在使无效(请求立即刷新)后也调用UpdateWindow()
,则性能会变慢(必须重新处理/重新显示图像),如果没有,刷新将在稍后(明显)时间进行。这是通过响应WM_MOUSEMOVE
消息而采用drawign(不作画)来解决的:在该处无效,而是仅绘制选择矩形(还原先前选择消息修改的部分后-我仅备份/还原这四个)框架的两侧,而不是整个矩形)。结果,尽管库运行缓慢,该应用程序仍具有响应能力并且操作流畅,即使在跟踪选择内容时(即使切换到另一个应用程序然后又返回到该应用程序,也可以正确显示图像和选择内容(虚线))
关于实现的一些注释和建议(存在很多问题)
OnPaint()
。特别是Invalidate()
之后的那些调用绝对没有意义。如果要立即更新,请致电UpdateWindow()
。OnPaint()
内执行计算不是很好,我指的是这些点的计算(尽管在您的情况下,计算是微不足道的)。 OnPaint()
应该只显示在代码另一部分中计算出的数据。m_cDiagram
文本并从OnPaint()
内部重新绘制也不行(可能会导致其他绘制请求)。最好将它们移到OnBnClickedButtongo()
中。sleep_for()
函数处于阻塞状态,并且在循环运行时不会发送和处理WM_PAINT
消息。sleep()
”(获取CWinApp::Run()
中的部分代码并对其进行修改)。
OnPaint()
并不是一个好的实现,因为它会影响(绘制)整个客户区。对于CView
或CScrollView
之类的类(或通常自定义绘制CWnd
的类)来说,它最有用。您将图形绘制在对话框的表面上,并且必须执行计算以获取m_cDiagram
中的坐标(顺便说一句,您可以先使用GetWindowRect()
,然后再使用ScreenToClient()
),但是最好使用所有者绘制的控件(用于绘制/绘制图形),这并不难,您只需要响应绘制请求(就像在OnPaint()
中一样),就可以在设备上绘制设备上下文。仅控制,不在对话框上;坐标相对于控件的客户区域,从(0,0)开始。希望这会有所帮助