Mark Summerfield (перевод Racheengel) |
Часто необходимо представить пользователю список строк с возможностью добавления новых, редактирования и удаления существующих. В этой статье описан общий класс StringListEdit и подкласс FileListEdit для упрощения этих задач.
Давайте посмотрим, как такой класс может быть использован:
QStringList fontList = QFontDatabase().families(); StringListEdit listEdit = new StringListEdit(fontList); listEdit.setCaption(tr("Font List")); listEdit.setTexts(tr("Add Font Name"), tr("&Name:"), tr("Edit Font Name")); if (listEdit.exec()) fontList = listEdit.list();
Создадим список шрифтов, доступных приложению, и новый объект StringListEdit, в который передается данный списое при инициализации. Установим заголовок и текст, который будет использован во всплывающих диалогах редактирования, а затем откроем диалог. Если пользователь нажмет OK, заменим текущий список измененным. Диалог, который отобразится по вызову exec(), выглядит примерно так:
Если пользователь нажметEdit, появится диалог редактирования:
Давайте объявим наш класс.
class StringListEdit : public QDialog { Q_OBJECT public: StringListEdit(const QStringList &list, QWidget *parent = 0); void setTexts(const QString &addCaption, const QString &addLabel, const QString &editCaption, const QString &editLabel = ""); bool askBeforeRemoving() const { return ask; } void setAskBeforeRemoving(bool a) { ask = a; } bool allowDuplicates() const { return duplicatesOk; } void setAllowDuplicates(bool allow) { duplicatesOk = allow; } QStringList list() const; void setList(const QStringList &list); protected slots: virtual void addString(); virtual void editString(); virtual void removeString(); void moveUp(); void moveDown(); void updateButtons(); protected: QString addCaption; QString addLabel; QString editCaption; QString editLabel; bool ask; bool duplicatesOk; QListBox *listBox; QPushButton *editButton; QPushButton *removeButton; QPushButton *upButton; QPushButton *downButton; };
Мы реализовали несколько простых функций доступа и сделали addString(), editString() и removeString() виртуальными для облегчения наследования.
StringListEdit::StringListEdit(const QStringList &list, QWidget *parent) : QDialog(parent), ask(false), duplicatesOk(false) { addCaption = tr("Add String"); editCaption = tr("Edit String"); addLabel = editLabel = tr("String:"); QHBoxLayout *hbox = new QHBoxLayout(this, 5, 5); QVBoxLayout *vbox = new QVBoxLayout; listBox = new QListBox(this); connect(listBox, SIGNAL(currentChanged(QListBoxItem*)), this, SLOT(updateButtons())); hbox->addWidget(listBox, 1); QPushButton *button = new QPushButton(tr("&Add..."), this); connect(button, SIGNAL(clicked()), this, SLOT(addString())); vbox->addWidget(button); editButton = new QPushButton(tr("&Edit..."), this); connect(editButton, SIGNAL(clicked()), this, SLOT(editString())); vbox->addWidget(editButton); ... vbox->addStretch(1); button = new QPushButton(tr("OK"), this); connect(button, SIGNAL(clicked()), this, SLOT(accept())); vbox->addWidget(button); button = new QPushButton(tr("Cancel"), this); connect(button, SIGNAL(clicked()), this, SLOT(reject())); vbox->addWidget(button); hbox->addLayout(vbox); setList(list); }
Кнопки Remove, Up и Down создаются так же, как и Edit; единственная разница в том, что мы используем свои защищенные указатели, каждый из которых соединен с соответствующим слотом.
void StringListEdit::setList(const QStringList &list) { listBox->clear(); listBox->insertStringList(list); QFontMetrics fm(listBox->font()); int width = 0; for (int i = 0; i < (int)list.count(); ++i) { int w = fm.width(list[i]); if (w > width) width = w; } if (listBox->verticalScrollBar()) width += listBox->verticalScrollBar()->width(); listBox->setMinimumWidth( QMIN(width, qApp->desktop()->screenGeometry() .width() * 0.8)); updateButtons(); }
Для удобства, попытаемся сделать так, чтобы ширина списка была достаточна для отображения строк без использования горизонтальной прокрутки, но не более 80% ширины экрана.
QStringList StringListEdit::list() const { QStringList lst; for (int i = 0; i < (int)listBox->count(); ++i) lst.append(listBox->text(i)); return lst; } void StringListEdit::setTexts( const QString &addCaption, const QString &addLabel, const QString &editCaption, const QString &editLabel) { this->addCaption = addCaption; this->addLabel = addLabel; this->editCaption = editCaption; this->editLabel = editLabel.isEmpty() ? addLabel : editLabel; }
Функции list() и setTexts() простые.
void StringListEdit::updateButtons() { bool hasItems = (listBox->count() > 0); editButton->setEnabled(hasItems); removeButton->setEnabled(hasItems); int i = listBox->currentItem(); upButton->setEnabled(hasItems && i > 0); downButton->setEnabled(hasItems && i < (int)listBox->count() - 1); }
Кнопки Add, OK и Cancel должны быть доступны всегда, другие же должны быть доступны, только если их нажатие будет иметь смысл. За это отвечает функция updateButtons().
void StringListEdit::addString() { bool ok; QString str = QInputDialog::getText(addCaption, addLabel, QLineEdit::Normal, listBox->currentText(), &ok, this); if (ok && !str.isEmpty()) { if (!duplicatesOk && listBox->findItem(str, CaseSensitive | ExactMatch)) return; listBox->insertItem(str); listBox->setCurrentItem(listBox->count() - 1); listBox->ensureCurrentVisible(); updateButtons(); } }
Если пользователь нажмет Add, используем диалог ввода для ввода строки. Если он нажмет OK (и введенная строка не повторяется в списке, либо duplicatesOk имеет значение true), мы добавим строку в список, сделаем ее текущей и обновим кнокпи.
void StringListEdit::editString() { bool ok; QString original = listBox->currentText(); if (!original.isEmpty()) { QString str = QInputDialog::getText( editCaption, editLabel, QLineEdit::Normal, original, &ok, this); if (ok && !str.isEmpty()) { if (!duplicatesOk && listBox->findItem( str, CaseSensitive | ExactMatch)) return; listBox->changeItem(str, listBox->currentItem()); updateButtons(); } } else { addString(); } }
Код editString() несколько отличен от addString(). Если нет строки для редактирования, мы просто вызываемl addString(); иначе мы получаем строку (отображая по умолчанию оригинальный текст), и, если новая строка не пустая (либо повторяется при duplicatesOk равному false), мы изменяем оригинальный элемент в списке и обновляем кнопки.
void StringListEdit::removeString() { QString original = listBox->currentText(); if (original.isEmpty() || (ask && QMessageBox::question(this, tr("Remove"), tr("Remove '%1'?").arg(original), QMessageBox::Yes | QMessageBox::Default, QMessageBox::No | QMessageBox::Escape) == QMessageBox::No)) return; listBox->removeItem(listBox->currentItem()); updateButtons(); }
Если есть, что удалять, (и пользователь ответил "да" на вопрос об удалении при ask равному true), мы удаляем строку из списка.
void StringListEdit::moveUp() { int i = listBox->currentItem(); if (i > 0) { QString temp = listBox->text(i - 1); listBox->changeItem(listBox->text(i), i - 1); listBox->changeItem(temp, i); listBox->setCurrentItem(i - 1); updateButtons(); } }
Для изменения порядка строк в списке используются функции moveUp() и moveDown(). Мы меняем местами текущую строку с предыдущей, если текущая строка не первая в списке. Код для moveDown() очень похож на код moveUp().
Редактор списка файлов |
Для списков имен файлов нам нужно несколько иное поведение. Мы можем захотеть указать фильтр имен, и функции addString() и editString() должны использовать файловые диалоги вместо строковых. Вот объявления класса FileListEdit, который расширяет StringListEdit для обработки имен файлов:
class FileListEdit : public StringListEdit { Q_OBJECT public: FileListEdit(const QStringList &list, QWidget *parent = 0) : StringListEdit(list, parent), filter(tr("Any (*;*.*)")), path(".") {} QString fileFilter() const { return filter; } void setFileFilter(const QString &newFilter) { filter = newFilter; } private slots: void addString(); void editString(); private: QString filter; QString path; };
Конструктор пустой. Также мы создали функции чтения и установки фильтра имен.
void FileListEdit::addString() { QString fileName = QFileDialog::getOpenFileName(path, filter, this, "", addCaption); if (!fileName.isEmpty()) { if (!duplicatesOk && listBox->findItem( fileName, CaseSensitive | ExactMatch)) return; listBox->insertItem(fileName); listBox->setCurrentItem(listBox->count() - 1); listBox->ensureCurrentVisible(); path = fileName; updateButtons(); } }
Эта функция очень похожа на addString() базового класса. Мы запоминаем имя файла в переменной path, чтобы файловый диалог всегда показывал последний использованный каталог.
void FileListEdit::editString() { QString fileName = QFileDialog::getOpenFileName( listBox->currentText(), filter, this, "", editCaption); if (!fileName.isEmpty()) { if (!duplicatesOk && listBox->findItem( fileName, CaseSensitive | ExactMatch)) return; listBox->changeItem(fileName, listBox->currentItem()); path = fileName; updateButtons(); } }
Мы не вызываем addString(), если пользователь не выбрал имя файла, т.к. это не имеет смысла.
Copyright © 2004 Trolltech | Trademarks |