为啥对话框会在不刷新的情况下删除其内容?

Posted

技术标签:

【中文标题】为啥对话框会在不刷新的情况下删除其内容?【英文标题】:Why would a dialog erase its content without refreshing?为什么对话框会在不刷新的情况下删除其内容? 【发布时间】:2016-06-17 22:41:32 【问题描述】:

我遇到了一个非常奇怪的问题,一个窗口似乎正在擦除它的内容,并且在擦除它之后没有重新绘制它。此对话框源自CDhtmlDialog,我认为这是问题的一部分。发生了某种不确定的代码执行,导致某些代码在某些情况下先于其他代码执行,而在其他情况下则相反。

涉及的消息处理程序是:

BEGIN_MESSAGE_MAP(CCalcDrillDownDlg, CDHtmlDialog)
    ON_WM_PAINT()
END_MESSAGE_MAP()

BEGIN_EVENTSINK_MAP(CCalcDrillDownDlg, CDHtmlDialog)
     ON_EVENT(CCalcDrillDownDlg, AFX_IDC_BROWSER, 250 /* BeforeNavigate2 */, _OnBeforeNavigate2b, VTS_DISPATCH VTS_PVARIANT VTS_PVARIANT VTS_PVARIANT VTS_PVARIANT VTS_PVARIANT VTS_PBOOL)
END_EVENTSINK_MAP()

OnInitDialog()函数如下:

BOOL CCalcDrillDownDlg::OnInitDialog()

    SetHostFlags(DOCHOSTUIFLAG_FLAT_SCROLLBAR);

    CDHtmlDialog::OnInitDialog(); // << will eventually call _OnBeforeNavigate2b()

    // Set the icon for this dialog.  The framework does this automatically
    //  when the application's main window is not a dialog
    SetIcon(m_hIcon, TRUE);         // Set big icon
    SetIcon(m_hIcon, FALSE);        // Set small icon

    LoadFromResource(IDR_CALC_DRILLDOWN); // << will eventually call _OnBeforeNavigate2b()
    CString title = getStr2Ptr(22574);
    SetWindowText(title);
    ShowWindow(SW_SHOW);

    return TRUE;  // return TRUE  unless you set the focus to a control

这是OnPaint() 函数:

void CCalcDrillDownDlg::OnPaint() 

    if (IsIconic())
    
        CPaintDC dc(this); // device context for painting

        SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);

        // Center icon in client rectangle
        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;

        // Draw the icon
        dc.DrawIcon(x, y, m_hIcon);
    
    else
    
        CDHtmlDialog::OnPaint();
    

我没有放 _OnBeforeNavigate2b() 函数的内容,因为它似乎与重绘系统没有任何关系。

所以似乎发生的情况是,有时,对话框内容会在调用CCalcDrillDownDlg::OnPaint() 之前以某种方式绘制。如果发生这种情况,那么对CDHtmlDialog::OnPaint() 的调用将清除窗口中的内容。

其他时候,在调用CCalcDrillDownDlg::OnPaint() 之前,不会在窗口上绘制内容。如果发生这种情况,那么对CDHtmlDialog::OnPaint() 的调用可能仍会将尚未绘制的窗口中的内容擦除,然后在调用CCalcDrillDownDlg::OnPaint() 之后的某个时间,它会被重新绘制。

当系统正确重绘窗口时,Spy++ 不会捕获任何消息,因此我删除了此问题生成的消息。

有没有人知道重绘是如何完成的以及为什么有时订单会被搁置?

编辑

这是IDR_CALC_DRILLDOWN资源的内容:

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Calculation Drilldown</title>
    <style type="text/css">
        body  overflow-y: auto; font-family: arial, Helvetica, sans-serif; font-size: 90%; 

        a:link  color: black; 
        a:visited  color: black; 
        table  border-collapse: collapse; 

        tr.runcache td  background-color: #B5B5B5; color: black; 
        tr.runcache td a:link  color: black; 
        tr.runcache td a:visited  color: black; 

        tr.tracker td  background-color: white; color: black; 
        tr.tracker td a:link  color: black; 
        tr.tracker td a:visited  color: black; 

        td.numericvalue  text-align: right; 

        tr.paramTitle td  background-color: #4A4A4A; color: white; 

        tr.resultTitle td  background-color: #4A4A4A; color: white; 
        tr.resultTitle td a:link  color: white; 
        tr.resultTitle td a:visited  color: white; 

        tr.param td  background-color: white; color: black; 
        tr.param td a:link  color: black; 
        tr.param td a:visited  color: black; 

        span.selection  background-color: #EBEBEB; 
    </style>
</head>
<body>
    <div id="calculation"></div>
    <div id="details" style="padding-left: 0.1in; display: none;"></div>
</body>
</html>

编辑#2

进一步的调查似乎表明,无论我的CCalcDrillDownDlg::OnPaint() 是否调用CDHtmlDialog::OnPaint(),CDHtmlDialog 类(或其基类)都会绘制窗口,这很奇怪而且不直观。 :(

此外,这似乎与线程有关,因为这似乎取决于渲染窗口所需的时间。如果需要很短的时间,则显示正常。如果需要半秒或更长时间,它就会搞砸。

目前,我正在使用一种解决方法,我在类中有一个 m_bRepaint 标志,该标志最初设置为 true。在调用 CCalcDrillDownDlg::OnPaint() 并且它不是标志性的时,我检查标志并强制调整大小。这不是最优的,因为它会导致初始闪烁,但它至少可以确保绘制窗口的内容。

    if (!m_bRepaint)
    
        CDHtmlDialog::OnPaint();
    
    else
    
        CRect winRect;
        GetWindowRect(&winRect);
        SetWindowPos(NULL, 0, 0, winRect.Width() - 1, winRect.Height(), SWP_NOMOVE | SWP_NOZORDER);
        SetWindowPos(NULL, 0, 0, winRect.Width()    , winRect.Height(), SWP_NOMOVE | SWP_NOZORDER);
        m_bRepaint = false;
    

使用Invalidate() 不起作用。我必须将其调整为当前大小以外的大小,然后重新调整大小。

这个CDHtmlDialog 类是一个可以使用的 PITA,如果他们有选择的话,我不建议任何人使用它。

【问题讨论】:

没有minimal reproducible example,我们只能猜测。 @theB,如果我能做到这一点,我就完蛋了。 ??????一些可能的建议会很好。 如果不能提供minimal reproducible example,请提供相关的sn-p代码。 @QPaysTaxes,在这种情况下什么被认为是相关的?类正在处理的消息处理程序? @Adrian 我试图想出一种方法可以将代码从问题中删除。您完全有可能发现了 CDHtmlDialog 的错误,因此从等式中删除您的代码应该可以做到这一点。当然,问题可能出在您的 HTML 上。也许您不是 LoadFromResource,而是 LoadFromUrl("google.com") 【参考方案1】:

好的,所以这似乎是由于 windows 消息队列不是确定性的,所以看起来底层的 COM 控件在 WM_PAINT 消息之前在它的 DC 上绘制。

为了解决这个问题,我通过等待WM_WINDOWPOSCHANGED 消息来等待窗口显示自己,然后发布另一个应用程序消息,然后调用Invalidate()UpdateWindow(),从而强制重绘窗口。

Raymond Chen 的博客“The Old New Thing”中描述了这种技术here。

【讨论】:

【参考方案2】:

不要处理 WM_ERASEBKGND。在对话框上设置 WM_CLIPCHILDREN 样式。

ON_WM_ERASEBKGND()
...

BOOL CCalcDrillDownDlg::OnEraseBkgnd(CDC* pDC)

    // TODO: Add your message handler code here and/or call default
    return TRUE;

【讨论】:

所以,我不确定你在说我要在这里做什么。如果OnEraseBkgnd() is called before OnPaint(),那么为什么它不只是在擦除后绘制窗口呢?为什么会被删除? 在CDHtmlDialog中,OnPaint()对重绘无效。通常html页面会覆盖any绘图,所以不能重绘。没有足够的信息来进一步证明。 好的,如果是这样的话,你是说用OnEraseBkgnd()代替OnPaint()吗? .rc 文件(它指向的本地 html 文件)的值是多少? ///////////////////////////////////////// /////////////////////// // HTML // IDR_HTML_CALC_DRILLDOWN_DIALOG HTML "???.html" 你能详细说明一下吗!?

以上是关于为啥对话框会在不刷新的情况下删除其内容?的主要内容,如果未能解决你的问题,请参考以下文章

如何在不使用对话框的情况下在操作栏上方显示视图?

如何在不刷新页面的情况下使用 JS SDK 检测从 fb 注销

如何在不强制“另存为”对话框的情况下设置响应文件名

psql - 如何在不删除表的情况下刷新数据库内容

在不重新加载的情况下向 div 添加/删除内容

mfc中让编辑框透明为啥总显示黑色背景的编辑框啊?