Пример "Find Files"
Файлы:
Пример "Find Files" показывает, как использовать QProgressDialog для предоставления обратной связи с прогрессом выполнения медленной операции. Пример также показывает как использовать QFileDialog для помощи при просмотре, как использовать потоковые операторы QTextStream'а для чтения файла, и как использовать QTableWidget чтобы предоставить приложениям стандартные возможности табличного вывода на экран. Кроме того, файлы можно открыть используя класс QDesktopServices.

С приложением "Find Files" пользователь может искать в заданном каталоге файлы, соответствующие указанному имени файла (с использованием спецсимволы если подходит) и содержащих заданный текст.
Пользователь обеспечивается опцией Browse, а результат поиска выводится на экран в таблице с именами найденных файлов и их размером. Кроме того приложение предоставляет итоговое количество найденных файлов.
Определение класса Window
Класс Window унаследован от QWidget, и является основным виджетом приложения. Он показывает опции поиска и выводит на экран результаты поиска.
class Window : public QDialog
{
Q_OBJECT
public:
Window(QWidget *parent = 0);
private slots:
void browse();
void find();
void openFileOfItem(int row, int column);
private:
QStringList findFiles(const QDir &directory, const QStringList &files,
const QString &text);
void showFiles(const QDir &directory, const QStringList &files);
QPushButton *createButton(const QString &text, const char *member);
QComboBox *createComboBox(const QString &text = QString());
void createFilesTable();
QComboBox *fileComboBox;
QComboBox *textComboBox;
QComboBox *directoryComboBox;
QLabel *fileLabel;
QLabel *textLabel;
QLabel *directoryLabel;
QLabel *filesFoundLabel;
QPushButton *browseButton;
QPushButton *findButton;
QTableWidget *filesTable;
};
Нам нужно два закрытых слота: Слот browse() вызывается всякий раз когда пользователь хочет просмотреть каталог для поиска, а слот find() вызывается всякий раз когда пользователь запрашивает выполнение поиска нажимая на кнопку Find.
Кроме того мы объявили несколько закрытых функций: Мы используем функцию findFiles() чтобы искать файлы соответствующие пользовательским спецификациям, мы вызваем функцию showFiles() для вывода на экран результатов и мы используем createButton(), createComboBox() и createFilesTable() когда создаём виджет.
Реализация класса Window
В конструкторе мы сначала создаём виджеты приложения.
Window::Window(QWidget *parent)
: QDialog(parent)
{
browseButton = createButton(tr("&Browse..."), SLOT(browse()));
findButton = createButton(tr("&Find"), SLOT(find()));
fileComboBox = createComboBox(tr("*"));
textComboBox = createComboBox();
directoryComboBox = createComboBox(QDir::currentPath());
fileLabel = new QLabel(tr("Named:"));
textLabel = new QLabel(tr("Containing text:"));
directoryLabel = new QLabel(tr("In directory:"));
filesFoundLabel = new QLabel;
createFilesTable();
Создаём кнопки приложения используя закрытую функцию createButton(). Затем создаём комбинированные списки выбора, связанные со спецификациями поиска, используя закрытую функцию createComboBox(). Также мы создаём метки приложения до использования закрытой функции createFilesTable(), чтобы создать табличный вывод на экран результатов поиска.
QHBoxLayout *buttonsLayout = new QHBoxLayout;
buttonsLayout->addStretch();
buttonsLayout->addWidget(findButton);
QGridLayout *mainLayout = new QGridLayout;
mainLayout->addWidget(fileLabel, 0, 0);
mainLayout->addWidget(fileComboBox, 0, 1, 1, 2);
mainLayout->addWidget(textLabel, 1, 0);
mainLayout->addWidget(textComboBox, 1, 1, 1, 2);
mainLayout->addWidget(directoryLabel, 2, 0);
mainLayout->addWidget(directoryComboBox, 2, 1);
mainLayout->addWidget(browseButton, 2, 2);
mainLayout->addWidget(filesTable, 3, 0, 1, 3);
mainLayout->addWidget(filesFoundLabel, 4, 0);
mainLayout->addLayout(buttonsLayout, 5, 0, 1, 3);
setLayout(mainLayout);
setWindowTitle(tr("Find Files"));
resize(700, 300);
}
Затем добавляем все виджеты в основную компоновку используя QGridLayout. Тем не менее, сначала мы поместили кнопки Find и Quit и фактор растяжения (stretchable space) в отдельную компоновку QHBoxLayout, чтобы заставить кнопки появляться в верхнем правом углу виджета Window.
void Window::browse()
{
QString directory = QFileDialog::getExistingDirectory(this,
tr("Find Files"), QDir::currentPath());
if (!directory.isEmpty()) {
directoryComboBox->addItem(directory);
directoryComboBox->setCurrentIndex(directoryComboBox->currentIndex() + 1);
}
}
Слот browse() показывает пользователю диалог выбора файла, используя класс QFileDialog. QFileDialog разрешает пользователю пройти по файловой системе для того, чтобы выбрать один или несколько файлов или каталогов. Самый лёгкий способ создать QFileDialog - использовать вспомогательные статические функции.
Здесь мы используем статическую функцию QFileDialog::getExistingDirectory(), которая возвращает существующий каталог выделенный пользователем. Затем мы показываем каталог в комбинированном каталоге выбора каталогов, используя функцию QComboBox::addItem(), и обновляем текущий индекс.
QComboBox::addItem() добавляет элемент в комбинированный список выбора с переданным текстом и содержащий заданные userData. Элемент добавляется в список существующих элементов. Текущий индекс содержит индекс текущего элемента в комбинированном списке выбора. Поэтому для того, чтобы вывести на экран только что добавленный элемент, нам нужно также обновить индекс.
void Window::find()
{
filesTable->setRowCount(0);
QString fileName = fileComboBox->currentText();
QString text = textComboBox->currentText();
QString path = directoryComboBox->currentText();
Слот find() вызывается каждый раз, когда пользователь запрашивает новый поиск нажимая на кнопку Find.
Сначала мы уничтожаем все результаты предыдущего поиска установив количество строк табличного виджета равным нулю. Затем мы извлекаем заданное имя файла, текст и путь к каталогу из соответствующих комбинированных списков выбора.
QDir directory = QDir(path);
QStringList files;
if (fileName.isEmpty())
fileName = "*";
files = directory.entryList(QStringList(fileName),
QDir::Files | QDir::NoSymLinks);
if (!text.isEmpty())
files = findFiles(directory, files, text);
showFiles(directory, files);
}
Мы используем путь к каталогу для создания QDir; класс QDir предоставляет доступ к структуре каталогов их содержимому. Мы создаём список файлов (содержащихся во вновь созданном QDir), которые соответствуют заданному имени файла. Если имя файла пустое, список будет содержать все файлы каталога.
Затем мы проводим поиск по всем файлам списка, используя закрытую функцию findFiles(), убирая те, которые не содержат заданный текст. И в заключение, мы выводим на экран результаты, используя закрытую функцию showFiles().
Если пользователь не задаст какой-либо текст, для поиска по файлам нет причины и мы выводим результаты незамедлительно.

QStringList Window::findFiles(const QDir &directory, const QStringList &files,
const QString &text)
{
QProgressDialog progressDialog(this);
progressDialog.setCancelButtonText(tr("&Cancel"));
progressDialog.setRange(0, files.size());
progressDialog.setWindowTitle(tr("Find Files"));
В закрытой функции findFiles() мы проводим поиск по списку файлов, разыскивая те из них, которые содержат заданный текст. Эта операция может быть очень медленной в зависимости от количества файлов, а также от их размеров. На случай большего количества файлов или большого размера некоторых файлов в списке, мы предоставляем QProgressDialog.
Класс QProgressDialog предоставляет обратную связь с прогрессом медленной операции. Он используется чтобы дать пользователю подсказку, как долго выполняется операция, и чтобы продемонстрировать, что приложение не заморожено. Также он может дать пользователю возможность прервать операцию.
QStringList foundFiles;
for (int i = 0; i < files.size(); ++i) {
progressDialog.setValue(i);
progressDialog.setLabelText(tr("Searching file number %1 of %2...")
.arg(i).arg(files.size()));
qApp->processEvents();
Мы запускаем на файлах, по одному за раз, и для каждого файла обновляем значение QProgressDialog. Это свойство сохраняет текущее значение выполненного прогресса. Также мы обновляем метку диалога индикатора выполнения.
Затем мы вызываем функцию QCoreApplication::processEvents(), используя объект QApplication. Таким способом мы чередуем вывод на экран прогресс процесса выполнения поиска по файлам, поэтому приложение не выглядит замороженным.
Класс QApplication управляет потоком управления приложения ГПИ и основными настройками. Он содержит главный цикл обработки событий, где обрабатываются и координируются все события из оконной системы и других источников. QApplication унаследован от QCoreApplication. Функция QCoreApplication::processEvents() обрабатывает все незавершённые события, согласно заданным флагам в QEventLoop::ProcessEventFlags до тех пор, пока есть события для обработки. Флаги по умолчанию - QEventLoop::AllEvents.
QFile file(directory.absoluteFilePath(files[i]));
if (file.open(QIODevice::ReadOnly)) {
QString line;
QTextStream in(&file);
while (!in.atEnd()) {
if (progressDialog.wasCanceled())
break;
line = in.readLine();
if (line.contains(text)) {
foundFiles << files[i];
break;
}
}
}
}
return foundFiles;
}
После обновления QProgressDialog, мы создаём QFile используя функцию QDir::absoluteFilePath(), которая возвращает абсолютный путь к файлу в каталоге. Мы открываем файла в режиме только для чтения и читаем по одной строке используя QTextStream.
Класс QTextStream предоставляет удобный интерфейс для чтения и записи текста. Используя потоковые операторы QTextStream'а вы можете легко читать и записывать слова, строки и числа.
Для каждой прочитанной строки мы проверяем, не был ли отменён QProgressDialog. Если он был отменён, мы прерываем операцию, в противном случае мы проверяем содержит ли строка заданный текст. Когда мы находим текст внутри файла, мы добавляем имя файла в список найденных файлов, содержащих заданный текст, и начинаем искать новый файл.
В заключение, мы возвращаем список найденных файлов.
void Window::showFiles(const QDir &directory, const QStringList &files)
{
for (int i = 0; i < files.size(); ++i) {
QFile file(directory.absoluteFilePath(files[i]));
qint64 size = QFileInfo(file).size();
QTableWidgetItem *fileNameItem = new QTableWidgetItem(files[i]);
fileNameItem->setFlags(fileNameItem->flags() ^ Qt::ItemIsEditable);
QTableWidgetItem *sizeItem = new QTableWidgetItem(tr("%1 KB")
.arg(int((size + 1023) / 1024)));
sizeItem->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
sizeItem->setFlags(sizeItem->flags() ^ Qt::ItemIsEditable);
int row = filesTable->rowCount();
filesTable->insertRow(row);
filesTable->setItem(row, 0, fileNameItem);
filesTable->setItem(row, 1, sizeItem);
}
filesFoundLabel->setText(tr("%1 file(s) found").arg(files.size()) +
(" (Double click on a file to open it)"));
}
Обе функции - findFiles() и showFiles() - вызываются из слота find(). В функции showFiles() мы проходим по списку имён файлов, добавляем каждое имя файла в первый столбец табличного виджета и находим размер файла используя QFile и QFileInfo для второго столбца.
Мы также обновляем общее количество найденных файлов.
QPushButton *Window::createButton(const QString &text, const char *member)
{
QPushButton *button = new QPushButton(text);
connect(button, SIGNAL(clicked()), this, member);
return button;
}
Закрытая функция createButton() вызывается из конструктора. Мы создаём QPushButton с предоставленным текстом, соединяем её с предоставленным слотом и возвращаем указатель на кнопку.
QComboBox *Window::createComboBox(const QString &text)
{
QComboBox *comboBox = new QComboBox;
comboBox->setEditable(true);
comboBox->addItem(text);
comboBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
return comboBox;
}
Закрытая функция createComboBox() также вызывается из конструктора. Мы создаём QComboBox с полученным текстом и делаем его редактируемым.
Когда пользователь вводит новую строку в редактируемом комбинированном списке выбора, виджет может вставить или не вставить её, а также он может вставить её в нескольких местах, в зависимости от QComboBox::InsertPolicy. Политика по умолчанию - QComboBox::InsertAtBottom.
Затем мы добавляем предоставленный текст в комбинированный список выбора и задаём политики размера виджета, перед возвращением указателя на комбинированный список выбора.
void Window::createFilesTable()
{
filesTable = new QTableWidget(0, 2);
filesTable->setSelectionBehavior(QAbstractItemView::SelectRows);
QStringList labels;
labels << tr("File Name") << tr("Size");
filesTable->setHorizontalHeaderLabels(labels);
filesTable->horizontalHeader()->setResizeMode(0, QHeaderView::Stretch);
filesTable->verticalHeader()->hide();
filesTable->setShowGrid(false);
connect(filesTable, SIGNAL(cellActivated(int, int)),
this, SLOT(openFileOfItem(int, int)));
}
Закрытая функция createFilesTable() вызывается из конструктора. В этой функции мы создаём QTableWidget, который будет отображать результаты поиска. Мы установим его горизонтальные заголовки и их режим изменения размера.
QTableWidget унаследован от QTableView, который предоставляет реализацию по умолчанию модель/представление представления таблицы. Функция QTableView::horizontalHeader() возвращает горизонтальный заголовок представления таблицы как QHeaderView. Класс QHeaderView предоставляет для представлений элементов строку заголовка или столбец заголовка, а функция QHeaderView::setResizeMode() устанавливает ограничения того, как может изменять размеры секция в заголовке.
В заключение, мы скрываем вертикальные заголовки QTableWidget'а, используя функцию QWidget::hide(), и убираем в таблице рисование сетки по умолчанию, используя функцию QTableView::setShowGrid().
void Window::openFileOfItem(int row, int )
{
QTableWidgetItem *item = filesTable->item(row, 0);
QDesktopServices::openUrl(item->text());
}
Слот openFileOfItem() вызывается когда пользователь дважды щёлкнет по ячейке таблицы. QDesktopServices::openUrl() знает, как открыть файл по переданному имени файла.
|