Пишем редактор списка на QT
Всегда неувядающее приложение - какой-нибудь список строк, который можно редактировать. Сначала решим базовую задачу - добавление, редактирование, удаление и сортировка элементов. Виджет создадим на основе класса QMainWindow
, главный файл main.cpp
менять не придётся:
#include "mainwindow.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); }
Содержащийся в шаблоне файл mainwindow.h
используем для описания интерфейса приложения и его обработчиков событий:
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QGridLayout> #include <QLabel> #include <QListView> #include <QStringListModel> #include <QPushButton> #include <QMessageBox> namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); private: Ui::MainWindow *ui; QGridLayout *myLayout; QLabel *ListCount, *ListLabel; QStringListModel *Model; QListView *ListView; QPushButton *AddButton, *EditButton, *DeleteButton, *SortButton; QStringList List; public slots: void add_clicked(); void edit_clicked(); void delete_clicked(); void sort_clicked(); void showSelectedItem(QModelIndex); }; #endif // MAINWINDOW_H
Слоты add_clicked
, ..., sort_clicked
понадобятся для обработки нажатий 4 кнопок виджета - "Добавить", "Редактировать", "Удалить" и "Сортировать", пятый слот будет обрабатывать щелчок мышкой на элементе списка.
Сам список разместится в контейнерном объекта класса QListView
. В соответствии с поддержкой в QT идеологии MVC, списку также понадобится модель класса QStringListModel
, через которую мы будем осуществлять доступ к элементам. Список строк QStringList List
может содержать предустановленные значения для QListView
, для простоты оставим его пустым.
Остаётся написать реализацию класса MainWindow
в файле mainwindow.cpp
. Для большей универсальности кода виджета и независимости его от "дизайнерской" работы "мышкой", интерфейс приложения сделаем логическим и создадим его в конструкторе объекта на основе табличного размещения в виджете класса QGridLayout
. Обратите также внимание на то, что списку ListView
нужно назначить модель, а готовый объект класса QGridLayout
правильно добавить к родительскому виджету (метод setLayout
) - иначе увидим пустое окно. В конце кода конструктора назначаются слоты для 4 основных кнопок виджета.
#include "mainwindow.h" #include "ui_mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); myLayout = new QGridLayout; ListCount = new QLabel ("Количество строк = 0"); ListLabel = new QLabel ("Текущий список"); ListView = new QListView; Model = new QStringListModel(this); Model->setStringList(List); ListView->setModel(Model); ListView->setEditTriggers(QAbstractItemView::NoEditTriggers); connect(ListView, SIGNAL(clicked(QModelIndex)),SLOT(showSelectedItem(QModelIndex))); //определяем обработчик щелчка по списку AddButton = new QPushButton ("Добавить"); EditButton = new QPushButton ("Заменить"); DeleteButton = new QPushButton ("Удалить"); SortButton = new QPushButton ("Сортировать"); myLayout->addWidget(ListCount, 0, 0, 1, 4); myLayout->addWidget(ListLabel, 1, 0, 1, 4); myLayout->addWidget(ListView, 2, 0, 2, 4); myLayout->addWidget(AddButton, 4, 0); myLayout->addWidget(EditButton, 4, 1); myLayout->addWidget(DeleteButton, 4, 2); myLayout->addWidget(SortButton, 4, 3); this->centralWidget()->setLayout(myLayout); connect(AddButton, SIGNAL(clicked()), this, SLOT(add_clicked())); connect(EditButton, SIGNAL(clicked()), this, SLOT(edit_clicked())); connect(DeleteButton, SIGNAL(clicked()), this, SLOT(delete_clicked())); connect(SortButton, SIGNAL(clicked()), this, SLOT(sort_clicked())); } MainWindow::~MainWindow() { delete ui; }
Обработчик щелчка "мышкой" по элементу списка сделаем просто в учебных целях, заодно показав, как вывести модальное окно сообщения класса QMessageBox
:
void MainWindow::showSelectedItem(QModelIndex mIndex) { //Просто демо, ничего не делает if (!mIndex.isValid()) return; QString dataStr = mIndex.data().toString(); QMessageBox msgBox; msgBox.setText(dataStr); msgBox.exec(); return; }
Оставшийся код слотов состоит, в общем-то, также из типовых действий с моделью:
void MainWindow::add_clicked() { int row = Model->rowCount(); // в числовую переменную заносим общее количество строк Model->insertRows(row, 1); // добавляем строки, количеством 1 шт. QModelIndex index = Model->index(row); // создаем ссылку на элемент модели ListView->setCurrentIndex(index); // передаем этот индекс ListView ListView->edit(index); // переводим курсор на указанную позицию для ожидания ввода данных ListCount->setText("Количество строк = "+QString::number(Model->rowCount())); // пересчитываем количество строк и выводим результат } void MainWindow::edit_clicked() { int row = ListView->currentIndex().row(); QModelIndex index = Model->index(row); ListView->setCurrentIndex(index); ListView->edit(index); } void MainWindow::delete_clicked() { Model->removeRows(ListView->currentIndex().row(),1); ListCount->setText("Количество строк = "+QString::number(Model->rowCount())); } void MainWindow::sort_clicked() { Model->sort(0); }
Вот что у нас вышло в итоге:
Окно проекта Containers
Скачать проект QT Containers в архиве .ZIP (5 Кб)
Приложение неплохое, но ему недостаёт жизненно важных для любого списка возможностей - например, проверки введённых значений на уникальность и возможности ограничить диапазон разрешённых к вводу символов (например, "только латинские буквы и цифры", ограниченный размер поля ввода).
К сожалению, путь к решению этих задач на QT не очень прост и не очевиден. Следует написать потомка класса QItemDelegate
, предназначенного для управления редактированием данных модели и содержащего несколько виртуальных методов. Зато потом, в соответствии с идеологией MVC, можно будет применять это решение с разными моделями. Добавим в проект класс Delegate
, являющийся потомком QItemDelegate
и напишем файл delegate.h
:
#ifndef DELEGATE_H #define DELEGATE_H #include <QDebug> #include <QMessageBox> #include <QRegExp> #include <QPainter> #include <QLineEdit> #include <QItemDelegate> #include <QEvent> #include <QMouseEvent> class Delegate : public QItemDelegate { Q_OBJECT public: explicit Delegate(QObject *parent = 0, bool valid = true); QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const; void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; void setEditorData(QWidget *editor, const QModelIndex &index) const; void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const; bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index); void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; public slots: void setUnique(bool isUnique); signals: void del(QModelIndex index); private: QPoint pressPos; bool validate; bool uniqueData; }; #endif // DELEGATE_H
Видно, что почти всем методам передаётся указатель на родительский виджет и модель. Совокупность обработчиков можно использовать как для "особой" отрисовки редактируемой строки (мы просто добавим ей обрамляющий прямоугольник, см. метод paint
), так и для проверки данных, например, с помощью регулярного выражения (разрешим ввод строк, состоящих из латинских букв и цифр длиной от 1 до 12 символов, см. метод createEditor
). Для проверки введённого строкового значения на уникальность пригодится метод setModelData
. Вот полный код файла файла delegate.cpp
:
#include "delegate.h" Delegate::Delegate(QObject *parent, bool valid) : QItemDelegate(parent), validate(valid) { uniqueData = true; // по умолчанию } QWidget* Delegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { QLineEdit* e = new QLineEdit(parent); if (validate) { QRegExp rx("[a-zA-Z0-9]{1,12}"); QValidator *validator = new QRegExpValidator(rx, (QObject*)this); e->setValidator(validator); } return e; } void Delegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QRect r = option.rect; if (option.state & QStyle::State_Selected) { painter->fillRect(r, QColor(230, 230, 230)); } painter->save(); QString data = index.model()->data(index).toString(); painter->setBrush(option.palette.text()); painter->drawText(r, Qt::AlignVCenter, data); //QPixmap delPixmap("del.png"); //painter->drawPixmap(r.width() - 20, r.y(), 20, 20, delPixmap); painter->restore(); } void Delegate::setEditorData(QWidget *editor, const QModelIndex &index) const { QLineEdit* e = qobject_cast<QLineEdit*>(editor); e->setText(index.model()->data(index).toString()); } void Delegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { //этот метод вызывается раньше, чем эмитятся сигналы closeEditor и commitData QLineEdit* e = qobject_cast<QLineEdit*>(editor); QString text = e->text(); int item = index.row(); bool isUnique = true; for (int i=0; i<model->rowCount(); i++) if (i != item) if (model->data(model->index(i, 0)).toString() == text) { isUnique = false; break; } if (!isUnique) { qDebug() << "ERROR: The text already exists"; return; } model->setData(index, text); } bool Delegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) { qDebug() << "EDITOR EVENT"; QRect r(option.rect.width()-20, option.rect.y(), 20, 20); if (event->type() == QEvent::MouseButtonPress) { pressPos = static_cast <QMouseEvent*> (event)->pos(); return r.contains(pressPos); } else if(event->type() == QEvent::MouseButtonRelease) { QPoint releasePos = static_cast <QMouseEvent*> (event)->pos(); if (r.contains(pressPos) && r.contains(releasePos)) { emit del(index); return true; } } return false; } void Delegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const { QRect r = option.rect; r.setWidth(r.width() - 25); editor->setGeometry(r); qDebug() << r; } QSize Delegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { return QSize(option.rect.width(), 20); } void Delegate::setUnique(bool isUnique) { uniqueData = isUnique; }
Само приложение на этот раз сделаем наследником класса QDialog
(окно диалога), управление списком, для простоты, ограничим кнопками "Добавить" и "Удалить", а к редактированию будем переходить двойным щелчком на элементе списка. При попытке добавить в список повторяющееся значение, вместо него будет возвращено значение по умолчанию вида "Item_1
". Элементы интерфейса разместим в столбец с помощью виджета QVBoxLayout
. Вот коды файла cateditdialog.h
:
#ifndef CATEDITDIALOG_H #define CATEDITDIALOG_H #include <QDialog> #include <QtGui> #include <QListView> #include <QPushButton> #include <QVBoxLayout> namespace Ui { class CatEditDialog; } class CatEditDialog : public QDialog { Q_OBJECT public: CatEditDialog(QWidget *parent = 0); ~CatEditDialog(); private: QListView* listView; QStringListModel* myModel; QPushButton* appendButton, * deleteButton; inline bool isUnique(QString &text, QAbstractItemView* view); private slots: void removeItem(); void addItem(); void checkUniqueItem(QWidget* editor); signals: void tryToWriteItem(bool on); }; #endif // CATEDITDIALOG_H
...и cateditdialog.cpp
:
#include "delegate.h" #include "cateditdialog.h" #include "ui_cateditdialog.h" CatEditDialog::CatEditDialog(QWidget *parent) { QVBoxLayout *layout = new QVBoxLayout(this); listView = new QListView(this); Delegate *myDelegate = new Delegate(this); myModel = new QStringListModel(this); listView->setModel(myModel); listView->setItemDelegate(myDelegate); appendButton = new QPushButton("Append", this); deleteButton = new QPushButton("Delete", this); layout->addWidget(listView); layout->addWidget(appendButton); layout->addWidget(deleteButton); setLayout(layout); connect(myDelegate, SIGNAL(closeEditor(QWidget*)), this, SLOT(checkUniqueItem(QWidget*))); //связываем сигнал закрытия редактора со слотом проверки connect(this, SIGNAL(tryToWriteItem(bool)), myDelegate, SLOT(setUnique(bool))); //для передачи в делегат булевой переменной connect(appendButton, SIGNAL(clicked()), this, SLOT(addItem())); connect(deleteButton, SIGNAL(clicked()), this, SLOT(removeItem())); listView->setCurrentIndex(myModel->index(0)); } CatEditDialog::~CatEditDialog() { } bool CatEditDialog::isUnique(QString &text, QAbstractItemView *view) { //метод, проверяющий уникальность элемента int item = view->currentIndex().row(); QStringListModel* model = static_cast<QStringListModel*>(view->model()); for (int i=0; i<model->rowCount(); i++) { if (i != item) if (model->data(model->index(i), Qt::DisplayRole).toString() == text) return false; } return true; } void CatEditDialog::removeItem() { QModelIndex index = listView->currentIndex(); myModel->removeRow(index.row()); listView->setCurrentIndex(index.row() != myModel->rowCount() ? index : myModel->index(index.row()-1)); } void CatEditDialog::addItem() { QModelIndex index = listView->currentIndex(); int i=myModel->rowCount(); myModel->insertRow(i); myModel->setData(myModel->index(i), QString("Item%1").arg(i)); listView->setCurrentIndex(myModel->index(myModel->rowCount()-1)); } void CatEditDialog::checkUniqueItem(QWidget *editor) { qDebug() << "CLOSE EDITOR"; QString text = static_cast<QLineEdit*>(editor)->text(); emit tryToWriteItem(isUnique(text, listView)); //сигнал делегату, isUnique(text, catView) - результат проверки }
Главному файлу main.cpp
осталось создать экземпляр окна диалога и показать его:
#include "cateditdialog.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); CatEditDialog w; w.show(); return a.exec(); }
Внешний вид окна приложения:
Окно приложения CatEditDialog
Скачать проект QT CatEditDialog в архиве .ZIP (7 Кб)
Важно понимать, что нашего делегата можно было бы подключить к первому проекту, в чём и сила подхода:
Delegate* catDelegate = new Delegate(this); ListView->setItemDelegate(catDelegate);
в конструкторе MainWindow
- и всё.
P.S. Замена QRegExp на QRegularExpression в Qt6.
01.05.2014, 18:08 [23195 просмотров]