09
Ian
2010

Introducere

De obicei nu este nevoie si nici nu-i prea indicat sa se schimbe fotul default la un property sheet. Totusi, uneori aceasta poate fi o cerinta. Sa zicem, un client vrea un font corporatist sau pur si simplu trebuie folosit intr-o aplicatie un font foarte mare pentru a fi cat mai vizibil. In acest caz n-avem ce face, trebuie sa rezolvam problema.

Primele incercari

Unii ar putea spune: "Un fleac! Tot ce avem de facut este ca in editorul de resurse sa modificam fontul pentru fiecare template de dialog din care se creeaza paginile de proprietati (property pages) care intra in componenta property sheet-ului".
Aceasta nu functioneaza. Functia WinAPI care creaza property sheet-ul utilizeaza fontul dintr-o resursa pre-definita pe care nu o putem modifica. In continuare, framework-ul MFC seteaza exact acelasi font si pentru pagini, ignorand setarile pe care le-am facut in editorul de resurse.

Altii ar propune o alta solutie: "Hai mai bine sa lasam Windows-ul si MFC-ul sa creeze un property sheet cu ce font vor, apoi schimbam noi fontul la runtime cu functia CWnd::SetFont".
Intradevar, aceasta ar putea fi o soutie dar nu ar fi una prea comoda. Pe langa faptul ca trebuie schimbat fontul pentru pentru toate contralele, mai e mult de lucru pentru a redimensiona controalele, paginile, contolul tab, cat si insusi property sheet-ul.

O solutie mai buna

Windows API foloseste pentru crearea unui property sheet o structura tip PROPSHEETHEADER. In aceasta structura, putem seta in membrul pfnCallback adresa unei functii callback definita in aplicatie. Aceasta functie este apelata inainte de crearea property sheet-ului, iar noi putem schimba aici numele si marimea fontului intr-o structura tip DLGTEMPLATE a carei adresa se transmite functiei prin parametrul lParam.

   // psh is a PROPSHEETHEADER structure used to create the property sheet
   psh.pfnCallback = PropSheetProc;
   psh.dwFlags |= PSH_USECALLBACK;

int CALLBACK PropSheetProc(HWND hWndDlg, UINT uMsg, LPARAM lParam)
{
   switch(uMsg)
   {
   case PSCB_PRECREATE: // property sheet is being created
      {
         DLGTEMPLATE* pResource = (DLGTEMPLATE*)lParam;
         // modyfy the font face name and size in pResource. 
      }
      break;
   }
   return 0;
}

De notat in primul rand ca, pentru a fi apelata functia callback, trebuie setat si flag-ul PSH_USECALLBACK.
I al doilea rand, modificarea fontului in pResource nu-i chiar foarte simpla. Asta pentru ca DLGTEMPLATE este urmata de un numar de campuri de lungine variabila apoi de un sir de structuri tip DLGITEMTEMPLATE de asemenra urmate de campuri de lungime variabla. Si ca sa fie si mai naspa, este posibil sa avem variantele "EX" ale acestor structuri (DLGTEMPLATEEX, respectiv DLGITEMTEMPLATEEX), destul de diferite. In plus, trebuie sa mai avem si grija sa aliniem structurile la DWORD.
Din fericire, MFC-ul foloseste deja intern o clasa numita CDialogTemplate (definita in AFXPRIV.H) care are grija de toate, ajutandu-ne sa rezolvam problema fara dureri de cap. Veti vedea cum am folosit-o putin mai tarziu, in implementarea clasei CCBPropertySheet.

Buuuun... Pana acum am rezolvat cu fontul in butoanele din property sheet ("OK", "Cancel", "Apply", etc), cat si la textele din controlul tab. In continuare trebuie sa ne ocupam si de fontul din pagini.
In prima faza am fost tentat sa urmez aceeasi cale: "Modificam structura PROPSHEETPAGE pentru a seta o functie callback in care sa schmb fontul din pagini." Asta poate sa functioneze dar nu intotdeauna. Depinzand de versiunea de Windows sau MFC, functia callback poate fi apelata prea tarziu, rezultand o redimensionare incorecta. Dupa cateva cafele si tigari, am gasit un loc pentru a rezolva probema in mod corect suprascriind functia virtuala CPropertySheet::BuildPropPageArray (de asemenea o veti putea vedea in implementarea clasei CCBPropertySheet).

Clasa CCBPropertySheet

CCBPropertySheet este derivata din clasa MFC CPropertySheet si contine functiile pentru font custom. Cum am promis mai sus, iata aici implementarea functie callback:

int CALLBACK CCBPropertySheet::PropSheetProc(HWND hWndDlg, UINT uMsg, LPARAM lParam)
{
   switch(uMsg)
   {
   case PSCB_PRECREATE:
      {
         if((m_wFontSize > 0) && (NULL != m_pszFontFaceName))
         {
            LPDLGTEMPLATE pResource = (LPDLGTEMPLATE)lParam;
            CDialogTemplate dlgTemplate(pResource);
            dlgTemplate.SetFont(m_pszFontFaceName, m_wFontSize);
            memmove((void*)lParam, dlgTemplate.m_hTemplate, dlgTemplate.m_dwTemplateSize);
         }
      }
      break;
   }
   return 0;
} 

Iar aici este implementarea functiei suprascrise BuildPropPageArray:

void CCBPropertySheet::BuildPropPageArray()
{
   CPropertySheet::BuildPropPageArray();

   if((m_wFontSize > 0) && (NULL != m_pszFontFaceName))
   {
      LPCPROPSHEETPAGE ppsp = m_psh.ppsp;
      const int nSize = static_cast< int >(m_pages.GetSize());

      for(int nPage = 0; nPage < nSize; nPage++)
      {
         const DLGTEMPLATE* pResource = ppsp->pResource;
         CDialogTemplate dlgTemplate(pResource);
         dlgTemplate.SetFont(m_pszFontFaceName, m_wFontSize);
         memmove((void*)pResource, dlgTemplate.m_hTemplate, dlgTemplate.m_dwTemplateSize);

         (BYTE*&)ppsp += ppsp->dwSize;
      }
   }
}

In final doua exemple de utilizare pentru CCBPropertySheet. Primul creeaza un property sheet modal cu font default, pe cand al doilea creaza un property sheet modal cu font custom.

   // CMyPropertySheet is derived from CCBPropertySheet
   CMyPropertySheet pps(_T("Property Sheet with default font"), this);
   // creates a modal property sheet with default font
   pps.DoModal();

   // CMyPropertySheet is derived from CCBPropertySheet
   CMyPropertySheet pps(_T("Property Sheet with Arial Bold 20"), this);
   // creates a modal property sheet with custom font
   pps.DoModal(_T("Arial Bold"), 20);

Puteti gasi tot codul clasei CCBPropertySheet in fisierele sursa CBPropertySheet.h and CBPropertySheet.cpp atasate la articol.

Aplicatia demo

Aplicatia demo este o aplicatie simpla MFC dialog-based, care demonstreaza cum se foloseste CCBPropertySheet pentru a crea property sheet-uri cu font custom.

Trebuie doar sa selectati numele fontului (font face name) cat si marimea si apoi sa apasati butonul "Show...". Se creeaza un property sheet modal cu fontul dorit. Acesta poate arata ca in imagine.

Link-uri

Note la final

Am compilat aplicatia demo cu Visual C++ 6.0 si cu Visual Studio 2005 si am testat-o pe Windows XP, Vista si Windows 7.
Daca cineva inalneste probleme, il rog sa-mi dea un "beep".