Создание MFC Property Sheet изменяемого размера

Иногда для приложения требуется большей гибкости в использовании property sheet, чем может предоставить Microsoft Foundation Classes (MFC). В данной статье, демонстрируются шаги, по созданию класса property sheet, который так же содержит полоску меню.

Приведённые ниже инструкции, показывают, как добавить новый класс property sheet в существующий проект MFC и настроить окошко, обработав запросы на изменение размера, а так же добавив меню. В галвной роли будет выступать класс, CMyPropertySheet, который можно использовать для воплощения нашего замысла. Этот класс мог бы стать достойной заменой  для MFC-шного CPropertySheet так как обеспечивает дополнительную функциональность, описанную выше.

  1. В проекте, основанном на MFC, добавление нового property sheet осуществляется при помощи галереи компонентов. Для этого в меню Project кликните на Add to project, а затем на Component and Controls. В появившемся диалоговом окошке сделайте двойной щелчёк на папку "Components", а затем выберите Property Sheet.

  2. Для того, чтобы сделать рамку изменяемого размера, необходимо переопределить DoModal() и создать callback функцию, которая установит соответствующие стили для окошка свойств (property sheet). Поэтому, необходимо добавить следующие две функции (как в заголовочном файле, так и в файле исходника для класса CMyPropertySheet.):

    • Статическая callback функция окошка свойств, XmnPropSheetCallback():

      // Эта функция должна быть STATIC.
      //Callback позволяет нам установить стили окна поумолчанию
      // для property sheet
      int CALLBACK CMyPropertySheet::XmnPropSheetCallback(HWND hWnd,
      UINT message, LPARAM lParam)
      {
      extern int CALLBACK AfxPropSheetCallback(HWND, UINT message, LPARAM lParam);
      // XMN: Вызывает MFC-шный callback
      int nRes = AfxPropSheetCallback(hWnd, message, lParam);

      switch (message)
      {
      case PSCB_PRECREATE:
      // Устанавливаем наши собственные стили окна
      ((LPDLGTEMPLATE)lParam)->style |= (DS_3DLOOK | DS_SETFONT
      | WS_THICKFRAME | WS_SYSMENU | WS_POPUP | WS_VISIBLE | WS_CAPTION);
      break;
      }
      return nRes;
      }
    • Переопределение DoModal():

      // Переопределение DoModal() позволяет нам перехватить наш callback
      // при создании окошка свойств
      int CMyPropertySheet::DoModal()
      {
      // Ловушка в коде создания property sheet
      AFX_OLDPROPSHEETHEADER* psh = GetPropSheetHeader();
      psh->dwFlags |= PSH_USECALLBACK;
      psh->pfnCallback = XmnPropSheetCallback;
      return CPropertySheet::DoModal();
      }
  3. Теперь мы имеем property sheet, размеры которого можно изменять при помощи мышки. Чтобы добавить меню, Вам необходимо переопределить OnInitDialog(). В меню View выберите Class Wizard, затем в диалоговом окне Class Wizard выберите проект и класс для property sheet. Далее в выпадающем списке Messages выберите пункт OnInitDialog. Приведённый ниже код, показывает, как должно выглядеть переопределение OnInitDialog():

    BOOL CMyPropertySheet::OnInitDialog() 
    {
    BOOL bResult = CPropertySheet::OnInitDialog();

    // Добавляем новое меню
    CMenu *pMenu = new CMenu; pMenu->LoadMenu(IDR_PS_MENU);
    SetMenu(pMenu);

    // Корректируем размер propsheet под новое меню
    CRect r; GetWindowRect(&r);
    r.bottom += GetSystemMetrics(SM_CYMENU);
    MoveWindow(r);

    return bResult;
    }
  4. Теперь нам необходимо обеспечить политику изменения размеров. Наиболее правильный способ сделать это, заключается в том, чтобы изменить размеры внедрённого контрола tab в соответствии с запросом на изменение размеров и перемещая кнопки, присутствующие на окне. Для этого проделаем следующее:

    1. В класс CMyPropertySheet добавьте следующие переменные:
      protected:
      BOOL m_bNeedInit;
      CRect m_rCrt;
      int m_nMinCX;
      int m_nMinCY;
    2. Инициализируйте эти переменные и установите m_bNeedInit в TRUE в конструкторе CMyPropertySheet:

      CMyPropertySheet::CMyPropertySheet(CWnd* pWndParent)
      : CPropertySheet(IDS_PROPSHT_CAPTION, pWndParent)
      , m_bNeedInit(TRUE)
      , m_nMinCX(0)
      , m_nMinCY(0)

      {
      AddPage(&m_Page1);
      AddPage(&m_Page2);

      }
    3. Добавьте следующие строки в конец OnInitDialog():

      BOOL CMyPropertySheet::OnInitDialog() 
      {
      // ...
      // Инициализируем m_nMinCX/Y
      m_nMinCX = r.Width();
      m_nMinCY = r.Height();
      // After this point we allow resize code to kick in
      m_bNeedInit = FALSE;
      GetClientRect(&m_rCrt);

      return bResult;
      }
    4. Делаем обработчики для события WM_SIZE. Для этого в меню View кликаем Class Wizard, а затем в диалоговом окошке Class Wizard выбираем проект и класс окна свойств (property sheet). Далее, в выпадающем списке Messages выбираем пункт WM_SIZE. Ниже показана реализаци обработчика сообщения:

      // Обрабатываем событие WM_SIZE путём изменения размера контрола tab
      // и перемещая все кнопки на property sheet.
      void CMyPropertySheet::OnSize(UINT nType, int cx, int cy)
      {
      CRect r1;
      CPropertySheet::OnSize(nType, cx, cy);

      if (m_bNeedInit)
      return;

      CTabCtrl *pTab = GetTabControl();
      ASSERT(NULL != pTab && IsWindow(pTab->m_hWnd));

      int dx = cx - m_rCrt.Width();
      int dy = cy - m_rCrt.Height();
      GetClientRect(&m_rCrt);

      HDWP hDWP = ::BeginDeferWindowPos(5);

      pTab->GetClientRect(&r1);
      r1.right += dx; r1.bottom += dy;
      ::DeferWindowPos(hDWP, pTab->m_hWnd, NULL,
      0, 0, r1.Width(), r1.Height(),
      SWP_NOACTIVATE|SWP_NOMOVE|SWP_NOZORDER);

      // Перемещаем все кнопки с нижних правых сторон
      for (CWnd *pChild = GetWindow(GW_CHILD);
      pChild != NULL;
      pChild = pChild->GetWindow(GW_HWNDNEXT))
      {
      if (pChild->SendMessage(WM_GETDLGCODE) & DLGC_BUTTON)
      {
      pChild->GetWindowRect(&r1); ScreenToClient(&r1);
      r1.top += dy; r1.bottom += dy; r1.left+= dx; r1.right += dx;
      ::DeferWindowPos(hDWP, pChild->m_hWnd, NULL,
      r1.left, r1.top, 0, 0,
      SWP_NOACTIVATE|SWP_NOSIZE|SWP_NOZORDER);
      }
      // Ресайзим всё остальное...
      else
      {
      pChild->GetClientRect(&r1);
      r1.right += dx; r1.bottom += dy;
      ::DeferWindowPos(hDWP, pChild->m_hWnd, NULL, 0, 0,
      r1.Width(), r1.Height(),
      SWP_NOACTIVATE|SWP_NOMOVE|SWP_NOZORDER);
      }

      }

      ::EndDeferWindowPos(hDWP);
      }
    5. В заключении, Вам необходимо создать обработчик сообщения WM_GETMINMAXINFO. Для этого в меню View кликаем Class Wizard, а затем в диалоговом окошке Class Wizard выбираем проект и класс окна свойств (property sheet). Далее, в выпадающем списке Messages выбираем пункт WM_GETMINMAXINFO. Ниже приведена реализация обработчика WM_GETMINMAXINFO:

      void CMyPropertySheet::OnGetMinMaxInfo(MINMAXINFO FAR* lpMMI) 
      {
      CPropertySheet::OnGetMinMaxInfo(lpMMI);
      lpMMI->ptMinTrackSize.x = m_nMinCX;
      lpMMI->ptMinTrackSize.y = m_nMinCY;
      }