БлогNot. Пишем редактор списка на QT

Пишем редактор списка на 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
Окно проекта 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
Окно приложения CatEditDialog

 Скачать проект QT CatEditDialog в архиве .ZIP (7 Кб)

Важно понимать, что нашего делегата можно было бы подключить к первому проекту, в чём и сила подхода:

Delegate* catDelegate = new Delegate(this);
ListView->setItemDelegate(catDelegate);

в конструкторе MainWindow - и всё.

 Пример проекта, где добавлен поиск по списку с выделением найденных записей + такой же делегат, как во втором проекте (Containers2) (10 Кб)

P.S. Замена QRegExp на QRegularExpression в Qt6.

01.05.2014, 18:08 [22719 просмотров]


теги: c++ программирование список qt

К этой статье пока нет комментариев, Ваш будет первым