-
Пишу наследника TTreeView и хочу сделать так, чтобы при добавлении нодов создавался экземпляр наследника TTreeNode . Сам дошел до того, что надо переписать метод TTreeView.CreateNode , т.е. скрыть его. Класс нода, в принципе можно задавать в OnCreateNodeClass , но как-то мне кажется нехорошо иметь в компоненте обработчик события. Вопрос в следующем: нет ли другого способа дать понять TTreeView , ноды какого класса ему создавать, кроме двух вышеоуказанных? И еще - что еще надо учесть, если я все-таки скрою TTreeView.CreateNode . Т.е. какие еще методы надо при этом перекрыть/скрыть?
-
> т.е. скрыть его Не скрыть, а перекрыть. Метод виртуальный. Специально для сабжа и предназначен. > нехорошо иметь в компоненте обработчик события. Не просто нехорошо, а вообще недопустимо. Пользователь компонента имеет полное право назначить свой обработчик, и тогда компонент работать перестанет. > нет ли другого способа Зачем? Разве недостаточно того, что уже есть? > какие еще методы надо при этом перекрыть/скрыть? Никакие.
function TMyTreeView.CreateNode: TTreeNode; var
LClass: TTreeNodeClass;
begin
LClass := TMyTreeNode; if Assigned(OnCreateNodeClass) then
OnCreateNodeClass(Self, LClass);
Result := LClass.Create(Items);
end;
-
> function TMyTreeView.CreateNode: TTreeNode; // override > var > LClass: TTreeNodeClass; > begin > LClass := TMyTreeNode; // Это и будет Ваш класс по умолчанию. > > if Assigned(OnCreateNodeClass) then > OnCreateNodeClass(Self, LClass); > Result := LClass.Create(Items); > end; >
да, это я и имел в виду. это никак не отличается от скрытия, поскольку не вызывается метод предка.
Спасибо за информацию.
-
Оказалось все не так просто :( Нужен еще наследник TTreeNodes , который бы знал, какой класс имеют ноды. Иначе теряется смысл всей этой кутерьмы :) Т.е. сейчас я перекрыл CreateNode , но при попытке сделать что-нибудь вроде MyTreeView.Items[1].MyNewProperty := x; Получаю сообщение о том, что MyNewProperty - неизвестный идентификатор. Что, в общем то, естественно, т.к. у TTreeNode такого свойства нет.
-
придется, похоже, еще и геттер TTreeNodes.Item скопировать
-
> umbra > это никак не отличается от скрытия От скрытия это отличается как раз самым коренным образом. И вызов метода предка (или НЕ вызов) тут абсолютно ни при чем. TMyTreeNode(MyTreeView.Items[1]).MyNewProperty := x; И все будет работать. Насчет геттера - если он виртуальный или динамический, то все будет ОК. Но если он статический, то ПЕРЕкрыть его не удастся, а удастся именно ЗАкрыть. А этого делать нельзя, потому что у пользователя компонента запросто могут возникать баги.
-
> А этого делать нельзя, потому что у пользователя компонента > запросто могут возникать баги.
Да ну. Вот TComponentList закрывает методы TObjectList и не жужжит. :)
-
> TMyTreeNode(MyTreeView.Items[1]).MyNewProperty := x; > И все будет работать.
это-то понятно > От скрытия это отличается как раз самым коренным образом. > И вызов метода предка (или НЕ вызов) тут абсолютно ни при > чем.
а чем в данном конкретном случае это отличается от объявления function CreateNode: TTreeNode; reintroduce; virtual; ?
-
> function CreateNode: TTreeNode; reintroduce; virtual;
Ты тут переопределил вирт. метод предка и снова объявил его виртуальным. Похоже на бред. Я вообще не могу представить зачем такое делать.
-
Все что тебе надо сделать, «чтобы при добавлении нодов создавался экземпляр наследника TTreeNode» перекрыть CreateNode. Вот так: function CreateNode: TTreeNode; override; function T???TreeView.CreateNode: TTreeNode;
var
LClass: TTreeNodeClass;
begin
LClass := TTreeNode;
if Assigned(FOnCreateNodeClass) then
FOnCreateNodeClass(Self, LClass);
Result := LClass.Create(Items);
end; И надо еще будет соответственно сделать аналог TTVCreateNodeClassEvent , который принимает классы не младше твоего.
-
Забыл исправить. function T???TreeView.CreateNode: TTreeNode;
var
LClass: TTreeNodeClass;
begin
LClass := T???TreeNode;
if Assigned(FOnCreateNodeClass) then
FOnCreateNodeClass(Self, LClass);
Result := LClass.Create(Items);
end;
-
> Ты тут переопределил вирт. метод предка и снова объявил > его виртуальным. Похоже на бред.
а так я практически скопировал код метода предка и вставил в свой метод. Тоже не верх элегантности. Зато хоть понятно, что это как бы совершенно новый метод, не опирающийся, типа, на старый :)
-
> Тоже не верх элегантности. Зато хоть понятно, что это как > бы совершенно новый метод, не опирающийся, типа, на старый > :)
Это, как бы сказать то
, эти директивы это не комментарии, они кое-что значат. > а так я практически скопировал код метода предка и вставил > в свой метод.
Ну и нет в этом ничего страшного. Это нормально в данном случае. Это правильное решение которое и имел ввиду разработчик TCustomTreeView делая метод CreateNode виртуальным. Я бы на твоем сместе от TCustomTreeView отнаследовался бы, чтобы с событием TTVCreateNodeClassEvent правильно расправится.
-
пришел к выводу, что без полного копирования класса TCustomTreeView в свой модуль невозможно создать его полноценного наследника, использующего наследника TTreeNode . Потому как в проекте, ипользующем контрол-наследник, все равно придется явно приводить тип нодов к нужному.
-
Ты че. Тебе надо написать наследников TCustomTreeView, TTreeNodes, TTreeNode.
В них тебе надо перекрыть 1(один) CreateNode и 1(один) CreateNodes и свойства Items.
Вот и все.
-
Тебе для того и вирт методы сделали, чтобы перекрывать их, а ты весь модулькопировать собрался, а потом говоришь, что 4 строки из [10] это некрасиво
-
-
> 2. Вот это посмотри:
Ты хочешь сказать, что я должен еще и о тех, кто хакает думать? Или ты имеешь ввиду, что Borland, закрывшый в методы в TComponentList и аналогах идиот? Чукча не читатель?Что непононятно, поясни?
-
> Kolan © (07.04.08 18:42) [17]
> Ты хочешь сказать, что...
Я уже сказал (в [5]) и повторяю: попытка "перекрытия" статических методов предка чревата неоднозначностью поведения и соответствующими багами. Примеры см. в ветке по ссылке и хаки тут ни при чем.
> Что непононятно, поясни?
Поясняю: ты эту ветку не читал.
-
Так предложи решение сабжа. Или ты считаешь, что скопировать к себе все TCustomTreeVeiw лучше чем закрыть геттеры?
-
К тому же у той ветки, которую я таки прочел, есть отличия от этой. Какие интересно баги я получу если закрою геттеры вот так: TMyObject = class
end;
TMyList = class(TObjectList)
protected
function GetItem(Index: Integer): TMyObject;
procedure SetItem(Index: Integer; const Value: TMyObject);
public
property Items[Index: Integer]: TMyObject read GetItem write SetItem;
end;
function TMyList.GetItem(Index: Integer): TMyObject;
begin
Result := inherited Items[Index] as TMyObject;
end;
procedure TMyList.SetItem(Index: Integer; const Value: TMyObject);
begin
inherited Items[Index] := Value;
end; Я так понял имеется ввиду, что если теперь сделать так: procedure TForm1.BitBtn1Click(Sender: TObject);
var
ObjList: TMyList;
begin
ObjList := TMyList.Create;
try
ObjList.Add(TMyObject.Create);
ObjList.Items[0];
TObjectList(ObjList).Items[0];
finally
ObjList.Free;
end;
end; То в строке 2 мы обратимся не к закрытому методу, что естественно, а к методу предка. Ну и что страшного? В реализации GetItem и SetItem мы это и сделали, только код из [5] теперь в одном месте, чего и хотел (и правильно хотел) автор.
-
Можно еще посмотреть на TCollection сама суть которой в работе с наследниками ( Each TCollection holds a group of TCollectionItem descendants. ). Смотрим справку: The following table lists some typical descendants
Берем, например, TCookieCollection . И там: TCookieCollection = class(TCollection)
private
…
protected
function GetCookie(Index: Integer): TCookie;
procedure SetCookie(Index: Integer; Cookie: TCookie);
public
…
property Items[Index: Integer]: TCookie read GetCookie write SetCookie; default;
end;
function TCookieCollection.GetCookie(Index: Integer): TCookie;
begin
Result := TCookie(inherited Items[Index]);
end;
procedure TCookieCollection.SetCookie(Index: Integer; Cookie: TCookie);
begin
Items[Index].Assign(Cookie);
end; Тут даже старые геттеры не закрыты. Делая: var
CookieCollection: TCookieCollection;
begin
TCollection(CookieCollection).Items[0]
end; Попадаем в GetItem пердка, ну и что страшного? Этого мы и добивались ©. :)
-
Kolan © (08.04.08 09:43) [21]
Поищи в гугле термин Wannabee
-
Удалено модератором Примечание: Выражения выбираем, не в пивной
-
-
Удалено модератором Примечание: Offtopic
-
> Kolan © (08.04.08 09:08) [19]> Так предложи решение сабжа. Или ты считаешь... Это хорошо, что ты таки прочел ТУ ветку. А если бы прочел еще и ЭТУ, то совсем хорошо стало бы. > Какие интересно баги я получу если закрою геттеры Про TList со всеми его явными и неявными производными можешь мне не рассказывать, я их знаю не хуже тебя. И что в некоторых контейнерах переписаны статические методы - тоже знаю, и сам не раз так делал (приняв соответствующие меры безопасности - см. ниже). Однако же, код коду рознь и класс классу - тоже. Если ЗАкрытый потомком статический метод ТОЛЬКО вызывает метод предка и приводит класс, а вызванный из него метод предка НЕ использует других тоже ЗАкрытых потомком статических методов, то все нормально. Но если это не так - возможны баги. Возьмем тот же TComponentList. Например, я хочу, чтобы при вставке компонента в список или удалении из него происходило уведомление об этом какого-то моего объекта, причем стандартное решение (перекрытие метода Notify) меня по каким-то причинам не устраивает. Казалось бы, задача проще простого - пишем потомок TComponentList, в нем ЗАкрываем методы вставки и удаления (поскольу они статические), в которых и делаем нужное уведомление. Но не тут-то было. Юзер (прикладной программер) вдруг захотел (и имеет на это полное право) использовать мой список одновременно со стандартным. Для этого он пишет что-то вроде:
procedure АddComponentToList(Component: TComponent; List: TComponentList);
и совершенно справедливо полагает, что параметром List он будет передавать сюда нужный ему список (моего или стандартного класса) - и все обязано нормально работать. И действительно обязано. Но не будет. Не будет работать то самое уведомление, ради которого я и писал свой список и на которое юзер имеет полное право рассчитывать. И если он не имет моих исходников, то отловить этот баг и понять его причину ему будет очень непросто (если не сказать - вообще невозможно). Да даже если исходники и есть, то ловля багов такого рода - тоже совсем не сахар. Вот об этом обо всем я человека и предупредил. Дальнейшее решение (делать так или не делать) должен принять уже он сам (правда, после reintroduce; virtual; как-то грустно стало). А решение было дано еще в самом первом постинге. Сравни с тем, что ты написал спустя почти 10 постов и найди 2 отличия. Спрашивается - если ты ветку читал, то за каким лешим ты писал то же самое? А если не читал, то за каким лешим вообще писать?
-
> Вот об этом обо всем я человека и предупредил.
С примером согласен, это почти тоже самое что и в ветке про SetColor. Но (!), автору то не надо ничего добавлять, ему просто надо, чтобы Item у TTreeNodes уже были приведены к нужному классу, чтобы не делать это приведение повсеместно (кстати врядли кто-то еще, кроме автора, догодается, что объекты в дереве не TTreeNode если Items возвращает TTreeNode). Я не вижу ситуации где может возникнуть проблема если он просто закроет геттер для того, чтобы сделать приведение, подобно тому, как это делается в TObjectList и т.д
А раз, как я думаю, проблем не будет, то это и есть самое правильное решение. Вот что я пытаюсь сказать. > А решение было дано еще в самом первом постинге. Сравни > с тем, что ты написал спустя почти 10 постов и найди 2 отличия.
Во-прервых ты там забыл про событие, в котором можно подсунуть TTreeNode, а не потомка, и будет плохо. А во-вторых, я видел что ты написал, но раз человек пытается делать reintroduce; virtual; , то я подумал, что повторить не лишне.
-
> Kolan © (08.04.08 12:50) [27]
> ты там забыл про событие
Не забыл. Читай код.
> в котором можно подсунуть TTreeNode, а не потомка
Можно. И TTreeNode, и потомка, и любого другого потомка - тоже можно. Что юзер хочет - то он туда и подсовывает, имеет полное право. Это событие специально для того и сделано, чтобы юзер мог использовать свой собственный потомок TTreeNode, не переписывая больше ничего.
> и будет плохо.
С какой стати? Будут создаваться узлы того класса, который хотел юзер. Ничего плохого в этом нет.
-
> С какой стати?
С той стати, что автор компонента создает его для того, чтобы работать с TMyTreeNode или младше. И он будет считать, что Элементы у него не как не младше TMyTreeNode . И плохо будет когда автор внутри напишет что-то вроде TMyTreeNode(Items).MyProp := 10; , а пользователь компонента, в OnCreateNodeClass, передаст TTreeNode. Так что в OnCreateNodeClass может быть любой класс, но только TMyTreeNode или его потомок. > Не забыл. Читай код.
Имелось ввиду см. выше.
-
> Kolan © (08.04.08 13:20) [29]
> И он будет считать, что Элементы у него не как не младше TMyTreeNode.
Это подход прикладника, а не разработчика компонентов. Нормальный же разработчик компонентов либо должен закрыть это событие совсем (и документировать это), либо не имеет права так считать (и должен понимать это).
Дружище, скажи честно - сколько компонентов "не для себя" написал?
-
> закрыть это событие совсем
Я же это и предлагаю. Либо закрыть совсем и сделать вообще с другим именем, но тогда те, кто привык к обьычному TTreeView будут материться. Либо сделать точно такое же, только: TTVCreateNodeClassEvent = procedure(Sender: TCustomTreeView;
var NodeClass: TMyTreeNodeClass) of object; > Дружище, скажи честно сколько компонентов «не для себя» > написал?
В принципе, один, да и то
Так что можешь смело считать что ни одного. > (и должен понимать это).
Хорошо, как же быть? Все что нужно сделано CreateNode перекрыт и внутри все работает, как же теперь объяснить прикладнику, что теперь элементы дерева обладают доп. функциональностью? Как тогда видел порождение потомков разработчик TTreeView? Он же сделал вирт. методы не просто так
-
> Kolan © (08.04.08 15:02) [31]
> как же теперь объяснить прикладнику, что теперь элементы дерева > обладают доп. функциональностью?
Дык... в документации, естественно. Можно в справке. А где ж еще-то? Больше и негде, и не было никогда.
> Как тогда видел порождение потомков разработчик TTreeView?
Это лучше у него и спросить. Мне тоже в VCL не все нравится. Скажем, в TListView есть аналогичный механизм для создания Item'ов юзерского класса, но почему-то нет такого же механизма для юзерского класса колонок.
Но в целом написано все равно классно, ничего не скажешь.
-
> Дык
в документации, естественно.
Незнаю, если бы мне дали компоненет и казали «Вызывай Items так: TMyTreeNode(Tree.Items).SomeNewProp », я бы послал бы с таким компонентом куда подальше, и документацию тудаже. Я как не разработчик компонентов хучу писать: Tree.Items.SomeNewProp . И, имхо, это естественно.
-
> правда, после reintroduce; virtual; как-то грустно стало
я таки боюсь, что меня заплюют, но объясните мне что в этом такого плохого? без отсылок куда либо, а простыми русским словами
-
> umbra © (10.04.08 17:42) [34]
Атрибуты reintroduce; virtual; разрывают цепочку наследования виртуального метода и начинают новую. Зачем?
-
> Семеныч (10.04.08 18:07) [35]
Проще говоря, если юзер напишет TTreeView(YourComponent).CreateNode (на что он имеет полное право), то буден вызван старый метод CreateNode, а не Ваш. Соответственно, будет создан узел класса TTreeNode, а не Вашего.
-
> Зачем?
затем же, зачем и копировать код метода полностью из аналогичного метода предка. Причем этот предок - первый в иерархии.
По-моему в данном конкретном случае не имеет значения, прерывать цепочку или нет.
-
> если юзер напишет TTreeView(YourComponent).CreateNode
честно говоря, не могу придумать, зачем такая конструкция может понадобиться. :)
-
> umbra © (10.04.08 18:16) [38]Пример. У юзера на форме лежать два TreeView - Ваше и стандартное. Он хочет динамически заполнять их и чтобы не писать 2 отдельных метода, пишет один общий. Этот общий метод имеет параметр - дерево, которое надо заполнить. Юзер совершенно правильно пишет метод так:
procedure TForm1.FillTreeView(TreeView: TreeView);
begin
end;
и совершенно справедливо полагает, что все должно правильно работать. Но нарывается на Ваш баг.
-
> procedure TForm1.FillTreeView(TreeView: TreeView); > begin > // Где-то здесь многократно вызывается CreateNode. > end;
резонно. идея понятна. спасибо. :)
|