Нестандартное использование компонентов

Очень часто, нам необходимо расширить какую-то возможность определенного компонента. Я уже не раз встречался с такой проблемой. Самым правильным способом будет создание нового компонента потомка, в котором нужно реализовать необходимые действия. Правильно, но не всегда эффективно. Сегодня мы научимся использовать компоненты не совсем так, как задумывал дядя Борман.

Но иногда можно и даже возникает необходимость отступить от этого правила. Например, у вас в проекте используется 100 кнопок TButton, и вдруг мы захотели наделить эти кнопки новым одинаковым свойством. Если следовать классическому методу решения проблемы, то необходимо будет сначала создать новый компонент, а потом поступить одним из двух способов:

1. Вручную изменить в dfm и pas файле предка для каждого компонента. Когда их 100 штук, процесс изменения может затянуться и будет очень утомительным;

2. Заново создать все кнопки. Старые нужно удалить и создать новые кнопки от нового компонента. Этот процесс будет проходить еще дольше, особенно если ты не используешь действия (TAction) и придется восстанавливать каждое свойство и обработчик события для кнопки.

Намного проще изменить стандартный компонент TButton и больше никаких изменений в проекте делать не надо. Для этого есть два варианта – глобальное изменение и локальное. В любом случае, изменения производятся одинаково, разница только в том, как сделать так, чтобы Delphi компилировал наш вариант нового компонента.

Изменим стандартный компонент

У нас есть возможность изменения стандартных компонентов благодаря тому, что все исходные коды компонентов входят в поставку Delphi. Самое странное, что исходных кодов я не нашел в Delphi 2005. Возможно, это связано с тем, что у меня бета версия и в полной версии исходные коды будут, потому что они действительно необходимы.

В Delphi 7 исходные коды VCL компонентов находятся в директории Delphi7/Sources/Vcl. В последнее время все большую популярность получает Delphi 2005. Тут с исходными кодами напряг :(, Borland явно пожадничал.

Объявление кнопки TButton находится в модуле StdCtrls.pas. Давайте откроем этот файл и отредактируем объект TButton. Я специально выбрал именно этот объект, потому что с ним будет проблем выше крыши при локальном компилировании :).

Итак, найдите описание компонента TButton и добавьте в раздел public новую процедуру:

procedure ShowButtonMessage(sTest:String);

Нам не нужны сложные манипуляции, главное это доказательство изменения работы стандартного компонента. Нажимаем , чтобы Delphi создал шаблон для процедуры. В этой процедуре не будем выпендриваться, а просто вызовем окно с сообщением, которое передается в качестве параметра:

procedure TButtonControl.ShowButtonMessage(sTest: String);
begin
 MessageBox(0, PChar(sTest), 'Тест', 0);
end;

Компилируй правильно

Теперь необходимо, чтобы при компиляции Delphi использовал модифицированную версию файла. Сначала рассмотрим глобальный метод. Выбери меню Tools/Environment Options. Перед нами откроется окно настроек Delphi. Переходим на закладку Library. Здесь в строке Library path показаны пути, где компилятор должен искать модули. Щелкни по кнопке с тремя точками, чтобы было удобнее смотреть и редактировать список путей в специальном окне.

Свойства проекта, закладка Library

Окно настройки директорий для поиска модулей

Обрати внимание на первую строку в окне, где находиться путь $(DELPHI)\Lib. Первым делом компилятор будет искать модули в директории \lib, в директории, где установлен Delphi. Если нужный dcu или pas файл в этой директории не найден, то будут просматриваться остальные директории в списке. Понятно, к чему я клоню? Если нет, читаем дальше, а дальше будет интереснее.

Наша задача добавить путь к директории с исходными кодами VCL модулей и поставить этот путь первым, чтобы компилятор использовал эту директорию. Для этого выполняем следующие действия:

1. В строку ввода под списком директорий введи путь $(DELPHI)\Source\Vcl;

2. Нажми кнопку Add, чтобы добавить путь;

3. Выдели только что добавленный путь, и стрелкой вверх переместите его на первую строчку;

4. Сохраняем изменения, нажатиям кнопки ОК.

Теперь можно компилировать проект.

Таким образом, мы глобально изменили настройки, и теперь все модули из директории VCL будут компилироваться. Нет, не «абсолютно» все, а те, что используются в нашем проекте. Но и это, я скажу тебе, не мало. Даже средненький проект использует не менее 20 модулей. Это не очень хорошо, ведь мы изменили только один файл StdCtrls.pas.

Локальная компиляция

Для локального подключения файла StdCtrls.pas, его необходимо скопировать в ту же директорию, где ты хранишь исходный код проекта. В этом случае, в настройках Delphi ничего изменять не надо, потому что директория проекта всегда проверяется первой. Найдя в ней файл StdCtrls.pas, директория \lib проверяться не будет, поэтому компилятор использует модифицированную версию компонента TButton.

Попробуйте выполнить локальную компиляцию. Вот тут возникают проблемы. Сначала компилятор укажет на модуль Themes и скажет, что модуль Dialogs скомпилирован с другой версией TButton. В данном случае проблема решается просто. Посмотрите, модуль Themes подключен в uses раздела implementation. Если перенести подключение в uses раздела interface (в начале модуля), то ошибка исчезнет. Такой трюк работает не всегда и скорее исключение из правил. Ниже мы увидим более надежный вариант решения этой проблемы.

Снова попробуем скомпилировать программу, и снова произойдет ошибка. Ты должен увидеть сообщение, что модуль ExtCtrls скомпилирован с другой версией компонента TButton. Почему так произошло? Когда мы подключали измененный файл глобально, то абсолютно все используемые в программе модули из директории \Source\Vcl перекомпилировались. В данном случае, перекомпилируется только StdCtrls, где TButton модифицированный. Но мы используем в проекте еще и модуль ExtCtrls, который:

  • Берется из директории \lib;
  • Использует модуль stdctrls;
  • - Скомпилирован с другой версией TButton.

Два разных компонента с одним и тем же именем не могут существовать в одном проекте. Трюк, который мы провели с модулем Themes уже не пройдет. Тут нужно другое решение. А оно лежит на самой поверхности – перекомпилировать и модуль ExtCtrls. Для этого его тоже скопируйте в директорию с исходным кодом проекта. Теперь этот модуль перекомпилируется, и тоже будет использовать модифицированный TButton.

Чтобы не было больше проблем с компиляцией, скопируйте в директорию с исходным кодом проекта файлы Dialogs.pas и Buttons.pas.

Вот таким не хитрым способом, несколько лет назад я решил одну очень большую проблему. В одной из версий Delphi (точно не помню, но кажется это был Delphi 4) в сетке StringGrid один метод работал не совсем так, как мне надо было. Я модифицировал его и подключил к программе локально.

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

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

Я уже не раз замечал, что разработчики Delphi иногда прячут в области private или protected очень полезные возможности компонентов. Я понимаю, что они запрещают доступ к тем свойствам и методам, неверное обращение с которыми может привести к краху программы. И все же, иногда доступ к запретному плоду просто необходим.

Как получить доступ к переменной или методу, который закрыт? Рассмотрим проблему на примере кнопки. У компонента TButton есть закрытый метод SetButtonStyle. Этот метод объявлен в секции protected. Как обмануть Delphi и получить доступ к этому методу напрямую? Если написать в программе следующий код, то во время компиляции произойдет ошибка:

Button1.SetButtonStyle(true);

У компилятора Delphi есть один недостаток (а может быть преимущество, сказать трудно). Если два объекта объявлены в одном модуле, то все их свойства и методы будут доступны друг другу. При этом не имеет значения, где объявлены эти методы и свойства, открыты они или закрыты. Такие объекты называются дружественными и чисто по братски делятся всеми своими возможностями.

Но этого мало, ведь кнопка и наш проект объявлены в разных модулях, поэтому возможность на первый взгляд бесполезная. Не торопитесь. У объектно-ориентированного программирования есть очень полезное свойство – наследование. При этом потомку может быть назначен любой предок, и код будет работать верно.

Стань другом

Давайте объединим эти две возможности и получим доступ к закрытому методу. Итак, в модуле нашей формы объявляем новый класс, который будет происходить от TButton. Объявление будет следующим (оно должно быть в разделе type):

TMyButton = class(TButton)
end;

Теперь, если в любом месте этого модуля можно использовать закрытые методы кнопки. Допустим, что у нас на форме есть кнопка Button1 типа TButton. Этот объект является предком для TMyButton, поэтому запись TMyButton(Button1) является вполне корректной. А так как класс TMyButton объявлен в этом же модуле, то мы легко можем получить доступ к его закрытым методам и свойствам. Для этого достаточно написать код:

TMyButton(Button1).SetButtonStyle(true);

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

Этот метод хорош, только если вам нужно свойство или метод из раздела protected. Все, что объявлено в разделе private не доступно потомкам, а значит, не переносится в наш модуль.

Сетка DBGrid

Очень часто меня спрашивают, как можно изменить высоту определенной колонки в компоненте TDBGrid. Да, у этого компонента нет свойства, которое отвечало бы за высоту, но среди предков TDBGrid есть объект TCustomGrid, у которого есть свойство RowHeights. Чтобы его использовать, достаточно написать следующую строку:

TCustomGrid (DBGrid).RowHeights[2]:=50;

Но тут хочется предупредить, что Borland не зря закрыло это свойство. Дело в том, что если пролистать сетку DBGrid, то возникнут серьезные проблемы с прорисовкой. В некоторых случаях закрытые свойства и методы предка действительно могут спасти, но могут и нарушить работу программы. В данном случае, мы достаточно просто изменили высоту строки, но теперь придется попотеть, чтобы данные отображались правильно.

Сетка DBGrid, в которой строки имеют разную высоту

Compile Complete

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

Использование закрытых свойств и методов компонентов должно быть аккуратным. Разработчики Borland не зря закрывают методы, ведь прямой вызов некоторых из них может привести к серьезным проблемам в работе всей программы. Закрытые методы и свойства не документируются в файле помощи, поэтому сложно понять, как они используются. Но если проанализировать исходный код модуля, то можно легко понять, что, когда и как использовать.

Помни, хакерство – это образ мышления, образ жизни и постоянные исследования. Удачи в твоих дальнейших исследованиях.


Исходник Нннннада?

Тут есть еще и исходник, который можно скачать, просто кликни здесь



Внимание!!! Если ты копируешь эту статью себе на сайт, то оставляй ссылку непосредственно на эту страницу. Спасибо за понимание

Комментарии

Паника, что-то случилось!!! Ничего не найдено в комментариях. Срочно нужно что-то добавить, чтобы это место не оставалось пустым.

Добавить Комментарий

О блоге

Программист, автор нескольких книг серии глазами хакера и просто блогер. Интересуюсь безопасностью, хотя хакером себя не считаю

Обратная связь

Без проблем вступаю в неразборчивые разговоры по e-mail. Стараюсь отвечать на письма всех читателей вне зависимости от страны проживания, вероисповедания, на русском или английском языке.

Пишите мне