Intruder
Intruder3 февраля 2020 г. 5:47

QTableView и список значений в ячейке

QTableView, QList, QVector

Всем доброго времени суток!

Подскажите, пожалуйста, как лучше сделать:
1. Имеется модель данных, посторенная с помощью QAbstractTableView. Сама таблица отражает объект в плоском виде.
2. В некоторых колонках нужно отобразить список значений, потому что в самом объекте присутствуют QList/QVector, которые в свою очередь тоже содержат некоторые свойства.

Вопрос у меня возникает следующий, как сделать так, чтобы можно было записывать в модель данных объект, который имеет в своем составе массив, как свойство. Если бы это была простая БД, то особых проблем бы не было, я просто нормализовал бы данные, связал связями и получил обычный Select через первичные и внешние ключи.
Но тут у меня объект программный и для него нужна модель данных. Модель я построил. Но вопрос остался с ячейками, где должны храниться массивы. Как тут быть?

Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.

Вам это нравится? Поделитесь в социальных сетях!

10
Evgenii Legotckoi
  • 3 февраля 2020 г. 6:20

Добрый день,
Покажите модель данных и класс объекта, который отвечает за репрезентацию данных. Обычно достаточно иметь поле вектора, а в модель превращать список/вектор в строку требуемых значений и отдавать его для Display роли.

    Intruder
    • 3 февраля 2020 г. 7:35

    Вот это модель данных:

    QVariant FeedThruTableModel::headerData(int section, Qt::Orientation orientation, int role) const
    {
        if(role != Qt::DisplayRole){
            return QVariant();
        }
    
        if(orientation == Qt::Vertical){
            return section + 1;
        }
        switch(section){
        case HOLEIDENT:
            return tr("Hole Identification");
        case FUNCTIONALITEMNUMBER:
            return tr("Functional Item Number Identifier");
        case FUNCTIONALITEMTYPE:
            return tr("Functional Item Type");
        case IDFUNCTIONALITEMREF:
            return tr("Identifier");
        case INSTALLATIONIDENT:
            return tr("Installation Identifier");
        case APPLICREFID:
            return tr("Applicability Information");
        case CHANGETYPE:
            return tr("Change Type");
        case CHANGEMARK:
            return tr("Change Mark");
        case REASONFORUPDATEREFIDS:
            return tr("Reason For Update Ref Ids");
        case SECURITYCLASSIFICATION:
            return tr("Security Classification");
        case COMMERCIALCLASSIFICATION:
            return tr("Commercial Classification");
        case CAVEAT:
            return tr("Caveat");
        case AUTHORITYNAME:
            return tr("Authority Name");
        case AUTHORITYDOCUMENT:
            return tr("Authority Document");
        case CONTEXTIDENT:
            return tr("Context Identification");
        case MANUFACTURERCODEVALUE:
            return tr("CAGE Code");
        case ITEMORIGINATOR:
            return tr("Functional Item Originator");
        case NAMEVALUE:
            return tr("Functional Item Name");
        case CHANGETYPENAME:
            return tr("Change Type");
        case CHANGEMARKNAME:
            return tr("Change Mark");
        case REASONFORUPDATEREFIDSNAME:
            return tr("Reason For Update Ref Ids");
        case SHORTNAMEVALUE:
            return tr("Alternate Name");
        case DMREF: // Вот это поле объявлено как QVector<dmRef> *vectorDmRef
            return tr("References to data modules");
        case PMREF: // Вот это поле объявлено как QVector<pmRef> *vectorPmRef
            return tr("References to publications");
        case EXTERNALPUBREF: // Вот это поле как QVector<externalPubRef> *vectorExternalPubRef
            return tr("References to external documents");
        }
        return QVariant();
    }
    
    

    Сам объект описан вот так:

        // Attributes of the tag <funcitonalItemRef>
        QMap<QString, QString> *functionalItemRefAttrMap = nullptr;
        // Tag <name>
        Name *nameObject = nullptr;
        // Tag <shortName>
        QString shortNameValue;
        // Tag <refs>
        Refs *refsObject = nullptr;
    

    Объект Refs:

        // The tag <dmRef>
        QVector<DmRef> *dmRefVector = nullptr;
        // The tag <pmRef>
        // Здесь пока ничего нет, но по аналогии с dmRef
        // The tag <externalPubRef>
        // Здесь пока ничего нет, но по аналогии с dmRef
    

    Или я что-то неправильно понял?

      Evgenii Legotckoi
      • 3 февраля 2020 г. 16:32

      Немного не догоняю, вот этот вектор dmRefVector должен как-то мапиться во все эти enum?

      Может тогда лучше QMap ? А потом уже возвращать и устанавливать данные в зависимости от этого enum?

        Intruder
        • 3 февраля 2020 г. 23:00

        Евгений, добрый день.

        Я планировал для элемента dmRef получать агрегатное значение и его отображать. Иными словами в модели будет храниться его подготовленное значение. Если представлять его в каком-то виде, то я предполагал взять QListView для этих целей. А зачем QMap ?
        Но, сейчас у меня возник еще один вопрос, а как эти данные готовить. С однозначными(атомарными) значениями все понятно, а тут? Я могу показать весь класс для этой модели, если нужно.

          Intruder
          • 4 февраля 2020 г. 1:37

          Евгений, у меня с обычными таблицами проблем вообще никаких не возникло, разобрался как это работает и в путь. Но тут получается, что объект, который я кладу в модель данных - он составной и в его составе могут быть другие обеъкты и/или массивы/списки/словари. А вот как в этом случае строить модель данных для меня не совсем понятно.
          Я тут поразмыслил и мне пришла в голову мысль, что нужно как-то модернизировать эти объекты-"матрешки". Например, вместо объекта, который входит в состав хранить не сам объект, а его идентификатор-ключ. В объекте, который будет включаться ввести дополнительное свойство, идентификатор. Вот только придется следить за уникальностью этих идентификаторов программно при создании.
          Вот только одного я не учел. В файл мне нельзя класть эти идентификаторы иначе я не пройду валидацию. Как вариант придется ломать формат и вводить комментарии или элементы, которые не влияют на валидацию.

            Evgenii Legotckoi
            • 4 февраля 2020 г. 3:08
            • (ред.)

            Класс объекта, который вы храните в модели должен иметь нужные методы для возврата и установки значений, а логику по возврату значений из "составных" кусков инкапсулируйте внутрь класса. И там уже внутри разбиайтесь что там есть, а чего нет. И в зависимости от типа данных и составных кусков сможете возвращать информацию в удобовармом для модели виде.

            Для управления уникальными указателями попробуйте использовать shared_ptr

              Intruder
              • 4 февраля 2020 г. 3:21

              Евгений, спасибо.
              Буду пробовать.

                Intruder
                • 5 февраля 2020 г. 6:32

                Евгений, добрый день.

                Я понял о чем Вы меня спрашивали, когда речь шла про вектор. Нет, этот вектор должен отображаться только в одну ячейку. Получается, что с точки зрения БД - мое значение не атомарно, а представляет собой набор значений, в свою очередь эти значения представляют собой QMap.

                  Intruder
                  • 5 февраля 2020 г. 12:53

                  Решил пойти сначала. Создал тестовый проект.
                  Тестовая форма
                  Здесь отображаются данные в верхней части, это данные основного объекта, а в нижней части отображаются данные объекта, который входит в состав основного объекта. Этим объектом является QVector<составной_объект>.
                  Классы, которые описывают основной и составной объект достаточно просты:
                  Составной объект class Contact и его описание

                  #ifndef CONTACT_H
                  #define CONTACT_H
                  
                  #include <QMap>
                  
                  class Contact
                  {
                  public:
                      Contact();
                      ~Contact(){}
                  
                      QMap<QString, QString> *getMap() const;
                      void setMap(QMap<QString, QString> *value);
                  private:
                      QMap<QString, QString> *map = nullptr;
                  };
                  
                  #endif // CONTACT_H
                  
                  #include "contact.h"
                  Contact::Contact() {}
                  
                  QMap<QString, QString> *Contact::getMap() const {return map;}
                  
                  void Contact::setMap(QMap<QString, QString> *value) {map = value;}
                  

                  Основной объект class ConnectionList и его описание

                  #ifndef CONNECTIONLIST_H
                  #define CONNECTIONLIST_H
                  
                  #include "contact.h"
                  
                  class ConnectionList
                  {
                  public:
                      ConnectionList();
                      ~ConnectionList();
                  
                      QString getAttrValue() const;
                      void setAttrValue(const QString &value);
                  
                      QVector<Contact> *getContactList() const;
                      void setContactList(QVector<Contact> *value);
                  
                  private:
                      QString attrValue;
                      QVector<Contact> *contactList = nullptr;
                  };
                  
                  #endif // CONNECTIONLIST_H
                  
                  #include "connectionlist.h"
                  
                  ConnectionList::ConnectionList(){}
                  
                  QString ConnectionList::getAttrValue() const {return attrValue;}
                  
                  void ConnectionList::setAttrValue(const QString &value) {attrValue = value;}
                  
                  QVector<Contact> *ConnectionList::getContactList() const {return contactList;}
                  
                  void ConnectionList::setContactList(QVector<Contact> *value) {contactList = value;}
                  

                  Модель данных для отображения таблицы "Contact List" и описание:

                  #ifndef CONTACTMODEL_H
                  #define CONTACTMODEL_H
                  
                  #include "contact.h"
                  
                  #include <QAbstractTableModel>
                  
                  class ContactModel : public QAbstractTableModel
                  {
                      Q_OBJECT
                  
                  public:
                      ContactModel(QObject *parent = nullptr);
                  
                      void appendContact(Contact *item);
                      void resetModel();
                      QVector<Contact> getContactList();
                  
                  private:
                      enum Cols{
                          CONTACTIDENT = 0,
                          CONTACTFUNCTION,
                          CONTACTTYPE,
                          CONNECTEDFLAG,
                          CONTACTPARTNUMBER,
                          WIREINSTALLATION,
                          COLUMNCOUNT
                      };
                  
                      typedef QHash<Cols, QVariant> ContactData;
                      typedef QList<ContactData> ContactGroup;
                      ContactGroup m_contactGroup;
                  
                  
                      // QAbstractItemModel interface
                  public:
                      int rowCount(const QModelIndex &parent) const;
                      int columnCount(const QModelIndex &parent) const;
                      QVariant data(const QModelIndex &index, int role) const;
                  
                      // QAbstractItemModel interface
                  public:
                      QVariant headerData(int section, Qt::Orientation orientation, int role) const;
                  
                      // QAbstractItemModel interface
                  public:
                      Qt::ItemFlags flags(const QModelIndex &index) const;
                  };
                  
                  #endif // CONTACTMODEL_H
                  
                  
                  #include "contactmodel.h"
                  
                  ContactModel::ContactModel(QObject *parent)
                      : QAbstractTableModel(parent)
                  {
                  }
                  
                  QVariant ContactModel::headerData(int section, Qt::Orientation orientation, int role) const
                  {
                      if(role != Qt::DisplayRole){
                          return QVariant();
                      }
                  
                      if(orientation == Qt::Vertical){
                          return section + 1;
                      }
                      switch(section){
                      case CONTACTIDENT:
                          return tr("CONTACTIDENT");
                      case CONTACTFUNCTION:
                          return tr("CONTACTFUNCTION");
                      case CONTACTTYPE:
                          return tr("CONTACTTYPE");
                      case CONNECTEDFLAG:
                          return tr("CONNECTEDFLAG");
                      case CONTACTPARTNUMBER:
                          return tr("CONTACTPARTNUMBER");
                      case WIREINSTALLATION:
                          return tr("WIREINSTALLATION");
                      }
                      return QVariant();
                  }
                  
                  int ContactModel::rowCount(const QModelIndex &parent) const
                  {
                      Q_UNUSED(parent)
                      return m_contactGroup.count();
                  
                  }
                  
                  int ContactModel::columnCount(const QModelIndex &parent) const
                  {
                      Q_UNUSED(parent)
                      return COLUMNCOUNT;
                  }
                  
                  
                  void ContactModel::appendContact(Contact *item)
                  {    ContactData contactItem;
                       if(item->getMap()->contains("1")){
                           contactItem[CONTACTIDENT] = item->getMap()->value(item->getMap()->key("1"));
                       }
                       if(item->getMap()->contains("2")){
                           contactItem[CONTACTFUNCTION] = item->getMap()->value(item->getMap()->key("2"));
                       }
                       if(item->getMap()->contains("3")){
                           contactItem[CONTACTTYPE] = item->getMap()->value(item->getMap()->key("3"));
                       }
                       if(item->getMap()->contains("4")){
                           contactItem[CONNECTEDFLAG] = item->getMap()->value(item->getMap()->key("4"));
                       }
                       if(item->getMap()->contains("5")){
                           contactItem[CONTACTPARTNUMBER] = item->getMap()->value(item->getMap()->key("5"));
                       }
                       if(item->getMap()->contains("6")){
                           contactItem[WIREINSTALLATION] = item->getMap()->value(item->getMap()->key("6"));
                       }
                       int row = m_contactGroup.count();
                       beginInsertRows(QModelIndex(), row, row);
                       m_contactGroup.append(contactItem);
                       endInsertRows();
                  }
                  
                  
                  Qt::ItemFlags ContactModel::flags(const QModelIndex &index) const
                  {
                      Qt::ItemFlags flags = QAbstractTableModel::flags(index);
                      if(index.isValid()){
                          flags |= Qt::ItemIsEditable;
                      }
                  
                      return flags;
                  }
                  
                  
                  QVariant ContactModel::data(const QModelIndex &index, int role) const
                  {
                      if(!index.isValid() ||
                              m_contactGroup.count() <= index.row() ||
                              (role != Qt::DisplayRole && role != Qt::EditRole)){
                          return QVariant();
                      }
                      return m_contactGroup[index.row()][Cols(index.column())];
                  }
                  
                  

                  Модель данных для отображения верхней таблицы:

                  #ifndef CONNECTIONLISTMODEL_H
                  #define CONNECTIONLISTMODEL_H
                  
                  #include "connectionlist.h"
                  
                  #include <QAbstractTableModel>
                  #include <QObject>
                  
                  class ConnectionListModel : public QAbstractTableModel
                  {
                      Q_OBJECT
                  public:
                      ConnectionListModel(QObject *parent = nullptr);
                  
                      void appendConnection(ConnectionList *item);
                      void resetModel();
                      QVector<ConnectionList> getConnectionList();
                  
                  private:
                      enum Columns{
                          CONNECTION = 0,
                          CONTACTLIST,
                          COLUMNCOUNT
                      };
                  
                      typedef QHash<Columns, QVariant> ConnectionData;
                      typedef  QMap<QString, QVariant> ContactData;
                      typedef  QVariantList ContactList;
                      typedef QList<ConnectionData> ConnectionGroup;
                      ConnectionGroup m_connectionGroup;
                      ContactData m_contactMap;
                      ContactList m_contactList;
                  
                      // QAbstractItemModel interface
                  public:
                      int rowCount(const QModelIndex &parent) const;
                      int columnCount(const QModelIndex &parent) const;
                      QVariant data(const QModelIndex &index, int role) const;
                      QVariant headerData(int section, Qt::Orientation orientation, int role) const;
                      Qt::ItemFlags flags(const QModelIndex &index) const;
                  };
                  
                  #endif // CONNECTIONLISTMODEL_H
                  
                  
                  #include "connectionlistmodel.h"
                  
                  ConnectionListModel::ConnectionListModel(QObject *parent)
                      : QAbstractTableModel(parent)
                  {
                  
                  }
                  
                  void ConnectionListModel::appendConnection(ConnectionList *item)
                  {
                      ConnectionData connectionItem;
                      if(!item->getAttrValue().isEmpty()){
                          connectionItem[CONNECTION] = item->getAttrValue();
                      }
                      if(item->getContactList() != nullptr){
                          m_contactMap.insert(item->getContactList()->at(0).getMap()->key("1"), item->getContactList()->at(0).getMap()->value("1"));
                          m_contactMap.insert(item->getContactList()->at(0).getMap()->key("2"), item->getContactList()->at(0).getMap()->value("2"));
                          m_contactList.append(m_contactMap);
                          m_contactList.append(m_contactMap);
                          connectionItem[CONTACTLIST] = QVariant::fromValue(m_contactList);
                      }
                  
                      int row = m_connectionGroup.count();
                      beginInsertRows(QModelIndex(), row, row);
                      m_connectionGroup.append(connectionItem);
                      endInsertRows();
                  
                  }
                  
                  
                  int ConnectionListModel::rowCount(const QModelIndex &parent) const
                  {
                      Q_UNUSED(parent)
                      return m_connectionGroup.count();
                  }
                  
                  int ConnectionListModel::columnCount(const QModelIndex &parent) const
                  {
                      Q_UNUSED(parent)
                      return COLUMNCOUNT;
                  }
                  
                  QVariant ConnectionListModel::data(const QModelIndex &index, int role) const
                  {
                      if(!index.isValid() ||
                              m_connectionGroup.count() <= index.row() ||
                              (role != Qt::DisplayRole && role != Qt::EditRole)){
                          return QVariant();
                      }
                      return m_connectionGroup[index.row()][Columns(index.column())];
                  }
                  
                  QVariant ConnectionListModel::headerData(int section, Qt::Orientation orientation, int role) const
                  {
                      if(role != Qt::DisplayRole){
                          return QVariant();
                      }
                  
                      if(orientation == Qt::Vertical){
                          return section + 1;
                      }
                      switch(section){
                      case CONNECTION:
                          return tr("CONNECTION");
                      case CONTACTLIST:
                          return tr("CONTACTLIST");
                      }
                      return QVariant();
                  }
                  
                  Qt::ItemFlags ConnectionListModel::flags(const QModelIndex &index) const
                  {
                      Qt::ItemFlags flags = QAbstractTableModel::flags(index);
                      if(index.isValid()){
                          flags |= Qt::ItemIsEditable;
                      }
                  
                      return flags;
                  }
                  
                  

                  Логика работы формы:

                  #ifndef MAINWINDOW_H
                  #define MAINWINDOW_H
                  
                  #include "contactmodel.h"
                  #include "connectionlist.h"
                  #include "connectionlistmodel.h"
                  
                  #include <QMainWindow>
                  
                  namespace Ui {
                  class MainWindow;
                  }
                  
                  class MainWindow : public QMainWindow
                  {
                      Q_OBJECT
                  
                  public:
                      explicit MainWindow(QWidget *parent = nullptr);
                      ~MainWindow();
                  
                  private:
                      Ui::MainWindow *ui;
                      ContactModel *_cModel = nullptr;
                      ConnectionListModel *_connListModel = nullptr;
                  
                  };
                  
                  #endif // MAINWINDOW_H
                  
                  #include "mainwindow.h"
                  #include "ui_mainwindow.h"
                  
                  MainWindow::MainWindow(QWidget *parent) :
                      QMainWindow(parent),
                      ui(new Ui::MainWindow)
                  {
                      ui->setupUi(this);
                      _cModel = new ContactModel();
                      ui->contactTable->setModel(_cModel);
                      ui->contactTable->horizontalHeader()->resizeSections(QHeaderView::ResizeMode::ResizeToContents);
                  
                      _connListModel = new ConnectionListModel();
                      ui->connectionTable->setModel(_connListModel);
                      ui->connectionTable->horizontalHeader()->resizeSections(QHeaderView::ResizeMode::ResizeToContents);
                  
                      Contact *cont1 = new Contact();
                      Contact *cont2 = new Contact();
                      ConnectionList *newConnectionList = new ConnectionList();
                  
                      auto attrMap = new QMap<QString, QString>();
                      attrMap->insert("1", "1");
                      attrMap->insert("2", "2");
                      attrMap->insert("3", "3");
                      attrMap->insert("4", "4");
                      attrMap->insert("5", "5");
                      attrMap->insert("6", "6");
                      cont1->setMap(attrMap);
                      cont2->setMap(attrMap);
                      _cModel->appendContact(cont1);
                      _cModel->appendContact(cont2);
                  
                      newConnectionList->setAttrValue("direct");
                      QVector<Contact> *contactList = new QVector<Contact>();
                      contactList->append(*cont1);
                      contactList->append(*cont2);
                      newConnectionList->setContactList(contactList);
                      _connListModel->appendConnection(newConnectionList);
                  
                  }
                  
                  MainWindow::~MainWindow()
                  {
                      delete ui;
                  }
                  
                  

                  Так вот при запуске проекта я получаю список в объекте m_connectionGroup в ячейке CONTACTLIST. Но данные этой ячейки не отображаются в таблице.
                  В принципе я добился того, что положил данные объекта в модель данных, но теперь я не понимаю:
                  1. Как отобразить эти данные в ячейку?
                  2. Если отобразить данные в ячейку не получится, то как связать верхнюю таблицу с нижней? С тем расчетом, что двигаясь по строкам верхней таблицы менялись значения в нижней.
                  3. Как в этом случае редактировать(добавлять/удалять) данные из модели?
                  4. Думал про делегаты, типа в ячейку добавить кнопку, которая бы вызывала дочернюю форму, а там отображались бы данные из списка. Но не знаю, правильное ли решение это будет? Да и с делегатами пока еще не работал.

                  Еще раз оговорюсь, что с простыми таблицами вроде бы все понятно. Я вроде бы разобрался, как это работает, а вот с таког рода объектами... Темный лес в общем.

                  Файл проекта приложил. Спасибо всем, кто поможет разобраться в такой задачке.
                  TestTableView.7z TestTableView.7z

                    Intruder
                    • 6 февраля 2020 г. 8:25

                    Итак, продолжаю вести что-то вроде рассказа о том, как и что у меня получается. В общем решил задачу с отоображением данных в основной и дочерней таблице. Если в крадце, то я сохранил в ячейку список QVariantList внутрь которого положил QMap . Эту колонку, которая содержит список элементов я не отображаю, но теперь я могу легко взять этот список элементов. Дальше на выбор сторки в повесил построение дочерней модели.
                    Как все сделаю, то напишу итоговый результат.
                    Просто я смотрю, много информации по работе с таблицами из БД, а чтобы это делалось программно такой информации практически нет.

                      Комментарии

                      Только авторизованные пользователи могут публиковать комментарии.
                      Пожалуйста, авторизуйтесь или зарегистрируйтесь
                      AD

                      C++ - Тест 004. Указатели, Массивы и Циклы

                      • Результат:50баллов,
                      • Очки рейтинга-4
                      m
                      • molni99
                      • 26 октября 2024 г. 7:37

                      C++ - Тест 004. Указатели, Массивы и Циклы

                      • Результат:80баллов,
                      • Очки рейтинга4
                      m
                      • molni99
                      • 26 октября 2024 г. 7:29

                      C++ - Тест 004. Указатели, Массивы и Циклы

                      • Результат:20баллов,
                      • Очки рейтинга-10
                      Последние комментарии
                      i
                      innorwall12 ноября 2024 г. 4:12
                      Django - Урок 055. Как написать функционал auto populate field Freckles because of several brand names retin a, atralin buy generic priligy
                      i
                      innorwall12 ноября 2024 г. 0:23
                      QML - Урок 035. Использование перечислений в QML без C++ priligy cvs 24 Together with antibiotics such as amphotericin B 10, griseofulvin 11 and streptomycin 12, chloramphenicol 9 is in the World Health Organisation s List of Essential Medici…
                      i
                      innorwall11 ноября 2024 г. 21:50
                      Qt/C++ - Урок 052. Кастомизация Qt Аудио плеера в стиле AIMP It decreases stress, supports hormone balance, and regulates and increases blood flow to the reproductive organs buy priligy online safe Promising data were reported in a PDX model re…
                      i
                      innorwall11 ноября 2024 г. 20:19
                      Алгоритм сортировки кучей The role of raloxifene in preventing breast cancer priligy precio
                      i
                      innorwall11 ноября 2024 г. 19:55
                      PyQt5 - Урок 006. Работа с QTableWidget buy priligy 60 mg 53 have been reported by Javanovic Santa et al
                      Сейчас обсуждают на форуме
                      i
                      innorwall12 ноября 2024 г. 2:56
                      добавить qlineseries в функции buy priligy senior brother Chu He, whom he had known for many years
                      i
                      innorwall11 ноября 2024 г. 16:55
                      Всё ещё разбираюсь с кешем. priligy walgreens levitra dulcolax carbs The third ring was found to be made up of ultra relativistic electrons, which are also present in both the outer and inner rings
                      9
                      9Anonim25 октября 2024 г. 15:10
                      Машина тьюринга // Начальное состояние 0 0, ,<,1 // Переход в состояние 1 при пустом символе 0,0,>,0 // Остаемся в состоянии 0, двигаясь вправо при встрече 0 0,1,>…
                      ИМ
                      Игорь Максимов3 октября 2024 г. 10:05
                      Реализация навигации по разделам Спасибо Евгений!

                      Следите за нами в социальных сетях