InfoCity
InfoCity - виртуальный город компьютерной документации
Реклама на сайте







Размещение сквозной ссылки

 

Создание собственного редактора свойств(property editor) в C++Builder 4


Базовые аспекты написания редакторов свойств


Редакторы свойств и редакторы компонент - вот два самых важных свойства, которые разработчик компонент может добавить в C++ Builder IDE. Изначально, они созданы чтобы предоставить вам или другому разработчику более простой, удобный и надежный путь для изменения ключевых свойств вашего компонента.

Редакторы свойств обычно проектируются так, чтобы они могли быть вызваны из object inspector, хотя они выводят диалоговые окна, которые работают независимо от него.

Редакторы свойств должны наследовать как минимум от TPropertyEditor, но они могут также наследовать и от TStringProperty или от множества разнообразных специализированных редакторов, которые описаны в Component Developer's Guide и on-line help.

Редакторы свойств должны знать и о свойстве, которое они редактируют, и о компоненте, в котором это свойство существует. Существуют специальные функции для редакторов свойств, предоставляющие доступ к значениям свойств. Они включают в себя:

  • GetOrdValue(), которая используется для получения указателя на свойство-класс.
  • GetIntValue(), которая используется для получения значения целого свойства.
  • GetStrValue(), которая используется для получения значения свойства-строки.

Чтобы получить информацию о компоненте требуется знать его класс или класс его предка. Указатель на компонент можно получить с помощью GetComponent(); потом этот указатель должен быть приведен к типу, в котором прдполагается наличие редактируемого свойства.

Пример редактора свойств


Этот редактор свойств предполагается использовать в компоненте, порожденном от TDataSet. Он должен получить имена полей из компонента и позволить пользователю выбрать одно из них, которое затем должно быть записано в свойство.

Пример класса свойства


Класс объекта-свойства объявлен следующим образом:

//---------------------------------------------------------------------------
#ifndef aStringObjectH
#define aStringObjectH

#include <SysUtils.hpp>
#include <Controls.hpp>
#include <Classes.hpp>
#include <Forms.hpp>
//---------------------------------------------------------------------------
class aStringObject: public TPersistent
{
   public:

      __fastcall aStringObject(void){};
      __fastcall aStringObject(String theString){myString = theString;};
      __fastcall aStringObject(aStringObject &theStringObject){myString = theStringObject.AsString;};

      operator String(void){return myString;};
      aStringObject &operator = (String theString){myString = theString; return *this;};

      void __fastcall ReadData(TReader *theReader)
      {
         myString = theReader->ReadString();
      };

      void __fastcall WriteData(TWriter *theWriter)
      {
         theWriter->WriteString(myString);
      };

      void __fastcall DefineProperties(TFiler* theFiler)
      {
         bool HasData = false;

         if (theFiler->Ancestor != NULL) // This is on a descendant form
         {
            aStringObject *AncestorObject = dynamic_cast<aStringObject *>(theFiler->Ancestor);

            if (AncestorObject != NULL)
            {
               HasData = !(myString == *AncestorObject);
            }
            else
            {
               HasData = true;
            };
         }
         else
         {
            HasData = true;
         };

         theFiler->DefineProperty("String",ReadData,WriteData,HasData);
      };

      int __fastcall Length(void){return myString.Length();};
      __property String AsString = {read=myString,write=myString};

   private:

      String myString;
};

#endif

Как видите, этот класс - всего лишь обертка вокруг AnsiString, необходимая, поскольку AnsiString не потомок TObject. Но несмотря на это, все механизмы редактора свойств останутся такими же.

Класс использует потоковую систему для сохранения и загрузки не опубликованного подсвойства String. Это достигается путем переопределения ReadData, WriteData, and DefineProperties (которая заставляет потоковую систему использовать ReadData и WriteData).

Также проверки HasData и AncestorObject внутри DefineProperties позволяют определить, находится ли компонент в форме-наследнике. Если да, то следует проверка, отличается ли значение свойства в текущей форме (потомке) от значения на родительской форме, и сохранение скрытого подсвойства String происходит только тогда, когда эти значения отличаются. Это очень важно для правильной поддержки наследования форм.

Код редактора свойств


//---------------------------------------------------------------------------
#ifndef aDataSetFieldNamePropertyEditorH
#define aDataSetFieldNamePropertyEditorH

#include <SysUtils.hpp>
#include <Controls.hpp>
#include <Classes.hpp>
#include <Forms.hpp>
#include <DB.hpp>
#include <aStringObject.h>
//---------------------------------------------------------------------------
class PACKAGE aDataSetFieldNamePropertyEditor: public TStringProperty
{
   public:

      __fastcall aDataSetFieldNamePropertyEditor(void){};
      __fastcall ~aDataSetFieldNamePropertyEditor(void){};

      TPropertyAttributes __fastcall GetAttributes(void)
      {
         return TPropertyAttributes() << paValueList;
      };

      void __fastcall GetValues(Classes::TGetStrProc AddValueToList)
      {
         TDataSet *HostComponent = dynamic_cast<TDataSet *>(GetComponent(0));

         if (HostComponent != NULL)
         {
            TStringList *FieldName = new TStringList;
            FieldName->Sorted = true;

            HostComponent->GetFieldNames(FieldName);

            for (int Index = 0; Index < FieldName->Count; Index++)
            {
               AddValueToList(FieldName->Strings[Index]);
            };

            delete FieldName;

         }
         else
         {
            AddValueToList("n/a");
         };
      };

      AnsiString __fastcall GetValue(void)
      {
         aStringObject *StringObject = dynamic_cast<aStringObject *>((TObject *) GetOrdValue());
         return StringObject != NULL ? (String) *StringObject : AnsiString("");
      };

      void __fastcall SetValue(AnsiString theValue)
      {
         aStringObject *StringObject = dynamic_cast<aStringObject *>((TObject *) GetOrdValue());
         if (StringObject != NULL) *StringObject = theValue;
         Modified();
      };
};

#endif

  • Заметьте, что хотя я и редактирую свойство-класс, я порождаю свой редактор от TStringProperty. Это возможно, и позволяет мне использовать все свойства TStringProperty.
  • GetAttributes - переопределенная функция из TPropertyEditor - в этом случае, она сообщает IDE, что мое свойство - это строка и оно породит множество значений, из которых должен выбирать пользователь.
  • GetValues - переопределенная функция из TPropertyEditor - вызывается потому, что GetAttributes сообщает IDE, что этот редактор свойств выдает несколько значений для возможного выбора. Однако ее действия немного странные. Вместо того, чтобы возвратить все значения, например, через TStringList, она вызывает передавемую от IDE в качестве аргумента функцию типа TGetStrProc для каждой строки, которая должна появиться в Object Inspector. В этом случае, редактор извлекает data source из компонента(GetComponent(0) -функция в TPropertyEditor, которая возвращает указатель на TComponent - это тот компонент, который сейчас виден в Object Inspector), потом список имен полей из набора данных этого источника, и наконец, организует цикл по этому списку, вызывая для каждого члена функцию IDE.
  • GetValue переопределенная функция из TPropertyEditor, которая возвращает строковое значение для свойства - в даном случае имя поля из свойства aStringObject. Обратите внимание на приведение к нужному типу значения, возвращаемого GetOrdValue(), которое является указателем на свойство. Это делается для того, чтобы обеспечить доступ к свойству способом, соответствующим типу свойства. Обратите также внимание, что возвращается пустая строка, если приведение было неудачным.
  • SetValue переопределенная функция из TPropertyEditor, которая устанавливает значение свойства на основании выбранной пользователем строки. Заметьте, что она использует то же приведение, что и предыдущая, и если оно неудачно, ничего не делает. Также заметьте, что она вызывает Modified() - функцию TPropertyEditor, которая извещает IDE, что свойство было изменено, а соответственно, был изменен компонент, и модуль(а может быть, и проект) должен быть сохранен. Другой (возможно лучший) подход к этой проблеме заключается в том, чтобы сравнить входное значение с уже существующим, и только в том случае, если они различаются, вызвать Modified().

Использование редактора свойств


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

namespace Agridwiththeeditor
{
   void __fastcall PACKAGE Register()
   {
      TComponentClass classes[1] = {__classid(aGridWithTheEditor)};

        RegisterPropertyEditor
      (
         __typeinfo(PVStringObject),
         __classid(aGridWithTheEditor),
         "IDFieldName",
         __classid(PVDBGridFieldNamePropertyEditor)
      );
        RegisterPropertyEditor
      (
         __typeinfo(PVStringObject),
         __classid(aGridWithTheEditor),
         "SequenceFieldName",
         __classid(PVDBGridFieldNamePropertyEditor)
      );

      RegisterComponents("Test Page", classes, 0);
   }
}

Отладка редактора свойств


Это тяжелая часть проекта. Теоритически, вы можете запустить вторую копию среды и установить точку останова в вашем редакторе, но практически это не всегда возможно.

Однако, вы можете выдавать Application->MessageBox из любой точки вашего редактора или задублировать свойство, выдающее строку из вашего.

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

Куда все это поместить?


Класс, выступающий в качестве свойства, редактор свойства и компонент, который их использует, должны находиться в одном .bpk.

Заключение


Конечно, код, приведенный здесь, далек от совершенства, но это, в конце концов, только пример. Вы сами должны доработать(и доработаете, я уверен) его до нужного уровня. Написание редакторов компонент сделает ваши компоненты более надежными и удобными во время проектирования программ, а кроме того, немного поразмыслив, можно сделать их разделяемыми с другими авторами компонент. Удачи!


Реклама на InfoCity

Яндекс цитирования



Финансы: форекс для тебя








1999-2009 © InfoCity.kiev.ua