Главная » C++, C++ Builder » Программа поиска в потоке CBuilder

0

В нашем втором примере работы с потоками мы собираемся написать программу поиска, использующую потоки. Она позволит искать заданную строку в заданном каталоге. Также мы предоставим возможность выбрать маску файлов (например, все исходные файлы *.cpp), по которым будет происходить поиск. Когда будет нажата кнопка Начать  поиск,  форма  запустит поток, который станет искать файлы в заданном каталоге, которые содержат нужную строку, и выводить имена файлов в окно списка, находящееся в главной форме.

Пока поток работает, пользователь может захотеть посмотреть любой из этих файлов, такая возможность и предоставляется в нашей программе. Мы позволим пользователю выбирать файл из списка двойным щелчком мыши, после чего загрузится форма с этим файлом в текстовом виде (если, конечно, файл влезет в память). Мы не можем сразу узнать, что поток закончил работу, так что не можем обрабатывать элементы по мере их поступления. Однако чем хорошо использова ние потока: программа только и делает, что обрабатывает  ввод пользователя и оставляет управление  элементами  в  окне  списка  на  попечение  потоку.  Нет  необходимости  проверять

сообщения от пользователя во время поиска, так как поток работает полностью самостоятельно. Мы пишем обычный код, как будто потока и нет.

В этом проекте две формы. В главной форме находятся поля ввода для задания параметров поиска и окно списка, в котором будут содержаться результаты. На второй форме находится только компонент TMemo (записная книжка), который мы используем для отображения  содержимого файла. На рис. 13.2 показана главная форма приложения, а на рис. 13.3 — вторичная форма.

Рис. 13.2. Главная форма приложения «поиск в потоке»

Рис. 13.3. Форма приложения «поиск в потоке», отображающая файлы

Построение главной формы

Главная форма приложения строится из трех меток, трех полей ввода, кнопки и окна списка. Поля ввода только передают нам информацию. Никакой проверки введенных данных мы осуществлять не будем. Если пользователь введет неправильные данные в поля Маска файлов или Каталог, то мы просто ничего не найдем. Если бы мы писали коммерческое приложение, то, естественно, мы бы добавили кнопку обзора диска, что позволило бы пользователю выбрать каталог, а также, вероятно, предоставили бы возможность выбрать одну из предопределенных масок файлов. Однако это не имеет прямого отношения к нашему примеру, так что не станем этим заниматься.

Во-первых, надо разобраться с обработкой кнопки Начать поиск , которая будет начинать процесс поиска,   запуская   нужный   поток.   Создайте  обработчик  нажатия   на   кнопку  Начать поиск  и

поместите в обработчик следующий код:

void __Fastcall TForm1::Button1Click(TObject *Sender)

{

pThread = new TSearchThread(Edit2->Text, Edit3->Text, Edit1->Text, FALSE);

SetOkToSearch( false );

}

Все, что мы делаем, — это создаем новый потоковый объект и передаем ему данные, введенные пользователем в три поля ввода на форме. Мы позже разберемся с созданием класса потока, пока же будем считать, что там все работает. Метод SetOkToSearch используется для указания, происходит ли в данный момент поиск, а также для запрещения и разрешения кнопки поиска. Вот реализация этой функции, которую вы можете написать прямо в заголовочном  файле,  в определении класса:

void SetOkToSearch(bool bSearchOK)

{

Button1->Enabled = bSearchOk;

}

Зачем нам для этого понадобилась функция? Дело в том, что таким образом мы защищаем реализацию кода, запрещающего кнопку. Если бы нам понадобилось делать что-нибудь еще в этой функции, например обновление информации в панели состояния, вывод какого-нибудь сообщения или даже запись события в какой-нибудь внешний файл, то классы, использующие эту функцию, ничего не будут знать обо всех этих побочных действиях. Все, что знают вызывающие функции об этом методе, — это что он останавливает поиск либо же начинает новый.

Разобравшись с этим, следующим делом надо разобраться с отображением текстового файла, который пользователь выбирает двойным щелчком мыши в окне списка. Создайте обработчик события DblClick (двойной щелчок) для объекта ListBox1 (окно списка) и добавьте в обработчик следующий код:

void __fastcall TForm1::ListBox1DblClick(TObject *Sender)

{

// Получить имя выбранного файла

int nIdx = ListBox1->ItemIndex; if ( nIdx != -1 )

{

AnsiString strFile = ListBox1->Items->Strings[nIdx];

// Создать новую форму для просмотра файла

pFileViewer = new TForm3(this);

pFileViewer->Memo1->Lines->LoadFromFile( strFile ); pFileViewer->Show();

}

}

В этом методе мы получаем имя файла, которое пользователь  выбрал из  окна списка. Список будет содержать имена файлов с указанием полного пути, так что функция может считать, что имя файла корректно (мы убедимся в этом позже) и просто использовать его для отображения файла. Само отображение целиком на совести объекта VCL TMemo, который сам знает, как напрямую загружать свое содержимое из файла. Используя метод  LoadFromFile, мы загружаем весь выбранный  файл  прямо  в  память  компонента  TMemo,   находящегося  на  вторичной  форме.

Заметьте,  что  вам  вообще  не  надо  писать  никакого  кода  для  этой  вторичной  формы  (класс TForm3). Вся работа за вас уже сделана. Приятно иногда ничего не писать, не правда ли?

Все это реализует все визуальные аспекты загрузки формы, поиска и отображения текста файлов, подходящие под заданные критерии. Так что единственное, что осталось — это сам поиск. Поиск должен происходить внутри нашего потокового объекта, так что настала пора заняться и этим.

Создание потока для поиска

В данном примере поток создается точно так же, как и в предыдущем. Используй те систему создания объекта «поток», встроенную в CBuilder, для создания нового потокового класса с названием TSearchThread. CBuilder сгенерирует исходный файл (в данном примере Unit2.cpp), в котором будет определен класс.

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

__fastcall TSearchThread::TSearchThread(AnsiString strDir,

AnsiString strMask, AnsiString strText, bool CreateSuspended)

: TThread (true)

{

FstrDirectory = strDir;

if ( FstrDirectory[FstrDirectory.Length()-1] != ‘\\’ ) FstrDirectory += ‘\\';

FstrFileMask = strMask; FstrSearchText = strText; if ( !CreateSuspended ) Resume();

}

Вкупе с этими изменениями нам нужно немножко подправить и заголовочный файл для класса потока, чтобы он содержал описания переменных  и  методов  класса  TSearchThread.  Вот изменения (выделенные подсветкой) в заголовочном файле Unit2.h для класса TSeachThread:

class TSearchThread : public TThread

{

private: protected:

void __fastcall Execute();

private:

AnsiString FstrDirectory; AnsiString FstrFileMask;

AnsiString FstrSearchText;

AnsiString FstrCurFileName;

int DoFind( AnsiString strSearchText, AnsiString& text );

public:

__fastcall TSearchThread(AnsiString strDir, AnsiString strMask, AnsiString strText, bool CreateSuspended); void __fastcall AddToListBox(void);

};

Переменные-члены класса FstrDirectory, FstrFileMask и  FstrSearchText  используются  для определения файлов, по которым производится поиск, а также текста, который нужно в них найти. Переменная, указывающая имя текущего файла (FstrCurFileName), нужна  здесь,  так  как  метод, через который происходит общение с объектом VCL на форме — окном списка, — не имеет параметров. И наконец, в  секцию private мы добавляем функцию  DoFind,  которая  будет определять, содержит ли файл искомую строку.

Когда мы проинициализировали поток и запустили его (вызвав метод Resume в конце конструктора), в игру вступает метод Execute. В предыдущем примере метод Execute выполнялся вечно, так как не было разумных причин для его использова ния. В нашем же случае цель ясна: искать среди заданного конечного числа файлов все те, которые содержат заданную строку текста. Поэтому нам не нужен вечный цикл; нам нужно прерывать выполнение потока,  когда  запас файлов для поиска иссякнет. Вот код метода Execute класса TSearchThread:

void __fastcall TSearchThread::Execute()

{

// Проходим по всем файлам, которые подходят под маску

// Для этого используем функцию Win32 API WIN32_FIND_DATA  FindFileData;

AnsiString strSearchFiles = FstrDirectory + FstrFileMask;

HANDLE hFirstFileHandle = FindFirstFile( strSearchFiles.c_str(), &FindFileData ); while ( hFirstFileHandle && !Terminated )

{

// Пытаемся открыть файл на чтение

FILE *fp = fopen(FindFileData.cFileName, "r" ); FstrCurFileName = FstrDirectory + FindFileData.cFileName; if (fp == NULL)

{

if ( FindNextFile( hFirstFileHandle, &FindFileData)

== FALSE )

break; continue;

}

// Ищем в данном файле построчно

char szBuffer[ 256 ]; while ( !feof(fp) )

{

if ( fgets(szBuffer, 255, fp) == NULL ) break;

AnsiString s = szBuffer;

if ( DoFind( FstrSearchText, s ) )

{

Synchronize(AddToListBox); break;

}

}

// Закрываем файл и переходим к следующему в списке

fclose(fp);

if ( FindNextFile( hFirstFileHandle, &FindFileData )

== FALSE )

break;

}

Form1->SearchComplete();

}

Этот код следует простому и прямолинейному процессу. Во-первых, маска файла и каталог используются для создания списка файлов для поиска, используя функции API FindFirstFile и FindNextFile, которые мы рассматривали выше, в главе, посвященной Windows API.

Каждый файл открывается (если не открывается, то мы его пропускаем), и из него по  одной читаются строки текста. Если нужная строка текста найдена в данной строке файла, то файл закрывается, и имя файла добавляется в окно списка формы, и внутренний цикл поиска по файлу завершается. Когда файл уже обработан, он закрывается и берется следующий при помощи функции API.  Пока все это происходит,  пользователь может спокойно выбрать любой файл из списка и посмотреть его, используя форму просмотра файлов, что мы реализовали выше в этой главе.

Когда все файлы просмотрены или поток завершил свою работу, вызывается метод формы SearchComplete. Этот метод, приведенный в следующем листинге, просто завершает поток, если тот не был остановлен  или  еще  не  завершился,  а  затем  снова  делает  доступной  кнопку Начать поиск на форме. Это подготавливает форму ко второму, третьему поиску и т. д.:

void __fastcall TForm1::SearchComplete(void)

{

pThread->Terminate(); SetOkToSearch( true );

}

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

// Метод осуществляет тупой поиск строки

// текста во входной строке

int TSearchThread::DoFind(AnsiString strSearchText, AnsiString& text)

{

int i=0;

int nCount = 0;

while ( i < text.Length() )

{

if ( !strncmp(text.c_str()+i, strSearchText.c_str(), strSearchText.Length()) )

{

i += strSearchText.Length(); nCount ++;

}

else

{ i++;

}

}

return nCount;

}

Наконец, последней частью головоломки является функция AddToListBox. Эта функция, которую мы настроили на синхронизацию с формой в конструкторе класса, отвечает за добавление имени текущего файла в окно списка, находящее ся в главной форме. Нам не нужно волноваться о том, работает ли в это время кто-нибудь со списком, так как эта функция синхронизирована с формой. Вот код функции AddToListBox:

void __fastcall TSearchThread::AddToListBox(void)

{

if ( FstrCurFileName.Length() )

Form1->ListBox1->Items->Add( FstrCurFileName );

}

Этот код просто проверяет, есть ли вообще хоть какие-нибудь символы в имени файла (а вдруг?), а затем добавляет имя файла в конец списка на форме. После чего пользователь может выбрать желанный файл из списка двойным щелчком мыши в любой момент работы приложения.

Рис. 13.4. Программа фонового поиска в действии

На рис. 13.4 показан типичный поиск файлов на моем жестком диске,  которому было  сказано искать все исходные файлы (*.cpp), содержащие слово «Text». Результаты показаны в окне списка, и в то же время один из файлов показан в окне просмотра файлов. Поиск закончен, и кнопка Начать поиск снова доступна для запуска нового поиска.

Источник: Теллес М. – Borland C++ Builder. Библиотека программиста – 1998

По теме:

  • Комментарии