Как сделать так, чтобы в combobox`е было дерево с переключателями (CheckBox)?Действующие лица: TcxPopupEdit и TcxDBTreeList.
В cxDBTreeList требуется включить отображение переключателей (<TcxTreeList>.OptionsView.CheckGroups). К сожалению, переключатели сами по себе не отобразятся, поэтому потребуется пройтись в цикле по всем узлам и принудительно показать CheckBox`ы (CheckGroupType или ncgRadioGroup), а также указать их состояния (cbsChecked, cbsUnChecked или cbsGrayed).
На форме панель или cxTabControl, на которой лежит дерево cxDBTreeList.
Также на cxTabControl лежит 4 кнопки: развернуть всё, свернуть всё, ОК и Отмена.
В настройках TcxPopupEdit выбираем в свойстве PopupControl наш заранее подготовленный cxTabControl с деревом и кнопками.
Дерево (cxDBTreeList) подключаем к НД (Набору Данных, DataSet`у). Создаём нужные колонки. Настраиваем дерево.
Панель или cxTabControl во время исполнения программы будет автоматически скрыто, невидимо, поэтому дополнительных телодвижений выполнять не нужно.
В базе, в каком-то спец. поле хранится список ID Через запятую. Тех узлов, которые отметил пользователь.
Как отобразить CheckGroups и указать состояние переключателей узлов дерева?
Подготавливаем дерево и отображаем чекбоксы:
Procedure PrepeareTree(Tree: TcxDBTreeList; ARootValue: Variant; const ASmartLoad: boolean; bChecks: boolean = true);
Var
vNode: TcxTreeListNode;
begin
if tree = nil then
begin
MessageBox(constMsgErrorImportTree1, constError, MB_ICONSTOP or MB_OK);//показываем ошибку
exit;
end;
if tree.DataController.DataSource = nil then
begin
MessageBox(constMsgErrorImportTree2, constError, MB_ICONSTOP or MB_OK);//показываем ошибку
exit;
end;
try
tree.DataController.BeginUpdate;//чтобы шибче работало
//если нужна загрузка таблицы типов, то НД нужно переоткрыть с нужным RootValue
if ASmartLoad then// см. справку по ASmartLoad - это для загрузки, начиная с нужного нам узла, чтобы не грузить всё дерево
begin
if tree.DataController.DataSet.Active then
tree.DataController.DataSet.Close;
tree.RootValue := ARootValue;//ID начальной записи из таблицы, будем грузить дочерние записи толького этого узла
tree.OptionsData.SmartLoad := ASmartLoad;//включить загрузку только указанного узла в RootValue
end;//if ASmartLoad then
if not tree.DataController.DataSet.Active then
tree.DataController.DataSet.open;
vNode := tree.Root;//получаем корневой узел
//показываем чекбоксы или скрываем их
if bChecks then
while vNode <> nil do
begin
vNode.CheckGroupType := ncgCheckGroup;
vNode.CheckState := cbsUnChecked;
vNode := vNode.GetNext;//след. узел
end;//while
finally
tree.FullCollapse;//сворачиваем все узлы
tree.DataController.EndUpdate;//
end;
end;
Теперь требуется пробежаться по всем узлам и отметить флажками нужные узлы.
Procedure SetCheckedNodes(Tree: TcxDBTreeList; const sRegionsIdList: string; iKeyCol: Integer);
Var
sl: TStringList;
i: integer;
begin
// в списоке передаём ID узлов через запятую, нужно отметить флажками нужные узлы
sl := TStringList.Create;
try
if not Tree.DataController.DataSource.DataSet.Active then Tree.DataController.DataSource.DataSet.Open;
Tree.BeginUpdate;//чтобы работало шибче
if sRegionsIdList = '' then Exit;// не удалось получить корневой узел - выходим их процедуры
sl.Text := StringReplace(sRegionsIdList, ',', sLineBreak, [rfReplaceAll]);// заменяем список через зяпятую
for I := 0 to tree.AbsoluteCount - 1 do
if not VarIsNull(Tree.AbsoluteItems[i].Values[iKeyCol]) then
if IdInStringList(sl, Tree.AbsoluteItems[i].Values[iKeyCol]) then//если текущий узел в списке отмеченных ID
Tree.AbsoluteItems[i].CheckState := cbsChecked;//отмечаем узел флажком
finally
Tree.EndUpdate;// отпускаем
FreeAndNil(sl);
end;
end;
// проверка, существует ли ID узла в списке
function IdInStringList(const StringList: TStringList; const ID: string): boolean;
var
i: integer;
begin
result := false;
i := 0;
repeat
if id = StringList[i] then
begin
result := true;
Break;
end;
inc(i);
until (StringList.Count <= i);
end;
На панели, рядом с деревом лежит кнопка Отмена. При нажатии закрываем дерево
procedure ClosePopupEdit(ParentControl: TWinControl);
Var
PopupWnd: TcxPopupEditPopupWindow;
begin
PopupWnd := TcxPopupEditPopupWindow(ParentControl);
PopupWnd.ClosePopup();
//если дерево привязано к гриду, то нужно выйти из режима редактирования
<cxVerticalGrid1>.HideEdit();
end;
При нажатии на кнопка "Развернуть всё" и "Свернуть всё":
<Tree>.FullExpand и <Tree>.FullCollapse соответственно.
Теперь следует, при нажатии на кнопку "ОК", сохранить отмеченные узлы в некий список через запятую, показать, что отметил пользователь, и сохранить список в базу:
type
type
TRecCheckedNodes = record//здесь будет ID и название узла
id_list, name_list: string;
end;
...
...
...
procedure btnSelect();
var
RecCheckedNodes: TRecCheckedNodes;
begin
with dbTreeRegion do
begin
if TcxDBTreeListNode(FocusedNode) = nil then exit;//если пользователь всё же ничего не отметил
if IsCheckedNodes(dbTreeRegion) then//если есть хоть один отмеченный узел
begin
RecCheckedNodes := GetCheckedNodes(dbTreeRegion, false);//что отметил пользователь, получаем имя узла и его ID
sRegionsIdList := RecCheckedNodes.id_list;//список отмеченных ID через запятую для записи в базу
//colRegion - строка таблицы, в ней пользователь видит, что отмечено
colRegion.Properties.Value := RecCheckedNodes.name_list;//список отмеченных названий показываем пользователю
colRegion.Properties.Hint := RecCheckedNodes.name_list;
end;
end;//with
//tcRegions - TcxTabControl, на котором лежит дерево с кнопками
ClosePopupEdit(tcRegions.Parent, cxVerticalGrid1);//скрываем дерево и прячем редактор
end;
Function IsCheckedNodes(Tree: TcxDBTreeList): Boolean;
Var
vNode: TcxTreeListNode;
begin
//проверяем, есть ли хоть один отмеченный флажок
Result := false;
vNode := tree.Root;
while vNode <> nil do
begin
if vNode.CheckState = cbsChecked then
begin
//есть, даже нет смысла проверять, выходим
result := true;
exit;
end//if
else
vNode := vNode.GetNext;
end;//While
end;
//получаем список ID и названий узлов
Function GetCheckedNodes(Tree: TcxDBTreeList; iKeyCol, iNameCol: integer; bAddBraces: boolean = true): TRecCheckedNodes;
var
i: Integer;
begin
result.id_list := '';
result.name_list := '';
for I := 0 to Tree.AbsoluteCount - 1 do
begin
if Tree.AbsoluteItems[i].CheckState = cbsChecked then
begin
//записываем в строку через запятую названия и ID узлов, названия отобразим пользователю, и список ID запишем в базу
if result.id_list = '' then
result.id_list := VarToStr(Tree.AbsoluteItems[i].Values[iKeyCol])
else
result.id_list := result.id_list + ',' + VarToStr(Tree.AbsoluteItems[i].Values[iKeyCol]);
if result.name_list = '' then
result.name_list := VarToStr(Tree.AbsoluteItems[i].Values[iNameCol])
else
result.name_list := result.name_list + ',' + VarToStr(Tree.AbsoluteItems[i].Values[iNameCol]);
end;//if
end;// for
//если требуется обрамить список скобками - (1,129,328,1577,21,7)
if (result.id_list <> '') and bAddBraces then
result.id_list := '(' + result.id_list + ')';// возращаем все значения через запятую, в скобках, чтобы можно было легко вставить с SQL запрос, в условие where pole1 in(.....)
end;