Форум программистов, компьютерный форум, киберфорум
C++ Builder
Войти
Регистрация
Восстановить пароль
Карта форума Темы раздела Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.66/270: Рейтинг темы: голосов - 270, средняя оценка - 4.66
Супер-модератор
Эксперт Pascal/DelphiАвтор FAQ
32991 / 21297 / 8180
Регистрация: 22.10.2011
Сообщений: 36,591
Записей в блоге: 8
1

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

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

Author24 — интернет-сервис помощи студентам
Иногда возникает необходимость изменить поведение определенного 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

48
Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
05.02.2015, 15:06
Ответы с готовыми решениями:

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

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

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

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

0
05.02.2015, 15:06
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
05.02.2015, 15:06
Помогаю со студенческими работами здесь

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

Classes must not be nested
написал только: class sokd {} и выдает ошибку 1131, если пробую создавать класс в пакете, выдает...

Visual C/C++ Classes
Уважаемые программисты! :gsmile: Помогите, пожалуйста, разобраться с программой. Цель...

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


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

Или воспользуйтесь поиском по форуму:
1
Закрытая тема Создать тему
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2024, CyberForum.ru