Дополнительные операции с деревом

Архангельский Андрей

       После того как дерево отображено на экране, появляется потребность в ряде простых операциях с узлами.
       Этими операциями могут быть:
       1) Добавить новый узел
       2) Перетащить узел в другой узел
       2а) Переместить узел внутри родительского узла
       3) Редактировать узел
       4) Удалить узел.
       5) Работа с клавишами Cntrl+C, Cntrl+X, Cntrl+V
       Дополнительные операции лучше всего привязывать к всплывающему контекстному меню на правую кнопку мыши. Это уже стало стандартом де-факто.

Добавление нового узла в дерево

       Легче всего добавить новый узел как ребенка для текущего узла. Необходимо понимать, что реально нужно добавить узел не в TreeView, который только отображает состояние дерева, а в таблицу People.

procedure TForm1.pmnAddChildNodeClick(Sender: TObject); {Example01}
Var
  ANode, NewNode : TTreeNode;
  inStr,inCod : String;
  iParnt : Integer;
begin
  inherited;
01  inCod := InputBox('Введите Ф.И.О.','','Ф.И.О.');
02  inStr := InputBox('Введите дату рождения','','мм/дд/гггг');
03  ANode := tvPeople.Selected;    iParnt := ANode.ImageIndex;
04  qrExeProc.Close;   qrExeProc.SQL.Clear;
05  qrExeProc.SQL.Add('Insert into People(Parent,PSurName,PBrDate)');
06  qrExeProc.SQL.Add(' Values('+IntToStr(iParnt)+',');
07  qrExeProc.SQL.Add(''''+inCod+''','''+inStr+''')');
08  qrExeProc.ExecSQL;   qrExeProc.Close;
09  NewNode := tvPeople.Items.AddChild(ANode,inCod+' - '+inStr);
10  ANode.Collapse(True);
end;

       Строки 01-02 запрашивают у пользователя значения для полей PSurName и PBrDate таблицы, так как эти поля составляют первичный ключ таблицы.
       Строка 03 определяет выбранный узел и идентификатор узла.
       И, наконец, строки 04-07 формирует запрос на вставку нового элемента таблицы и строка 08 выполняет его.
       Строка 08 создает новый узел в отображении дерева TreeView, но только для того чтобы установить свойство HasChildren в True, так как идентификатор узла мы вставить еще не можем — он формируется на стороне сервера. И в строке 10 сворачиваем выбранный узел, для того чтобы при его раскрытии сформировать получить всех детей из таблицы People. Но раскрывать его будет пользователь – если нужно.
       Для того чтобы вставить узел на текущий уровень можно пойти на определенную хитрость — нужно просто найти родителя текущего узла и вставить узел как ребенка найденного родителя.

procedure TForm1.pmnAddNodeCurrentClick(Sender: TObject);  {Example01}
Var
  ANode, NewNode : TTreeNode;
  inStr,inCod : String;
  iParnt : Integer;
begin
01  inCod := InputBox('Введите Ф.И.О.','','Ф.И.О.');
02  inStr := InputBox('Введите дату рождения','','мм/дд/гггг');
03  ANode := tvPeople.Selected;
04  If (ANode.Parent<>nil) then iParnt := ANode.Parent.ImageIndex
05                         else iParnt := 0;
06  qrExeProc.Close;   qrExeProc.SQL.Clear;
07  qrExeProc.SQL.Add('Insert into People(Parent,PSurName,PBrDate)');
08  qrExeProc.SQL.Add(' Values('+IntToStr(iParnt)+','
09  qrExeProc.SQL.Add(''''+inCod+''','''+inStr+''')');
10  qrExeProc.ExecSQL;   qrExeProc.Close;
11  NewNode := tvPeople.Items.AddChild(ANode,inCod+' - '+inStr);
12  If iParnt=0 then PeopleDatasetOpen()
13              else ANode.Parent.Collapse(True);
end;

       Отличия заключены в строках 04-05, где определяется является ли текущий узел корнем дерева, и в строках 12-13, где в зависимости от предыдущей проверки сворачивается родительский узел или перестраивается все дерево.

Перетаскивание узла по дереву в другой узел

       Не устану повторять, что перетаскивание узла происходит не на компоненте TreeView, а в таблице БД. И сразу встает проблема — как определить куда перетаскивать узел – в другой узел или внутри узла. С точки зрения интерфейса это должны быть разные события. В добавок нужно учитывать использование клавиш со стрелками для перемещения по дереву.
       Для перетаскивания узла по дереву необходимо сразу определить исходный (SRCNode) и целевой (TRGNode), после чего можно построить запрос на изменение положения элемента в таблице.
       Для этой операции задействуются следующие события — onStartDrag, onDragDrop, onDragOver, onEndDrag.

procedure TForm1.tvPeopleStartDrag(Sender: TObject;
                      var DragObject: TDragObject); {Example01}
begin
  inherited;
  SRCNode := tvPeople.Selected;
end;

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

procedure TForm1.tvPeopleDragDrop(Sender, Source: TObject; X, Y: Integer); 
begin
  inherited;
  TRGNode := tvPeople.GetNodeAt(X,Y);

end;

       Задача процедуры DragDrop запомнить узел (TRGNode), в который нужно перетаскивать выбранный (SRCNode) узел. Его координаты (X,Y) соответствуют позиции курсора на экране, когда отпускается левая кнопка мыши.

procedure TForm1.tvPeopleDragOver(Sender, Source: TObject; X, Y: Integer;
                                  State: TDragState; var Accept: Boolean);
begin
  inherited;
  If (TRGNode=SRCNode) then Accept:=False else Accept := True;
end;

       Задача процедуры DragOver выдать разрешение на выполнение перетаскивания в виде переменной Accept. Естественно, что один из признаков запрета перетаскивания это равенство узлов исходного и целевого. Нельзя перетаскивать узел сам в себя. Опять нужно помнить, что для отображения дерева TreeView это не страшно, но реально все узлы перетаскиваются в БД и возникнет ситуация, когда этот узел не будет отображаться. В зависимости от конкретной ситуации в этой процедуре можно определять и другие условия разрешения/запрещения перетаскивания.

procedure TForm1.tvPeopleEndDrag(Sender, Target: TObject; X, Y: Integer);
begin
  inherited;
  iTRGIndex := TRGNode.ImageIndex;   iSRCIndex := SRCNode.ImageIndex;
  If iTRGIndex<>iSRCIndex then begin
     qrExeProc.Close;
     qrExeProc.SQL.Clear;
     qrExeProc.SQL.Add('Update People set Parent='+IntToStr(iTRGIndex));
     qrExeProc.SQL.Add(' where PID='+IntToStr(iSRCIndex));
     qrExeProc.ExecSQL;
     qrExeProc.Close;
     TRGNode.Parent.Collapse(True);
     SRCNode.Parent.Collapse(True);
  end;
end;

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

Изменение порядка узлов в дереве внутри одного узла

       Главная проблема — это построение интерфейса. Если при перетаскивании узла в другой узел можно просто захватить мышкой, то при перетаскивании внутри узла это может привести к тому, что исходный узел станет дочерним узлом целевого. Вторая проблема, что нужно поменять числа в поле Order, которые могут быть сформированы различным образом.
       Наиболее разумным решением будет всплывающее меню, привязанное к правой кнопке мыши, в котором возможны четыре пункта:
       — Переместить узел наверх
       — Переместить узел на один уровень вверх
       — Переместить узел на один уровень вниз
       — Переместить узел вниз
       Первое, с чего нужно начинать при выполнении любой из этих операций — определить, что все узлы имеют разные значения в поле Order. Если этого нет, то необходимо исправить это с помощью следующего кода:

procedure TForm1.TreeOrderCheck();
Var
  x,cnt : Integer;
begin
qrExeProc.Close;   qrExeProc.SQL.Clear;
qrExeProc.SQL.Add('Select Count(POrder) as Cnt from People');
qrExeProc.SQL.Add(' where Parent='+IntToStr(tvPeople.Selected.ImageIndex));
qrExeProc.SQL.Add(' group by POrder');
qrExeProc.SQL.Add(' order by 1 desc');
qrExeProc.Open;   qrExeProc.First;
cnt := qrExeProc.Fieldvalues['Cnt'];
If cnt>1 then Begin
   qrNodePeople.First;  x := 1;
   While not qrNodePeople.Eof do begin
        qrNodePeople.Edit;
        qrNodePeople.FieldByName('Porder').AsInteger := x;
        qrNodePeople.Post;
        qrNodePeople.Next;  x := x + 1;
      end; // While not qrNodePeople.Eof do
   qrNodePeople.ApplyUpdates;
   end;
end;

       В этой процедуре сначала строится запрос, который группирует детей узла по полю POrder, считает количество записей с одинаковым значением поля POrder и сортирует результат в порядке убывания. Таким образом, если первая запись результата имеет значение больше 1, то поле POrder среди потомков узла имеет повторяющиеся значения.
       Если это так, то в наборе данных, который формируется в процедуре TForm1.tvPeopleChange (подробнее см. "Отображение содержимого узла в форме".) в qrNodePeople, записи нумеруются в цикле от 1 до max значения.
       Однако, не проблем перенести эту операцию на сервер, написав хранимую процедуру, которая будет выполнять нумерацию без проверки ее необходимости. Вариант такой процедуры показан ниже:

create procedure TreeOrderCheck (Node Integer)
as
Declare variable Cnt Integer;
Declare variable PID Integer;
Declare variable Ord Integer;
begin
   Cnt = 0;
   for select PersonID,POrder from People where Parent=:Node order by POrder
       into :PID,:Ord
     do begin 
           Cnt = Cnt + 1;
           Update People set POrder = :Cnt where PeopleID = :PID;
        end
end!!

       Процедура принимает в качестве параметра ID узла, строит запрос, который находит всех прямых детей узла, сортирует их по существующему номеру POrder и заново нумерует их начиная с 1.
       Такая процедура эффективнее чем процедура в клиентской программе.

Переместить узел вверх

       После исправления порядка узлов, можно переместить выбранный узел в начало двумя следующими запросами:
       Первый устанавливает значение POrder выбранной записи в 0

procedure TForm1.pmnTreeNodeTopClick(Sender: TObject); {Example01}
begin
   TreeOrderCheck();
// Первый запрос устанавливает значение для текущего узла = 0
   qrExeProc.Close;   qrExeProc.SQL.Clear;
   qrExeProc.SQL.Add('Update People set POrder=0');
   qrExeProc.SQL.Add(' where PID='+IntToStr(tvPeople.Selected.ImageIndex));
   qrExeProc.ExecSQL;
// Второй смещает все записи узла, к которому принадлежит выбранная запись на 1 вниз
   qrExeProc.Close;   qrExeProc.SQL.Clear;
   qrExeProc.SQL.Add('Update People set POrder=POrder+1 where');
   qrExeProc.SQL.Add(' Parent='+IntToStr(tvPeople.Selected.Parent.ImageIndex));
   qrExeProc.ExecSQL;      qrExeProc.Close;
   tvPeople.Selected.Parent.Collapse(True);
end;

       Конечно, при этом образуется "дырка" в последовательности значений полей POrder, но это никому не мешает. После выполнения этих двух запросов необходимо свернуть родителя выбранного узла, для того чтобы при дальнейшем его раскрытии обновить отображаемую информацию.

Переместить узел на один уровень вверх

       Для этой операции, требуется немного — ID выбранной записи и ее ближайшего "родственника", а также их порядковые номера. После чего порядковые номера меняются местами, как показано в нижеприведенной процедуре:

procedure TForm1.pmnTreeNodeOnePosUpClick(Sender: TObject); {Example01}
Var
   SelID,PrevID,SelNo,PrevNo : Integer;
   PrevNode :  TTreeNode;
Begin
   TreeOrderCheck();
01 SelID := tvPeople.Selected.ImageIndex;
02 PrevNode := tvPeople.Selected.getPrevSibling;
03 PrevID   := PrevNode.ImageIndex;
// Получение текущего порядкового номера
04 qrExeProc.Close;    qrExeProc.SQL.Clear;
05 qrExeProc.SQL.Add('Select POrder from People where PID='+IntToStr(SelID));
06 qrExeProc.Open;
07 SelNo := qrExeProc.FieldValues['POrder'];
// Получение предыдущего порядкового номера
08 qrExeProc.Close;    qrExeProc.SQL.Clear;
09 qrExeProc.SQL.Add('Select POrder from People where PID='+IntToStr(PrevID));
10 qrExeProc.Open;
11 PrevNo := qrExeProc.FieldValues['POrder'];
// Установка текущего порядкового номера для предыдущей строки
12 qrExeProc.Close;   qrExeProc.SQL.Clear;
13 qrExeProc.SQL.Add('Update People Set POrder='+IntToStr(SelNo));
14 qrExeProc.SQL.Add(' where PID='+IntToStr(PrevID));
15 qrExeProc.ExecSQL;
// Установка предыдущего порядкового номера для текущей строки
16 qrExeProc.Close;   qrExeProc.SQL.Clear;
17 qrExeProc.SQL.Add('Update People Set POrder='+IntToStr(PrevNo));
18 qrExeProc.SQL.Add(' where PID='+IntToStr(SelID));
19 qrExeProc.ExecSQL;      qrExeProc.Close;
// свертывание родительского узла
20 tvPeople.Selected.Parent.Collapse(True);
End;

       Заметьте, что информация о предыдущем узле берется из отображения дерева, а не из базы данных.
       В строке 01 получаем ID текущего узла.
       В строке 02 находим предыдущий узел и в строке 03 получаем его ID.
       В строках 04-07 через запрос к БД получаем порядковый номер текущего узла.
       В строках 08-11 таким же образом получаем порядкковый номер для предыдущего узла.
       В строках 12-15 устанавливаем порядковый номер предыдущего узла для текущего узла.
       И в строках 16-19 устанавливаем порядковый номер текущего узла для предыдущего узла.
       Завершающая 20 строка сворачивает родительский узел для дальнейшего обновления информации.

 

Переместить узел на один уровень вниз

       Подобным же образом можно переместить узел вниз по дереву, только вместо Предыдущего узла используется Следующий.

procedure TForm1.pmnTreeNodeOnePosDownClick(Sender: TObject); {Example01}
Var
   SelID,NextID,SelNo,NextNo : Integer;
   NextNode :  TTreeNode;
Begin
   TreeOrderCheck();
   SelID := tvPeople.Selected.ImageIndex;
   NextNode := tvPeople.Selected.getNextSibling;
   NextID := NextNode.ImageIndex;
// Получение текущего порядкового номера
   qrExeProc.Close;    qrExeProc.SQL.Clear;
   qrExeProc.SQL.Add('Select POrder from People where PID='+IntToStr(SelID));
   qrExeProc.Open;
   SelNo := qrExeProc.FieldValues['POrder'];
// Получение следующего порядкового номера
   qrExeProc.Close;    qrExeProc.SQL.Clear;
   qrExeProc.SQL.Add('Select POrder from People where PID='+IntToStr(NextID));
   qrExeProc.Open;
   NextNo := qrExeProc.FieldValues['POrder'];
// Установка текущего порядкового номера для следующей строки
   qrExeProc.Close;   qrExeProc.SQL.Clear;
   qrExeProc.SQL.Add('Update People Set POrder='+IntToStr(SelNo));
   qrExeProc.SQL.Add(' where PID='+IntToStr(NextID));
   qrExeProc.ExecSQL;
// Установка следующего порядкового номера для текущей строки
   qrExeProc.Close;       qrExeProc.SQL.Clear;
   qrExeProc.SQL.Add('Update People Set POrder='+IntToStr(NextNo));
   qrExeProc.SQL.Add(' where PID='+IntToStr(SelID));
   qrExeProc.ExecSQL;      qrExeProc.Close;
// свертывание родительского узла
   tvPeople.Selected.Parent.Collapse(True);
End;

 

Переместить узел вниз

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

procedure TForm1.pmnTreeNodeBottomClick(Sender: TObject); {Example01}
Var
  OrdMax : Integer;
Begin
   TreeOrderCheck();
   qrExeProc.Close;   qrExeProc.SQL.Clear;
   qrExeProc.SQL.Add('Select MAX(POrder) as OMAX from People where');
   qrExeProc.SQL.Add(' Parent='+IntToStr(tvPeople.Selected.Parent.ImageIndex));
   qrExeProc.Open;
   OrdMax := qrExeProc.FieldValues['OMAX']+1;
// 
   qrExeProc.Close;      qrExeProc.SQL.Clear;
   qrExeProc.SQL.Add('Update People set POrder='+IntToStr(OrdMax));
   qrExeProc.SQL.Add(' where PID='+IntToStr(tvPeople.Selected.ImageIndex));
   qrExeProc.ExecSQL;    qrExeProc.Close;
// свертывание родительского узла
   tvPeople.Selected.Parent.Collapse(True);
End;

       Эта процедура подобна TForm1.pmnTreeNodeTopClick, но вместо установки порядкового номера в 0, здесь в первом запросе находится максимальное значение порядкового номера, которое увеличивается на единицу и вторым запросом устанавливается как порядкового номера для текущего узла.
       И после того как были произведены изменения в БД сворачивается родительский узел, для обновления отображаемой информации.

Редактирование узла

       Для редактирования узла есть два способа:
       1) Можно сформировать необходимое количество диалогов, запрашивающих новые значения полей, сформировать из них запрос к БД и выполнить его. При этом необходимо закрыть узел и открыть его снова, чтобы обновить информацию.
       Запрос на редактирование можно взять из события onEdit, но при этом возникают "ложные срабатывания" при выделении узла. Поэтому лучше сделать всплывающее меню и привязать его к правой кнопке мыши. И при том, что технически это реализовывается достаточно просто, пользователь будет чуствовать себя некомфортно — поля показываются по одному, нужно вспомнить, что было в предыдущем поле, трудно вернуться для исправления ошибки.
       2) Второй способ — отобразить всю запись на форме и редактировать в ней. См. "Отображение содержимого узла в форме". Этот способ много комфортнее для пользователя — он может вернуться к любому полю и исправить его без дополнительных диалогов. Но еще лучше отобразить детей узла в Grid, а на форме отображать строку этого Grid.
       Однако, если используется узел с объектом Data, то редактирование отдельного узла можно организовать независимо от других, как это показано в примере {Example02}. Вот что при этом получилось:


Рис.1-6 Отображение содержимого узла с объектом Data

       Для отображения содержимого узла используются обычные элементы TEdit и TLabel.
       Все отображение начинается с события onChange, которое возникает при изменении выбранного узла. При этом узел может выбираться, как мышкой, так и клавишами клавиатуры со стрелками — вверх, вниз, влево, вправо. При этом стрелки вправо/влево раскрывает/сворачивает узел. Что для этого требуется показано ниже:

procedure TForm1.tvPeopleChange(Sender: TObject; Node: TTreeNode); {Example02}
Var
   RecNode : PItemRec;
begin
  New(RecNode); RecNode := Node.Data;
  edxPStatus.Text := RecNode.PStatus;
  edxPSurName.Text := RecNode.PsurName;
  edxPSurName.Tag := RecNode.PId;
  edxPBrDate.Text := DateToStr(RecNode.PBrDate);
  edxPSalary.Text := FloatToStr(RecNode.PSalary);
end;

       При изменении выбранного узла создается указатель на запись и в него копируется указатель на запись в узле. После чего каждый элемент записи копируется в соответствующее поле типа TEdit, при необходимости производя соответствующие преобразования.
       Однако, когда узел покидается необходимо сохранить измененные данные в базу данных. Для этого используется событие onChanging:

procedure TForm1.tvPeopleChanging(Sender: TObject; Node: TTreeNode;
                                  var AllowChange: Boolean);   {Example02}
Var
   ndPID  : Integer;
   sBrDate : String;
begin
  If (edxPStatus.Modified or edxPSurName.Modified
   or edxPBrDate.Modified or edxPSalary.Modified) then begin
     ndPID := edxPSurName.Tag;
     sBrDate:=FormatDateTime('mm"/"dd"/"yyyy',StrToDateTime(edxPBrDate.Text));
     qrExeProc.Close;      qrExeProc.SQL.Clear;
     qrExeProc.SQL.Add('Update People Set');
     qrExeProc.SQL.Add('  PStatus='''+edxPStatus.Text+''',');
     qrExeProc.SQL.Add('  PSurName='''+edxPSurName.Text+''',');
     qrExeProc.SQL.Add('  PBrDate='''+sBrDate+''',');
     qrExeProc.SQL.Add('  PSalary='''+edxPSalary.Text+'''');
     qrExeProc.SQL.Add('  where PID='+IntToStr(ndPID));
     qrExeProc.ExecSQL;
  end;
end;

       Обработка этого события с одной стороны проще — если какое-либо поле было модифицировано, то строится запрос на обновление соответствующей записи в таблице и выполняется.
       С другой стороны вся ответственность на проверку правильности введенных данных, преобразование форматов и др. ложится на программиста.
       Заметьте, что идентификатор записи сохраняется в свойстве edxPSurName.Tag. Это свободное свойство, типа Integer, которое программист может использовоать по своему усмотрению.

Удаление узла

       Для удаления узла также есть два способа:
       1) Можно взять событие onDelete и в нем сформировать запрос к БД, выполнить его, закрыть узел и открыть его снова для обновления информации. Несмотря на то, что это достаточно просто — пользователь испытывает дискомфорт из-за постоянной перестройки узла.
       Запрос на удаление можно также взять из всплывающего меню, привязанного к правой кнопке мыши, что предподчтительней.

procedure TForm1.pmnDelNodeCurrentClick(Sender: TObject); {Example01}
Var
  DNode : TTreeNode;
  DelID,SelCnt : Integer;
begin
   DNode := tvPeople.Selected.Parent;
   DelID := tvPeople.Selected.ImageIndex;
// Получение количества детей узла
   qrExeProc.Close;    qrExeProc.SQL.Clear;
   qrExeProc.SQL.Add('Select PCount from People where PID='+IntToStr(DelID));
   qrExeProc.Open;
   SelCnt := qrExeProc.FieldValues['PCount'];
   If SelCnt=0 then Begin
// Удаление, если узел не имеет детей
        qrExeProc.Close;      qrExeProc.SQL.Clear;
        qrExeProc.SQL.Add('Delete from People where PID='+IntToStr(DelID));
        qrExeProc.ExecSQL;    qrExeProc.Close;
        DNode.Collapse(True);
      end Else Begin
      if MessageDlg('Этот узел имеет детей.'+

              +'Вы уверены что хотите его удалить?',
              mtConfirmation, [mbYes, mbNo], 0) = mrYes then begin
// Удаление, если узел имеет детей
        qrExeProc.Close;      qrExeProc.SQL.Clear;
        qrExeProc.SQL.Add('Delete from People where PID='+IntToStr(DelID));
        qrExeProc.ExecSQL;    qrExeProc.Close;
        DNode.Collapse(True);
        end;
      end;
end;

       Для того, чтобы при удалении узла удалялись и его дети необходимо в описании таблицы указать "on delete cascade" для соответствующей связи. Однако, это источник ошибок — при удалении узла, который предполагался пустым, может быть удалено половина дерева. Как минимум, нужно задавать вопрос пользователю, как это сделано в предложенном примере. Как максимум — запретить каскадное удаление. Все зависит от конкретного приложения и его задач.
       2) Второй способ — отобразить всех детей на Gridе и удалять в нем. См. "Отображение содержимого узла в форме". Этот способ много комфортнее для пользователя — он может видеть какие записи еще остались в Gridе и это ничем не отличается от редактирования.

Работа с клавишами Cntrl+C, Cntrl+X, Cntrl+V

       При больших деревьях перетаскивание узла в другой конец дерева становиться головной болью. Много проще скопировать узел в буфер обмена и вставить в нужное место используя стандартные комбинации клавиш — Cntrl+C, Cntrl+X, Cntrl+V. Для этого можно использовать событие onKeyPress — но тогда придется самостоятельно отрабатывать нажатие клавиши Control. Несколько удобнее использовать событие onKeyUp, которое возникает при отпускании клавиши. Это событие содержит в себе состояние клавиш Control и Shift.
       Обработка клавиш Ctrl+C, Ctrl+X не представляет проблем — при их нажатии/отпускании просто запоминается выбранный узел. НО! Если при использовании клавиши Ctrl+X проблем не возникает — узел переноситься в другое место дерева, то при использовании клавиши Ctrl+C создается новый узел, который будет нарушать уникальность по полю с Primary key. Чтобы этого не произошло, одно из полей, которое участвует в Primary key — PSurName — изменяется путем добавления некоторой информации:

procedure TForm1.tvPeopleKeyUp(Sender: TObject; var Key: Word;
                               Shift: TShiftState);
Var
   ndText,ndPrnt : String;
begin
If ssCtrl in Shift then begin
   If (Char(Key)='X') then begin
      SRCNode := tvPeople.Selected;
      CtrlX := True;
   end;
   If (Char(Key)='C') then begin
      SRCNode := tvPeople.Selected;
      CtrlX := False;
   end;

       Запомнили узел и установили признак Ctrl+X

   If (Char(Key)='V') then begin
      TRGNode := tvPeople.Selected;
      If (TRGNode<>SRCNode) then begin
         If CtrlX then begin
            trIBSQL.Active := True;
            IBSQL.Close;
            IBSQL.SQL.Clear;
            IBSQL.SQL.Add('Update People set Parent='
                         +IntToStr(TRGNode.ImageIndex));
            IBSQL.SQL.Add(' where PID='+IntToStr(SRCNode.ImageIndex));
            IBSQL.ExecQuery;
            trIBSQL.Commit;
            TRGNode.Parent.Collapse(True);
            SRCNode.Parent.Collapse(True);

       Если используется комбинация клавиш Ctrl+X, то выполняется изменение родительского поля, так же как при перетаскивании узла.

         end else Begin
            ndText := Copy(SRCNode.Text,0,166)+' (2)';
            ndPrnt := IntToStr(TRGNode.ImageIndex);
            trIBSQL.Active := True;
            IBSQL.Close;
            IBSQL.SQL.Clear;
            IBSQL.SQL.Add('Insert into People(Parent,PSurName,PBrDate)');
            IBSQL.SQL.Add(' values('+ndPrnt+','''+ndText+''',''01/01/1900'')');
            IBSQL.ExecQuery;
            trIBSQL.Commit;
            TRGNode.Parent.Collapse(True);
            SRCNode.Parent.Collapse(True);

       Если используется комбинация клавиш Ctrl+C, то нужно создать новую запись. Так как текст на дереве состоит из двух полей, то для простоты примера в качестве фамилии берется весь текст из ветки дерева, из которого копируется первые 166 символов с тем, чтобы иметь возможность добавить дополнительную информацию, в данном случае ' (2)'. В качестве даты рождения берется произвольная дата, с тем чтобы впоследствии отредактировать ее на форме. Также впоследствии на форме редактируются остальные поля.

         end;
      end;
      CtrlX := True;
   end;
end;
end;

       Конечно, можно немножко поколдовать и сформировать достаточно правильную запись сразу, но для демонстрации принципа этого не нужно. В любом случае нужно помнить, что при использовании последовательности Ctrl+C ==> Ctrl+V в базе данных создается новая запись, для которой нужно решить какой идентификатор в Primary key для него будет.

© 01.08.2009, Архангельский А.Г.

<<Пред. Оглавление
Об Авторе
Все персоны
Главная страница
След.>>



Поддержите культуру
ЯндексЯндекс. ДеньгиХочу такую же кнопку

Google
 
Web azdesign.ru az-libr.ru


Дата последнего изменения:
Wednesday, 23-Oct-2013 09:02:58 UTC