Először nézzük meg mi is egy képernyővédő program.

Első ránézésre is kiderül, hogy a kiterjesztése .scr, ami azt sugallja, hogy nem egy szabvány Windows-os .exe. Ez persze nem igaz. A felépítése teljesen ugyanaz, át is írhatnánk .exe-re, és el is indulna, ha olyan parancs-sor argumentummal hívnánk, amilyennel a Windows kezeli őket:

A képernyővédőnek átadott parancs-sor argumentumok a következők lehetnek:

  • /p handle

  • PreView. Child Window-ban való megjelenés. Ekkor a második argumentum a Parent Window Handle-je. Ezt a Handle-t használva tudjuk lekérdezni a megjelenítésre kijelölt helyet egy egyszerű GetClientRect-el. Mikor a Display properties-nél képernyővédőt választunk, ezeket a paramétereket használva hívja meg a Windows programunkat, ezért látszik ott kicsiben.
  • /c

  • Konfigurálás. Mikor képernyővédőt választunk, a Settings… gombra bökve ezzel a paraméterrel hívódik meg a programunk. Ekkor a képernyővédőnek biztosítania kell, hogy felhasználói beállításokat eszközölhessen. Ezt egy egyszerű dialógusablakban szokták megoldani. A beállításokat pedig el kell tudni menteni, és persze beolvasni.
  • /s

  • Save. Mehet a cuccos. Képernyővédés egész képernyőn. Bármely eseményre (billentyűlenyomás, egérmozgatás, clickelés) azonnal kilépni.
Tulajdonképpen a képernyővédők is ugyanolyan programok, mint a többi, csak ezekkel az argumentumokkal kell őket hívni, különben nem történik semmi.

A programunknak átadott parancs-sori argumentumok lekérdezése:

Ezt természetesen többféleképpen megtehetjük.

A legegyszerűbb megoldás a sima DOS-os C-s programozásnál is létező __argc és a __argv használata. Ezek globális változók, minden előkészítés nélkül használhatóak (sima C-nél include-olni kellett a dos.h-t). Az __argc az argumentumok számát jelenti. Az __argv egy tömb, melynek minden n-dik eleme az n-dik argumentumot tartalmazó stringre mutat. Maga a programfájl is egy argumentumnak számít, ezért az __argv 0-dik eleme mindig a programfájl neve lesz, és az __argc is egyel több, mint a ténylegesen átadott argumentumok száma. Ezekkel a változókkal egyszerűen megnézhetünk minden argumentumot, sőt a programfájl nevét is.

A bonyolultabb, szebb, és MFC-s megoldás az, ha az objektumokat is belekeverjük. Az MFC-nek léteznek az argumentumokat kezelő dolgai. Ezeket az AppWizard minden alkalommal előszeretettel berakja a programunkba. Az Application osztály InitInstace-ébe:
 
 

// Parse command line for standard shell commands, DDE, file open CCommandLineInfo cmdInfo; ParseCommandLine(cmdInfo);// Dispatch commands specified on the command line if (!ProcessShellCommand(cmdInfo)) return FALSE;

Talán ismerős. Ezek csak a standard parancsokat kezelik (DDE, egy fájl megnyitása stb.). Ha vannak speciális argumentumai a programnak, ezeket a dolgokat kell egy kicsit átírni. A parancs-sor információt tartalmazó osztály a CCommandLineInfo. Ebből leszármaztathatjuk a saját parancs-sor információnkat tartalmazó osztályt. Az osztálynak van egy pár adattagja, amik az információt tárolják, és egy ParseParam nevű függvénye (meg sok minden, ami most nem érdekes).

virtual void CCommandLineInfo::ParseParam( LPCTSTR lpszParam, BOOL bFlag, BOOL bLast );

Ez a függvény végzi egy argumentum vizsgálatát, és az argumentumnak megfelelően állítja be az adattagokat. Az első paramétere az argumentum, a második azt jelenti, hogy volt-e előtte - vagy / jel. A harmadik azt mondja meg, hogy ez-e az utolsó.

A CWinApp::ParseCommandLine függvény hívja meg minden argumentumra a paraméterként átadott CCommandLineInfo osztály ParseParam függvényét.

Egy képernyővédő argumentumait kezelő CCommandLineInfo-ból származtatott osztály így néz ki:
 
 

class CMyCommandLine : public CCommandLineInfo { public: HWND m_ParentHandle; enum Operations {   NOP=0,   CONFIG,   DOSAVE,   PREVIEW, } m_Operation;public: CMyCommandLine() {   m_Operation=NOP; } virtual void ParseParam( LPCTSTR lpszParam, BOOL bFlag, BOOL bLast ) {   //Preview-nal a masodik(utolso) argumentum a parent Handle-je   if (bLast && m_Operation==PREVIEW) m_ParentHandle=(HWND) atol(lpszParam);   switch(lpszParam[0])   {   case 'c' : case 'C' :    m_Operation=CONFIG;    break;   case 's' : case 'S' :    m_Operation=DOSAVE;    break;   case 'p' : case 'P' :    m_Operation=PREVIEW;    break;   } } };

A konstruktorban csak nullázni kell az adattagot, ami a teendőt fogja tárolni. A ParseParam függvény beállítja mi lesz a teendő, és a Parent window Handle-jét is eltárolja, ha van.

Az adatok tárolása

Az adatok eltárolására nem INI file-t használunk, hanem a registry-t. A registry key beállításához a CWinApp::SetRegistryKey függvénye kell.

void SetRegistryKey( LPCTSTR lpszRegistryKey );

Paraméterként a Registry Key-t kell megadni. Ekkor a GetProfileInt, WriteProfileInt, GetProfileString, és WrtieProfileString függvények a beállított registry key-ből dolgoznak. (Ezekről a függvényekről egy előző részben már volt szó.)

Az adataink a következő helyen lesznek a registryben:

HKEY_CURRENT_USER\Software\lpszRegistryKey>\<application name>\<section name>\<value name>

Ezenkívül

Ezt az InitInstace-ból kell meghívni. Nézzük is:
 
 

BOOL CSSApp::InitInstance() { Enable3dControlsStatic(); // Call this when linking to MFC statically //itt fogjuk tarolni az adatainkat a registryben: SetRegistryKey(_T("MFC ScreenSaver example.")); CMyCommandLine cmdInfo; ParseCommandLine(cmdInfo); //argumentumok be switch(cmdInfo.m_Operation) { case cmdInfo.NOP : case cmdInfo.CONFIG :   DoConfig();   break; case cmdInfo.DOSAVE :   DoSave();   return TRUE; case cmdInfo.PREVIEW :   DoPreView(cmdInfo.m_ParentHandle);   return TRUE; } return FALSE; }

Elintéztük az argumentumok lekezelését is. Ha nem adtunk meg argumentumot, akkor is konfigurálunk. Ez elég egyszerű, csak be kell olvasni a konfigurációt, elindítani a dialog window-t, majd lementeni a változásokat:
 
 

void CSSApp::DoConfig() { CConfigDlg ConfigDlg; ReadConfig(&ConfigDlg.m_Config); m_pMainWnd = &ConfigDlg; if (ConfigDlg.DoModal()==IDOK) WriteConfig(ConfigDlg.m_Config); }

Közben az Application m_pMainWnd-jét be kell állítanunk.

A másik két esetben, mikor valóban el kell indítani a screen savert, nem ilyen egyszerű a helyzet. A rajzolást végző osztályt (CSSWnd) a CWnd class-ból származtattuk, így ott külön meg kell hívni az osztály Create (vagy CreateEx) függvényét, hogy létrehozza az ablakot. Ebből a rajzolgatást végző osztályból van származtatva az, (CSSsave) amelyik az egész képernyős saving-et végzi.

A rajzolást végző osztály Create függvényét kicsit meg változtatni, hogy megfeleljen. Ki kell kapcsolni az egérkurzort, ezt úgy érjük el, hogy létrehozunk egy ablakosztályt üres kurzorral. Ezután a CreateEx függvényt hívjuk meg, nem a Create-t, mert az ablak lehet kibővített stílusú. Az egész képernyős játéknál biztosítani kell, hogy minden ablakot takarjunk el, ez a WS_EX_TOPMOST stílus. A Create meghívása után meghívódik az OnCreate, ahol beállítjuk a Timert, és már minden megy.

Az átírt Create függvény:
 
 

BOOL CSSWnd::Create(DWORD dwExStyle, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd) {     // Register a class with no cursor if (m_lpszClassName == NULL) {      m_lpszClassName = AfxRegisterWndClass(CS_HREDRAW|CS_VREDRAW,    ::LoadCursor(AfxGetResourceHandle(),    MAKEINTRESOURCE(IDC_NULLCURSOR))); } return CreateEx(dwExStyle, m_lpszClassName, _T(""), dwStyle,   rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top,   pParentWnd->GetSafeHwnd(), NULL, NULL ); }

Paraméterként a kibővített stílust, a stílust, az ablak elhelyezkedését, és a szülőt kell megadni. Szülő csak PreView-nál lesz, bővített stílus pedig csak egész képernyőn. A CreateEx függvénynek átadandó paraméterek sorrendben a bővített stílus, az ablakosztály, az ablak címe, a stílus, bal felső sarok koordinátái, szélesség, magasság, a szülő Handle-je, Child window ID-je, majd egy pointer a createstruct struktúra lpCreateParams elemére.

Látható, hogy a szülőablak Handle-jét a GetSafeWnd függvénnyel tudjuk lekérdezni. Egyszerűbb lett volna rögtön a Handle-t átadni, de most már mindegy.

Tehát a preview elindítása:
 
 

void CSSApp::DoPreView(HWND ParentHandle) { CWnd* pParent = CWnd::FromHandle(ParentHandle); ASSERT(pParent != NULL); CSSWnd* pPreViewWnd = new CSSWnd(); CRect rect; pParent->GetClientRect(&rect); pPreViewWnd->Create(NULL, WS_VISIBLE|WS_CHILD, rect, pParent); m_pMainWnd = pPreViewWnd; }

Csak a szülő azonosítóját tudjuk (handle) a parancssorból, kell hozzá rendelni egy CWnd classt, mert onnan kell lekérdeznünk az ablakterületet. Ez a CWnd::FromHandle függvényével lehetséges. Így egy GetClientRect-el le tudjuk kérdezni az ablakterületet. A Create után megadjuk a főablakot az Application osztály számára.

Teljes képernyőre:
 

void CSSApp::DoSave() { CSSsave* pSaveWnd = new CSSsave; pSaveWnd->Create(); m_pMainWnd = pSaveWnd; }

Egy másik osztály végzi, mint a Child window-ban való futást, de abból van származtatva. Tartalmaz egy csomó üzenetkezelőt, hogy bármely eseményre ki tudjon lépni. Ilyenkor egyszerűen egy WM_CLOSE üzenetet küld. Egy üzenetkezelő a sok közül:
 
 

void CSSsave::OnLButtonDown(UINT nFlags, CPoint point) { PostMessage(WM_CLOSE); CSSWnd::OnLButtonDown(nFlags, point); }

Egyedül az OnMouseMove néz ki másképpen, figyelni kell rá, hogy rögtön az ablak létrehozásakor is meghívódik egyszer, ilyenkor pedig nem jó kilépni.

Az osztály Create függvénye biztosítja, hogy fusson a képernyővédő:
 
 

void CSSsave::Create() { CRect rect(0, 0, ::GetSystemMetrics(SM_CXSCREEN),   ::GetSystemMetrics(SM_CYSCREEN)); CSSWnd::Create(WS_EX_TOPMOST, WS_VISIBLE|WS_POPUP, rect, NULL); }

Lekérdezzük a képernyő méreteit, ezt adjuk át ablakterületnek az ősosztályunk Create-jának, ami mindent elintéz.

Ha a programot megírja az ember, azt sem árt elintézni, hogy ne exe-kiterjesztésűre, hanem scr-re fordítson a Visual C++. Egyszerűen beszabadulunk a Project\Settings… -be, és ahol csak exe-t látunk átírjuk scr-re.

Fontos:

A Display properties a ScreenSaver listán megjelenő neveket a programok erőforrásából szedi. A String táblán az 1-es string lesz a listán. Ez a programban az IDS_DESCRIPTION azonosítójú string. Az azonosító mindegy, az a lényeg, hogy a hozzá rendelt érték 1 legyen. Ezt úgy érhetjük el, hogy a String Properties-nél az ID-hez hozzáírunk egy "=1"-et. Pl.:

ID: IDS_DESCRIPTION=1

Egy másik megoldás, ha a resource.h-ban írjuk át az értéket 1-re.
Ha nincs ilyen értékű string a string táblán, a Windows csak egy üres sort tesz a lista elejére, és nem enged vele semmit csinálni.

Hát ennyi.
Ja és még felhívnám a figyelmet, hogy ha lefordítod a forráskódot, akkor át kell nevezni az SS.scr-t valami másra, mert SS.scr néven nem látja a Windows 9x képernyővédő beállítója... (Az SS egymásutáni karakterek valami spec. célra lehetnek fenntartva. Windows NT alatt nincs ilyen probléma. Azért megjegyezném a teljesség kedvéért, hogy az MS féle "túldokumentáltságnak" köszönhetően egy hetem ment rá, hogy vajon miért megy WinNT alatt és miért nem megy Win9x alatt.. Talán a resource filelal van a gond (gondoltam naívan) ? Ilyen triviális dologra, hogy nem lehet a képernyővédő program neve SS.scr - mint a ScreenSaver rövidítése -, nem gondoltam, csak végső elkeseredésemben. Lehet, hogy nem ártott volna a helpben erre utalni ?)