PDA

Ver la Versión Completa : Llenar TreeView desde una tabla


mRoman
20-05-2020, 04:43:43
Hola amigos buenas noches.

Iniciando con un proyecto. Trabajo Delphi6/Firebird 2.0/Win10

Explico:

Quiero llenar un TreeView con las opciones del menu principal y en cada Nodo agregar un checkbox para que el usuario pueda habilitar o deshabilitar dicha opción. Esta selección deberá ser guardada en una tabla etiquetando esta configuración con un nombre para después ser asignado a un USUARIO del sistema.

Hasta ahorita he consultado un código de Neftali (https://neftali.clubdelphi.com/convertir-un-menu-en-un-treeview/) que hace justamente esto, agregar un checkbox a cada nodo, pero no se como almacenarlo en una tabla y luego ser recuperado para volver a llenar el TreeView con esta configuración de opciones del menu y poder modificarlo si asi lo desea el usuarios-administrador.

Vi este video (https://www.youtube.com/watch?v=dyO9uNZ1ASk), el cual el código es de visual basic, pero la verdad no le entiendo bien, pero de ahi tomé la estructuras de las tablas tratando de hacer lo que quiero pero no doy bien...me es complicado el manejo de TreeView, almacenarlo para despues recuperarlo de la tabla, montarlo en el treeview y estando ahí, modificarlo si es necesario...(habilitar/deshabilitar).
(Me gustaría mas tomar las opciones de una tabla que desde el mismo TMainMenu)

Talvez me este complicando de mas, pero me gustaría poder hacerlo con treeview, q el usuario seleccione las opciones a habilitar y después grabarlo para asignarlo posteriormente a un usuario. Esta forma de quererlo hacer es porque me parece una manera muy fácil y gráfica, para el usuario final, para que a el le sea mas fácil y de mejor compresión, la habilitación o deshabilitación de opciones.

También he consultado los diferentes hilos de este club asi como otros, y he leido que lo han desarrollado de diferentes formas, desde NIVELES del 1 al 5 usando el TAG hasta configurar formularios con permisos especiales a ejecutar, dentro del mismo formulario(ALTAS, BAJAS, CAMBIOS, IMPRIMIR,etc), que me parecen bien, pero no es lo que busco.

Espero haberme explicado.

Saludos y muchas gracias por su tiempo.

p.d. Tengo esta tabla
CREATE TABLE SEG_OPCIONES_MENU (
NODO_PADRE INTEGER NOT NULL,
NODO INTEGER NOT NULL,
OPC_DESCRIPCION VARCHAR(50),
OPC_TAG INTEGER
);

Neftali [Germán.Estévez]
20-05-2020, 10:47:02
...pero no se como almacenarlo en una tabla y luego ser recuperado para volver a llenar el TreeView con esta configuración de opciones del menu y poder modificarlo si asi lo desea el usuarios-administrador.

Al final sólo debes recorrer los nodos del TreeView y para cada nodo crear un registro en la tabla que has adjuntodo.
¿Cual es el problema concretamente?
¿El recorrido del TreeView, guardarlo en la tabla?

Para recuperar lo contrario, realizar un recorrido por toda la tabla e ir creando los nodos en el TreeView.
Lo mismo de antes. ¿Cual es concretamente el problema? ¿Hacer el recorrido, crear los nodos en runtime?

mRoman
20-05-2020, 16:16:01
;537312']Al final sólo debes recorrer los nodos del TreeView y para cada nodo crear un registro en la tabla que has adjuntodo.
¿Cual es el problema concretamente?
¿El recorrido del TreeView, guardarlo en la tabla?

Para recuperar lo contrario, realizar un recorrido por toda la tabla e ir creando los nodos en el TreeView.
Lo mismo de antes. ¿Cual es concretamente el problema? ¿Hacer el recorrido, crear los nodos en runtime?

Hola Neftalí gracias por contestar.

Si, esto es el problema:
¿Hacer el recorrido, crear los nodos en runtime? /*DESDE EL DATASET*/

Y también esto otro: :)
¿El recorrido del TreeView, guardarlo en la tabla?


Mi problema es este:
me es complicado el manejo de TreeView, almacenarlo para después recuperarlo de la tabla, montarlo en el treeview y estando ahí, modificarlo si es necesario...(habilitar/deshabilitar). (y luego grabarlo otra vez en la tabla)

No sé como hacerlo. No sé como implementar código para hacer esto. Mostrar las opciones tomándolas de un DATASET -ligado obviamente a una tabla con todas las opciones del menu-, estando éstas cargadas al TreeView seleccionar cuales si y cuales no serán visibles para el usuario, y luego grabar esta configuración en la TABLA para luego ser asignado a cualquier usuario...como permisos de acceso al menu del sistema.

Estoy tratando con el siguiente código (q lo tome de uno de los foros consultados) de implementarlo para lo que quiero, pero en realidad apenas lo estoy analizando...

procedure TfrmSegMenuNiv.CargarTreeView;
var
i,x,n:integer;
Nod : TTreeNode;
NodSuplementario : TTreeNode;
MiPunteroInteger : ^integer;
begin
qryMenu.Close;
qryMenu.Open;
qryMenu.Last;
n:=qryMenu.RecordCount;
qryMenu.First;
for x:=0 to (n-1) do
begin
i:=qryMenu.FieldByName('nodo_padre').AsInteger;
if i=0 then
begin
Nod:=TreeView1.Items.Add (nil,qryMenu.FieldByName('opc_Descripcion').asstring);
New(MiPunteroInteger);
MiPunteroInteger^:=qryMenu.Fieldbyname('nodo').asinteger;
nod.Data:=MiPunteroInteger;
nod.Selected :=true;
end
Else
begin
n:=TreeView1.Items.Count-1;
While MiPunteroInteger(TreeView1.Items[n].Data)^ <> i do
Dec(n);
nodSuplementario:=TreeView1.Items.AddChild(TreeView1.Items[N],qryMenu.FieldByName('opc_Descripcion').asstring);
New(MiPunteroInteger);
MiPunteroInteger^:= qryMenu.Fieldbyname('nodo').asinteger;
nodSuplementario.Data:=MiPunteroInteger;
Nod.selected:=True;
Nod.Expanded:=False;
end;
qryMenu.Next;
end;
end;


Espero me puedas/puedan orientar.

MUCHAS GRACIAS !! por tu tiempo

Saludos.

Neftali [Germán.Estévez]
21-05-2020, 08:57:03
Creo que es más sencillo...
Con este código puedes recorrer el treeview y almacenar en la tabla ls items:

var
i:integer;
tv:TTreeNode;
begin
tblOpcMenu.Open; // Abrir la tabla
for i := 0 to (TreeView1.Items.Count -1) do begin // recorro el treeview
tv := TreeView1.Items[i]; // accedo al nodo
tblOpcMenu.Append;
// Si es un nodo de primer nivel (-1), si no, me apunto el padre
tblOpcMenu.FieldByName('NODO_PADRE').AsString := ifThen(Assigned(tv.Parent), tv.Parent.Index.ToString, '-1');
tblOpcMenu.FieldByName('NODO').AsInteger := tv.Index;
tblOpcMenu.FieldByName('OPC_DESCRIPCION').AsString := tv.Text;
tblOpcMenu.FieldByName('OPC_TAG').AsInteger := tv.AbsoluteIndex;

tblOpcMenu.Post;
end;
tblOpcMenu.Close;




Y con este otro recuperrarlos y añadirlos a un Treeview vacío:

var
i, iParent:integer;
tv, tvParent:TTreeNode;
begin
tblOpcMenu.Open; // Abrir la tabla
while not tblOpcMenu.Eof do begin // Recorrer los registros hasta el final
iParent := tblOpcMenu.FieldByName('NODO_PADRE').AsInteger; // buscar el nodo padre (los de primer nivel = -1)
if (iParent = -1) then begin // Los de primer nivel...
tv := TreeView1.Items.Add(nil, tblOpcMenu.FieldByName('OPC_DESCRIPCION').AsString); // Se añaden
tvParent := tv; // es un nodo padre
end
else // Los que no son de primer nivel, loos añado al padre
tv := TreeView1.Items.AddChild(tvParent, tblOpcMenu.FieldByName('OPC_DESCRIPCION').AsString);
tblOpcMenu.Next;
end;
tblOpcMenu.Close;

mRoman
21-05-2020, 22:53:28
;537317']Creo que es más sencillo...
Con este código puedes recorrer el treeview y almacenar en la tabla ls items:

var
i:integer;
tv:TTreeNode;
begin
tblOpcMenu.Open; // Abrir la tabla
for i := 0 to (TreeView1.Items.Count -1) do begin // recorro el treeview
tv := TreeView1.Items[i]; // accedo al nodo
tblOpcMenu.Append;
// Si es un nodo de primer nivel (-1), si no, me apunto el padre
tblOpcMenu.FieldByName('NODO_PADRE').AsString := ifThen(Assigned(tv.Parent), tv.Parent.Index.ToString, '-1');
tblOpcMenu.FieldByName('NODO').AsInteger := tv.Index;
tblOpcMenu.FieldByName('OPC_DESCRIPCION').AsString := tv.Text;
tblOpcMenu.FieldByName('OPC_TAG').AsInteger := tv.AbsoluteIndex;

tblOpcMenu.Post;
end;
tblOpcMenu.Close;




Y con este otro recuperrarlos y añadirlos a un Treeview vacío:

var
i, iParent:integer;
tv, tvParent:TTreeNode;
begin
tblOpcMenu.Open; // Abrir la tabla
while not tblOpcMenu.Eof do begin // Recorrer los registros hasta el final
iParent := tblOpcMenu.FieldByName('NODO_PADRE').AsInteger; // buscar el nodo padre (los de primer nivel = -1)
if (iParent = -1) then begin // Los de primer nivel...
tv := TreeView1.Items.Add(nil, tblOpcMenu.FieldByName('OPC_DESCRIPCION').AsString); // Se añaden
tvParent := tv; // es un nodo padre
end
else // Los que no son de primer nivel, loos añado al padre
tv := TreeView1.Items.AddChild(tvParent, tblOpcMenu.FieldByName('OPC_DESCRIPCION').AsString);
tblOpcMenu.Next;
end;
tblOpcMenu.Close;



Gracias Neftali por contestar. Lo aplico y haré mis comentarios al respecto. Todo esto es porq estoy desarrollando el módulo de seguridad del sistema, crear usuarios, roles y configuraciones de las opciones del menu principal del sistema...que es el tema q nos ocupa.

Nuevamente gracias y estamos pendientes...

Saludos.

mRoman
22-05-2020, 01:02:16
Hola Neftali

Estoy analizando tu código que muy amablemente me respondiste. Tengo una duda, manejas otra versión de Delphi?, yo uso Delphi6, lo anterior debido a la siguiente instrucción:

tblOpcMenu.FieldByName('NODO_PADRE').AsString := ifThen(Assigned(tv.Parent), tv.Parent.Index.ToString, '-1');

(IfThen)
Mi delphi no la reconoce...

Neftali [Germán.Estévez]
22-05-2020, 08:16:32
Es un IF..THEN..ELSE abreviado que tienen las últimas verisones de delphi.
http://docwiki.embarcadero.com/Libraries/Rio/en/System.StrUtils.IfThen


Al final es equivalente a poner:

if Assigned(tv.Parent) then
tblOpcMenu.FieldByName('NODO_PADRE').AsString := tv.Parent.Index.ToString
else
tblOpcMenu.FieldByName('NODO_PADRE').AsString := '-1';


Usar el ToString, es equivalente à la función IntToStr, BoolToStr,...


Str:= tv.Parent.Index.ToString;

// Es equivalente a:


Str := IntToStr(tv.Parent.Index);

mRoman
22-05-2020, 08:39:33
;537317']Creo que es más sencillo...

[/DELPHI]

Que tal Neftali

Anexo el código completo donde se carga las opciones del menu principal, agregando un checkbox a cada opción, para habilitar o deshabilitar la misma, para mas adelante, cuando se conecta el usuario al sistema, solo vea las opciones habilitadas, bueno ya se graba la información a la tabla -que por cierto le agregue campos-, pero como que el arbol no lo esta cargando bien desde la tabla, es decir, las subopciones no las inserta en los niveles que son.

Aparte estoy buscando la forma de como saber que opciones tiene CHECKED en el treeview para almacenar el dato y me sirva para habilitar o deshabilitar.

Envio el código completo: (Tengo un pagecontrol en el mismo formulario y hay código que nada que ver con el TreeView)


unit SegMenuNiv;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ComCtrls, Buttons, ToolWin, ExtCtrls, CommCtrl, StdCtrls, Mask, DB,
IBCustomDataSet, DBCtrls, sSkinProvider, IBQuery, Menus, IBTable, StrUtils;

type
TfrmSegMenuNiv = class(TForm)
Panel2: TPanel;
PageControl1: TPageControl;
TabSheet1: TTabSheet;
TabSheet2: TTabSheet;
Panel1: TPanel;
Panel4: TPanel;
Label1: TLabel;
mskIDMenu: TMaskEdit;
GroupBox1: TGroupBox;
dsTipoMenu: TIBDataSet;
dSoTipoMenu: TDataSource;
dsTipoMenuMENU_TIPO_ID: TSmallintField;
dsTipoMenuMENU_TIPO_DESCRIP: TIBStringField;
dsTipoMenuMENU_HABILITAR: TIBStringField;
DBEdit1: TDBEdit;
Panel3: TPanel;
btnGrabar: TBitBtn;
btnCancelar: TBitBtn;
sSkinProvider1: TsSkinProvider;
Panel5: TPanel;
Panel7: TPanel;
Panel8: TPanel;
Label2: TLabel;
cbxTipoMenu: TDBLookupComboBox;
GroupBox2: TGroupBox;
TreeView1: TTreeView;
Panel6: TPanel;
btnGrabarCFG: TBitBtn;
btnCancelarCFG: TBitBtn;
GroupBox3: TGroupBox;
DBCheckBox1: TDBCheckBox;
btnEliminar: TBitBtn;
qryTipoMenu: TIBQuery;
dSoTipoMenuCFG: TDataSource;
qryTipoMenuMENU_TIPO_ID: TSmallintField;
qryTipoMenuMENU_TIPO_DESCRIP: TIBStringField;
qryTipoMenuMENU_HABILITAR: TIBStringField;
dsMenuOpCFG: TIBDataSet;
dSoMenuOpCFG: TDataSource;
qryMenu: TIBQuery;
btnEliminarCFG: TBitBtn;
qryMenuNODO_PADRE: TIntegerField;
qryMenuNODO: TIntegerField;
qryMenuOPC_DESCRIPCION: TIBStringField;
qryMenuOPC_TAG: TIntegerField;
tblOpcMenu: TIBTable;
dsMenuOpCFGMENU_TIPO_ID: TSmallintField;
dsMenuOpCFGNODO: TIntegerField;
dsMenuOpCFGNODO_PADRE: TIntegerField;
dsMenuOpCFGOPC_VISIBLE: TIBStringField;
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure Panel1Enter(Sender: TObject);
procedure Panel1Exit(Sender: TObject);
procedure btnGrabarClick(Sender: TObject);
procedure btnEliminarClick(Sender: TObject);
procedure btnCancelarClick(Sender: TObject);
procedure FormShow(Sender: TObject);
procedure Panel7Enter(Sender: TObject);
procedure cbxTipoMenuCloseUp(Sender: TObject);
procedure TreeView1Click(Sender: TObject);
procedure btnGrabarCFGClick(Sender: TObject);
private
{ Private declarations }
procedure ToggleTreeViewCheckBoxes(Node:TTreeNode; cUnChecked, cChecked: Integer);
public
{ Public declarations }
i, iParent:integer;
tv, tvParent:TTreeNode;
end;

var
frmSegMenuNiv: TfrmSegMenuNiv;
const cStateUnCheck = 1;
cStateChecked = 2;

implementation
uses ModuloBD,MenuPrincipal;
{$R *.dfm}

procedure TfrmSegMenuNiv.FormClose(Sender: TObject;
var Action: TCloseAction);
begin
Action:=caFree;
end;

procedure TfrmSegMenuNiv.Panel1Enter(Sender: TObject);
begin
dMod.trscMMS.Active:=False;
dMod.trscMMS.StartTransaction;
mskIDMenu.Text:='';

btnGrabar.Enabled:=False;
btnEliminar.Enabled:=False;
btnCancelar.Enabled:=False;
end;

procedure TfrmSegMenuNiv.Panel1Exit(Sender: TObject);
begin
dsTipoMenu.Close;
dsTipoMenu.ParamByName('menu_id').AsString:=mskIDMenu.Text;
dsTipoMenu.Open;
if dsTipoMenu.IsEmpty then
begin
btnGrabar.Enabled:=True;
btnEliminar.Enabled:=False;
btnCancelar.Enabled:=True;
dsTipoMenu.Append;
dsTipoMenu.FieldByName('menu_tipo_id').AsString:=mskIDMenu.Text;
end Else
begin
btnGrabar.Enabled:=True;
btnEliminar.Enabled:=True;
btnCancelar.Enabled:=True;
dsTipoMenu.Edit;
end;
end;

procedure TfrmSegMenuNiv.btnGrabarClick(Sender: TObject);
begin
try
dsTipoMenu.Post;
dsTipoMenu.ApplyUpdates;
dMod.trscMMS.Commit;
Application.MessageBox('Datos grabados correctamente','Aviso',mb_ok+mb_IconInformation);
except
on E: Exception do
begin
Application.MessageBox('Los registros no pueden ser grabados, consulte al administrador del sistema','Error', mb_ok+mb_IconError);
ShowMessage(E.Message);
dMod.trscMMS.Rollback;
end;
end;
mskIDMenu.SetFocus;
end;

procedure TfrmSegMenuNiv.btnEliminarClick(Sender: TObject);
begin
try
dsTipoMenu.Delete;
dsTipoMenu.ApplyUpdates;
dMod.trscMMS.Commit;
Application.MessageBox('El Registro ha sido eliminado de forma correcta','Aviso',mb_ok+mb_IconInformation);
except
on E: Exception do
begin
Application.MessageBox('El Registro no se puede eliminar, consulte al administrador del sistema','Error', mb_ok+mb_IconError);
ShowMessage(E.Message);
dMod.trscMMS.Rollback;
end;
end;
mskIDMenu.SetFocus;
end;

procedure TfrmSegMenuNiv.btnCancelarClick(Sender: TObject);
begin
mskIDMenu.SetFocus;
end;

procedure TfrmSegMenuNiv.FormShow(Sender: TObject);
begin
PageControl1.ActivePageIndex:=0;
mskIDMenu.SetFocus;
end;

procedure TfrmSegMenuNiv.Panel7Enter(Sender: TObject);
begin
dMod.trscMMS.Active:=False;
dMod.trscMMS.StartTransaction;
btnGrabarCFG.Enabled:=False;
btnEliminarCFG.Enabled:=False;
btnCancelarCFG.Enabled:=False;
dsMenuOpCFG.Close;
qryMenu.Close;
qryTipoMenu.Close;
qryTipoMenu.Open;
qryTipoMenu.FetchAll;

end;

procedure TfrmSegMenuNiv.cbxTipoMenuCloseUp(Sender: TObject);
//--------------------------------------------------------------------------
procedure VerificaMenu(Menu: TMenuItem; Nod: TTreeNode);
Var
i: Integer;
Nodo: TTreeNode;
Begin
// Para cada elemento del menu
for i:= 0 To (Menu.Count - 1) Do Begin
// Es un elemento correcto
If Not(Menu.Parent Is TMenuItem) then begin
// Crear el elemento asignarle el índice de la imagen
Nodo:= TreeView1.Items.Add(Nil, Menu.Items[i].Caption);
Nodo.ImageIndex := Menu.Items[i].ImageIndex;
End
Else begin
// Crear un hijo
Nodo:= TreeView1.Items.AddChild(Nod, Menu.Items[i].Caption);
Nodo.ImageIndex := Menu.Items[i].ImageIndex;
end;

// Llamada recursiva para los submenus
If Menu.Items[i].Count > 0 Then begin
VerificaMenu(Menu.Items[i], Nodo);
end;
End;
End;
//--------------------------------------------------------------------------

begin
// Pasar el menú
tblOpcMenu.Filter:='MENU_TIPO_ID='+IntToStr(cbxTipoMenu.KeyValue);
tblOpcMenu.Filtered:=True;
tblOpcMenu.Open;
if tblOpcMenu.IsEmpty then
begin
btnGrabarCFG.Caption:='Grabar';
btnGrabarCFG.Enabled:=True;
btnEliminarCFG.Enabled:=False;
btnCancelarCFG.Enabled:=False;
VerificaMenu(frmMenu.MainMenu1.Items, Nil);
SetWindowLong(TreeView1.Handle, GWL_STYLE, GetWindowLong(TreeView1.Handle, GWL_STYLE) or TVS_CHECKBOXES);
TreeView1.FullExpand;
TreeView1.Items.Item[0].Selected:=True;
end Else
begin
btnGrabarCFG.Caption:='Actualizar';
btnGrabarCFG.Enabled:=True;
btnEliminarCFG.Enabled:=True;
btnCancelarCFG.Enabled:=True;
tblOpcMenu.First;
while not tblOpcMenu.Eof do
begin // Recorrer los registros hasta el final
iParent := tblOpcMenu.FieldByName('NODO_PADRE').AsInteger; // buscar el nodo padre (los de primer nivel = -1)
if (iParent = -1) then begin // Los de primer nivel...
tv := TreeView1.Items.Add(nil, tblOpcMenu.FieldByName('OPC_DESCRIPCION').AsString); // Se añaden
tvParent := tv; // es un nodo padre
end
else // Los que no son de primer nivel, loos añado al padre
tv := TreeView1.Items.AddChild(tvParent, tblOpcMenu.FieldByName('OPC_DESCRIPCION').AsString);
tblOpcMenu.Next;
end;
tblOpcMenu.Close;
end;
end;

procedure TfrmSegMenuNiv.TreeView1Click(Sender: TObject);
var
P: TPoint;
begin
GetCursorPos(P);
P := TreeView1.ScreenToClient(P);
if (htOnStateIcon in TreeView1.GetHitTestInfoAt(P.X, P.Y)) then
ToggleTreeViewCheckBoxes(TreeView1.Selected, cStateUnCheck, cStateChecked);
end;

procedure TfrmSegMenuNiv.ToggleTreeViewCheckBoxes(Node: TTreeNode; cUnChecked,
cChecked: Integer);
begin
if Assigned(Node) then
begin
if Node.StateIndex = cUnChecked then
Node.StateIndex := cChecked
else if Node.StateIndex = cChecked then
Node.StateIndex := cUnChecked;
end;
end;


procedure TfrmSegMenuNiv.btnGrabarCFGClick(Sender: TObject);
var
i, nNodoPadre : integer;
// tv : TTreeNode;
begin

tblOpcMenu.Open; // Abrir la tabla
for i := 0 to (TreeView1.Items.Count -1) do begin // recorro el treeview
tv := TreeView1.Items[i]; // accedo al nodo
tblOpcMenu.Append;
// Si es un nodo de primer nivel (-1), si no, me apunto el padre
if ( Assigned(tv.Parent) ) then
nNodoPadre:=tv.Parent.Index
else
nNodoPadre:=-1;

tblOpcMenu.FieldByName('MENU_TIPO_ID').AsInteger:=cbxTipoMenu.KeyValue;
tblOpcMenu.FieldByName('NODO_PADRE').AsInteger := nNodoPadre;
tblOpcMenu.FieldByName('NODO').AsInteger := tv.Index;
tblOpcMenu.FieldByName('OPC_DESCRIPCION').AsString := tv.Text;
tblOpcMenu.FieldByName('OPC_TAG').AsInteger := tv.AbsoluteIndex;
tblOpcMenu.FieldByName('OPC_VISIBLE').AsInteger:=tv.StateIndex;

tblOpcMenu.Post;

end;
tblOpcMenu.Close;
end;

end.



Espero me puedan ayudar a almacenar el CHECKED y el UNCHECKED de cada nodo en la tabla, que por cierto tuvo cambios:

CREATE TABLE SEG_MENU_OPC_CONFIG (
MENU_TIPO_ID NUMERIC(2,0) NOT NULL,
NODO_PADRE INTEGER NOT NULL,
NODO INTEGER NOT NULL,
OPC_VISIBLE VARCHAR(5),
OPC_DESCRIPCION VARCHAR(60),
OPC_TAG INTEGER
);


Saludos y muchas gracias por su tiempo a todos.

Neftali [Germán.Estévez]
22-05-2020, 11:50:21
Tal vez deberías enviar un proyecto (puedes adjuntar un ZIP) con lo mínimo para poder probar el código.

mRoman
22-05-2020, 18:17:40
;537362']Tal vez deberías enviar un proyecto (puedes adjuntar un ZIP) con lo mínimo para poder probar el código.

Que tal Neftali.

Anexe un link de google-drive ya q el archivo zip llega casi a los 2 Mb, espero lo puedas bajar.

https://drive.google.com/file/d/139M1-KiJaRwahtq2wHs-F_1HWhFrKSrB/view?usp=sharing

Te comento q estoy trabajando con Delphi6/Firebird 2.0 sobre Win10.

Explico el funcionamiento, seleccionas la opción "Seguridad" y luego "Configurar Menu del Sistema", te mostrará el formulario el cual tiene un PageControl. En la primera pestaña es para dar de alta la descripcion del menu a configurar, y la segunda pestaña es donde estoy tratando de configurar las opciones del menú.

En la 2da pestaña, das click sobre el combo para seleccionar la descripción de un menú -talvez la creación previa de la descripción del tipo de menú, la pueda omitir, ya que la DESCRIPCIÓN la puedo grabar al mismo tiempo que se grabarían los nodos del TreeView-

Al cerrarse el combo se mostraran los datos, si la tabla contiene ya una configuración del menú previamente grabada, si no, se cargaran las opciones de TMainMenu al treeview....

Al haber información previa de un menú configurado, los carga al TreeView, pero no lo hace bien, sobre todo en los nodos "nietos", los carga como si fueran "hijos"...

Estaré al pendiente de cualquier comentario. Saludos y muchas gracias por ayudarme, igualmente -por adelantado- para aquellos otros compañeros del foro que aporten o consulten.

MUCHAS GRACIAS A TODOS!.

mRoman
31-05-2020, 21:31:45
Hola amigos.

Ya solucioné el problema. Dejaré el código completo por si alguien mas quisiera hacer los mismo.

Explico el objetivo: Llenar un treeview con todas los items de un tmainmenu. seleccionar en el treeview, el item que deberá mostrarse en las opciones del menu principal del sistema. almacenar dicha configuracion con una clave del tipo de menu creado, asi como todas los items del tmainmenu, almacenando en un campo de control, la clave que permitirá ser comparada cuando el usuario se conecte al sistema y se activen solamente aquellas opciones que coincidan con el campo de control.

Para lograr lo anterior, cree una tabla con la siguiente estructura:
CREATE TABLE SEG_MENU_OPC_CONFIG (
MENU_TIPO_ID NUMERIC(2,0) NOT NULL,
NODO_PADRE INTEGER NOT NULL,
NIVEL INTEGER,
NODO INTEGER NOT NULL,
OPC_VISIBLE VARCHAR(5),
OPC_DESCRIPCION VARCHAR(60),
OPC_TAG INTEGER,
OPC_ABSOLUTE_INDEX INTEGER
);


MENU_TIPO_ID.- Clave del tipo de menú con el cual se guardará la configuración de las opciones del menu(EJ. MENU DE ADMINISTRADOR, MENU DE CAPTURISTA, etc).
NODO_PADRE.- Almacenará el nodo padre del TreeView.
NIVEL.- Almacenaré el nivel que guarda el item en el treeview
NODO.- Numero de nodo del item.
OPC_VISIBLE.- Campo de control que servirá para identificar si se mostrará o no la opción en el menú principal.(Ej. 0=visible,1=no visible, T=True, F=False ,etc). ya aqui lo pueden hacer como gusten.
OPC_DESCRIPCION.- Nombre o descripción del item que ostenta en el TMainMenu
OPC_TAG.- El TAG que guarda el item en el TMainMenu (A todos los item les puse un numero el cual servirá luego para comparar y ver si se mostrara o no la opción)
OPC_ABSOLUTE_INDEX.- El numero que tiene almacenado en la propiedad de TreeView.AbsoluteIndex.

Y este es el código completo con el cual se cumplió con el objetivo: (Por cierto aclaro que el form tiene un PageControl, en donde en la primer TabSheet es la pantalla para crear/guardar la descripción del tipo de menu que voy a configurar)

unit SegMenuNiv;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ComCtrls, Buttons, ToolWin, ExtCtrls, CommCtrl, StdCtrls, Mask, DB,
IBCustomDataSet, DBCtrls, sSkinProvider, IBQuery, Menus, IBTable, StrUtils,
ImgList, Grids, DBGrids;

type
TfrmSegMenuNiv = class(TForm)
Panel2: TPanel;
PageControl1: TPageControl;
TabSheet1: TTabSheet;
TabSheet2: TTabSheet;
Panel1: TPanel;
Panel4: TPanel;
Label1: TLabel;
mskIDMenu: TMaskEdit;
GroupBox1: TGroupBox;
dsTipoMenu: TIBDataSet;
dSoTipoMenu: TDataSource;
dsTipoMenuMENU_TIPO_ID: TSmallintField;
dsTipoMenuMENU_TIPO_DESCRIP: TIBStringField;
dsTipoMenuMENU_HABILITAR: TIBStringField;
DBEdit1: TDBEdit;
Panel3: TPanel;
btnGrabar: TBitBtn;
btnCancelar: TBitBtn;
sSkinProvider1: TsSkinProvider;
Panel5: TPanel;
Panel7: TPanel;
Panel8: TPanel;
Label2: TLabel;
cbxTipoMenu: TDBLookupComboBox;
GroupBox2: TGroupBox;
TreeView1: TTreeView;
Panel6: TPanel;
btnGrabarCFG: TBitBtn;
btnCancelarCFG: TBitBtn;
GroupBox3: TGroupBox;
DBCheckBox1: TDBCheckBox;
btnEliminar: TBitBtn;
qryTipoMenu: TIBQuery;
dSoTipoMenuCFG: TDataSource;
qryTipoMenuMENU_TIPO_ID: TSmallintField;
qryTipoMenuMENU_TIPO_DESCRIP: TIBStringField;
qryTipoMenuMENU_HABILITAR: TIBStringField;
dsMenuOpCFG: TIBDataSet;
dSoMenuOpCFG: TDataSource;
qryMenu: TIBQuery;
btnEliminarCFG: TBitBtn;
qryMenuNODO_PADRE: TIntegerField;
qryMenuNODO: TIntegerField;
qryMenuOPC_DESCRIPCION: TIBStringField;
qryMenuOPC_TAG: TIntegerField;
tblOpcMenu: TIBTable;
dsMenuOpCFGMENU_TIPO_ID: TSmallintField;
dsMenuOpCFGNODO: TIntegerField;
dsMenuOpCFGNODO_PADRE: TIntegerField;
dsMenuOpCFGOPC_VISIBLE: TIBStringField;
PopupMenu1: TPopupMenu;
Activar1: TMenuItem;
Desactivar1: TMenuItem;
ImageList1: TImageList;
DataSource1: TDataSource;
qryActVisible: TIBQuery;
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure Panel1Enter(Sender: TObject);
procedure Panel1Exit(Sender: TObject);
procedure btnGrabarClick(Sender: TObject);
procedure btnEliminarClick(Sender: TObject);
procedure btnCancelarClick(Sender: TObject);
procedure FormShow(Sender: TObject);
procedure Panel7Enter(Sender: TObject);
procedure cbxTipoMenuCloseUp(Sender: TObject);
procedure TreeView1Click(Sender: TObject);
procedure btnGrabarCFGClick(Sender: TObject);
procedure Activar1Click(Sender: TObject);
procedure Desactivar1Click(Sender: TObject);
procedure Visible();
private
{ Private declarations }
procedure ToggleTreeViewCheckBoxes(Node:TTreeNode; cUnChecked, cChecked: Integer);
public
{ Public declarations }
i, iParent,nivel,nodo_padre,nodo, nodo_ant:integer;
tv, tvParent, tv_hijo,tv_nieto:TTreeNode;
bitMap:TBitmap;
end;

var
frmSegMenuNiv: TfrmSegMenuNiv;
const cStateUnCheck = 1;
cStateChecked = 2;

implementation
uses ModuloBD,MenuPrincipal;
{$R *.dfm}

procedure TfrmSegMenuNiv.FormClose(Sender: TObject;
var Action: TCloseAction);
begin
Action:=caFree;
end;

procedure TfrmSegMenuNiv.Panel1Enter(Sender: TObject);
begin
dMod.trscMMS.Active:=False;
dMod.trscMMS.StartTransaction;
mskIDMenu.Text:='';

btnGrabar.Enabled:=False;
btnEliminar.Enabled:=False;
btnCancelar.Enabled:=False;
end;

procedure TfrmSegMenuNiv.Panel1Exit(Sender: TObject);
begin
dsTipoMenu.Close;
dsTipoMenu.ParamByName('menu_id').AsString:=mskIDMenu.Text;
dsTipoMenu.Open;
if dsTipoMenu.IsEmpty then
begin
btnGrabar.Enabled:=True;
btnEliminar.Enabled:=False;
btnCancelar.Enabled:=True;
dsTipoMenu.Append;
dsTipoMenu.FieldByName('menu_tipo_id').AsString:=mskIDMenu.Text;
end Else
begin
btnGrabar.Enabled:=True;
btnEliminar.Enabled:=True;
btnCancelar.Enabled:=True;
dsTipoMenu.Edit;
end;
end;

procedure TfrmSegMenuNiv.btnGrabarClick(Sender: TObject);
begin
try
dsTipoMenu.Post;
dsTipoMenu.ApplyUpdates;
dMod.trscMMS.Commit;
Application.MessageBox('Datos grabados correctamente','Aviso',mb_ok+mb_IconInformation);
except
on E: Exception do
begin
Application.MessageBox('Los registros no pueden ser grabados, consulte al administrador del sistema','Error', mb_ok+mb_IconError);
ShowMessage(E.Message);
dMod.trscMMS.Rollback;
end;
end;
mskIDMenu.SetFocus;
end;

procedure TfrmSegMenuNiv.btnEliminarClick(Sender: TObject);
begin
try
dsTipoMenu.Delete;
dsTipoMenu.ApplyUpdates;
dMod.trscMMS.Commit;
Application.MessageBox('El Registro ha sido eliminado de forma correcta','Aviso',mb_ok+mb_IconInformation);
except
on E: Exception do
begin
Application.MessageBox('El Registro no se puede eliminar, consulte al administrador del sistema','Error', mb_ok+mb_IconError);
ShowMessage(E.Message);
dMod.trscMMS.Rollback;
end;
end;
mskIDMenu.SetFocus;
end;

procedure TfrmSegMenuNiv.btnCancelarClick(Sender: TObject);
begin
mskIDMenu.SetFocus;
end;

procedure TfrmSegMenuNiv.FormShow(Sender: TObject);
begin
PageControl1.ActivePageIndex:=0;
end;

procedure TfrmSegMenuNiv.Panel7Enter(Sender: TObject);
begin
dMod.trscMMS.Active:=False;
dMod.trscMMS.StartTransaction;
btnGrabarCFG.Tag:=0;
TreeView1.Items.Clear;
btnGrabarCFG.Enabled:=False;
btnEliminarCFG.Enabled:=False;
btnCancelarCFG.Enabled:=False;
dsMenuOpCFG.Close;
qryMenu.Close;
qryTipoMenu.Close;
qryTipoMenu.Open;
qryTipoMenu.FetchAll;
tblOpcMenu.Close;

end;

procedure TfrmSegMenuNiv.cbxTipoMenuCloseUp(Sender: TObject);
//--------------------------------------------------------------------------
procedure VerificaMenu(Menu: TMenuItem; Nod: TTreeNode);
Var
i: Integer;
Nodo: TTreeNode;
Begin
// Para cada elemento del menu
for i:= 0 To (Menu.Count - 1) Do Begin
// Es un elemento correcto
If Not(Menu.Parent Is TMenuItem) then begin
// Crear el elemento asignarle el índice de la imagen
Nodo:= TreeView1.Items.Add(Nil,Menu.Items[i].Caption+'-'+IntToStr(Menu.Items[i].Tag) );
Nodo.StateIndex:=0;
End
Else begin
// Crear un hijo
Nodo:= TreeView1.Items.AddChild(Nod,Menu.Items[i].Caption+'-'+IntToStr(Menu.Items[i].Tag) );
Nodo.StateIndex:=0;
end;

// Llamada recursiva para los submenus
If Menu.Items[i].Count > 0 Then begin
VerificaMenu(Menu.Items[i], Nodo);
end;
End;
End;
//--------------------------------------------------------------------------

begin
// Pasar el menú
TreeView1.Items.Clear;
tblOpcMenu.Filter:='MENU_TIPO_ID='+IntToStr(cbxTipoMenu.KeyValue);
tblOpcMenu.Filtered:=True;
tblOpcMenu.Open;
if tblOpcMenu.IsEmpty then
begin
btnGrabarCFG.Caption:='Grabar';
btnGrabarCFG.Tag:=0;
btnGrabarCFG.Enabled:=True;
btnEliminarCFG.Enabled:=False;
btnCancelarCFG.Enabled:=False;
VerificaMenu(frmMenu.MainMenu1.Items, Nil);
TreeView1.Items.Item[0].Selected:=True;
end Else
begin
btnGrabarCFG.Tag:=1;
btnGrabarCFG.Caption:='Actualizar';
btnGrabarCFG.Enabled:=True;
btnEliminarCFG.Enabled:=True;
btnCancelarCFG.Enabled:=True;
tblOpcMenu.First;
//******************************************************************************
while not (tblOpcMenu.Eof) do
begin
if tblOpcMenu.FieldValues['NIVEL']=0 then
begin
tv:= TreeView1.Items.Add(nil, tblOpcMenu.FieldValues['OPC_DESCRIPCION']);
end;
if tblOpcMenu.FieldValues['NIVEL']=1 then
begin
tv_hijo:= TreeView1.Items.AddChild(tv, tblOpcMenu.FieldValues['OPC_DESCRIPCION']);
end;
if tblOpcMenu.FieldValues['NIVEL']=2 then
begin
TreeView1.Items.AddChild(tv_hijo,tblOpcMenu.FieldValues['OPC_DESCRIPCION']);
end;
tblOpcMenu.Next;
end;
Visible();
TreeView1.Items.Item[0].Selected:=True;

//******************************************************************************
end;
end;

procedure TfrmSegMenuNiv.Visible;
var
i : integer;
begin
tblOpcMenu.First;
While not(tblOpcMenu.Eof) do
begin
for i:=0 to TreeView1.Items.Count - 1 do
begin
if tblOpcMenu.FieldValues['opc_descripcion']=TreeView1.Items[i].Text then
TreeView1.Items[i].StateIndex:=tblOpcMenu.FieldValues['OPC_VISIBLE'];
tblOpcMenu.Next;
end;
end;
end;

procedure TfrmSegMenuNiv.TreeView1Click(Sender: TObject);
var
P: TPoint;
begin
if TreeView1.Selected.StateIndex=0 then
begin
if TreeView1.Selected.Count>0 then
begin
TreeView1.Selected.Expanded:=False;
Application.MessageBox('Para poder expandir y ver las sub-opciones, '+CHR(13)+
'deberá primeramente ACTIVAR la opción dando click '+CHR(13)+
'derecho y seleccionar "Activar"','Aviso',mb_ok+mb_IconWarning);
end;
end else
begin
TreeView1.Selected.Expanded:=True;
end;
end;

procedure TfrmSegMenuNiv.ToggleTreeViewCheckBoxes(Node: TTreeNode; cUnChecked,
cChecked: Integer);
begin
end;


procedure TfrmSegMenuNiv.btnGrabarCFGClick(Sender: TObject);
var
i, nNodoPadre : integer;
// tv : TTreeNode;
begin
tblOpcMenu.Open; // Abrir la tabla
if btnGrabarCFG.Tag=0 then
begin
for i := 0 to (TreeView1.Items.Count -1) do // recorro el treeview
begin
tv := TreeView1.Items[i]; // accedo al nodo
tblOpcMenu.Append;
// Si es un nodo de primer nivel (-1), si no, me apunto el padre
if ( Assigned(tv.Parent) ) then
nNodoPadre:=tv.Parent.Index
else
nNodoPadre:=-1;
tblOpcMenu.FieldByName('MENU_TIPO_ID').AsInteger:=cbxTipoMenu.KeyValue;
tblOpcMenu.FieldByName('NODO_PADRE').AsInteger := nNodoPadre;
tblOpcMenu.FieldByName('NIVEL').AsInteger:=tv.Level;
tblOpcMenu.FieldByName('NODO').AsInteger := tv.Index;
tblOpcMenu.FieldByName('OPC_DESCRIPCION').AsString := tv.Text;
tblOpcMenu.FieldByName('OPC_TAG').AsString :=COPY( tv.Text,AnsiPOS('-',tv.Text)+1,LENGTH(tv.Text) );
tblOpcMenu.FieldByName('OPC_ABSOLUTE_INDEX').AsInteger:=tv.AbsoluteIndex;
tblOpcMenu.FieldByName('OPC_VISIBLE').AsInteger:= tv.StateIndex;

tblOpcMenu.Post;
end;
tblOpcMenu.ApplyUpdates;
tblOpcMenu.Close;
end Else
begin
for i:=0 to TreeView1.Items.Count - 1 do
begin
qryActVisible.Close;
qryActVisible.ParamByName('visible').AsInteger:=TreeView1.Items[i].StateIndex;
qryActVisible.ParamByName('menu').AsInteger:=cbxTipoMenu.KeyValue;
qryActVisible.ParamByName('absolute_index').AsInteger:=TreeView1.Items[i].AbsoluteIndex;
qryActVisible.ExecSQL;
end
end;
dMod.trscMMS.Commit;
Application.MessageBox('Configuración de Menú grabado exitosamente','Aviso',mb_ok+mb_IconInformation);
Panel7Enter(Sender);
end;

procedure TfrmSegMenuNiv.Activar1Click(Sender: TObject);
begin
TreeView1.Selected.StateIndex:=1;
TreeView1.Selected.Expanded:=True;
end;

procedure TfrmSegMenuNiv.Desactivar1Click(Sender: TObject);
begin
TreeView1.Selected.StateIndex:=0;
TreeView1.Selected.Expanded:=False;
end;

end.



Tengo pendiente el proceso de comparación para que muestre o no las opciones del menú, cuando el usuario se conecte. En cuanto lo tengo lo publicaré para tenerlo todo completo.

Sé que puede quedar mejor, pero considero que mis conocimientos no son tan avanzados como algunos o la gran mayoría de los que integran este club, asi que dejo en ustedes -en la medida de sus tiempo y si asi lo desean- lo puedan mejorar, enriquecerlo y dejarlo disponible para los demás.
(Lo escribí en mayúsculas solo para resaltar el mensaje, no significa q este gritando...jejejeje)

Saludos.

mRoman
02-06-2020, 02:28:37
Bueno pues aqui el código pendiente: Habilitar o Deshabilitar las opciones del menú de acuerdo a la configuración almacenada en la tabla SEG_MENU_OPC_CONFIG


CREATE TABLE SEG_MENU_OPC_CONFIG (
MENU_TIPO_ID NUMERIC(2,0) NOT NULL,
NODO_PADRE INTEGER NOT NULL,
NIVEL INTEGER,
NODO INTEGER NOT NULL,
OPC_VISIBLE VARCHAR(5),
OPC_DESCRIPCION VARCHAR(60),
OPC_TAG INTEGER,
OPC_ABSOLUTE_INDEX INTEGER
);


Dentro del formulario principal:

procedure TfrmMenu.FormShow(Sender: TObject);
var
s:string;
i:integer;
begin
qryUsuario.close;
qryUsuario.Open;
VerInfoRes:=TVerInfoRes.Create(Application.ExeName);
frmMenu.Caption:='Sistema Control de Inventarios - Menu Principal Ver.'+Copy(VerInfoRes.FileVersion,1,14);
ToolBar1.Width:=frmMenu.Width;
DateTimeToString(s,'dddddd',now);
stBar.Panels[0].Text:=s;
stBar.Panels[1].Text:='Usuario: '+qryUsuario.FieldByName('usu_id').AsString;
stBar.Panels[2].Text:='Rol: '+qryUsuario.FieldByName('rol_id').AsString;
stBar.Panels[3].Text:='Tipo Menu: '+qryUsuario.FieldByName('menu_descrip').AsString;
stBar.Panels[4].Text:=qryUsuario.FieldByName('nombre').AsString;

{ HABILITAR O DESHABILITAR OPCIONES DEL MENU PRINCIPAL}
qryTipoMenu.Close;
qryTipoMenu.Open;
for i:=0 to MainMenu1.Items.Count -1 do
begin
ActivarOPCMenu(MainMenu1.Items[i]);
end;

{HABILITAR O DESHABILITAR LOS BOTONES DEL TOOLBAR}
for i:=0 to ToolBar1.ButtonCount -1 do
begin
qryBotones.Close;
qryBotones.ParamByName('TAG').AsInteger:=ToolBar1.Buttons[i].Tag;
qryBotones.Open;
if qryBotones.FieldByName('OPC_VISIBLE').AsInteger=0 then
ToolBar1.Buttons[i].Enabled:=False
else
ToolBar1.Buttons[i].Enabled:=True;
end;
end;


Aquí el código del Procedimiento "ActivarOPCMenu":

procedure TfrmMenu.ActivarOPCMenu(V_MENU : TMenuItem);
var
i:integer;
begin
qryTipoMenu.First;
While not(qryTipoMenu.Eof) do
begin
for i:=0 to ComponentCount-1 do
begin
if Components[i] is TMenuItem then
begin
if TMenuItem(Components[i]).Tag = qryTipoMenu.FieldByName('OPC_TAG').AsInteger then
begin
if qryTipoMenu.FieldByName('OPC_VISIBLE').AsInteger=0 then
TMenuItem(Components[i]).Visible:=False
else
TMenuItem(Components[i]).Visible:=True;
end;
end;
end;
qryTipoMenu.Next;
end;
end;


Espero sea de utilidad para los demás compañeros de este club.

Saludos

Neftali [Germán.Estévez]
02-06-2020, 13:19:01
Gracias.
^\||/^\||/^\||/