Отправляет email-рассылки с помощью сервиса Sendsay

Borland C++ Builder - всякая всячина

  Все выпуски  

Borland C++ Builder - всякая всячина (№16. Связывание компонентов - продолжение)


Служба Рассылок Subscribe.Ru

Приветствую всех получателей рассылки Borland C++ Builder - всякая всячина!

№16. Связывание компонентов (продолжение)

"...
На узком операционном столе лежал, раскинувшись, пес Шарик и голова его беспомощно колотилась о белую клеенчатую подушку. Живот его был выстрижен и теперь доктор Борменталь, тяжело дыша и спеша, машинкой вьедаясь в шерсть, стриг голову Шарика. Филипп Филиппович, опершись ладонями на край стола, блестящими, как золотые обода его очков, глазами наблюдал за этой процедурой...
..."
(М. Булгаков "Собачье сердце")

 

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

Но, ближе к телуделу... Подробно о процедуре создания компонента я уже писал в выпуске №11, поэтому поясню предельно кратко: в диалоге создания нового компонента укажите в поле Ancestor type значение TRxSpinEdit а в поле ClassName значение TMySpinEdit и в ваш пакет будет включен новый компонент. Начнем реализацию, как всегда, с заголовочного файла MySpinEdit.h. Если вы помните, в прошлом выпуске мы хотели объявить два вспомогательных типа для определения типа поля (минимум или максимум) и для выбора варианта коррекции парных полей ("тормозить" значение текущего поля или уменьшать/увеличивать значение парного поля). Вот их объявление:

enum TSpinEditType { stMinSpinEdit, stMaxSpinEdit };
enum TCorrectPairType { ctStopThis, ctCorrectPair };

А далее, собственно, следует само объявление класса нашего компонента:

//---------------------------------------------------------------------------
 
class PACKAGE TMySpinEdit : public TRxSpinEdit
{
private:
    TMySpinEdit *FPairField;
    TSpinEditType FType;
    TCorrectPairType FCorrectType;
    bool FNonTestZero;
    void __fastcall SetPairField(TMySpinEdit *APairField);
    void __fastcall SetType(TSpinEditType AType);
    void __fastcall SetCorrectType(TCorrectPairType ACorrectType);
    void __fastcall TestPair(void);
protected:
    virtual void __fastcall UpdatePair(void);
    DYNAMIC void __fastcall Change(void);
public:
    __fastcall TMySpinEdit(TComponent* Owner);
    __fastcall ~TMySpinEdit();
__published:
    __property TMySpinEdit *PairField = { read= FPairField, write= SetPairField };
    __property TSpinEditType Type= { read= FType, write= SetType };
    __property TCorrectPairType CorrectType= { read= FCorrectType, write= SetCorrectType };
    __property bool NonTestZero= { read= FNonTestZero, write= FNonTestZero };
};
//---------------------------------------------------------------------------

Итак, во-первых, мы объявили четыре свойства и четыре соответствующих переменных для хранения значений этих свойств. Про свойства Type и CorrectType я уже говорил выше, свойство PairField является указателем на парное поле, а свойство NonTestZero - нововведение, которое я придумал за неделю отсутствия (причем, придумал по собственной нужде): вопрос звучит так - как пользователь может указать, что одну из границ (одно из парных полей) при проверке учитывать не следует? Правильно, вводом в это поле нулевого значения. То есть, данное свойство указывает, что пользователь может указать нулевое значение в поле как отсутствие границы.

Кроме того, при записи первых трех свойств нам необходимо будет произвести некоторые проверочные действия (параметр write этих свойств), поэтому мы объявим еще три соответствующих функции: SetPairField, SetType и SetCorrectType. То есть при попытке изменить значения данных свойств будут вызываться соответствующие функции. В этой же секции объявлена функция TestPair. Она будет использоваться для проверки того, что поле, на которое указывает свойство PairField, содержит в идентичном свойстве указатель на наше поле (во сказанул!), то есть будет производиться проверка обратной связи между двумя парными полями.

В секции public мы объявляем конструктор и деструктор компонента. Это и ежику ясно. А вот в секции protected есть два интересных момента: во-первых, мы перекрываем виртуальную (динамическую) функцию Change компонента TRxSpinEdit, о ней я уже говорил в выпуске №15, а во-вторых, мы объявляем свою виртуальню функцию UpdatePair для коррекции значения парного поля. Зачем "виртуальную"? Затем же, зачем была объявлена виртуальной функция Change - для того, чтобы в дальнейшем кто-нибудь мог создать потомка вашего компонента и переопределить алгоритм коррекции.

Теперь переходим к файлу реализации методов MySpinEdit.cpp: описание конструктора класса там уже присутствует, трогать его мы не будем, добавим к нему описание деструктора:

//===========================================================================
 
__fastcall TMySpinEdit::~TMySpinEdit()
{
    if (FPairField!=NULL) FPairField->FPairField= NULL;
}
//===========================================================================

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

//===========================================================================
 
void __fastcall TMySpinEdit::TestPair(void)
{
    if (FPairField!=NULL)
    {
      if (FPairField->FPairField!=this)
      {
        if (FPairField->FPairField!=NULL)
          FPairField->FPairField->FPairField= NULL;
        FPairField->FPairField= this;
      }
    }
}
//===========================================================================

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

//===========================================================================
 
void __fastcall TMySpinEdit::SetPairField(TMySpinEdit *APairField)
{
    if (APairField!=this && APairField!=NULL)
    {
      FPairField= APairField;
      TestPair();
      SetType(FType);
      SetCorrectType(FCorrectType);
      UpdatePair();
    }
    else FPairField= NULL;
}
//---------------------------------------------------------------------------
 
void __fastcall TMySpinEdit::SetType(TSpinEditType AType)
{
    FType= AType;
    if (FPairField!=NULL)
    {
      TestPair();
      if (FType==stMaxSpinEdit) FPairField->FType= stMinSpinEdit;
      else FPairField->FType= stMaxSpinEdit;
      UpdatePair();
    }
}
//---------------------------------------------------------------------------
 
void __fastcall TMySpinEdit::SetCorrectType(TCorrectPairType ACorrectType)
{
    FCorrectType= ACorrectType;
    if (FPairField!=NULL)
    {
      TestPair();
      if (FCorrectType==ctStopThis) FPairField->FCorrectType= ctStopThis;
      else FPairField->FCorrectType= ctCorrectPair;
      UpdatePair();
    }
}
//===========================================================================

Метод SetPairField после проверки (ссылается ли полученный указатель на реальный компонент, причем - не наш, не this) полученного указателя присваевает его значение внутренней переменной FPairField, проверяет вышеописанным методом TestPair парное поле на "готовность сотрудничать" (и кто его, вообще, спрашивать будет), затем вызовом методов SetType и SetCorrectType проверяет правильность установки соответствующих им свойств (на всякий случай) и, наконец, вызывает метод UpdatePair для коррекции значений обоих связанных полей (этот метод мы рассмотрим чуть позже).

Метод SetType после сохранения значения типа во внутренней переменной и проверки существования связанного поля делает одну очень интересную вещь: он присваивает обратное значение от полученного аргумента аналогичной внутренней (!) переменной парного (!) поля. То есть можно все-таки обратиться к внутренней переменной (private) другого объекта, если обращаешься из объекта того-же класса! Лично для меня это было большим откровением (кто-нибудь, поправьте меня, если я совсем неправ...). И, напоследок, метод вызывает UpdatePair для коррекции значений обоих связанных полей.

Ну, а метод SetCorrectType действует аналогично SetType за исключением того, что он присваевает переменной FCorrectType парного поля то же значение, что и идентичной переменной "своего" объекта.

Окончательную работу производит метод UpdatePair:

//===========================================================================
 
void __fastcall TMySpinEdit::UpdatePair(void)
{
    if (FPairField!=NULL)
    {
      if (FType==stMinSpinEdit)
      {
        if (Value>FPairField->Value)
        {
          if (FCorrectType==ctCorrectPair)
          {
            if (FPairField->MinValue!=FPairField->MaxValue)
            {
              if (Value<=FPairField->MaxValue)
                FPairField->Value= Value;
              else
              {
                Value= FPairField->MaxValue;
                FPairField->Value= FPairField->MaxValue;
              }
            }
            else FPairField->Value= Value;
          }
          else Value= FPairField->Value;
        }
      }
      else
      {
        if (Value<FPairField->Value)
        {
          if (FCorrectType==ctCorrectPair)
          {
            if (FPairField->MinValue!=FPairField->MaxValue)
            {
              if (Value>=FPairField->MinValue)
                FPairField->Value= Value;
              else
              {
                Value= FPairField->MinValue;
                FPairField->Value= FPairField->MinValue;
              }
            }
            else FPairField->Value= Value;
          }
          else Value= FPairField->Value;
        }
      }
    }
}
//===========================================================================

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

Если значение (Value) "нашего" поля превысило значение парного поля, начинаем суетиться и выправлять положение: если тип коррекции нашего поля установлен как ctStopThis то устанавливаем значение Value "нашего" поля таким-же, как идентичное значение парного поля, если же нет, идем дальше. Если у парного поля нет ограничений на значение Value (FPairField->MinValue равно FPairField->MaxValue), то устанавливаем его значение Value равное идентичному значению нашего поля, если ограничения есть, проверяем дальше: если значение Value "нашего поля не превысило максимальное значение парного поля, то корректируем Value парного поля, если же максимальное значение парного поля уже превышено, то ограничиваем этим значением свойства Value обоих полей. Вот так, длинно, нудно, но подробно... То же самое, "с точностью до наоборот", делается во второй ветке (если "наше" поле является полем максимума).

И, наконец, метод Change, из-за которого, собственно, и заварилась вся каша:

//===========================================================================
 
void __fastcall TMySpinEdit::Change(void)
{
    if (!(FNonTestZero && Value==0)) UpdatePair();
    TRxSpinEdit::Change();
}
//===========================================================================

Он (она) вызывает метод коррекции UpdatePair только в том случае, если не установлено свойство NonTestZero - не проверять при нулевом значении, или если установлено, но свойство Value не равно 0. В конце данный метод вызывает идентичный перекрытый метод своего класса-предка - TRxSpinEdit::Change для того, чтобы тот произвел все остальные необходимые вычисления.

После того, как вы скомпонуете по-новой весь пакет и установите его в палитре компонент (это обязательно, иначе в вашей программе не произойдет никаких изменений), создайте новое приложение, положите на главную форму два поля TMySpinEdit, выделите на форме одно из них (MySpinEdit1) и в Object Inspector откройте выпадающий список свойства PairField. Там будут два имени: MySpinEdit1и MySpinEdit2, причем вы сможете выбрать только имя соседнего поля (MySpinEdit2). Выберите его, а затем выделите это соседнее поле (MySpinEdit2) на форме и посмотрите его свойство PairField: Там будет имя первого поля (MySpinEdit1)! То есть компоненты сами без посторонней помощи установили обратную связь. Причем, при удалении одного из полей свойство PairField второго поля автоматически очистится (станет равным NULL).

Честно говоря, мне только после таких экспериментов стал более-менее понятен смысл создания компонентной системы VCL. В самом деле, эта библиотека позволяет создавать не только функционально эффективные, но и просто красивые и удобные в использовании вещи. Так что пробуйте, ищите, экспериментируйте и "обрящете"...

 

 

"Про ежиков"

В последнем номере журнала "Компьютерра" (№43 от 13.11.2001) в разделе "Новости" встретил очень интересную заметку, которую позволю себе процитировать полностью:

Доигрались...
Решением федерального судьи Говарда Метца (Howard Metz) крупнейший Интернет-провайдер в мире - компания America Online должна прекратить дистрибуцию программного пакета AOL 6.0. Суровый приговор стал результатом полугодовой тяжбы между America Online и небольшой компанией PlayMedia Systems: последняя подала иск против гиганта в апреле этого года, предъявив обвинение в незаконном использовании компонентов ее программ в продуктах AOL.
Камнем преткновения стал декодер AMP, работающий с цифровым звуком в формате MP3. AMP достался AOL вместе с плейером Winamp и компанией Nullsoft, купленной еще в 1999 году. Nullsoft поначалу использовала AMP в своем плейере без разрешения PlayMedia, но потом (по решению суда) лицензию все же пришлось купить. Формально действие этой лицензии распространялось только на Winamp, но America Online использовала AMP и в другой программе - AOL Media Player (входит в пакет AOL 6.0), под тем предлогом, что она "основана на WinAmp".
Суд потребовал от AOL не только прекратить продажу пакета, но и выпустить специальный апдейт, с помощью которого уже купившие AOL 6.0 пользователи смогли бы исключить из его состава AMP. Запрет Метца - пока лишь предварительный, у America Online еще есть возможность обжаловать это решение. Если апелляция будет неудачной, потери AOL, по предварительным оценкам, составят около 70 млн. долларов.
Евгений Золотов

Фактов, изложенных в статье, конечно, маловато, но, даже основываясь на них, в мою голову лезут следующие мысли:

  1. Национальный американский вид спорта - "Таскание по судам с препятствиями" живет и процветает. В самом деле, появился такой реальный шанс погреться на ошибке богатого дяди - грех не использовать.

  2. Если в лицензии на AMP НЕ было черным по-русски (по-английски) сказано, что данный модуль предназначен только для использования в программе Winamp, то кому, собственно, какое дело, как и где AOL его использовала, а если все-таки такое ограничение в лицензии было, то менеджеры и юристы AOL проявили образец недальновидности и непрофессионализма, не предугадав подобный исход. По-моему, крупные корпорации не могут позволить себе творить такие ошибки по причине, изложенной в пункте 1.

  3. Когда я прочитал часть статьи, выделенную (у меня) жирным шрифтом, меня разобрал истерический смех: представляете себе ситуацию, когда пользователю предлагают скачать апдейт, не добавляющий к программе какие-то новые фичи, а убирающий существующие. Не сомневаюсь, что ftp-сервер AOL лег в первый же час от наплыва желающих получить вожделенный fix. Хотя, опять таки согласно пункту 1, судебные исполнители теперь совершенно законно могут проверить комплектность пакета AOL 6.0 у любого пользователя и прицепить его к паровозу (к AOL) как соучастника преступления.

  4. Теперь еще раз внимательно перечитайте пункт 1 и, я уверен, вы тут же сделаете наиболее вероятный прогноз: правильно, самые одаренные американские пользователи (а таких будет много, ибо, по моему скромному мнению, американцы, при всех их закидонах, кретинизмом не страдают) тут же подадут иск на AOL за причиненный моральный ущерб (надежды на AOL 6.0, покупка, разочарование, депрессия, лечение у психиатора, консультации психоаналитика, реабилитационный курс, нарушение деловых планов и т.д.). Так что оценив возможный ущерб в 70 млн., они, право, поскромничали.

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



С уважением, Васильев Евгений...
Почта: vasco@homeline.ru
Сайт: http://www.homeline.ru/vasco


http://subscribe.ru/
E-mail: ask@subscribe.ru
Отписаться
Убрать рекламу

В избранное