Image
27.6.2016 0 Comments

C++ pod Windows / Kolekcie / 18. časť

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

Rok 2002 sa už stal realitou. Zase nám bude pár týždňov trvať, než prestaneme na dokumnety písať „01“ či „2001“ a „zosynchronizujeme sa s našou časovou osou“, ako by povedal nejeden fanúšik sci-fi. Dúfam, že ste Silvestra prežili bez ujmy na zdraví a začali s veľkým odhodlaním plniť novoročné predsavzatia (moment, koľkého že dnes máme? Okolo 10. januára? Tak to hej, to sa predsavzatia ešte plnia ). 

Medzi najvýznamnejšie udalosti roku 2002 bude pre WIN programátorský svet predstavovať uvoľnenie novej verzie Microsoft Visual Studia – Visual Studio NET(ohlásené na 13. februára) a následný boom „NET aplikácií“, ktoré po jeho uvedení zaplavia softvérový trh. Som veľmi zvedavý, ako sa mu bude dariť. Ide totiž skôr o úplne nový produkt ako o pokračovanie. Microsoft sa rozhodol pre použitie minima kódu zo starého VStudia a takmer všetko sa písalo nanovo (predsa len je VS 6.0 už tamer štyri roky starý a jeho jadro nebolo príliš vhodné na zakomponovanie všetkých nových „features“). Uvidíme teda, ako si NET poradí s existujúcou konkurenciou, každopádne treba mu držať palce.

Seriál až do svojho konca zostane pri Visual Studiu 6.0, azda niekedy v poslednej časti si opíšeme najdôležitejšie črty jeho novej verzie.

Touto časťou sa opäť vraciame ku klasickému výkladu, ktorý sme na mesiac prerušili. Preberieme si veľmi dôležitú a rozsiahlu problematiku kolekcií (Collections).

COLLECTIONS. Označenie „kolekcia“ sa nepoužíva len v súvislosti s Vianocami, ale predstavuje aj dôležitú súčasť knižnice MFC. Aby ste si pod týmto pojmom nepredstavovali niečo zložité a ktovieako abstraktné, stačí si uvedomiť, že kolekciu tvorí skupina objektov (dalo by sa povedať aj pole objektov, ale pre kolekcie má pole osobitný význam, preto ostaneme pri označení skupina objektov). Kolekcia je sama osebe takisto objektom, ktorý môže uchovávať nejaké dáta, resp. ako sme už povedali, aj iné objekty. Kolekcie sa vo všeobecnosti delia podľa dvoch kritéríí. Podľa svojej vnútornej štruktúry (angl. shape), ktorá určuje, ako sú dáta/objekty v kolekcii usporiadané. Druhé kritérium rozdeľuje kolekcie podľa tried kolekcií MFC.

Podľa vnútornej štuktúry rozlišujeme kolekcie: List, ArrayMap (zoznam, pole a mapa).

List – predstavuje usporiadaný, neindexovaný zoznam prvkov. Má svoj začiatok (označovaný head) a koniec (tail). Vyznačuje sa hlavne svojou rýchlosťou pri vkladaní/odstraňovaní prvkov.

Array – predstavuje usporiadané, indexované pole objektov (indexy môžu byť len celočíselné hodnoty). Podobá sa klasickému poľu elementárnych prkov.

Map – na prvý pohľad najkomplikovanejší, ale nenahraditeľný typ kolekcie, ktorý nejakému kľúčovému objektu priraďuje objekt s hodnotou. Tento typ kolekcie sa zvykne označovať aj ako slovník.

Porovnanie vlastností typov kolekcií vidíte v tab. 1.

Z pohľadu MFC existujú tri typy tried kolekcií, pričom v každej skupine existujú triedy, ktoré pracujú s kolekciami typu List, Array a Map (pozri tab. 2 a tab. 3). Rozlišujeme teda:

1.   triedy kolekcií založené na šablónach jazyka C++, založené na kolekciách objektov rôznych typov

(tab. 2),

2.   triedy kolekcií založené na šablónach jazyka C++, založené na kolekciách typovo bezpečných

ukazovateľov na objekty rôznych typov  (tab. 2),

3.   triedy kolekcií, ktoré nie sú založené na šablónach jazyka C++ (tab. 3).

Tieto triedy sa ešte ďalej môžu deliť na triedy, ktoré sú serializovateľné, triedy, ktoré podporujú dumpovanie, a triedy, ktoré sú typovo bezpečné. V súčasnosti sa odporúča využívať triedy založené na šablónach C++, jednak sú novšie a viacobjektové, jednak sú typovo bezpečné (type-safe). Samozrejme, nič vám nebráni používať nešablónové triedy.

Typ kolekcie

Usporiadané

Indexované

Vkladanie prvku

Hľadanie konkrétneho prvku

Duplicitné prvky

List

Áno

Nie

Rýchle

Pomalé

Podporuje

Array

Áno

Áno (pod¾a celoèíselného indexu)

Pomalé

Pomalé

Podporuje

Map

Nie

Áno (pod¾a k¾úèa)

Rýchle

Rýchle

K¾úèe nie
Hodnoty áno

Tab. 1 Vlastnosti kolekcií podľa vnútornej štruktúry prvkov

Obsah kolekcie

Triedy pre typ kolekcie Array

Triedy pre typ kolekcie List

Triedy pre typ kolekcie Map

Kolekcia objektov rôznych typov

CArray

CList

CMap

Kolekcia ukazovateľov na objekty rôznych typov

CTypedPtrArray

CTypedPtrList

CTypedPtrMap

Tab. 2 Triedy kolekcií MFC založené na šablónach C++

Triedy pre typ kolekcie Array

Triedy pre typ kolekcie List

Triedy pre typ kolekcie Map

CObArray

CObList

CMapPtrToWord

CByteArray

CPtrList

CMapPtrToPtr

CDWordArray

CStringList

CMapStringToOb

CPtrArray

 

CMapStringToPtr

CStringArray

 

CMapStringToString

CWordArray

 

CMapWordToOb

CUIntArray

 

CMapWordToPtr

Tab. 3 Nešablónové triedy kolekcií MFC

Ďalšie informácie o týchto triedach nájdete v helpe.

PREDPRÍPRAVA NA PRÍKLADY. Aby sme si ukázali opäť niečo nové v prostredí Visual C++, nasledujúci príklad + všetky úlohy, ktoré si budete doma skúšať, si uložte do jedného Workspace (pracovného prostredia). V praxi sa podobný postup využíva veľmi často, preto bude dobré, ak si ho osvojíte a začnete aktívne používať (navrhujem vám napr. všetky príklady z jednej časti seriálu udržovať v samostatných pracovných prostrediach). Z menu File vyberte New, kliknite na kartu Workspaces a vyberte Blank Workspace. Do textového poľa Workspace name napíšte Collections a potvrďte (OK). Teraz máme vytvorené čisté pracovné prostredie a doň budeme postupne pridávať jednotlivé projekty.

PRÍKLAD – POUŽITIE CTypedPtrList. V príklade si ukážeme použitie kolekcie typu List s pomocou na šablóne založenej triedy MFC CTypedPtrList, ktorá „uchováva“ objekty ukazovateľov na nejakú triedu (resp. objekt). Za predpokladu, že máte otvorené Workspace Collections, vytvorte nový projekt typu Win32 Console Application s podporou MFC (pozri predchádzajúcu časť). Vloženie tohto projektu do Workspace Collections zabezpečíte tak, že na karte výberu typu nového projektu označíte Add to current Workspace. Meno projektu je: SimpleList

Ak všetko prebehlo bez problémov, vo Workspace Collections sa teraz nachádza jeden projekt SimpleList. Pomocou menu Insert-New Class vložte do tohto projektu novú triedu CSimpleListClass.

Kód hlavičkového súboru triedy CSimpleListClass upravte takto:          

class CSimpleListClass : public CObject
{
      // pre dumping
      DECLARE_DYNAMIC(CSimpleListClass)
public:
      CSimpleListClass(int nPoradie);
      virtual ~CSimpleListClass();
      void Dump(CDumpContext &dc) const;
      void PrintPoradie();
private:
      int m_nPoradieObjektu;
};

V implementačnom súbore triedy CSimpleListClass zmeňte konštruktor a pridajte kód funkcie PrintPoradie a Dump:

CSimpleListClass::CSimpleListClass(int nPoradie)
{
      m_nPoradieObjektu=nPoradie;
}
 
void CSimpleListClass::PrintPoradie()
{
printf("Cislo objektu: %d\n", m_nPoradieObjektu);
}
 
void CSimpleListClass::Dump(CDumpContext &dc) const
{
      CObject::Dump(dc);
      dc << "Poradie objektu: " << m_nPoradieObjektu << "\n";
}

Pre správnu funkciu dumpingu pridajte ešte makro IMPLEMENT_DYNAMIC na začiatok tohto súboru:

IMPLEMENT_DYNAMIC(CSimpleListClass, CObject)

Teraz prepíšte vetvu else vo funkcii _tmain (kde sa nachádza komentár: // TODO: code your application's behavior here).

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
      int nRetCode = 0;
 
      // initialize MFC and print and error on failure
      if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
      {
            // TODO: change error code to suit your needs
            cerr << _T("Fatal Error: MFC initialization failed") << endl;
            nRetCode = 1;
      }
      else
      {
            // Vetva else – tento kód pridajte
            CSimpleListClass* simpleListObject;
           
            // objekt ukazovatelov na CSimpleListClass
            CTypedPtrList <CObList, CSimpleListClass*> m_SimpleList;
           
            // vytvarame a vkladame ukazovatele na objekty typu CSimpleListClass
            // do zoznamu simpleList
            for (int i=0; i<5; i++)
            {
                  simpleListObject=new CSimpleListClass(i);
                  m_SimpleList.AddTail(simpleListObject);
            }
 
            // posuvame sa po objektoch v kolekcii
            POSITION pos=m_SimpleList.GetHeadPosition();
            while (pos!=NULL)
            {
                  // GetNext vracia ukazovatel na CSimpleListClass
                  // preto je priradenie spravne!
                  simpleListObject=m_SimpleList.GetNext(pos);
                  simpleListObject->PrintPoradie();
            }
 
            // nastavime hlbku Dumpovania
            afxDump.SetDepth(1);
           
            // Dump m_SimpleList
            afxDump << m_SimpleList;
 
            // vyberieme ukazovatele na objekty CSimpleListClass
            // a zrusime ich
            while (!m_SimpleList.IsEmpty())
            {
                  simpleListObject=m_SimpleList.RemoveHead();
                  afxDump <<"Upraveny m_SimpleList: \n";
                  afxDump << m_SimpleList;
                  delete simpleListObject;
            }
      }
 
      return nRetCode;
}
 

Opíšeme si niektoré nové konštrukcie, ktoré by vám mohli robiť problémy. Riadok

CTypedPtrList <CObList, CSimpleListClass*> m_SimpleList;

deklaruje objekt ukazovateľov na triedu CSimpleListClass. Prvý parameter je základnou triedou kolekcie. Môže to byť len trieda CPtrList alebo nami použitá CObList. CObList sa odporúča používať, ak ukladáte objekt odvodený od CObject (čo v našom prípade aj robíme – pozri triedu CSimpleListClass), v opačnom prípade sa používa CPtrList (skúste si príklad aj s touto triedou a sledujte, ako sa zmení Dump výstup). Na riadku

POSITION pos=m_SimpleList.GetHeadPosition();

deklarujeme premennú pos typu POSITION a priraďujeme jej ukazovateľ na začiatok zoznamu m_SimpleList (podrobnejšie v helpe). Táto premenná reprezentuje polohu prvku v zozname. Ďalej v kóde používame túto premennú v spojení s funkciou GetNext, ktorá vracia ukazovateľ na prvok, ktorého pozícia je práve pos, a zároveň inkrementuje túto premennú, čiže po zavolaní funkcie bude obsahovať polohu prvku pos+1(ak sme na poslednom prvku, tak pos=NULL). Preto pri posúvaní sa po prvkoch v zozname vykonávame test:

while (pos!=NULL)

či sme už náhodou nedosiahli jeho koniec. Za povšimnutie ešte stojí proces rušenia objektov simpleListObject. Pri odstraňovaní objektov z kolekcie vracia funkcia RemoveHead ukazovateľ na práve rušený objekt, ktorý použijeme, aby sme zrušili aj samotný objekt (vyňatím ukazovateľa zo zoznamu sa objekt nezruší!).  Do súboru stdafx.h vložte ešte túto include direktívu

#include <afxtempl.h>

Tá je potrebná, ak chcete používať kolekcie založené na šablónových triedach (pre nešablónové triedy by ste includovali súbor afxcoll.h).

To bolo použitie triedy CtypedPtrList; skúste si aj použitie ostatných tried, pričom ďalšie projekty pridávajte do Workspace Collections. Odporúčam vám všetky triedy kolekcií MFC dobre naštudovať, niekedy sa budete musieť rozhodnúť, ktorú z nich použijete. A správne sa rozhodnete len v takom prípade, že budete dokonale poznať každú z týchto tried.

Zlepšenie projektu Študenti. Teraz s využitím kolekcií zlepšíme projekt StudentList zo 16. časti. Nová verzia bude schopná uchovávať informácie o viacerých študentoch, pričom bude možné posúvať sa po jednotlivých záznamoch a vkladať/mazať údaje o konkrétnom študentovi. Nevytvárajte zbytočne nový projekt, všetky zmeny vykonajte do už existujúceho projektu StudentList

Krok 1. Pridajte menu Student, ktoré bude obsahovať položky: First, Last, Next, Prev, |Separator|, Insert, Remove. Takisto pridajte tlačidlá na toolbar, ktoré budú mať rovnaký význam (a hlavne rovnaké ID), ich grafickú interpretáciu nechám na vás. Pomocou ClassWizardu namapujte obslužné správy týchto tlačidiel (v tomto prípade je potrebné namapovať pre každé tlačidlo okrem tlačidla Insert aj správu UPDATE_COMMAND_UI – podrobnosti sú v tab. 4).

Táto správa je potrebná na to, aby sme mohli podľa aktuálnej pozície v zozname zneprístupniť niektoré tlačidlo (napr. ak sa nachádzame na prvom zázname, zneprístupníme tlačidlá First a Prev). Podrobnejší význam tlačidiel je v tab. 5. Nakoniec ešte správne odstráňte tlačidlo IDC_GETDATA („správne“ znamená zo zdrojov + nezabudnúť odstrániť ho pomocou ClassWizardu + vymazať telo obslužnej funkcie). Ako by malo vyzerať okno aplikácie, to vidíte na obr. 3.

Krok 2.  Teraz to už stačí celé iba naprogramovať.

Zmeny student.h

---CUT---
 
#include <afxtempl.h>
 
---CUT---
 
typedef CTypedPtrList<CObList, CStudent*> CStudentList;
 
Zmeny StudentListDoc.h
 
public:
      CStudentList* GetList() { return &m_studentList; }
 
private:
      CStudentList m_studentList;
 
Zmeny StudentListDoc.cpp
 
---CUT---
 
CStudentListDoc::CStudentListDoc()
{
      afxDump.SetDepth(1);
}
 
---CUT---
 
void CStudentListDoc::Dump(CDumpContext& dc) const
{
      CDocument::Dump(dc);
      dc << "\n" << m_studentList << "\n";
}
 
---CUT---
 
void CStudentListDoc::DeleteContents()
{
      Dump(afxDump);
 
      while (m_studentList.GetHeadPosition())
      {
            delete m_studentList.RemoveHead();
      }
}
 
Zmeny StudentListView.h
 
protected:
      POSITION      m_position; // uchovava aktualnu poziciu v zozname
      CStudentList* m_pList;    
 
      virtual void InsertEntry(POSITION position);
      virtual void GetEntry(POSITION position);
 
Zmeny StudentListView.cpp
 
---CUT---
 
CStudentListView::CStudentListView()
      : CFormView(CStudentListView::IDD)
{
      //{{AFX_DATA_INIT(CStudentListView)
      m_nKredity = 0;
      m_strMeno = _T("");
      m_strPriezvisko = _T("");
      m_nRocnik = 0;
      //}}AFX_DATA_INIT
      // TODO: add construction code here
      m_position=NULL;
}
 
---CUT---
 
void CStudentListView::OnInitialUpdate()
{
      m_pList = GetDocument()->GetList();
      CFormView::OnInitialUpdate();
}
 
---CUT---
 
void CStudentListView::OnWritedata()
{
      InsertEntry(m_position);
      GetDocument()->SetModifiedFlag();
      GetDocument()->UpdateAllViews(this);
}
 
---CUT---
 
void CStudentListView::OnStudentInsert()
{
      InsertEntry(m_position);
      GetDocument()->SetModifiedFlag();
      GetDocument()->UpdateAllViews(this);
}
 
---CUT---
 
void CStudentListView::OnStudentFirst()
{
if (!m_pList->IsEmpty()) {
            m_position = m_pList->GetHeadPosition();
            GetEntry(m_position);
      }
}
 
---CUT---
 
void CStudentListView::OnUpdateStudentFirst(CCmdUI* pCmdUI)
{
      POSITION pos;
 
      pos = m_pList->GetHeadPosition();
      pCmdUI->Enable((m_position != NULL) && (pos != m_position));
}
 
---CUT---
 
void CStudentListView::OnStudentLast()
{
      TRACE("Entering CStudentView::OnCommandEnd\n");
      if (!m_pList->IsEmpty()) {
            m_position = m_pList->GetTailPosition();
            GetEntry(m_position);
      }
     
}
 
---CUT---
 
void CStudentListView::OnUpdateStudentLast(CCmdUI* pCmdUI)
{
      POSITION pos;
 
      pos = m_pList->GetTailPosition();
      pCmdUI->Enable((m_position != NULL) && (pos != m_position));
}
 
---CUT---
 
void CStudentListView::OnStudentNext()
{
POSITION pos;
      TRACE("Entering CStudentView::OnCommandNext\n");
      if ((pos = m_position) != NULL) {
            m_pList->GetNext(pos);
            if (pos) {
                  GetEntry(pos);
                  m_position = pos;
            }
      }
     
}
 
---CUT---
 
void CStudentListView::OnUpdateStudentNext(CCmdUI* pCmdUI)
{
      POSITION pos;
 
      pos = m_pList->GetTailPosition();
      pCmdUI->Enable((m_position != NULL) && (pos != m_position));
     
}
 
---CUT---
 
void CStudentListView::OnStudentPrev()
{
      POSITION pos;
      TRACE("Entering CStudentView::OnCommandPrev\n");
      if ((pos = m_position) != NULL) {
            m_pList->GetPrev(pos);
            if (pos) {
                  GetEntry(pos);
                  m_position = pos;
            }
      }
     
}
 
---CUT---
 
void CStudentListView::OnUpdateStudentPrev(CCmdUI* pCmdUI)
{
      POSITION pos;
 
      pos = m_pList->GetHeadPosition();
      pCmdUI->Enable((m_position != NULL) && (pos != m_position));
}
 
---CUT---
 
void CStudentListView::OnStudentRemove()
{
POSITION pos;
      if ((pos = m_position) != NULL) {
            m_pList->GetNext(pos);
            if (pos == NULL) {
                  pos = m_pList->GetHeadPosition();
                  TRACE("GetHeadPos = %ld\n", pos);
                  if (pos == m_position) {
                        pos = NULL;
                  }
            }
            GetEntry(pos);
            CStudent* ps = m_pList->GetAt(m_position);
            m_pList->RemoveAt(m_position);
            delete ps;
            m_position = pos;
            GetDocument()->SetModifiedFlag();
            GetDocument()->UpdateAllViews(this);
      }
     
}
 
---CUT---
 
void CStudentListView::OnUpdateStudentRemove(CCmdUI* pCmdUI)
{
      pCmdUI->Enable(m_position != NULL);
}
 
---CUT---
 
void CStudentListView::InsertEntry(POSITION position)
{
      if (UpdateData(TRUE)) {
            // UpdateData returns FALSE if it detects a user error
            CStudent* pStudent = new CStudent;
            pStudent->m_strMeno = m_strMeno;
            pStudent->m_strPriezvisko = m_strPriezvisko;
            pStudent->m_nRocnik = m_nRocnik;
            pStudent->m_nKredity = m_nKredity;
 
            m_position = m_pList->InsertAfter(m_position, pStudent);
      }
}
 
---CUT---
 
void CStudentListView::GetEntry(POSITION position)
{
      if (position) {
            CStudent* pStudent = m_pList->GetAt(position);
            m_strMeno= pStudent->m_strMeno;
            m_strPriezvisko= pStudent->m_strPriezvisko;
            m_nRocnik= pStudent->m_nRocnik;
            m_nKredity=pStudent->m_nKredity;
      }
      else {
            OnClearview();
      }
      UpdateData(FALSE);
}
 
---CUT---
 
void CStudentListView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)
{
      m_position = m_pList->GetHeadPosition();
      GetEntry(m_position); // natiahne prvotne data pre pohlad
}

 

ID ovládacieho prvku

Správa

Obslužná funkcia

Trieda

ID_STUDENT_FIRST

COMMAND

OnStudentFirst

CStudentListView

ID_STUDENT_FIRST

UPDATE_COMMAND_UI

OnUpdateStudentFirst

CStudentListView

ID_STUDENT_LAST

COMMAND

OnStudentLast

CStudentListView

ID_STUDENT_LAST

UPDATE_COMMAND_UI

OnUpdateStudentLast

CStudentListView

ID_STUDENT_NEXT

COMMAND

OnStudentNext

CStudentListView

ID_STUDENT_NEXT

UPDATE_COMMAND_UI

OnUpdateStudentNext

CStudentListView

ID_STUDENT_PREV

COMMAND

OnStudentPrev

CStudentListView

ID_STUDENT_PREV

UPDATE_COMMAND_UI

OnUpdateStudentPrev

CStudentListView

ID_STUDENT_INSERT

COMMAND

OnStudentInsert

CStudentListView

ID_STUDENT_REMOVE

COMMAND

OnStudentRemove

CStudentListView

ID_STUDENT_REMOVE

UPDATE_COMMAND_UI

 

OnUpdateStudentRemove

CStudentListView

Tab. 4 Ovládacie prvky projektu StudentList

ID ovládacieho prvku

Opis

ID_STUDENT_FIRST

Posun na začiatok zoznamu (na prvý záznam)

ID_STUDENT_LAST

Posun na koniec zoznamu (na posledný záznam)

ID_STUDENT_NEXT

Posun na ďalší prvok zoznamu

ID_STUDENT_PREV

Posun na predchádzajúci prvok zoznamu

ID_STUDENT_INSERT

Vloženie nového záznamu

ID_STUDENT_REMOVE

Odstránenie aktívneho záznamu

Tab. 5 Význam ovládacích prvkov projektu StudentList

Tipy na doma. V helpe si pozrite informácie o kolekciách, vhodne doplnia výklad z tejto časti. Takisto sa snažte aspoň sčasti pochopiť príklad Collect, uvedený v helpe. Skutočne, aj keď sa to nezdá, kolekcie sú veľmi dôležitou súčasťou aplikáčného systému. Ich dokonalým zvládnutím sa zaradíte medzi „vyšších“ MFC programátorov. A čo je najdôležitejšie, kolekcie určite využijete pri mnohých projektoch a pomôžu vám sprehľadniť váš kód.  

Nabudúce. V nasledujúcej časti sa naplno vrhneme na serializáciu, opíšeme si, ako ju spojazdniť v našich projektoch, samozrejme, nezabudneme na nejaký ukážkový príklad. Takže „dočítania“ o mesiac...


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

Mohlo by Vás zaujímať

Ako na to

Ako zbaviť fotky hmly

08.12.2016 11:59

Hmla alebo dym sú často veľmi kreatívne nástroje. No všetkého veľa škodí. Fotka potom stráca kontrast a v podstate na nej nič nevidieť. Hmlu môžete neraz následnými úpravami odstrániť alebo zredukovať ...

Ako na to

Užitočné SW nástroje

08.12.2016 11:53

AllDup v4.0.3 Určenie: program na vyhľadávanie a odstraňovanie duplicitných súborov Vlastnosti: duplicitné súbory sa vyhľadávajú len na zvolených diskových jednotkách alebo len v rámci vybraných ...

Ako na to

Fotografovanie s bleskom

08.12.2016 11:47

Ak máte moderný fotoaparát so vstavaným alebo externým bleskom, zdá sa vám téma článku triviálna. Jednoducho nastavíte vhodný režim, vyberiete najlepšiu kompozíciu záberu, exponujete a o zvyšok sa už ...

Žiadne komentáre

Vyhľadávanie

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

Najnovšie videá