15 мая 2023 года "Исходники.РУ" отмечают своё 23-летие!
Поздравляем всех причастных и неравнодушных с этим событием!
И огромное спасибо всем, кто был и остаётся с нами все эти годы!

Главная Форум Журнал Wiki DRKB Discuz!ML Помощь проекту


Создание 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;
      } 



ССЫЛКИ

Для получения дополнительной информации, можно посмотреть следующие статьи из Microsoft Knowledge Base:

Q143291 Как изменить размер CPropertyPages во время выполнения

Q146916 Как создать немодальный CPropertySheet со стандартными кнопками