Сообщений 7    Оценка 10        Оценить  
Система Orphus

Как управлять размерами MDI-окон MFC приложения?

Автор: Игорь Ткачёв
Опубликовано: 27.09.2001
Исправлено: 13.03.2005

Демонстрационный проект - 40 KB

Наверняка, Вас не раз раздражал вид приложений, открывающих дочерние окна следующим образом:


Неправильное приложение

Мне это тоже не очень нравится, поэтому в своих приложениях я использую следующий порядок открытия окон:


Правильное приложение

В этом случае окна занимают всю рабочую поверхность приложения и нет необходимости использовать по умолчанию режим Maximize.

Вы тоже сможете управлять размерами своих MDI окон и, если Вам не нравится мой вариант, придумаете свой собственный.

Прежде всего, для этого нам необходимо научиться создавать MDI окно, которое будет занимать всю рабочую область приложения (или такую её часть, которая Вас больше устраивает). Легче всего такую возможность организовать на уровне взаимодействия фреймов приложения и дочерних MDI-окон. Создайте новый MFC MDI проект, затем откройте окно создания нового класса "Menu->Insert->New Class" и заполните в нём поля как показано ниже:


С помощью ClassWizard (Ctrl+W) добавьте в класс CSizeFrame метод PreCreateWindow и внесите в него следующий код:

BOOL CSizeFrame::PreCreateWindow(CREATESTRUCT& cs) 
{
    if (cs.y < 0) {
        CMainFrame *mfrm = (CMainFrame*)AfxGetMainWnd();
        CRect r;
        mfrm->GetDlgItem(AFX_IDW_PANE_FIRST)->GetClientRect(r);
        cs.x  = r.left;
        cs.y  = r.top;
        cs.cx = r.Width();
        cs.cy = r.Height();
    }
    return CMDIChildWnd::PreCreateWindow(cs);
}

Также не забудьте включить "MainFrm.h" в файл SizeFrame.cpp

// SizeFrame.cpp : implementation file
//

#include "stdafx.h"
#include "MDISize.h"
#include "MainFrm.h"
#include "SizeFrame.h"

и заменить старый CChildFrame новым CSizeFrame в файле MDISize.cpp:

// MDISize.cpp : Defines the class behaviors for the application.
//

#include "stdafx.h"
#include "MDISize.h"

#include "MainFrm.h"
//#include "ChildFrm.h"
#include "SizeFrame.h"

...

BOOL CMDISizeApp::InitInstance()
{
    ...
    
    CMultiDocTemplate* pDocTemplate;
    pDocTemplate = new CMultiDocTemplate(
        IDR_MDISIZTYPE,
        RUNTIME_CLASS(CMDISizeDoc),
        RUNTIME_CLASS(CSizeFrame), // custom MDI child frame
        RUNTIME_CLASS(CMDISizeView));
    AddDocTemplate(pDocTemplate);
    
    ...

Откомпилируйте приложение, запустите его и убедитесь, что размеры создаваемых дочерних окон теперь точно соответствуют размерам рабочей области.

Теперь научим дочерние окна вычислять свои размеры так, чтобы они не перекрывали заголовок предыдущего окна. Добавьте следующий код в PreCreateWindow:

BOOL CSizeFrame::PreCreateWindow(CREATESTRUCT& cs) 
{
    if (cs.y < 0) {
        CMainFrame *mfrm = (CMainFrame*)AfxGetMainWnd();
        CRect r;
        mfrm->GetDlgItem(AFX_IDW_PANE_FIRST)->GetClientRect(r);

        CMDIChildWnd *wnd = mfrm->MDIGetActive();
        if (wnd) {
            CRect ra;
            wnd->GetWindowRect(ra);
            mfrm->GetDlgItem(AFX_IDW_PANE_FIRST)->ScreenToClient(ra);
            int top = ra.top;
            top += ::GetSystemMetrics(SM_CYSIZEFRAME) +
                   ::GetSystemMetrics(SM_CYCAPTION) - 1;
    
            if (top < r.Height()/3) 
                r.top += top;
        }

        cs.x  = r.left;
        cs.y  = r.top;
        cs.cx = r.Width();
        cs.cy = r.Height();
    }
    return CMDIChildWnd::PreCreateWindow(cs);
}

Ну вот, теперь всё работает как надо, за исключением некоторых мелочей. В частности, если Вы попытаетесь изменить размер главного окна приложения, то размеры дочерних окон останутся неизменными. Можно было бы не обращать на это особого внимания, но, так как мы с вами очень любим своих пользователей, то постараемся сделать всё, что бы им было работать как можно комфортнее :o)

Для реализации этой возможности нам понадобится изменить класс CMainFrame. С помощью ClassWizard добавьте в класс CMainFrame метод RecalcLayout и измените его код следующим образом:

void CMainFrame::RecalcLayout(BOOL bNotify) 
{
    CMDIFrameWnd::RecalcLayout(bNotify);

    CRect r;
    GetDlgItem(AFX_IDW_PANE_FIRST)->GetClientRect(r);
    if (r != m_MDIRect) {
        m_MDIRect = r;
        
        POSITION  ptemp = AfxGetApp()->GetFirstDocTemplatePosition();
        while (ptemp) {
            CDocTemplate *temp = AfxGetApp()->GetNextDocTemplate(ptemp);
            POSITION      pdoc = temp->GetFirstDocPosition();
            while (pdoc) {
                CDocument  *doc = temp->GetNextDoc(pdoc);
                POSITION    pview = doc->GetFirstViewPosition();
                while (pview) {
                    CFrameWnd *frame = doc->GetNextView(pview)->GetParentFrame();
                    if (frame)
                        frame->PostMessage(WM_COMMAND,IDM_RECALC_SIZE);
                }
            }
        }
    }
}

Заголовочный файл MainFrm.h подкорректируйте, как показано ниже:

#define IDM_RECALC_SIZE (WM_USER+987)

class CMainFrame : public CMDIFrameWnd
{
    DECLARE_DYNAMIC(CMainFrame)
public:
    CMainFrame();

    CRect m_MDIRect;
    
    ...

Теперь при изменении размеров главного окна приложения оно будет рассылать сообщение IDM_RECALC_SIZE всем дочерним MDI окнам. Нам осталось только реализовать обработчик этого сообщения. Добавьте в класс CSizeFrame прототип функции OnRecalcSize:

afx_msg void OnRecalcSize();

и её реализацию в файл SizeFrame.cpp

void CSizeFrame::OnRecalcSize ()
{
    CRect rf, r;
    GetWindowRect(r);
    CWnd *first = AfxGetMainWnd()->GetDlgItem(AFX_IDW_PANE_FIRST);
    first->ScreenToClient(r);
    first->GetClientRect(rf);
    rf.top = r.top;
    MoveWindow(rf);
}

Не забудьте также добавить её в карту сообщений

BEGIN_MESSAGE_MAP(CSizeFrame, CMDIChildWnd)
    //{{AFX_MSG_MAP(CSizeFrame)
        // NOTE - the ClassWizard will add and remove mapping macros here.
    //}}AFX_MSG_MAP
    ON_COMMAND(IDM_RECALC_SIZE, OnRecalcSize)
END_MESSAGE_MAP()

Готово! Правда у нас появилась очередная проблема - если пользователь всё же захочет изменить размеры окна, то последующее изменение размера приложения пересчитает размеры окна по нашим правилам. Устраним это недоразумение.

Добавьте в класс CSizeFrame переменные m_sync, m_cx и m_cy как показано ниже:

class CSizeFrame : public CMDIChildWnd
{
    DECLARE_DYNCREATE(CSizeFrame)
protected:
    CSizeFrame();           // protected constructor used by dynamic creation

    bool  m_sync;
    int   m_cx, m_cy;

    ...    
CSizeFrame::CSizeFrame()
: m_sync(true), m_cx(0), m_cy(0)
{
}

Измените OnRecalcSize следующим образом:

void CSizeFrame::OnRecalcSize ()
{
    if (m_sync) {
        CRect rf, r;
        GetWindowRect(r);
        CWnd *first = AfxGetMainWnd()->GetDlgItem(AFX_IDW_PANE_FIRST);
        first->ScreenToClient(r);
        first->GetClientRect(rf);
        rf.top = r.top;
        MoveWindow(rf);
        m_sync = true;
    }
}

С помощью ClassWizard, добавьте в класс CSizeFrame обработчик сообщения WM_SIZE и добавьте в него следующий код:

void CSizeFrame::OnSize(UINT nType, int cx, int cy) 
{
    CMDIChildWnd::OnSize(nType, cx, cy);
    if (nType == SIZE_RESTORED && (m_cx && m_cx != cx || m_cy && m_cy != cy)) {
        m_sync = false;
    }
    m_cx = cx;
    m_cy = cy;
}

На этом, пожалуй, всё.


Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав.
    Сообщений 7    Оценка 10        Оценить