Форум программистов, компьютерный форум, киберфорум
Наши страницы
C++ Builder
Войти
Регистрация
Восстановить пароль
 
Рейтинг 4.90/58: Рейтинг темы: голосов - 58, средняя оценка - 4.90
volvo
Супер-модератор
Эксперт Pascal/DelphiАвтор FAQ
28022 / 18680 / 7363
Регистрация: 22.10.2011
Сообщений: 32,727
Записей в блоге: 6
1

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

05.02.2015, 15:06. Просмотров 10725. Ответов 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

43
QA
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
05.02.2015, 15:06
Ответы с готовыми решениями:

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

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

Хуки (глобальные перехватчики событий от клавиатуры)
Знаю, что подобные темы имеются в просторах интернета, но все равно у меня не получается...

Inner classes
Зашорился в задачу: необходимо распечатать номер, которому звонишь по индексу. public class...

Classes
Здравствуйте. Подскажите пожалуйста. У меня есть структура проекта. Из всей структуры я обращаю...

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

classes, конструкторы..
Привет.. В общем такая фигня. Вот код: Employee.h: class Employee { public: Employee(int...

Применение [+wf.classes+]
Доброго времени суток. У меня вопрос. ? мне нужно тегу <a> класс передать. я не понимаю...

Compare classes
как сравнить через полиморфизм результаты методов классов которые реализуют один и тот же интерфейс?


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

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

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