Главная » C++, C++ Builder » Компонент FilterEdit в CBuilder

0

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

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

Формулировка  проблемы

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

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

Частное решение

В данном примере мы  собираемся воплотить не только частное решение, но  и общее, которое станет базовым классом для частного решения о разрешении ввода только цифр.

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

Общее же решение это отдельный вопрос. Давайте займемся им.

Общее решение

В случае обобщенного решения — о разрешении или запрещении ввода определенных данных в управляющий  элемент-поле  редактирования  —  нам  потребуются  от  пользователя  два  блока

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

— Include (то есть будут ли символы включены в текст, или нет).

Воплощение базового компонента

Для воплощения базового компонента создайте в CBuilder новый компонент и присвойте ему имя TFilterFdit. Установите ему в качестве предка класс TCustomEdit. Это предоставит нам все возможности компонента-поля редактирования и позволит  нам  отфильтровывать  ненужные данные, не дублируя все без исключения возможности лежащего в его основе управляющего элемента-поля редактирования Windows. После того, как базовый компонент был сгенерирован Мастером компонентов CBuilder, в него надо добавить необходимые нам свойства. Добавьте следующие строки кода в заголовочный файл компонента:

#include <vcl\System.hpp>

//——————————————————————-

class TFilterEdit : public TCustomEdit

{

private:

System::AnsiString     FFilterString; bool     FbInc;

protected:

virtual void __fastcall KeyPress(char &Key); public:

    fastcall TFilterEdit(TComponent* Owner);

__published:

__property System::AnsiString Allowed = {read=FFilterString, write=FFilterString};

__property bool Include = {read=FbInc, write=FbInc, default=true};

};

В данном случае мы добавляем несколько внутренних переменных-членов класса для хранения фактических значений свойств и замещаем функцию KeyPress —  член класса  TCustomEdit для собственно фильтрования вводимых данных.

Мы добавили свойство Allowed, которое представляет из себя строку типа AnsiString, и свойство Include, выступающее в качестве флага, логического (boolean) типа. Обратите внимание на то, что тип AnsiString заключен в области (namespace) System, так что нам надо задавать его, используя оператор разрешения видимости System:: для того, чтобы сообщить компилятору, где находится тип AnsiString. Кроме того, мы должны подключить заголовочный файл System.hpp из каталога заголовочных файлов системы (CBuilder\include\vcl) чтобы получить описание класса. Object Inspector и другие инструменты CBuilder понимают тип AnsiString, так что пользователь сможет редактировать эти строки, используя стандартный редактор строк системы.

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

сделанным нами ранее:

__fastcall TFilterEdit::TFilterEdit(TComponent*  Owner)

: TCustomEdit(Owner)

{

FbInc = true;

}

Единственным кодом для компонента станет код для метода KeyPress, который будет проверять вводимый символ на вхождение в список Allowed и, в зависимости от значений Allowed и Include, либо разрешать, либо не разрешать ввод. Вот как этот код будет выглядеть:

void __fastcall TFilterEdit::KeyPress(char &Key)

{

// Проверяем нажатие клавиши

for ( int i=0; i<FFilterString.Length(); ++i ) if ( Key == FFilterString[i] )

if ( FbInc )

return; else

{

Key = 0; return;

}

// Не нашли вводимого символа

// Очень плохо if ( FbInc ) Key = 0;

}

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

Сужение проблемы — класс TNumericEdit

После того, как компонент TFilterEdit создан, можно создать базирующийся на нем класс TNumericEdit. Этот класс даже проще, чем TFilterEdit, поскольку он допускает ввод только числовых значений. Для того, чтобы двигаться дальше, придется сначала откомпилировать и сынсталлировать компонент TFilterEdit, проделав всю обычную процедуру. Для того, чтобы убедиться в том, что компонент работает должным образом, протестируйте его, вводя различные значения для Allowed и Include. Если все хорошо, можно делать следующие шаги.

После того, как вы сынсталлировали новый компонент в CBuilder, вы можете его использовать для любых целей. Среди этих целей может быть и создание  класса,  наследующего  от  этого компонента. Если вы работали в других системах, в которых наследовать можно было только от базовых  классов  системы,   вы  наверняка  с  радостью  примите  подобное  ценное  добавление

возможностей. Помните — после того, как вы сынсталлировали компонент, он становится частью базовой системы CBuilder. Нет никаких различий между компонентом, который написали вы и компонентом, созданным командой Borland VCL. Возможность расширять систему CBuilder делает ее идеальной для работы в корпоративной среде.

Сынсталлировав компонент TFilterEdit, воспользуйтесь Мастером компонентов для создания нового компонента TNumericEdit. У этого нового компонента предком будет класс TFilterEdit (который теперь тоже будет присутствовать в комбинированном списке).

Все, что вам надо изменить в кода для класса TNumericEdit, это конструктор класса — для того, чтобы инициализировать свойства Include и Allowed так, чтобы пользователь мог вводить только числовые значения. Вот как будут выглядеть эти изменения:

__fastcall TNumericEdit::TNumericEdit(TComponent*  Owner)

: TFilterEDit(Owner);

{

Allowed = "1234567890";

Include = true;

}

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

void __fastcall TForm1::FormCreate(TObject *Sender)

{

pEdit = new TNumberEdit(this); pEdit->Left = 10;

pEdit->Top = 10;

pEdit->Width = 100;

pEdit->Height = 30; pEdit->Parent = this;

pEdit->Allowed = "ABCDEFG"; pEdit->Include = false;

}

Если вы теперь добавите заголовочный файл для компонента TNumericEdit в эту форму, то увидите, что приведенный выше код скомпилируется без ошибок. В чем же здесь дело? Проблема состоит в том, что свойства из компонента TFilterEdit были унаследованы в TNumericEdit, и пользователь имеет возможность заместить те установки, которые вы сделали в классе вашего нового компонента. Это нормальное поведение класса C++, так как раздел published описания класса аналогичен секции public. Очевидно, что подобный вариант работы нашего компонента нас не устраивает. Есть ли возможность как-то справиться с возникшей  проблемой не переписывая весь код для базового компонента заново? В некотором роде да. Итак, на самом деле проблема со свойствами, описанными в разделе published, состоит в том,  что  эти  свойства  всегда  будут доступы в любом наследующем классе. То есть, другими словами, мы не можем перекрыть программисту возможность использования свойств Allowed и Include в классах-наследниках при теперешнем положении вещей.

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

class TBaseFilterEdit : public TCustomEdit

{

private:

System::AnsiString     FFilterString; bool     FbInc;

protected:

virtual void __fastcall KeyPress(char &Key);

__property System::AnsiString Allowed = {read FFilterString; write FFilterString};

__property bool Include = {read=FbInc; write=FbInc; default = true}

public:

    fastcall TBaseFilterEdit(TComponent* Owner) : TCustomEdit(Owner)

{

}

__published:

};

Теперь класс TFilterEdit будет выглядеть так: class TFilterEdit : public TBaseFilterEdit

{

private: protected: public:

    fastcall TFilterEdit(TComponent* Owner);

__published:

__property Allowed;

__property Include;

};

В этом классе свойства Allowed и Include заново помещаются в секцию    published, чтобы конечный пользователь мог видеть их в Object Inspector. Класс TNumericEdit также наследует от класса TBaseFilterEdit:

class TNumericEdit : public TBaseFilterEdit

{

private: protected: public:

    fastcall TNumericEdit(TComponent* Owner);

__published:

};

Обратите внимание на то, что в этом классе свойства Allowed и Include не переопределяются, так что компонент типа TNumericEdit не позволяет конечному пользователю использовать  эти свойства. Внутри класса существует прямой доступ к свойствам Allowed и Include, поскольку они являются защищенными членами класса. Переменные-члены класса,  представляющие  эти свойства в базовом классе (FbInc и FFilterString) недоступны в обоих наследующих классах. Полный исходный код всех трех классов может быть найден — как вы уже догадались — на прилагаемом  компакт-диске.

Мы проделали довольно трудный путь до победного финала вместо того, чтобы сразу поговорить о конечном базовом классе по двум причинам. Во-первых, мы поняли причину того, что все компоненты в VCL строятся вокруг абстрактных классов типа TCustomEdit или TCustomListBox.

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

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

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

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

По теме:

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