Image
28.6.2016 0 Comments

C++ pod Windows / Serializácia SDI aplikácií I / 20. časť

Späť na úvod >> Späť na programovanie >> Späť na seriál

V tejto časti budeme pokračovať v začatom rozprávaní o serializácii a preberieme si serializáciu aplikácií typu SDI (Single Document Interface). Uvedieme si dva ukážkové príklady. Prvým bude už známa aplikácia StudentList, ktorú si rozšírime o možnosť ukladania/čítania dát na/z disku. Druhým príkladom bude jednoduchý textový editor, na ktorom si okrem serializácie ukážeme prácu s triedami CEditView a CRichEditView.

SERIALIZÁCIA A APLIKAČNÝ SYSTÉM. V predchádzajúcej časti sme si ukázali, ako implementovať serializáciu do našich aplikácií „manuálne“ čiže bez použitia výhod, ktoré nám poskytuje aplikačný systém. Museli sme urobiť veľa vecí (vytvoriť objekt CArchive, spojiť ho s objektom CFile, ošetriť výnimky, ktoré by mohli nastať atď.). Mali sme síce priamu kontrolu nad každým serializovaným bajtom, ale pri väčších aplikáciách je takéto programovanie veľmi prácne. Teraz si ukážeme, ako serializovať s využitím aplikačného systému. Keďže sa budeme venovať serializácii SDI aplikácií, musíme si povedať niečo aj o tom, ako tieto aplikácie pracujú.

POHĽAD DOVNÚTRA APLIKÁCIE SDI. V šestnástej časti sme si zhruba ukázali, ako aplikácia SDI pracuje. Ale čo sa deje v jej vnútri? Aby sme nehovorili veľmi abstraktne, všetko si vysvetlíme priamo na príklade. Vytvorte nový projekt (či ho pridáte do Workspace Files, alebo nie, nechám na vás) s názvom SDIApp. Všetky nastavenia nechajte default. Po vygenerovaní kostry aplikácie pridajte na začiatok funkcií (tab. 1) debug výpis pomocou objektu afxDump v tvare:

afxDump << "TRIEDA::FUNKCIA()\n";

Napríklad do funkcie CSDIAppApp::InitInstance by ste pridali:

afxDump << "CSDIAppApp::InitInstance()\n";

Funkcie, do ktorých ste pridali tento debug výpis, sú „nosnými“ funkciami každej aplikácie SDI (nerátajte konštruktory jednotlivých objektov – tam sme debug výpis pridávali z iného dôvodu – pozri ďalej). Niektoré sme si už opisovali v 16. časti, zvyšné si opíšeme teraz.

CMainFrame::CMainFrame

CSDIAppDoc::OnNewDocument

CMainFrame::~CMainFrame

CSDIAppDoc::Serialize

CMainFrame::OnCreate

CSDIAppView::CSDIAppView

CMainFrame::PreCreateWindow

CSDIAppView::~CSDIAppView

CSDIAppApp::CSDIAppApp

CSDIAppView::PreCreateWindow

CSDIAppApp::InitInstance

CSDIAppView::OnDraw

CSDIAppDoc::CSDIAppDoc

CSDIAppView::GetDocument

CSDIAppDoc::~CSDIAppDoc

 

Tab. 1 Zoznam funkcií na pridanie debug výpisu

int CMainFrame::OnCreate: Táto funkcia dostáva ako svoj parameter ukazovateľ na štruktúru CREATESTRUCT, ktorá obsahuje parametre vytváraného okna. Štandardne slúži na vytvorenie hlavného rámcového okna aplikácie (CFrameWnd::OnCreate(lpCreateStruct)), toolbaru (m_wndToolBar.CreateEx(...)) a statusbaru (m_wndStatusBar.Create(this)). Samozrejme, nič nám nebráni rozšíriť ju podľa našich potrieb. 

BOOL CMainFrame::PreCreateWindow: Funkcia, ktorá je volaná ešte pred samotným zavolaním funkcie CMainFrame::OnCreate. Je tým správnym miestom na zmenenie vzhľadu vytváraného okna (pomocou členov štruktúry CREATESTRUCT).

BOOL CSDIAppApp::InitInstance: Je volaná zakaždým, keď vytvárame novú inštanciu našej aplikácie. V nej sa napríklad vytvára šablóna dokumentov (podrobnejšie ďalej), zapisujú sa základné údaje do registrov (funkcia SetRegistryKey – špecifikuje, kam sa budú v registroch ukladať nastavenia aplikácie – podrobnejšie nabudúce), nahrá sa zoznam naposledy otvorených súborov (funkcia LoadStdProfileSettings), volá sa dôležitá funkcia CWinApp::OnFileNew (volá sa nepriamo cez funkciu CWinApp::ProcessShellCommand – odporúčam odkrokovať – Step Into), spracuje sa príkazový riadok aplikácie (objekt CCommandLineInfo – pozri help), pomocou funkcie (ShowWindow) sa zobrazí hlavné rámcové okno aplikácie. Mali bzMasaMali by sme si ešte vysvetliť, čo je inštancia aplikácie. Keďže Windows umožňuje beh viacerých kópií jedného programu súčasne, vzniká potreba nejakým spôsobom rozdeliť inicializáciu aplikácie do niekoľkých častí, aby sa pri spúšťaní jej ďalšej kópie nemusela zavádzať ešte raz celá aplikácia, ale len jej časť. Spúšťanie (inicializácia) aplikácie je preto rozdelené do dvoch častí. Prvú predstavuje tzv. one-time initialization, čo je inicializácia, keď sa spúšťa aplikácia po prvýkrát (jej prvá kópia). Zakaždým, keď sa spúšťa ďalšia kópia tejto aplikácie, prebieha tzv. instance initialization („inicializácia inštancie“), ale už nepriebieha  one-time initialization. Instance initialization prebieha aj pri prvom štarte aplikácie spolu s one-time initialization. Akýmsi opakom funkcie InitInstance je funkcia ExitInstance. Je volaná aplikačným systémom pri ukončovaní inštancie aplikácie po zatvorení všetkých okien (podrobnejšie v helpe).

Šablóna dokumentu:  Vo funkcii InitInstance si všimnite tento blok kódu:

CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
      IDR_MAINFRAME,
      RUNTIME_CLASS(CSDIAppDoc),
      RUNTIME_CLASS(CMainFrame),       // main SDI frame window
      RUNTIME_CLASS(CSDIAppView));
AddDocTemplate(pDocTemplate);

Tu sa vytvára objekt šablóny dokumentu aplikácie typu SDI. Funkcia AddDocTemplate potom spája triedy aplikácie, dokumentu, pohľadu a hlavného rámca našej aplikácie (pozor – nepliesť so spájaním objektov, ktoré sa deje vo funkcii OnFileNew – pozri ďalej). Zaujímavé na tom je, že keď sa takto spájajú tieto triedy, žiadne ich objekty okrem objektu aplikácie neexistujú, aplikačný systém ich vytvára dynamicky. Celý tento mechanizmus súvisí s dynamickou identifikáciou tried počas run-time a dynamickým vytváraním objektov. Je založený na makrách (DECLARE_DYNAMIC, DECLARE_DYNCREATE, RUNTIME_CLASS) a vo svojej podstate je podobný RTTI (runtime type information), ktoré bolo predstavené vo Visual C++ 4.0. Mechanizmus dynamickej identifikácie tried počas behu programu bol v MFC implementovaný ešte pred zavedením RTTI do C++ a stále sa aj používa, keďže poskytuje viac možností ako spomínané RTTI. Nebudeme sa tu teraz tejto problematike podrobne venovať, ak nám niekedy pri konci seriálu zostane čas, povieme si o nej viac. Nedočkavcov zatiaľ odkážem na help. O šablónach dokumentov sa ešte zmienime pri aplikáciách MDI.   

CWinApp::OnFileNew: Túto funkciu v kóde nevidno, ale vykoná pre nás veľký kus roboty, preto si o nej povieme viac. Ako sme už uviedli, je volaná prostredníctvom funkcie ProcessShellCommand. Vytvára pre nás objekt dokumentu, objekt hlavného rámca, hlavné rámcové okno aplikácie (nezobrazuje ho). Ďalej vytvára objekt pohľadu, okno pohľadu (ale takisto ho nezobrazuje) a spájs objekt dokumentu, objekt hlavného rámca a objekt pohľadu. Potom volá funkcie týchto objektov CDocument::OnNewDocument pre objekt dokumentu, CFrameWnd::ActivateFrame pre objekt hlavného rámca a CView::OnInitialUpdate pre objekt pohľadu. Funkciu CFrameWnd::ActivateFrame sme si ešte neopisovali, takže len stručne: táto funkcia zobrazuje hlavné rámcové okno aplikácie (to je to najpodstatnejšie, viac nájdete v helpe). Funkcia OnFileNew tiež reaguje na výber položky z menu File-New (prípadne stlačením príslušného tlačidla na toolbare, resp. skratkového klávesu). V tomto prípade sa však správa trochu inak. V aplikácii SDI sú objekty dokumentu, hlavného rámca a pohľadu vytvorené iba raz (pri štarte aplikácie). Pri výbere File-New z menu už logicky vytvorené musia byť (inak by ani žiadne menu nebolo), preto v takomto prípade funkcia OnFileNew preskočí ich vytváranie a následné spojenie a svoju činnosť začína až volaním CDocument::OnNewDocument.

Teraz už asi tušíte, prečo sme pridávali debug výpisy do jednotlivých funkcií. Takto sa totiž dá najľahšie zistiť, ako aplikácia SDI pracuje (v akom poradí sú uvedené funkcie volané, nám prezradí, čo sa deje pri spustení aplikácie, keď s aplikáciou pracujeme, zistíme, aká funkcia sa pri akej činnosti volá atď.). Spusťte teda aplikáciu SDIApp v debug móde (F5) a hneď ju zase ukončite. Pozrite sa na kartu Debug. Malo by vám vyjsť takéto poradie:

CSDIAppApp::CSDIAppApp()
CSDIAppApp::InitInstance()
CSDIAppDoc::CSDIAppDoc()
CMainFrame::CMainFrame()
CMainFrame::PreCreateWindow()
CMainFrame::PreCreateWindow()
CMainFrame::OnCreate()
CSDIAppView::CSDIAppView()
CSDIAppView::PreCreateWindow()
CSDIAppDoc::OnNewDocument()
CSDIAppView::OnDraw()
CSDIAppView::GetDocument()
CSDIAppView::~CSDIAppView()
CMainFrame::~CMainFrame()
CSDIAppDoc::~SDIAppDoc()

My sme si už poradie funkcií všeobecnejšie opísali (pozri obr. 1 v 16. časti seriálu), teraz sme si ho prebrali podrobnejšie. Asi sa pýtate, prečo sa CMainFrame::PreCreateWindow() volá dvakrát za sebou. Prvýkrát sa táto funkcia zavolá pri vytváraní objektu hlavného rámca a o druhé zavolanie sa stará kód vnútri funkcie CFrameWnd::LoadFrame (odporúčam odkrokovať). Skúste chvíľu s aplikáciou pracovať a sledujte, aká funkcia sa zavolá pri tej-ktorej činnosti (pomocou Class Wizardu si pridajte aj ďalšie funkcie – CView::OnInitialUpdate, CFrameWnd::ActivateFrame a pod. – a zistite, pri akej činnosti sú volané).

Implementácia serializácie. Teraz, keď už vieme ako aplikácia SDI pracuje, môžeme spojazdniť serializáciu. So serializáciou súvisia operácie File-> Open a File->Save, resp. File-> Save as.

Otvorenie súboru je obslúžené funkciou CWinApp::OnFileOpen. Táto funkcia je namapovaná na položku menu ID_FILE_OPEN. Funkcia zo svojho tela volá ďalšie funkcie, ktoré najprv zobrazia štandardný File-Open dialóg, potom volajú funkciu CDocument::OnOpenDocument (táto funkcia otvorí súbor, zavolá funkciu DeleteContents, aby bolo isté, že dokument je prázdny, potom volá funkciu CObject::Serialize, ktorá načíta dáta. Aplikačný systém volá túto funkciu na reinicializáciu existujúceho objektu dokumentu (teda pri otváraní súboru sa nevytvára nový objekt dokumentu, ale využije sa už existujúci – overte si to pomocou výpisov afxDump). 

Uloženie súboru sa začína volaním funkcie OnFileSave (štandardne mapovaná na položku menu ID_FILE_SAVE). Volá funkciu CDocument::OnSaveDocument, ktorá sa postará o všetko potrebné (volá funkciu CObject::Serialize a tá zapíše dáta z dokumentu na disk). V prípade Save as je situácia rovnaká, ibaže obslužná funkcia položky ID_FILE_SAVE_AS sa volá OnFileSaveAs.

Úprava projektu StudentList. Teraz pridáme serializáciu do nášho projektu StudentList (verzia z 18. časti). Čo tipujete, koľko bude potrebné pridať kódu? Keď vám poviem, že stačí pridať jednu funkciu a navyše ešte tri riadky kódu, uveríte mi? Ak nie, hneď vás o tom presvedčím. V hlavičkovom a implementačnom súbore triedy CStudent zmeňte makrá DECLARE_DYNAMICIMPLEMENT_DYNAMIC na DECLARE_SERIAL(CStudent) a IMPLEMENT_SERIAL(CStudent, CObject, 0). Týmito makrami sme sprístupnili serializáciu, ale „odstavili“ sme prístup k run-time informáciám o tejto triede, čo nám však teraz veľmi neprekáža. V hlavičkovom súbore pridajte deklaráciu virtuálnej funkcie Serialize:

virtual void Serialize(CArchive& ar);

a v implementačnom súbore zapíšte túto funkciu takto:

void CStudent::Serialize(CArchive& ar)
{
      if (ar.IsStoring())
      {
            ar << m_strMeno << m_strPriezvisko << m_nRocnik << m_nKredity;
      }
      else
      {
            ar >> m_strMeno >> m_strPriezvisko >> m_nRocnik >> m_nKredity;
      }
}

Teraz už len stačí pridať jeden riadok kódu na koniec funkcie CDocument::Serialize:

void CStudentListDoc::Serialize(CArchive& ar)
{
      if (ar.IsStoring())
      {
                  // TODO: add storing code here
      }
      else
      {
            // TODO: add loading code here
      }
      m_studentList.Serialize(ar);
}

a je koniec. Aplikáciu odskúšajte – vytvorte záznamy aspoň o troch študentoch a uložte ich, potom vytvorte nový dokument a údaje načítajte. Odporúčam vám aj do tejto aplikácie pridať informatívne výpisy do dôležitých funkcií, prípadne celú aplikáciu odkrokovať. Tak najlepšie pochopíte, čo sa kedy deje.

Prípony súborov a registrácia programu. Terajšia verzia aplikácie StudentList vám síce umožňuje uložiť dáta do súboru, ale tento súbor je bez prípony a bežnému používateľovi nie je jasné, s akou aplikáciou je spojený. Aby sme mohli vytvárať súbor s príponou a ten asociovať s našou aplikáciou, musíme si ešte vysvetliť, čo je to zdroj šablóny dokumentu. Zdroj šablóny dokumentu používame pri volaní funkcie AddDocTemplate. Je to prvý parameter konštruktora objektu typu CSingleDocTemplate (obyčajne sa nazýva IDR_MAINFARME). Keď sa pozriete do zdrojov na položku String Table a tam rozkliknete IDR_MAINFRAME, uvidíte jeden reťazec, ktorého podreťazce sú oddelené pomocou znaku \n. Upravte reťazec takto:

StudentList\n\nStudent\nStudentList Files (*.Slt)\n.Slt\n
StudentList.Document\nStudent Document

Pre lepšie pochopenie uvádzam ešte vysvetlenie každého z podreťazcov (tab. 2).

Podreťazec

Opis

StudentList\n

titulok okna aplikácie

\n

implicitné meno dokumentu (ak necháme nezadané (náš prípad),  bude Untitled

Student\n

meno typu dokumentu

StudentList Files (*.Slt)\n

opis typu dokumentu a filter

.Slt\n   

prípona dokumentu

StudentList.Document\n

ID typu dokumentu v registroch

Student Document

opis ID v registroch

Tab. 2  Vysvetlenie podreťazcov v IDR_MAINFRAME

Keď teraz spustíte StudentList, pri otváraní a ukladaní súborov už budete mať k dispozícii príponu, filter a opis typu dokumentu.

Obr. 1    Informácie o type prípony v registroch

Stále nám však ešte chýba asociácia súborov s príponou *.Slt s naším programom. Pridajte preto volanie funkcií EnableShellOpen a RegisterShellFileTypes do funkcie CStudentListApp::InitInstance:

EnableShellOpen();
RegisterShellFileTypes(TRUE);

Prvá funkcia umožní spúšťanie našej aplikácie priamo zo súborového prehliadača a druhá zaregistruje prípony súborov v registroch (podrobne pozri help). Teraz už môžeme spúšťať súbory programu priamo (pozrite sa v registroch do HKEY_CLASSES_ROOT, kde vyhľadajte .Slt – obr. 1). Samozrejmosťou je aj priradenie ikony súborom s príponou *.Slt. Zdroj tejto ikony predstavuje IDR_SDIAPPTYPE (nájdete v zložke ICON v zdrojoch).

K dokonalosti už ostáva len  sfunkčniť tzv. Drag and Drop čiže otvorenie súboru jeho priamym pretiahnutím na okno programu (jednoducho napr. z Explorera zoberiete súbor a ťaháte ho až nad okno programu, kde ho pustíte). Opäť do funkcie CStudentListApp::InitInstance pridajte tento riadok:

m_pMainWnd->DragAcceptFiles();

Opis tejto funkcie je v helpe. Drag and Drop teraz bude fungovať.

(Poznámka: Nastavenie týchto vlastností sa dá komfortnejšie urobiť už pri tvorení aplikácie – vo 4. kroku tvorenia aplikácie pomocou AppWizardu – kliknite na Advanced a vyplňte požadované polia, hlavne pole File extension). AppWizard teraz pridá všetky tu opisované funkcie automaticky a už žiadne ďalšie úpravy nebudú potrebné).

Nabudúce. Ako sa tak dívam na veľkosť súboru, opis sľúbených tried  CEditViewCRichEditView a ukážkovú aplikáciu si necháme do budúcej časti. O mesiac dovidenia.

Zobrazit Galériu

Nechajte si posielať prehľad najdôležitejších správ emailom

Mohlo by Vás zaujímať

Ako na to

Tipy a triky: Ako na snímku obrazovky na akomkoľvek počítači s Windows?

02.12.2016 00:13

Ak snímky obrazovky robíte často apotrebujete napríklad funkcie na posun stránok alebo snímanie zobrazenia pri vyššom rozlíšení displeja, zrejme používate nejakú špecializovanú aplikáciu. Väčšina použ ...

Ako na to 1

Tipy a triky: Ako aplikácii prednastaviť spúšťanie s administrátorskými právami?

30.11.2016 00:10

Väčšina aspoň trochu skúsenejších používateľov vie, že aj keď máte na operačnom systéme Windows vytvorený administrátorský účet, aplikácie pre bezpečnosť nefungujú vždy splnými administrátorskými práv ...

Ako na to 2

Tipy a triky: Ako vypnúť uzamykaciu obrazovku vo Windows 10?

29.11.2016 00:10

Rozčuľuje vás, že pred každým prihlásením doúčtu vášho počítača musíte prejsť uzamykacou obrazovkou? Windows 10 na tejto obrazovke ukazuje čas,dátum anejakú zaujímavú fotografiu zrôznych kútov sveta. ...

Žiadne komentáre

Vyhľadávanie

Kyocera - prve-zariadenia-formatu-a4-s-vykonom-a3

Najnovšie videá