提问人:Daniel Bauer 提问时间:5/7/2019 最后编辑:Peter MortensenDaniel Bauer 更新时间:7/2/2019 访问量:356
有些控件不是绘制的,似乎是随机的
Some controls are not drawing, seemingly at random
问:
我正在尝试为自己编写一个小的 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;
}
}
这是循环未运行时的样子:
这是循环运行时的样子:
答:
CWnd::OnPaint
是对WM_PAINT
消息的响应,不应直接调用。
WM_PAINT
calls ,调用 CPaintDC dc(this),而 CPaintDC dc(this)
又调用 BeginPaint
/EndPaint
API。此消息+响应序列应保持原样。CWnd::OnPaint
因此,必须出现一次 - 而且只出现一次 - 内部,而不是其他任何地方。覆盖如下: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);
}
}
评论
Invalidate()
WM_PAINT
WM_PAINT
Invalidate()
WM_PAINT
int m_AnimationTimerId;
if (IsIconic()) {...}
您似乎难以理解无效/绘画的工作原理。 您应该首先阅读的文档是: 绘画和绘图
虽然许多开发人员建议仅在处理中绘制(在 MFC 中),但这并不总是最佳解决方案,因为此消息是低优先级的,绘制可能不是即时的(有“断断续续”的感觉),并且您可能会得到“闪烁”效果。WM_PAINT
OnPaint()
相反,我有时会推荐绘画和绘画的混合:
- 在加工中使用油漆。这应该绘制整个工作区(或者只绘制其中的无效部分,如果你想要一个更“优化”的实现)。请注意,除了以编程方式使窗口无效外,由于移动、调整大小、取消隐藏窗口等原因,可能会因部分或全部工作区无效而收到消息。因此,为了响应消息,您应该执行完全重绘,即要显示的所有项目。
WM_PAINT
WM_PAINT
WM_PAINT
- 在应用程序繁忙时(不等待接收“异步”消息),使用绘图来显示您希望立即显示的更改。请注意,这些也应该在处理中,因此您最好编写一些绘图/绘画例程,将 (或) 作为参数(以及所需的任何其他参数),并从函数(传递那里)和所需的其他绘图操作(通过调用传递获取)调用它们。
WM_PAINT
WM_PAINT
HDC
CDC*
OnPaint()
ClientDC
CDC*
GetDC()
所以,让我分享一下我(很久以前)写的一个应用程序的经验。它是一个图像显示/操作(除其他外)应用程序,以自定义格式处理图像,并使用一个特殊的库,这相当“慢”,因为它只提供了一个在设备上下文中显示图像的功能(这包括可能的裁剪、调整、调整大小等,这些都是 CPU 成本高昂的操作)。这是一张图片:
您可以看到用户正在执行选择。应用程序必须显示图像,可能还有其顶部的选择矩形,当然就是这样。一个“简单”(尽管在技术上“正确”)的实现是调用或响应每个鼠标移动消息(在选择时)。这将导致完全重绘(这是“好的”),但由于图像库速度慢,也会遇到性能问题:如果您在无效(请求立即刷新)后也调用,性能将很慢(必须重新处理/重新显示图像),如果没有,刷新只会在一段时间后(明显)发生。这是通过使用 drawign(不是绘画)来响应消息来解决的:没有在那里无效,而是只绘制选择矩形(在恢复由上一个选择消息修改的部分之后 - 我只备份/恢复框架的四个边,而不是整个矩形)。因此,尽管库速度较慢,但应用程序响应迅速,操作流畅,并且正确显示图像和选择,即使您在跟踪选择时切换到另一个应用程序然后返回它(虚线)。OnPaint()
Invalidate()
InvalidateRect()
UpdateWindow()
WM_MOUSEMOVE
关于您的实现的一些说明和建议(它有很多问题):
- 正如其他成员所指出的,你不能给自己打电话。尤其是之后的那些电话完全没有意义。相反,如果您想要立即更新,请致电 。
OnPaint()
Invalidate()
UpdateWindow()
- Imo 在 中执行计算是不行的,我的意思是那些点计算(尽管在您的情况下,计算相当微不足道)。 应该只显示在代码的另一部分计算的数据。
OnPaint()
OnPaint()
- 此外,设置文本和从内部重新绘制也是不行的(可能会导致额外的绘画请求)。最好将它们移到 .
m_cDiagram
OnPaint()
OnBnClickedButtongo()
- 您无需使整个工作区失效(尤其是擦除)以重新绘制某些控件,而只需使这些控件失效。请记住,该函数是阻塞的,并且在循环运行时不会发送和处理消息。
sleep_for()
WM_PAINT
- 顺便说一句,考虑一种非阻塞方法,例如使用计时器,正如 Shemirani @Barmak建议的那样。或者,可以通过自己运行消息循环来编写“非blocing”(将部分代码放入并对其进行修改)。
sleep()
CWinApp::Run()
- 由于您有一个对话框并创建了单独的控件来显示数据,因此使用不是一个好的实现,因为它会影响(绘制)整个工作区。它主要用于像 or 这样的类(或一般的自定义绘画)。您在对话框的表面上绘制图形,并且必须执行计算才能获得坐标(顺便说一句,您可以使用然后代替),但最好使用所有者绘制的控件(在上面绘制/绘制图形),这并不难,您只需要响应绘制请求(就像在 中一样), 您获取的设备上下文只能在控件上绘制,而不能在对话框上绘制;坐标相对于控件的工作区,从 (0,0) 开始。
OnPaint()
CView
CScrollView
CWnd
m_cDiagram
GetWindowRect()
ScreenToClient()
OnPaint()
希望这会有所帮助
评论
Invalidate()
WM_MOUSEMOVE
WM_PAINT
WM_PAINT
WM_MOUSEMOVE
BitBlt()
DSTINVERT
DrawFocusRect()
DrawFocusRect()
DrawFocusRect
RECT
评论
OnPaint
WM_PAINT
WS_CLIPCHILDREN