Форум программистов, компьютерный форум, киберфорум
Наши страницы
C++ Builder
Войти
Регистрация
Восстановить пароль
 
Рейтинг 4.92/50: Рейтинг темы: голосов - 50, средняя оценка - 4.92
volvo
Супер-модератор
Эксперт Pascal/DelphiАвтор FAQ
25741 / 17295 / 6878
Регистрация: 22.10.2011
Сообщений: 30,524
Записей в блоге: 6
#1

Классы-перехватчики (interceptor classes)

05.02.2015, 15:06. Просмотров 9091. Ответов 0
Метки нет (Все метки)

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

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


Поставим задачу: при нажатии любой кнопки на форме выполнить кроме действия, предусмотренного обработчиком события OnClick, еще какое-либо действие. В Дельфи это делается так:
Delphi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
type
  TButton = class({Vcl.}StdCtrls.TButton) // Vcl. добавляется в новых версиях Дельфи
  public
    procedure Click; override; // Для примера - изменяем функционал метода Click
  end;
  
  TForm1 = class(TForm)
    // ...
    Button1: TButton; // У всех кнопок, лежащих на форме уже будет изменен функционал Click
    // ...
  end; { TForm1 }
  
implementation
 
{ TButton }
 
procedure TButton.Click;
begin
  inherited;
  // дополнительные действия
end;
 
// ...
Казалось бы, дословный перевод на Билдер должен выглядеть следующим образом:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    class TButton : public Stdctrls::TButton
    {
    public:
        /*
        __fastcall virtual TButton(Classes::TComponent* AOwner)
            : Stdctrls::TButton(AOwner) {}
        */
        DYNAMIC void __fastcall Click(void)
        {
            Stdctrls::TButton::Click();
            // Дополнительные действия
        }
    };
 
// -----
class TForm1 : public TForm
{
__published: // IDE-managed Components
 
    TButton *Button1;
    // ...
, но в таком виде перехват работать не будет, компилятор сигнализирует о неоднозначности: E2015 Ambiguity between 'TButton' and 'Vcl::Stdctrls::TButton', ведь теперь у нас есть два класса с одним и тем же именем TButton. Один - в VCL, со стандартным функционалом, а второй - только что созданный, с функционалом изменённым (одноименный со стандартным, чтобы дальше в классе формы не вносить никаких правок, а использовать привычное имя компонента).

Чтобы проект успешно скомпилировался, нужно эту неоднозначность устранить, для чего поместим наш класс-перехватчик в другое пространство имен, и подменим обращения к TButton на обращения с учетом namespace-а:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
namespace My_Button
{
    class TButton : public Stdctrls::TButton
    {
    public:
        /*
        __fastcall virtual TButton(Classes::TComponent* AOwner)
            : Stdctrls::TButton(AOwner) {}
        */
 
        DYNAMIC void __fastcall Click(void)
        {
            Enabled = false;
            Stdctrls::TButton::Click();
            Enabled = true;
        }
    };
};
#define TButton My_Button::TButton
 
// -----
class TForm1 : public TForm
// ...
. Вот теперь для всех кнопок (и уже находившихся на форме Form1, и добавляемых на нее в Design-тайме, и добавляемых в рантайме) будет работать новый метод Click(), который задизейблит кнопку перед началом работы обработчика события OnClick, и восстановит кнопку после того, как этот обработчик завершится. То есть, нажать на любую кнопку во время работы ее OnClick станет просто невозможно.


Очень часто класс-перехватчик используется для изменения спецификатора доступа. Набивший оскомину пример: удаление строки/столбца из TStringGrid. В классе TCustomGrid есть методы DeleteRow/DeleteCol, но они защищенные (protected), следовательно, нельзя просто так взять и использовать их для StringGrid-а. На форуме встречались самые неожиданные решения, от правки исходников VCL (физический перенос заголовков методов в секцию public) до написания своего класса - потомка TStringGrid с "заглушками", вызывающими соответствующие методы TCustomGrid, и вызов этих методов через приведение типа указателя на контрол к указателю на потомка. Но проще всего это сделать через класс-перехватчик:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
namespace StringGrid_Delete
{
    class TStringGrid : public Grids::TStringGrid
    {
    public: // открываем в перехватчике оба метода
        using Grids::TStringGrid::DeleteRow;
        using Grids::TStringGrid::DeleteColumn;
    };
};
// и подсовываем перехватчик вместо стандартного компонента
#define TStringGrid StringGrid_Delete::TStringGrid
 
// -----
class TForm1 : public TForm
, теперь для всех гридов на форме есть возможность удалять строк/столбцы одним вызовом метода.


Еще один случай - это реакция контрола на сообщение Windows. Можно, конечно, воспользоваться старым добрым способом, подменить оконную функцию через SetWindowLongPtr + GWLP_WNDPROC, но проще (мне, по крайней мере) снова использовать interceptor:

Те, кто знает, как перехватываются сообщения в VCL - могут сюда не заходить :)

Для того, чтобы перехватить сообщение Windows, в VCL используются три макроса:
C++
1
2
3
4
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(перехватываемое_сообщение_Windows, тип_Message, название_процедуры_обработчика)
    // ... MESSAGE_HANDLER может использоваться многократно
END_MESSAGE_MAP(класс_родитель)
и методы-обработчики (по одному для каждого перехватываемого сообщения)
C++
1
MESSAGE void __fastcall название_процедуры_обработчика(тип_Message &message);
Теперь, в случае когда компонент получает одно из перехватываемых_сообщений_Windows, перечисленных в таблице сообщений (message map), вызывается соответствующий полученному сообщению метод название_процедуры_обработчика

Подробнее можно прочитать здесь: Таблицы откликов


C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
namespace ListBox_DragDrop
{
    class TListBox : public Stdctrls::TListBox
    {
    protected:
        MESSAGE void __fastcall WMDropFiles(TWMDropFiles &message)
        {
            wchar_t chName[MAX_PATH];
            HDROP hdropHandle=(HDROP)message.Drop;
            DragQueryFileW(hdropHandle,0,chName,MAX_PATH);
            Items->Add(chName);
        }
    public:
        __fastcall virtual TListBox(Classes::TComponent* AOwner)
            : Stdctrls::TListBox(AOwner) {}
            
        // перехватываем сообщение WM_DROPFILES, приходящее в ListBox 
        BEGIN_MESSAGE_MAP
          MESSAGE_HANDLER(WM_DROPFILES, TWMDropFiles, WMDropFiles);
        END_MESSAGE_MAP(Stdctrls::TListBox);
    };
}
#define TListBox ListBox_DragDrop::TListBox
 
// -----
class TForm1 : public TForm
, осталось разрешить для ListBox-а перетаскивание файлов вызовом DragAcceptFiles, и больше уже ничего делать не нужно. А теперь представьте, сколько понадобилось бы написать (при использовании SetWindowLongPtr), если на форме находится, скажем, 5 ListBox-ов...


Ну, и немного о том, как определить, в какой области видимости должны располагаться те или иные методы, и какие методы вообще можно перегружать. Поиск в гугле по названию класса (на сайте онлайн-документации Embarcadero)
Код
tbutton site:docwiki.embarcadero.com
выводит на нужную страничку:
Классы-перехватчики (interceptor classes)

Выбираем в верхней строке Methods (или Properties), и получаем список всех методов/свойств класса с указанием области видимости:
Классы-перехватчики (interceptor classes)

. Чтобы увидеть, как именно должен описываться заголовок - переходим к описанию нужного метода:
Классы-перехватчики (interceptor classes)

, копируем оттуда заголовок, и помещаем его в нужную секцию класса-перехватчика...

Не по теме:

Пример приведен для онлайн-документации, потому что
1) далеко не все устанавливают себе файл справки
2) в XE и выше очень неудобная справка, гораздо удобнее пользоваться docwiki.embarcadero.com

39
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
05.02.2015, 15:06
Ответы с готовыми решениями:

Справочник VCL Classes
Добри вечер суток... Может кто нибуть скажет где я смогу наити справочник VCL...

Ошибка rdsplat.h(45): E2175 Too many storage classes in declaration
Выдает ошибку на строку /* Not the "politically correct" file for this def...

E2459 VCL style classes must be constructed using operator new
TRegExpr Re("<span>(.*?){1}</span>"); Ругается на данную строчку, код ошибки:...

Ошибка: E2459 VCL style classes must be constructed using operator new
Программа выводит красную точку, которой можно управлять с клавиатуры.Здесь...

[C++ Error] Unit1.cpp(11): E2459 VCL style classes must be constructed using operator new
почему в билдере нельзя объявить обычную переменную TBitmap? ...

0
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
05.02.2015, 15:06

Ошибка [BCC32 Error] vector(1179): E2459 Delphi style classes must be constructed using operator new
Пишу код в RAD XE студии. К сожалению не уверен, что пишу в нужной ветке, т.к....

Ругается [BCC32 Error] Unit1.cpp(35): E2015 Ambiguity between 'fmCreate' and 'Classes::fmCreate'
TFileStream* f = new TFileStream("lol.exe", fmCreate|fmOpenWrite);...

Перехватчики
Собственно, Была проблема, не заходило на некоторые сайты, исправил. Затем...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
1
Закрытая тема Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2018, vBulletin Solutions, Inc.
Рейтинг@Mail.ru