某些控件似乎没有随机绘制, [英] Some controls are not drawing, seemingly at random

查看:79
本文介绍了某些控件似乎没有随机绘制,的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试为自己编写一个小MFC应用程序,以测试我正在训练的一些AI.

I'm trying to write a little MFC app just for myself, to test some AI's I'm training.

因此,我添加了图片控件和静态控件,可以在其中使用主窗口的OnPaint()方法自由绘制内容.

So I added a picture control and a static control where I can paint stuff freely in the OnPaint() method of my main Window.

当我只绘制一次应用程序时,它似乎可以工作,但是现在我添加了一个循环,该循环在停止之前多次执行OnPaint().

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.

我的代码如下:

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

尽管许多开发人员建议仅在WM_PAINT处理中进行绘制(在MFC中为OnPaint()),但这并不总是最好的解决方案,因为此消息的优先级较低,因此绘制可能不是即时的(感觉有些不稳定") ),则您可能会获得闪烁"效果.

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:

  • WM_PAINT处理中使用绘画.这应该绘制整个客户区域(或者,如果您想要更优化"的实现,则仅绘制其中无效的部分).请注意,除了以编程方式使窗口无效,移动,调整大小,取消隐藏等之外,可能还会由于部分或全部客户区域无效而收到WM_PAINT消息.因此,为响应WM_PAINT消息,您应该执行完整的重新绘制,即要显示的所有项目.
  • 在应用程序忙时(不等待收到异步" WM_PAINT消息的情况下)使用绘图立即显示您想要的更改.请注意,这些也应在WM_PAINT处理中,因此您必须编写一些绘图/绘画例程,将HDC(或CDC*)作为参数(以及其他所需的参数),然后调用通过OnPaint()函数(在那里传递ClientDC)和所需的其他绘图动作(传递通过调用GetDC()获得的CDC*)它们.
  • 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()).

因此,让我分享我(很久以前)编写的应用程序的经验.这是一个图像显示/操作(除其他外)应用程序,以自定义格式处理图像,并使用一个相当慢"的特殊库,因为它仅提供了在设备上下文中显示图像的功能(这包括可能的裁剪,调整,调整大小等操作,这是CPU成本高昂的操作).这是一张图片:

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:

您可以看到用户正在执行选择.应用程序必须显示图像,并可能显示在其顶部的选择矩形,这当然是OnPaint()所做的.一个简单"(尽管从技术上来说是正确")实现是响应每个鼠标移动消息(选择时)以调用Invalidate()InvalidateRect().这将导致完全重新绘制(确定"),但由于图像库运行缓慢而导致性能问题:如果在无效(请求立即刷新)后也调用UpdateWindow(),则性能会变慢(具有以重新处理/重新显示图像),如果没有,刷新将在稍后(值得注意的)时间进行.这是通过响应WM_MOUSEMOVE消息而采用drawign(不作画)来解决的:在该处不作废,而是仅绘制选择矩形(还原先前选择消息修改的部分后-我仅备份/还原了四边)框架,而不是整个矩形).结果,即使库很慢,该应用程序仍会响应并且操作流畅,并且即使在跟踪选择内容时(即使切换到另一个应用程序然后又返回到该应用程序,也可以正确显示图像和选择内容(虚线))

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):

  • 正如其他成员所指出的那样,您自己不能打电话给OnPaint().尤其是Invalidate()之后的那些调用绝对没有意义.如果要立即更新,请致电UpdateWindow().
  • Imo不能在OnPaint()中执行计算,我的意思是这些点的计算(尽管在您的情况下,该计算相当琐碎). OnPaint()应该只显示代码另一部分中计算出的数据.
  • 此外,设置m_cDiagram文本并从OnPaint()内部重新粉刷也不行(可能会导致其他粉刷请求).最好将它们移到OnBnClickedButtongo().
  • 您不需要使整个工作区无效(特别是擦除)来使某些控件重新绘制,而只需使那些控件无效.请记住,sleep_for()函数正在阻塞,并且在循环运行时不会发送和处理WM_PAINT消息.
  • 顺便说一句,请考虑使用@Barmak Shemirani建议的非阻塞方法,例如使用计时器.另外,也可以自己运行消息循环来编写"non-blocing sleep()"(获取CWinApp::Run()中的部分代码并对其进行修改).
  • 由于您有一个对话框并创建了单独的控件来显示数据,因此使用OnPaint()并不是一个好的实现,因为它会影响(绘制)整个工作区.对于CViewCScrollView之类的类(或通常自定义CWnd的类)而言,它最有用.您将图形绘制在对话框的表面上,并且必须执行计算以获取m_cDiagram中的坐标(顺便说一句,您可以使用GetWindowRect()然后使用ScreenToClient()代替),但是最好使用所有者绘制的控件(绘制/绘制图形),这并不困难,您只需要响应绘制请求(就像在OnPaint()中一样),并且您获得的设备上下文只能在控件上绘制,而不能在对话框上绘制;坐标相对于控件的客户区域,从(0,0)开始.
  • 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).

希望这会有所帮助

这篇关于某些控件似乎没有随机绘制,的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆