Top.Mail.Ru
Мастера DELPHI, Delphi programming community Титульная страница Поиск, карта сайта Написать письмо 
| Новости | Поиск | FAQ | Статьи | Книги | Новости VCL |
| Форумы ЧАТ | Орешник | KOL и MCK |
Форумы -» Основная («Начинающим»)/ Базы / WinAPI / Компоненты / Сети / Media / Игры / Corba и COM / KOL / FreePascal / .Net / Прочее / RSDN.ORG

« предыдущая ветвь | форум | следующая ветвь »

Прошу помощи по сохранению TreeView [D7, 2003]


Страницы:   1 2 3

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]

Можно. Но это привилегия красных штанов (админов т.е.)


Страницы:   1 2 3

Наверх

Титульная страница Поиск, карта сайта Написать письмо