Продолжаем серию DBTreeView своими руками. В данной статье добавим Checkboxes к элементам дерева при помощи свойства StateIndex. Добавим CheckBoxes и свяжем их статус с базой данных. Скажу сразу, что Checkboxes здесь это картинки 16*16 и можно организовать любые другие картинки, например radioboxes, но там совершенно другая логика. В данной статье посмотрим только на Checkboxes и их связь с базой данных.
Начали!
Добавим ImageList на форму и добавим 3 картинки 16*16 таким образом
У TreeView1 выберем в свойстве StateImages ImageList1
Для того, чтобы выводить чекбоксы, воспользуемся свойством TTreeNode.StateIndex. Вот, что написано в Help по этому поводу
1 2 3 4 5 |
Indicates which image from the StateImages list to display for the node. Use StateIndex to display an additional image for the node that reflects state information. If StateIndex is -1 then no state image is drawn. Valid index values are 1..15 only. Values of 0 or -1 mean no state icon. |
StateIndex реагирует на нажатие OnCLick, а ImageIndex не реагирует, насколько я понял из экспериментов, поэтому для галочек, нам как раз подойдет StateIndex.
Отсутствие иконки это индексы 0 и -1!!! Это важный факт! Поэтому, в качестве рабочих подключим иконки 1 и 2.
Само присвоение иконок можно организовать следующим образом, немного обновив процедуру UpdateTree
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
procedure TDMDB.UpdateTree(Sender: Tobject;TreeView:TTreeView); var Obj:^Integer; Node,ChildNode:TTreeNode; begin TreeView.Items.BeginUpdate; ClearTree(TreeView); with qSelect do begin Open(); while eof<>true do begin New(Obj); Obj^:=FieldByName('id').AsInteger; Node:=Self.FindParentNode(FieldByName('idParent').AsInteger,TreeView); ChildNode:=TreeView.Items.AddChildObject(Node,FieldByName('name').AsString,Obj); if TreeView.StateImages<>nil then ChildNode.StateIndex:=1; //<<<<<ПРИСВОЕНИЕ ИКОНОК Next; end; Close; end; TreeView.Items.EndUpdate; end; |
По хорошему, надо запоминать статус узла в БД, и при чтении из БД отображать его. Но в данном случае, не будем усложнять и сделаем простой вывод картинок. Работу с базой данных отложим на конец статьи.
Код обработки чекбоксов при нажатии на узел
Переменная NotAllowed для победы над сложностью, которая состоит в том, что при разворачивании узла происходит одновременное нажатие на узел, и статус картинки чекбокса меняется. Попробуйте без переменной NotAllowed, которую я ввёл – разверните и сверните любой узел с детьми и увидите о чем я говорил.
1 2 3 |
///globalVars... var NotAllowed |
Запрещаем обработку TreeViewClick при разворачивании
1 2 3 4 5 6 7 |
procedure TMainForm.TreeView1Expanding(Sender: TObject; Node: TTreeNode; var AllowExpansion: Boolean); begin NotAllowed:=true; end; |
Запрещаем обработку TreeViewClick при сворачивании
1 2 3 4 5 6 7 |
procedure TMainForm.TreeView1Expanding(Sender: TObject; Node: TTreeNode; var AllowExpansion: Boolean); begin NotAllowed:=true; end; |
Разрешаем при отпускании мыши
1 2 3 4 5 6 7 |
procedure TMainForm.TreeView1MouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin NotAllowed:=false; end; |
Собственно обработка
1 2 3 4 5 6 7 8 |
procedure TMainForm.TreeView1Click(Sender: TObject); begin if NotAllowed then exit else DMDB.CheckBoxSelection(TreeView1.Selected,TreeView1); end; |
Во вспомогательном модуле DMDB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
procedure TDMDB.CheckBoxSelection(CurrentNode:TTreeNode; TreeView: TTreeView); var VeryLastChild:TTreeNode; begin if not Assigned(CurrentNode) then exit; // Поиск самого самого самого последнего узла, начиная с текущего VeryLastChild:=CurrentNode; while (VeryLastChild.HasChildren) do begin VeryLastChild:=VeryLastChild.GetLastChild; //<<<Записываем результат end; //Если узел не содержит детей if not CurrentNode.HasChildren then begin if CurrentNode.StateIndex=2 then CurrentNode.StateIndex:=1 else CurrentNode.StateIndex:=2 ; end; //Обходы поддеревьев - деревьев, содержащих детей //Если узел содержит детей и он Checked if (CurrentNode.HasChildren) and (CurrentNode.StateIndex=2) then begin while True do begin CurrentNode.StateIndex:=1; if (CurrentNode<>VeryLastChild) then CurrentNode:=CurrentNode.GetNext else break; end; // end; //Если узел содержит детей и он NOT Checked if CurrentNode.HasChildren and (CurrentNode.StateIndex=1) then begin while True do begin CurrentNode.StateIndex:=2; if (CurrentNode<>VeryLastChild) then CurrentNode:=CurrentNode.GetNext else break; end; // end; end; |
Как связать чекбоксы с базой данных?
Действительно, ведь мы пишем компонент, DBTreeView, соответственно и связь с базой данных также должна быть…
Для начала добавим в базе одно поле IsChecked
В программе MySQL WorkBench
Сначала сделаем модель через Reverse engeneering
Сделаем изменения в модели
Синхронизируем её через DataBase Synchronize model
Теперь перепишем процедуру DBSelection следующим образом. Эта процедура будет запоминать выбор и записывать его в базу данных
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
procedure TDMDB.CheckBoxSelection(CurrentNode:TTreeNode; TreeView: TTreeView); var VeryLastChild:TTreeNode; begin if not Assigned(CurrentNode) then exit; // Поиск самого самого самого последнего узла, начиная с текущего VeryLastChild:=CurrentNode; while (VeryLastChild.HasChildren) do begin VeryLastChild:=VeryLastChild.GetLastChild; //<<<Записываем результат end; //Обход с CurerentNode до VeryLastChild, включая все вложенные узлы //Если узел не содержит детей if not CurrentNode.HasChildren then begin if CurrentNode.StateIndex=2 then begin CurrentNode.StateIndex:=1; //Запись в базу данных qCheckedUpdate.Params.ParamValues['IsChecked']:=0; qCheckedUpdate.Params.ParamValues['id']:=Integer(CurrentNode.Data^); qCheckedUpdate.ExecSQL; end else begin CurrentNode.StateIndex:=2 ; //Запись в базу данных qCheckedUpdate.Params.ParamValues['IsChecked']:=1; qCheckedUpdate.Params.ParamValues['id']:=Integer(CurrentNode.Data^); qCheckedUpdate.ExecSQL; end; end; //Если узел содержит детей и он Checked if (CurrentNode.HasChildren) and (CurrentNode.StateIndex=2) then begin while True do begin CurrentNode.StateIndex:=1; //Запись в базу qCheckedUpdate.Params.ParamValues['IsChecked']:=0; qCheckedUpdate.Params.ParamValues['id']:=Integer(CurrentNode.Data^); qCheckedUpdate.ExecSQL; if (CurrentNode<>VeryLastChild) then CurrentNode:=CurrentNode.GetNext else break; end; // end; //Если узел содержит детей и он NOT Checked if CurrentNode.HasChildren and (CurrentNode.StateIndex=1) then begin while True do begin CurrentNode.StateIndex:=2; //Запись в базу qCheckedUpdate.Params.ParamValues['IsChecked']:=1; qCheckedUpdate.Params.ParamValues['id']:=Integer(CurrentNode.Data^); qCheckedUpdate.ExecSQL; if (CurrentNode<>VeryLastChild) then CurrentNode:=CurrentNode.GetNext else break; end; // end; end; |
Чтение из базы данных
Сам код чтения из базы следующий
1 2 3 4 5 6 7 |
// Расставляем галочки if TreeView.StateImages<>nil then begin if FieldByName('isChecked').AsBoolean=false then ChildNode.StateIndex:=1 else ChildNode.StateIndex:=2; end; |
его мы вставим в код UpdateTree процедуры, который написали ранее
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
procedure TDMDB.UpdateTree(Sender: Tobject;TreeView:TTreeView); var Obj:^Integer; Node,ChildNode:TTreeNode; begin TreeView.Items.BeginUpdate; ClearTree(TreeView); with qSelect do begin Open(); while eof<>true do begin New(Obj); Obj^:=FieldByName('id').AsInteger; Node:=Self.FindParentNode(FieldByName('idParent').AsInteger,TreeView); ChildNode:=TreeView.Items.AddChildObject(Node,FieldByName('name').AsString,Obj); // Расставляем галочки if TreeView.StateImages<>nil then begin if FieldByName('isChecked').AsBoolean=false then ChildNode.StateIndex:=1 else ChildNode.StateIndex:=2; end; Next; end; Close; end; TreeView.Items.EndUpdate; end; |
Запись в базу данных
Для записи в базу данных у нас в модуле DMDB было оформлено несколько процедур
1 2 3 4 5 |
//Вставка записи в БД и дерево function InsertNodeInDB(const idParent,index:integer; const name:string):integer; procedure InsertNodeInTree(Sender:TObject; TreeView:TTreeView); procedure InsertManyNodesInTree(Sender:TObject; TreeView:TTreeView); |
В каждой из них нужно добавить изменения
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
function TDMDB.InsertNodeInDB(const idParent, index: integer; const name: string): integer; begin //Вставка записи with qInsert.Params do begin ParamValues['idParent']:=idParent; ParamValues['Index']:=Index; ParamValues['Name']:=Trim(Name); ParamValues['isChecked']:=false; //<<<<< По умолчанию галочка не отмечена end; qInsert.ExecSQL; //Получение id последней записи with qGetKey do begin Open; Result:=qGetKey.FieldByName('NewKey').AsInteger; Close; end; end; |
Вставка одной записи в дерево
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
procedure TDMDB.InsertNodeInTree(Sender: TObject; TreeView: TTreeView); var name:string; Node:TTreeNode; idParent,ItemIndex:integer; // Ключ и индекс узла Obj:^Integer; begin if InputQuery('Новая запись','',name)=true then begin if Assigned(TreeView.Selected) then begin //Назначаем родителя новому узлу idParent:=Integer(TreeView.Selected.Data^); ItemIndex:=TreeView.Items.Count+1; end else //Если никакой узел не выбран, владельца нет begin idParent:=-1; ItemIndex:=0; end; //Собственно вставка в дерево New(Obj); // Вставка в БД Obj^:=InsertNodeInDB(idParent,itemIndex,name); // Вставка в само дерево Node:=TreeView.Items.AddChildObject( TreeView.Selected,name,obj ); Node.StateIndex:=1; // <<<<<<<<<<<<<<<<<<<<<<<<<<<ЗДЕСЬ!!! // Оставляем фокус на том узле, на который нажали if TreeView.Selected<>nil then begin TreeView.Selected.Expanded:=true; TreeView.SetFocus; TreeView.Selected.Selected:=true; end; // Node .Selected:=true; //Dispose(Obj) <<Высвобождать будем при закрытии либо //Удалении, иначе выскакивает ошибка Invalid Pointer Operation end; // InsertNodeInTree end; |
Вставка нескольких узлов сразу
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
procedure TDMDB.InsertManyNodesInTree(Sender: TObject; TreeView: TTreeView); var name:string; Node:TTreeNode; idParent,ItemIndex:integer; i: Integer; // Ключ и индекс узла Obj:^Integer; s:string; begin if InputQuery('Сколько узлов вставить?','',s)=true then begin if Assigned(TreeView.Selected) then begin //Назначаем родителя новому узлу idParent:=Integer(TreeView.Selected.Data^); ItemIndex:=TreeView.Items.Count+1; end else //Если никакой узел не выбран, владельца нет begin idParent:=-1; ItemIndex:=0; end; TreeView.Items.BeginUpdate; for i := 0 to s.ToInteger-1 do begin //Собственно вставка в дерево name:='N'+i.ToString; New(Obj); // Вставка в БД Obj^:=InsertNodeInDB(idParent,itemIndex,name); // Вставка в само дерево Node:=TreeView.Items.AddChildObject( TreeView.Selected,name,obj ); Node.StateIndex:=1; /// <<<<<<<< ЗДЕСЬ!!! end; TreeView.Items.EndUpdate; // Оставляем фокус на том узле, на который нажали if TreeView.Selected<>nil then begin TreeView.Selected.Expanded:=true; TreeView.SetFocus; TreeView.Selected.Selected:=true; end; end; end; |
Также добавить в DragDrop
Если без этого кода, то галочка не проставляется
1 2 3 4 5 |
... //Ставим CheckBox рядом с перемещенным узлом DMDB.CheckBoxSelection(DragNode,TreeView1); if DragNode.StateIndex=2 then DragNode.StateIndex:=1 else DragNode.StateIndex:=2; ... |
Также добавить в NodeUP и NodeDOWN
Если без этого кода, то галочка не проставляется
1 2 3 4 5 6 7 8 9 10 11 12 |
... //Обновление картинки StateIndex после перемещения CheckBoxSelection(CurrentNode,TreeView); if CurrentNode.StateIndex=2 then CurrentNode.StateIndex:=1 else CurrentNode.StateIndex:=2; CheckBoxSelection(BeforeNode,TreeView); if BeforeNode.StateIndex=2 then BeforeNode.StateIndex:=1 else BeforeNode.StateIndex:=2; // ... |
Тестируем работу приложения