为什么拥有窗口显示在拥有窗口上方?

时间:2019-07-16 23:30:06

标签: c++ windows mfc

我有一个非常奇怪的问题。我正在尝试复制一个窗口层次结构。因此,在创建第一级对话框时,我将启动第二级对话框的实例。

我已经以许多不同的方式完成了此操作,但是它总是显示为第二级低于第一级,然后通常会发生zorder反转(它们的翻转位置)。有时候,反转不会发生,但是如果我单击所有者,所有者将立即跳到zorder的顶部。

下面是一个小例子的主要部分,以显示这种情况:

const unsigned short WMA_DIALOGACTION        = WM_APP+1;
// Button event handler for the 0th level
void CdialogcallingdialogsDlg::OnBnClickedDlgLvl1()
{
    CDlgLvl1 x(this);
    x.DoModal();
}
BEGIN_MESSAGE_MAP(CDlgLvl1, CDialogEx)
    ON_WM_WINDOWPOSCHANGED()
    ON_MESSAGE(WMA_DIALOGACTION, OnDialogAction)
END_MESSAGE_MAP()

void CDlgLvl1::OnWindowPosChanged(WINDOWPOS* lpwndpos)
{
    if (!m_shownDlg) {
        m_shownDlg = true;
        PostMessage(WMA_DIALOGACTION);
    }
}

// Level 1 dialog opening up level 2 dialog
LRESULT CDlgLvl1::OnDialogAction(WPARAM wParam, LPARAM lParam)
{
    ShowWindow(SW_SHOW);
    CDlgLvl2 x(this);
    x.DoModal();
    return LRESULT();
}
BEGIN_MESSAGE_MAP(CDlgLvl2, CDialogEx)
    ON_WM_WINDOWPOSCHANGING()
END_MESSAGE_MAP()

// Level 2 dialog offseting its position
void CDlgLvl2::OnWindowPosChanging(WINDOWPOS* lpwndpos)
{
    ASSERT(lpwndpos->hwnd == m_hWnd);
    // Offset dialog to see the problem of dlg2 showing up below dlg1
    if (!(lpwndpos->flags & SWP_NOMOVE)) {
        lpwndpos->x += 10;
        lpwndpos->y += 10;
    }
}

在示例中,您单击主对话框中的按钮。然后启动CDlgLvl1,然后启动CDlgLvl2。除此处显示的消息处理和主应用程序对话框上的按钮外,这些对话框是默认对话框。如果仔细看,会发现反转。

我在做什么错?也许有更好的方法可以做到这一点?

如果有所作为,则该问题在Windows 10下更加明显,在Windows 8.1上似乎不明显。

可以从我的git repo中提取解决方案的副本:

https://github.com/Ma-XX-oN/dialog-calling-dialogs.git

我刚刚在对话框中添加了一些位图以真正显示问题,但尚未在8.1盒上进行测试。

我记录了它如何弹出,这是该记录的第0、2和3帧:

框架0 frame-0

框架2 frame-2

框架3 frame-3

如您所见,LVL1在第2帧的LVL2上方出现,然后翻转第3帧的位置。

可以找到here的完整视频。

使用这个示例项目,我无法复制停留在LVL2之上的LVL1,但是我相信未发生zorder反转的行为是某种竞争条件。

3 个答案:

答案 0 :(得分:2)

启用Windows“过渡动画”时会引起问题。 WM_WINDOWPOSCHANGED是在动画完成之前发送的。

要解决此问题,您可以简单地禁用对话框的过渡:

BOOL CDlgLvl2::OnInitDialog()
{
    BOOL res = CDialogEx::OnInitDialog();
    BOOL attrib = TRUE;
    DwmSetWindowAttribute(m_hWnd, DWMWA_TRANSITIONS_FORCEDISABLED, &attrib, sizeof(attrib));
    return res;
}


如果您不想禁用转换,则必须等到转换完成。我不知道如何检测它或如何确定过渡时间。似乎是250毫秒。 SystemParametersInfo(SPI_SETMENUSHOWDELAY...)的值是400毫秒,似乎有点长。

假设我们知道时间,请在过渡结束后使用SetTimer运行该函数:

BOOL CDlgLvl2::OnInitDialog()
{
    BOOL res = CDialogEx::OnInitDialog();
    ANIMATIONINFO info = { sizeof info };
    SystemParametersInfo(SPI_GETANIMATION, sizeof(ANIMATIONINFO), &info, 0);
    if (info.iMinAnimate)
        SetTimer(1, 250, nullptr);
    else
        SetTimer(1, 1, nullptr);
    return res;
}

void CDlgLvl2::OnTimer(UINT_PTR nIDEvent)
{
    CDialogEx::OnTimer(nIDEvent);
    if(nIDEvent == 1)
    {
        KillTimer(nIDEvent);
        CDlgLvl2(this).DoModal();//note, PostMessage is not needed in SetTimer
    }
}

答案 1 :(得分:0)

我在Visual Studio 2019中尝试了您的项目:

Results

我在调试模式下运行了它,并且工作正常。第三个对话框显示为第二个对话框的子级(即带有正确的ZORDER)。 RELEASE构建也是如此。

请参阅:https://www.dropbox.com/s/8f5z5ltq3vfc10r/Test.mp4?dl=0

更新

如果我有一个班级,我有一个计时器,我这样做了:

void CChristianLifeMinistryEditorDlg::OnTimer(UINT_PTR nIDEvent)
{
    READYSTATE eState = READYSTATE_UNINITIALIZED;

    if (nIDEvent == PRINT_PREVIEW_TIMER)
    {
        eState = m_pPrintHtmlPreview->GetReadyState();
        if (eState == READYSTATE_COMPLETE)
        {
            KillTimer(m_uPreviewTimer);
            PostMessage(WM_COMMAND,
                MAKELONG(IDC_BUTTON_PRINT_PREVIEW2, BN_CLICKED));
        }
    }

    CResizingDialog::OnTimer(nIDEvent);
}

您可以调整原理,然后仅模拟按下按钮以显示下一个第二个对话框。可能会工作。

答案 2 :(得分:0)

可能是由于第一级对话框在有机会显示自身之前创建了第二级对话框而引起的。是的,这可能因系统而异。并没有真正的解决方法,但是我建议采用计时器的解决方法。下面是一些代码。

CDlgLvl1的头文件:

class CDlgLvl1 : public CDialogEx
{
.
.
.
protected:
    UINT_PTR nIDTimer = 0; // Add this
};

CDlgLvl1的源文件:

BEGIN_MESSAGE_MAP(CDlgLvl1, CDialogEx)
    .
    .
    ON_MESSAGE(WMA_DIALOGACTION, OnDialogAction)
    ON_WM_TIMER()
END_MESSAGE_MAP()


BOOL CDlgLvl1::OnInitDialog()
{
    CDialogEx::OnInitDialog();

    nIDTimer = SetTimer(1, 250, NULL);
    return TRUE;
}

void CDlgLvl1::OnTimer(UINT_PTR nIDEvent)
{
    if (nIDTimer && nIDEvent == nIDTimer)
    {
        KillTimer(nIDTimer);
        nIDTimer = 0;
        PostMessage(WMA_DIALOGACTION);
        return;
    }

    CDialogEx::OnTimer(nIDEvent);
}

LRESULT CDlgLvl1::OnDialogAction(WPARAM wParam, LPARAM lParam)
{
    CDlgLvl2 x(this);
    x.DoModal();
    return 0;
}

您提供的防止第二个窗口多次显示的机制(m_shownDlg变量)已由nIDTimer选中。

请尝试使用计时器的经过时间值。我建议的设置(250-1/4秒)对于大多数系统来说是可以的,并且对用户来说是不可察觉的。

我是在SO编辑器中编写的,没有在VS中进行实际测试(因此它可能包含一些语法错误-如果可以,请修复它们)。

注意:如果只想设置第二个对话框的位置,则不需要覆盖OnWindowPosChanging()。它是相对于其父级的,因此您可以简单地设置对话框资源的X PosY Pos属性。