![]() |
| ||
|
|
Новости |
Поиск |
FAQ |
Статьи |
Книги |
Новости VCL
| | Форумы ЧАТ |
Орешник |
|
| |||
| |||
« предыдущая ветвь | форум | следующая ветвь »
Прошу помощи по сохранению TreeView [D7, 2003]
Vervolfe © (2013-09-14 06:34:00)Всех приветствую. Я нашел на этом форуме вот такую статью http://www.delphimaster.net/view/2-1278064750
Пожалуйста, помогите мне разобраться с этим. Уже которую неделю ничего не получается.
wervolfe (2013-09-18 10:32:00) [1]В моем случае такой способ тоже пойдет. У меня в программе для внесения данных пользователь заполняет форму. Добавлять или убавлять он поля не может. Производится проверка на обязательность заполнения всех полей. И все это можно делать только в level=1. Все делево у меня имеет только уровень 0 и уровень 1. Данный содержаться только в уровне 1. Так что все просто. (когда разберешься! ))
wervolfe (2013-09-18 10:26:00) [2]Туповатый>
Да. Так тоже работает. Получается такой длинный столбик! Это уже так...Я просто разбираюсь. Код написанный Юрием прекрасно работает. А, как известно, работает - не трогай! ))
Туповатый (2013-09-17 23:53:00) [3]а если добавить некий идентификатор перед каждой строкой (который потом выкидывать перед обратной загрузкой в дерево), то можно получить несколько большую проверку целостности данных, чем просто кратность числа строк количеству свойств персоны.
Туповатый (2013-09-17 23:50:00) [4]Слишком сложно, как-то.
если данные фиксированы, т.е. есть Некий Некиевич Некиев, у которого есть адрес телефон и еще что-то, и у всех остальных точно так же (нет вариантов, что у одного 6 свойств, у другого 4 вместо 4 и 2 пустых), то получается, что их можно без всяких табов и вк свалить в стринглист. И тогда легко восстановить, ведь мы знаем, что с начиная с первой и через каждые (к примеру из 6 свойств) 5 строк - фамилия, со второй через каждые 5 - имя и т.д.
fs:=TStringList.Create;
Node := TreeView.Items.GetFirstNode;
while Node <> nil do
begin
for i := 0 to Node.Level - 1 do begin
fs.Add(Node.Text);
fs.Add(PNodeOptions(Node.Data).Name);
и т.д.
end;
Node := Node.GetNext;
end;
fs.SaveToFile('Tree.dat');
fs.Free;
уже очень плохо помню обращение к нодам, поэтому этот пример - просто переделка [37], без претензии на абсолютную правильность
wervolfe (2013-09-17 13:28:00) [5]Варианты с отдельной формой неудобны в моем случае. Сделал просто менюшку из стандартной попАп.
Разбираюсь с теорией. Я так понимаю, что если данные заведомо текстовые, то можно было не наворачивать такие конструкции как разобраны выше, а сделать только :
procedure TMainForm.SaveTreeToFile;
const
TabChar = #9;
EndOfLine = #13#10;
var
i: Integer;
Node: TTreeNode;
Str: string;
p: pointer;
fs:TStringList;
begin
fs:=TStringList.Create;
Node := TreeView.Items.GetFirstNode;
while Node <> nil do
begin
Str := '';
for i := 0 to Node.Level - 1 do Str := Str + TabChar ;
Str := Str + Node.Text + EndOfLine + PNodeOptions(Node.Data).Name + EndOfLine + PNodeOptions(Node.Data).Surname + EndOfLine + PNodeOptions(Node.Data).Address + EndOfLine + PNodeOptions(Node.Data).EMail + EndOfLine;
p:=@Str;
fs.Add(string(p^));
Node := Node.GetNext;
end;
fs.SaveToFile('Tree.dat');
fs.Free;
end;
?? если из этого, конечно, получится восстановить теперь...))
Юрий Зотов (2013-09-16 00:05:00) [6]> wervolfe © (15.09.13 23:45) [34]
Ну да, отдельная (причем модальная) формочка редактора. Как и в самой Delphi.
Туповатый (2013-09-15 23:45:00) [7]
> по правой кнопке мыши вызвать всплывающее меню
туда еще можно добавить свертку-развертку всего дерева
wervolfe (2013-09-15 23:45:00) [8]А...Я понял о чем Вы! Отдельная форма с кнопками, вызываемая правым кликом. Да. Я не подумал об этом! Спасибо!
wervolfe (2013-09-15 23:37:00) [9]Две кнопки - жутко неудобно. Самое лучшее решение было бы по правой кнопке мыши вызвать всплывающее меню и в нем выбирать. Его и буду, наверное пытаться реализовывать.
wervolfe (2013-09-15 23:31:00) [10]Добавлять правой кнопкой мыши? Попробую...Спасибо.
Юрий Зотов (2013-09-15 23:29:00) [11]> wervolfe © (15.09.13 22:05) [29]
Кроме того, программа должна уметь работать и с клавиатуры, вообще без всякой мышки. Поэтому привязываться только к двойному клику нельзя. Нужны две кнопки (New item и New subitem) c горячими клавишами.
Юрий Зотов (2013-09-15 23:22:00) [12]> wervolfe © (15.09.13 22:05) [29]
Я тоже думаю, что это решение не лучшее. Во-первых, оно не очевидно для юзера - ну как он догадается, что для создания Subitem нужно делать двойной клик? Во-вторых, двойной клик юзер может сделать и случайно (а иногда это делает и сама мышка, если она не совсем исправна - так называемый "дребезг контактов").
Посмотрите, как устроен редактор дерева в самой Delphi - чем плохо? Я бы сделал так же.
wervolfe (2013-09-15 22:05:00) [13]Юрий Зотов>Спасибо. Учу теорию! Буду разбираться.
У меня такой вопрос...
Вот таким кодом (из того же примера) добавляем ноды.
procedure TMainForm.NewBtnClick(Sender: TObject);
var
NodeName:String;
NodeData:PNodeOptions;
NewNode:TTreeNode;
begin
if not InputQuery('Новый элемент', 'Введите имя нового элемента',
NodeName) then exit;
NewNode:=TreeView.Items.AddChild(TreeView.Selected, NodeName);
// Инициализация начальных данных структуры
NodeData:=new (PNodeOptions);
NodeData.Name:='';
NodeData.Surname:='';
NodeData.Address:='';
NodeData.EMail:='';
NewNode.Data:=NodeData;
end;
Происходит это так...Если не производить ни одного клика внутри TreeView, то нажатием кнопки добавляется корневая нода (level=0). Одна, две - сколько нужно.
Однако, если крикнуть по любой из них, то после клика происходит уже добавление ветвей (level 1?2 и тд). Еще одну ноду 0 уже добавить никак нельзя. Поэтому я на кнопку "создать" повесил
TreeView.Items.Add
а на двойной клик мыши уже TreeView.Items.AddChild.
Level=2 мн не нужен (наоборот, его создание у меня запрещено) поэтому все прекрасно работает.
Однако, я думаю, что это решение не красивое. Как следовало сделать?
Туповатый (2013-09-15 18:23:00) [14]А... нет, я вовсе не про конкретно ту ветку конкретно того форума. Имел ввиду вообще, что форумы не всегда содержат истину.
[ВладОшин] (2013-09-15 17:48:00) [15]
> Туповатый © (14.09.13 08:20) [1]
>
> это не статья. это - ветка форума, иными словами - многолог
> кого-то с кем-то и тем-то. и далеко не всегда там есть истина.
>
Там говорил 12 (с) и еще кто-то
Что неправильно гоаорил 12?
Туповатый (2013-09-15 17:07:00) [16]
> [ВладОшин] © (15.09.13 16:27) [25]
Не понял вопроса.
[ВладОшин] (2013-09-15 16:27:00) [17]
> Туповатый © (14.09.13 08:20) [1]
что именно 12(с) сказал не так?
Юрий Зотов (2013-09-15 15:57:00) [18]> wervolfe © (15.09.13 07:09) [23]
К основным моментам - можно. Главное для Вас - понять суть, основную идею кода. После этого уже можно будет переходить к разбору деталей реализации.
Итак, ставим себя на место создателя примера 1 и начинаем думать. Наша задача - сохранить данные, о которых нам заранее ничего не известно. Мы лишь знаем адрес этих данных (его содержит свойство Data) и знаем, что сами данные могут быть любыми. Как ни странно, именно последнее и дает ключ к решению.
Раз мы ничего о характере данных не знаем, значит, сразу ясно, что написать процедуры их записи/чтения мы не можем. Их должен написать наш будущий пользователь wervolfe - то есть, прикладной программист, который только один и знает, что это за данные. Предположим, что такие процедуры уже написаны - значит, наша задача состоит в том, чтобы вызвать их в нужном месте кода и передать им нужные параметры.
Задаемся вопросом - а что это за параметры? Мы решили сохранять данные в поток (почему именно в поток - об этом скажу позже). Значит, пользовательской процедуре сохранения нужно передать:
1. Адрес данных (Data), которые надо записать.
2. Вспомогательный объект (TWriter) - который, собственно, их и пишет.
Сам поток (TStream) передавать не надо, потому что TWriter его и так знает. Ура! - мы определили заголовок пользовательской процедуры сохранения:
procedure <любое_имя> (Writer: TWriter; Data: Pointer);
Аналогично и с пользовательской процедурой чтения. За исключением того, что:
1. Вместо TWriter ей нужен TReader - именно он, собственно, и читает данные.
2. Удобнее сделать ее не процедурой, а функцией - ведь она должна создать экземпляр данных в памяти, заполнить его из потока и вернутьнам его адрес.
Сам поток (TStream) тоже передавать не надо, потому что TReader его тоже знает. В итоге получаем такой заголовок пользовательской процедуры чтения:
function <любое_имя> (Reader: TReader): Pointer;
Теперь наша задача стала более конкретной - надо пройти по дереву и для каждой его ветки вызвать пользовательскую процедуру сохранения (или чтения). Но как мы ее вызовем? - ведь мы же не знаем ее имени, а оно может быть любым. Значит, вызывая нашу процедуру, юзер должен передать ей свою процедуру, как параметр. Такой прием называется "обратный вызов" (callback) - юзер вызывает наш код, а из нашего кода снова вызывается код юзера. Для реализации этого приема нужно определить прототипы пользовательских функций обратного вызова - что мы и делаем:
type
TNodeSaveDataProc = procedure(Writer: TWriter; Data: Pointer);
TNodeLoadDataProc = function(Reader: TReader): Pointer;
Теперь наш код, получив в своих параметрах юзерскую функцию обратного вызова будет знать, как именно ее нужно вызвать. Кроме этой функции, в параметрах наш код должен получить сам TreeView (чьи даннные нужно писать/читать) и поток TStream (в который/из которого нужно писать/читать). Еще раз ура! - мы определили заголовки наших процедур чтения/записи:
procedure SaveTreeViewToStream(TV: TTreeview; S: TStream; SaveProc: TNodeSaveDataProc);
procedure LoadTreeViewFromStream(TV: TTreeview; S: TStream; LoadProc: TNodeLoadDataProc);
Собственно, наша задача уже практически решена. Остался пустяк - написать код этих двух наших процедур. Но это уже вопрос реализации, к идее он не относится и мы его пока рассматривть не будем - сначала надо понять саму идею.
И последнее - почему мы решили, что данные следует читать/писать именно из потока/в поток TStream?
Для универсальности. Потоки бывают самые разные, но все они имеют общего предка - класс TStream. Хочет юзер работать с файлом - использует TFileStream (что мы в данном случае и сделали). Хочет работать с памятью - использует TMemoryStream. Хочет работать с базой данных - использует TBlobStream. Хочет, чтобы чтобы данные писались на сервер на Мальдивах, а читались с сервера на Канарах - создает нужные ему потомки TStream и использует их. А наш код во всех случаях остается неизменным - он универсален и может читать/писать что угодно и откуда/куда угодно.
Фуф! Для начала, думаю, Вам хватит. Осмыслите все это, потом можно будет переходить к вопросам конкретной реализации.
PS
И еще - осмыслите, что такое указатели и как с ними работать. Без этого Вы код не поймете.
wervolfe (2013-09-15 07:09:00) [19]Туповатый> понял! Два раза Ку сделал (и присел)! ))
Юрий Зотов> Юрий, я понмимаю, что наглость, но не могли бы Вы в примере №1 и в своих программах написать комментарии к строкам. Хотя бы к основным моментам. Это не срочно-если у Вас будет время. Хотелось бы понят как это делается, а не копировать как обезьяна! )))
Туповатый (2013-09-14 22:28:00) [20]Можно. Но это привилегия красных штанов (админов т.е.)