【问题标题】:Why is the owning window showing up above the owned window?为什么拥有窗口显示在拥有窗口上方?
【发布时间】:2019-07-16 23:30:06
【问题描述】:

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

我以许多不同的方式做到了这一点,但它总是显示为 2 级低于 1 级,然后通常会发生 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 存储库中提取解决方案的副本:

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

我刚刚在对话框中添加了一些位图来真正显示问题,但我还没有在我的 8.1 机器上进行测试。

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

第 0 帧

第 2 帧

第 3 帧

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

完整视频可以在here找到。

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

【问题讨论】:

  • OnDialogAction()中的ShowWindow(SW_SHOW);是什么意思?
  • 您介意分享一个完整的示例 .cpp 我可以粘贴到 Visual Studio 中并编译吗?
  • @ConstantineGeorgiou,在显示第二级之前调出(第一级)窗口。否则,在第二级窗口关闭之前它不会显示。
  • 覆盖 CDlgLvl1::OnInitDialog 并输入 DWORD attrib = TRUE; DwmSetWindowAttribute(m_hWnd, DWMWA_TRANSITIONS_FORCEDISABLED, &attrib, sizeof(attrib)); 以确保立即显示 CDlgLvl1
  • 非常很有趣@BarmakShemirani。所以这指向一个窗口管理器问题。如果您将此作为答案发布,我会将其标记为已接受的答案。

标签: c++ windows mfc


【解决方案1】:

这个问题是在启用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
    }
}

【讨论】:

    【解决方案2】:

    问题可能是因为第一级对话框在有机会显示之前创建了第二级对话框。是的,这可能因系统而异。没有真正的解决方法,但我会建议一种解决方法,使用计时器。下面是一些代码。

    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 属性。

    【讨论】:

    • 为什么不只测试父对话框句柄是否有效,即对话框是否可见。如果两者都为真,则发送消息以显示第二个对话框。否则,继续计时器。
    • 是的,我曾尝试使用它,但它很笨拙并且可能不稳定,具体取决于计算机的速度。感谢您的注意。没有意识到它可以在对话框编辑器中完成。
    【解决方案3】:

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

    我在调试模式下运行它,它工作正常。第三个对话显示为第二个对话的子项(即,具有正确的 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);
    }
    

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

    【讨论】:

    • 我没有说third dialogue 不会显示up as a child of the second dialog。但是,您注意到 zorder 倒置了吗?
    • 我告诉过你@Adrian,LVL2 出现在 LVL1 前面。 ZORDER 是正确的。在调试和发布版本中,我无法让它显示 ZORDER 不正确。
    • 一瞬间都没有?你在什么操作系统上运行?
    • @Adrian 我正在运行 Windows 10。
    • 嗯。有趣的。建什么? winver.exe 在我的电脑上给我版本 1809 build 17763.557。此外,视频似乎显示弹出的对话框非常快。您对系统进行过任何修改吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-02
    • 1970-01-01
    • 2013-09-19
    相关资源
    最近更新 更多