65.9K
CodeProject 正在变化。 阅读更多。
Home

居中 CMDIChildWnds 和其他技巧

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.36/5 (8投票s)

2000 年 6 月 10 日

viewsIcon

151628

downloadIcon

1561

将 CMDIChildWnd 居中放置在主框架窗口的客户区中。

Sample Image - CenterMDIWnd1.jpg

引言

您是否曾想过如何让 MDI 应用程序中的 MDI 子框架窗口在主框架窗口的客户区中居中显示?您是否尝试过使用 CenterWindow(),但发现窗口仍然没有正确居中?本文将向您展示如何做到这一点,我们还将讨论如何使子窗口保持居中,即使用户移动或调整主窗口大小,或者显示或隐藏工具栏和状态栏。

对于下面的代码,我将假设您熟悉 MFC 文档/视图架构,并且正在您的应用程序中使用该架构。本文随附的示例应用程序的起始项目是使用 Visual C++ 6 和 MFC AppWizard 创建的。本文中的代码已在所有版本的 Windows 98 和 Windows NT、2000 和 XP 家庭版和专业版上进行测试。

如果您有任何疑问,请随时在下方发布消息或给我发送电子邮件(有关电子邮件地址,请参阅示例程序)。现在,开始居中窗口!

调用 CenterWindow() 的正确方法

在本文中,我们将更仔细地研究 CenterWindow() 并确保其被正确调用。非常感谢本文消息板(下方)的发布者提供的更新。使用 CenterWindow() 的关键是确保您传入的 CWnd* 指针是正确的指针,并且 CenterWindow() 函数是为您要居中的正确窗口(视图、框架、MDI 子窗口等)调用的。

假设您正在使用 MFC 的文档/视图架构,您可以重写 CView::OnInitialUpdate 并将下面**粗体**显示的代码放入其中

列表 1:CView::OnInitialUpdate 的重写

void CCenterMDIWndView::OnInitialUpdate() 
{
    // Call base class first
    CView::OnInitialUpdate();

    GetParentFrame()->CenterWindow(AfxGetMainWnd());
}

请记住,此代码应放在您要居中的框架所包含的视图中。因此,我们看到我们调用 CMDIChildWnd::CenterWindow(它继承自 CWnd)并传入指向应用程序主框架窗口的指针,该指针由 AfxGetMainWnd() 返回。感谢 Ravi Bhavnani 和 Michael Zhao 指出这个简单的解决方案。

保持窗口居中

但是,当您想让新居中的子窗口保持居中时该怎么办,例如,如果用户调整主框架窗口的大小或将子窗口移到客户区之外?或者如果主框架窗口被最小化和最大化?我们需要处理所谓的“MFC 私有消息”WM_SIZEPARENTWM_SIZEPARENT 是一种所谓的“用户消息”,它在 <afxpriv.h> 中定义。框架将此消息发送到主框架窗口的子窗口,以响应主框架窗口调整大小(因此从 Windows 接收到 WM_SIZE 消息)。

因此,我提出了以下算法来处理用户移动或调整主框架窗口大小的情况(在这两种情况下,Windows 都会发送 WM_SIZE 消息)

图 2:当用户调整应用程序主框架窗口大小或移动主框架窗口时重新居中子窗口的算法。

让我们从流程图的末尾开始,直到处理 WM_SIZE 消息。以这种方式工作,我的第一步是为 WM_SIZEPARENT 消息向我的 CChildFrame(派生自 CMDIChildWnd)类添加一个处理程序。此消息的代码在 <afxpriv.h> 中声明,因此最好的策略是将其 #include 行添加到 STDAFX.H

列表 2:包含 WM_SIZEPARENT 的头文件

#include <afxpriv.h>          // MFC private messages

接下来,我们必须在 CHILDFRM.H 文件(我的 CChildFrame 类声明在此处)的 DECLARE_MESSAGE_MAP 部分添加一行

列表 3:CHILDFRM.H 中的消息映射声明

// Generated message map functions
protected:
    //{{AFX_MSG(CChildFrame)
        // NOTE - the ClassWizard will add and remove member functions here.
        //    DO NOT EDIT what you see in these blocks of generated code!
    //}}AFX_MSG
    afx_msg LRESULT OnSizeParent(WPARAM wParam, LPARAM lParam);
    DECLARE_MESSAGE_MAP()

接下来,我们将转到 CHILDFRM.CPP 文件,并为 WM_SIZEPARENT 消息添加一个消息映射条目和处理程序。请注意,您键入的代码是**粗体**,我们必须从头开始添加整个处理程序实现

列表 4:添加消息映射条目和处理程序实现

BEGIN_MESSAGE_MAP(CChildFrame, CMDIChildWnd)
    //{{AFX_MSG_MAP(CChildFrame)
        // NOTE - the ClassWizard will add and remove mapping macros here.
        //    DO NOT EDIT what you see in these blocks of generated code !
    //}}AFX_MSG_MAP
    ON_MESSAGE(WM_SIZEPARENT, OnSizeParent)
END_MESSAGE_MAP()
LRESULT CChildFrame::OnSizeParent(WPARAM, LPARAM)
{
    CenterWindow(AfxGetMainWnd());
    return 0;
}

WM_SIZEPARENT 消息将由我们添加到 CMainFrame 中用于 WM_SIZE 消息的处理程序发送。但首先,插一句。请注意我上面提到,如果用户隐藏或显示工具栏或状态栏,给定 MDI 子窗口的居中可能会被破坏?让我们也考虑一下这一点。首先,关于主窗口结构的题外话。

窗口句柄和 MDIClient

窗口句柄用于表示窗口,当我们想对相应窗口执行某些操作时,将它们传递给各种 Windows API 函数很有用。事实证明,典型 MFC 应用程序主窗口的客户区由一个名为 MDIClient 的窗口填充。实际上,MDIClient 是当前在主窗口中打开的所有 MDI 子窗口的父级。MDIClient 的窗口句柄存储在 CMDIFrameWnd::m_hWndMDIClient 成员变量中。

为了将 WM_SIZEPARENT 消息发送到所有 MDI 子窗口(因为我们事先不知道在任何给定时间有哪些 MDI 子窗口是打开的),我们将要求 MDIClient 负责将所述消息传递给其所有子级。我们使用 CWnd::FromHandle(这是一个静态函数)来获取指向 MDIClientCWnd 指针,然后使用 CWnd::SendMessageToDescendantsWM_SIZEPARENT 消息发送到 MDI 子窗口。顾名思义,CWnd::SendMessageToDescendants 只是将指定的消息发送到给定 CWnd 的子窗口,无论这些窗口是什么。

重写 CFrameWnd::RecalcLayout() 以发送 WM_SIZEPARENT

当用户隐藏或显示工具栏和状态栏时,框架会调用 CFrameWnd::RecalcLayout 来处理子窗口和主窗口其他元素的重新定位。RecalcLayout 是一个虚函数,我们可以重写它。因为调整主窗口大小也相当于修改其“布局”。因此,让我们让我们的 RecalcLayout 重写负责向当前在主窗口中的 MDI 子窗口发送 WM_SIZEPARENT 消息。

打开 ClassWizard,选择 CMainFrame 类,并重写 RecalcLayout。我们将采用上面提到的方法。填写下面**粗体**显示的代码

列表 5:重写 CMDIFrameWnd::RecalcLayout

void CMainFrame::RecalcLayout(BOOL bNotify) 
{
    // Call base class first
    CMDIFrameWnd::RecalcLayout(bNotify);

    if (::IsWindow(m_hWndMDIClient)) {
        CWnd* pClientWnd = CWnd::FromHandle(m_hWndMDIClient);

        // Hopefully this should notify CMDIChildWnds that their
        // client area is changing size -- better re-center themselves!
        pClientWnd->SendMessageToDescendants(WM_SIZEPARENT,
            0, 0, FALSE, FALSE);
    }
}

请注意,我们如何调用 IsWindow 来检查 m_hWndMDIClient 句柄是否有效,然后再继续。如果您的应用程序在启动时自动创建(例如)新文档,这一点至关重要。框架第一次调用 RecalcLayout 是在 MDIClient 创建之前,因此此时,如果您尝试从(现在无效的)m_hWndMDIClient 句柄获取 CWnd* 指针,CWnd::FromHandle 将导致异常。

在 CMainFrame 中处理 WM_SIZE 消息

好的,现在如果用户隐藏或显示工具栏或状态栏,我们都解决了。如果他们移动、调整大小、最小化、最大化或恢复主窗口呢?很可能什么都不会发生。要更新主窗口中打开的 MDI 子窗口的居中,我们需要处理 WM_SIZE 消息。在消息处理程序中,我们将调用我们的 RecalcLayout 重写来完成繁重的工作。这是代码(在使用 ClassWizard 创建处理程序后添加**粗体**显示的代码)

列表 6:处理 WM_SIZE

void CMainFrame::OnSize(UINT nType, int cx, int cy) 
{
    CMDIFrameWnd::OnSize(nType, cx, cy);

    RecalcLayout();
}

就是这样,我们完成了。程序现在应该在创建时居中其 MDI 子窗口。调整窗口大小/移动等,或者隐藏或显示工具栏或状态栏,都应该不会影响打开的 MDI 子窗口。

Copyright

本文最初发表于 1998 年 10 月的《Visual C++ Developer》月刊,由 Pinnacle Publishing 出版。本文内容受作者版权保护,未经明确书面许可,不得转载。

© . All rights reserved.