BlackTDN Search

terça-feira, 24 de abril de 2012

BlackTDN :: Tips & Tricks ~ ADVPL Class dbTree

image Segundo o TDN

Classe: DBTree Cria um objeto do tipo árvore de itens.


Vamos utilizar o exemplo, que gerou a imagem ao lado, obtido em: TDN :: Exemplo de DBTREE com algumas modificações para uso no IDE.

image

Usando o código acima, o protheus irá gerar, em seu client, uma tela com as seguintes características:

image


A imagem acima é a representação visual criada pela classe dbTree. Mas, por traz dessa imagem tem uma tabela de dados. Vamos Observar, através do IDE, as suas características:

image

O protheus, para montagem do Tree criará um arquivo temporário com a estrutura acima. Onde:

T_IDLIST É um numero sequencial e único para cada registro
T_IDTREE Representa o Node Pai
T_IDCODE Representa o Item do Tree
T_ISTREE “S” se for um NODE
T_PROMPT Descrição a ser apresentada 
T_CARGO Campo Auxiliar para permitir Localizar e Alterar o Prompt do Tree. No Exemplo CARGO contem um número sequencial de acordo com o número de item no Tree. Nesse caso saberei que o “Item 009” sempre terá T_CARGO como #0013. Utilizado, internamente, pelo metodo TreeSeek para localizar um item do dbTree.
T_BMP001 Armazena o Número da Imagem 1 utilizada na apresentação do dbTree para o Item em questão. Por exemplo, se o Tree for de uma árvore de diretórios imagem de uma “pasta fechada” quando “recolhido”.
T_BMP002 Armazena o Número da Imagem 2 utilizada na apresentação do dbTree para o Item em questão. Por exemplo, se o Tree for de uma árvore de diretórios imagem de uma “pasta fechada” quando “expandido”.
T_CLIENT Flag que identifica se o Client já foi atualizado com a informação corrente. Utilizado internamente para otimização na montagem da imagem referente ao Tree.

Essa tabela será indexada da seguinte forma:

Ordem Chave
1 T_IDLIST
2 T_IDTREE
3 T_IDCODE
4 T_IDCARGO

O nome do índice é criado baseado no nome do arquivo temporário onde o “prefixo” “S” é substituido e o “sufixo” correspondente à ordem do índice é adicionado.

Uma outra característica importante a notar é que o nome do arquivo, no dbTree corresponde ao seu ALIAS. Informação essa armazenada na propriedade cArqTree do objeto em questão.

image

Com isso, podemos afirmar que existem duas formas de se pesquisar registros nesse arquivo. A primeira usando o método odbTree:TreeSeek(cChave) e outra,  usando o Alias como em: (odbTree:cArqTree)->(dbSeek(cChave,.F.))

Ex.:

lFound := odbTree:TreeSeek(“#00013”). A questão é que o método TreeSeek() sempre irá usar a ordem correspondente ao T_CARGO. E, teremos um grande problema ao tentar usar GetCargo logo em Seguida; pois, pressupondo que TreeSeek ira posicionar o dbTree no item localizado, imaginamos que GetCargo() ira retornar a informação de T_CARGO corrente. O que não é verdade.

Observe:

image

Então, para muitas opções de pesquisa no dbTree prefiro usar dbSeek diretamente.

image

Uma delas para localizar os Nodes Superiores ou Todos os itens que pertence a um Node. Por exemplo. Para saber todos os itens relativos a “Menu 001” e, sabendo que T_CARGO do “Menu 001” é "#0001".

image

image

Observe acima que esse item é um Node Superior, pois T_ISTREE  está com “S”. Observe também, que T_IDTREE dele está como “0000000" caracterizando que ele faz parte do primeiro nível de Nodes. Então, neste caso para achar todos os itens que correspondam ao nível mais superior do dbTree faríamos.

image E assim por Diante. No caso acima localizamos os nodes principais. Para Encontrar seus filhos é só usar T_IDCODE na chave para pesquisa a T_IDTREE.

image

 image

Se o Node for um tree basta repetir a operação até !( lFound ) ou T_ISTREE em branco. Poderá montar uma função recursiva para isso. (Consulte o exemplo postado por Luis Lacombe: DbTree recursiva)

Vale lembrar que ao usar dbSeek diretamente o Tree Visual não será atualizado. Para isso deverá usar o Método TreeSeek da classe dbTree e que o método espera, como parâmetro T_CARGO.

Neste caso, poderia trabalhar em conjunto com dbSeek e TreeSeek criando sua própria função TreeSeek.

image

e usá-la como:

image

Neste caso, o exemplo completo ficaria como:

#include "protheus.ch"
#include "dbtree.ch"

#xtranslate USER PROCEDURE <p> => PROCEDURE U_<p>

USER PROCEDURE MyTree()

PUBLIC __TTSINUSE := .T. //(Para uso no IDE RecLock precisa disso)
PUBLIC __TTSPUSH := Array(0) //(Para uso no IDE RecLock precisa disso)
PUBLIC __cLogSiga := "" //(Para uso no IDE GravaLog em RecLock precisa disso)

// Cria um diálogo
DEFINE DIALOG oDlg TITLE "Teste de DBTree" FROM 10,10 TO 400,700 COLOR CLR_BLACK,CLR_WHITE PIXEL

// Cria o DbTree no diálogo, ocupando o tamanho total do mesmo
DEFINE DBTREE odbTree FROM 00,00 TO oDlg:nHeight,oDlg:nWidth OF oDlg CARGO

DBADDTREE odbTree PROMPT "Menu 001" RESOURCE "BMPTABLE" CARGO "#0001"
DBADDITEM odbTree PROMPT "Item 001" RESOURCE "BMPSXG" CARGO "#0002"
DBENDTREE odbTree

DBADDITEM odbTree PROMPT "Item 002" RESOURCE "BMPTRG" CARGO "#0003"
DBADDITEM odbTree PROMPT "Item 003" RESOURCE "BMPCONS" CARGO "#0004"
DBADDITEM odbTree PROMPT "Item 004" RESOURCE "BMPPARAM" CARGO "#0005"
DBADDTREE odbTree PROMPT "Menu 002" OPENED RESOURCE "BMPTABLE" CARGO "#0006"
DBADDITEM odbTree PROMPT "Item 005" RESOURCE "BMPSXG" CARGO "#0007"
DBADDTREE odbTree PROMPT "Menu 003" OPENED RESOURCE "BMPTABLE" CARGO "#0008"
DBADDITEM odbTree PROMPT "Item 006" RESOURCE "BMPSXG" CARGO "#0009"
DBADDTREE odbTree PROMPT "Menu 004" OPENED RESOURCE "BMPTABLE" CARGO "#0010"
DBADDITEM odbTree PROMPT "Item 007" RESOURCE "BMPSXG" CARGO "#0011"
DBENDTREE odbTree
DBADDITEM odbTree PROMPT "Item 008" RESOURCE "BMPSXG" CARGO "#0012"
DBENDTREE odbTree
DBENDTREE odbTree
DBADDITEM odbTree PROMPT "Item 009" RESOURCE "BMPSXB" CARGO "#0013"

ACTIVATE DIALOG oDlg CENTER ON INIT ShowNodeTree(@odbTree)

Return

Static Function ShowNodeTree(odbTree)

Local cCargo

IF TreeSeek( @odbTree , "#0013" , "T_CARGO" , @cCargo )
MsgInfo( "Seek: " + cCargo + CRLF + "GetCargo: " + odbTree:GetCargo() )
EndIF

Return( TreeSeek( @odbTree , "#0013" , "T_CARGO" , @cCargo ) )

Static Function TreeSeek( odbTree , cKeySeek , cIndexKey , cCargo )

Local aIndexes := Array(0)

Local cAliasTree := odbTree:cArqTree
Local cKey := ""

Local lFound := .F.

Local nOrder

(cAliasTree)->(aEval(Array(10),{|x,y|cKey:=IndexKey(y),IF(!Empty(cKey),aAdd(aIndexes,{y,cKey}),NIL)}))

DEFAULT cIndexKey := "T_CARGO"
cIndexKey := Upper( AllTrim( cIndexKey ) )

nOrder := aScan( aIndexes , { |aBag| aBag[2] == cIndexKey } )
IF ( nOrder == 0 )
cIndexKey := "T_CARGO"
nOrder := aScan( aIndexes , { |aBag| aBag[2] == cIndexKey } )
EndIF

(cAliasTree)->( dbSetOrder( aIndexes[nOrder][1] ) )
lFound := ( cAliasTree )->( dbSeek( cKeySeek , .F. ) )

IF ( lFound )
lFound := odbTree:TreeSeek( ( cAliasTree )->T_CARGO )
IF ( lFound )
cCargo := odbTree:GetCargo(( cAliasTree )->T_IDCODE)
EndIF
EndIF

Return( lFound )



Após a execução do exemplo, o Ponteiro do dbTree estará posicionado no item cujo T_CARGO corresponda a “#0013” ´que é o “Item 009”


image

Dica de última Hora: Utilize ClassMethArr( oDbTree ) para obter todos os Métodos da Classe dbTree.

image
e __ClsArr() para os Métodos da Classe Base TTree.  Para a build abaixo seu índice é o 117

image
image 
image


[]s
иαldσ dj

6 comentários:

  1. Rapaz... Me indica onde você aprende essas coisas!

    Muito obrigado pela dica, você consegue transmitir a informação com muita clareza e objetividade.

    Parabéns

    ResponderExcluir
    Respostas
    1. Renato,

      uma coisa que faltou mencionar e que é fundamental para a solução definitiva do seu problema é que você pode implementar a sua própria dbTree a partir da dbTree padrão implementado os métodos necessários para o seu dia a dia. Experimente criar a sua. CLASS RB_dbTree FROM dbTREE.

      Excluir
    2. Você dizendo fico até feliz, pois foi exatamente o que tinha feito. Extendi a classe DBTree criando o atributo aIdPai, que é um vetor com o currentNodeId do nó atual e do nó pai, se você permitir eu posto o fonte aqui para compartilhar. Só está com um problema na hora que vou instanciar que não está identificando o o atributo no construtor.

      Excluir
    3. #include "totvs.ch"

      /***************************************************************
      ****************************************************************
      Classe TExTree
      Arvore Extendida para tratar o avanço entre os nós filhos e
      relacionar um nó filho com o nó pai
      Criado por: Renato de Bianchi
      Em: 25/04/2012
      ****************************************************************
      ***************************************************************/
      user function TExTree
      return

      class TExTree from DbTree
      data aIdPai //ID do nó pai

      method New(nTop,nLeft,nBottom,nRight,oWnd,bChange,bRClick,lCargo,lDisable,oFont) constructor //Construtor
      method GoToUp() //Posiciona nó pai do nó posicionado
      method GoToTop() //Posiciona nó raiz do nó selecionado
      method GetNumSon() //Obtêm o número de nós filhos de um nó selecionado

      //Metodos sobreescritos
      method AddItem(cPrompt,cCargo,cRes1,cRes2,cFile1,cFile2,nTipo)
      method AddTree(cPrompt,lOpened,cRes1,cRes2,cFile1,cFile2,cCargo)
      method AddTreeItem(cPrompt,cRes,cFile,cCargo)
      method DelItem()

      endClass

      method New(nTop,nLeft,nBottom,nRight,oWnd,bChange,bRClick,lCargo,lDisable,oFont) class TExTree
      :New(nTop,nLeft,nBottom,nRight,oWnd,bChange,bRClick,lCargo,lDisable,oFont)

      ::aIdPai := {}
      return SELF

      method GetNumSon() class TExTree
      local nSon := 0
      local nTotNode := len(aIdPai)
      local cSeekID := SELF:CurrentNodeId

      if nTotNode > 1

      for nI := 1 to nTotNode
      if ::aIdPai[nI,1] == cSeekID
      nSon++
      endIf
      next

      endIf
      return nSon

      method GoToUp() class TExTree
      nPos := aScan(::aIdPai, {|x| x[2]==SELF:CurrentNodeID} )
      if nPos > 0
      if ::aIdPai != nil .and. ::aIdPai[nPos,1] != "" .and. ::aIdPai[nPos,1] != " "
      SELF:ptGotoToNode(::aIdPai[nPos,1])
      endIf
      endIf
      return nil

      method GoToTop() class TExTree
      nPos := aScan(::aIdPai, {|x| x[2]==SELF:CurrentNodeID} )
      if nPos > 0
      while ::aIdPai != nil .and. ::aIdPai[nPos,1] != "" .and. ::aIdPai[nPos,1] != " "
      SELF:GoToUp()
      nPos := aScan(::aIdPai, {|x| x[2]==SELF:CurrentNodeID} )
      endDo
      endIf
      return nil

      method AddItem(cPrompt,cCargo,cRes1,cRes2,cFile1,cFile2,nTipo) class TExTree
      :AddItem(cPrompt,cCargo,cRes1,cRes2,cFile1,cFile2,nTipo)
      cIdPai := SELF:CurrentNodeId

      SELF:TreeSeek(cCargo)
      aAdd(::aIdPai, {cIdPai, SELF:CurrentNodeId})
      return

      method AddTree(cPrompt,lOpened,cRes1,cRes2,cFile1,cFile2,cCargo) class TExTree
      :AddTree(cPrompt,lOpened,cRes1,cRes2,cFile1,cFile2,cCargo)

      SELF:TreeSeek(cCargo)
      aAdd(::aIdPai, {" ", SELF:CurrentNodeId})
      return

      method AddTreeItem(cPrompt,cRes,cFile,cCargo) class TExTree
      :AddTreeItem(cPrompt,cRes,cFile,cCargo)
      cIdPai := SELF:CurrentNodeId

      SELF:TreeSeek(cCargo)
      aAdd(::aIdPai, {cIdPai, SELF:CurrentNodeId})
      return

      method DelItem() class TExTree
      cIdDel := SELF:CurrentNodeId

      _Super:Método()
      aDel(::aIdPai, aScan(::aIdPai, {|x| x[2]==cIdDel} ) )
      return

      Excluir
    4. Naldo, vc chegou a fazer um teste com o fonte acima?
      É necessário alterar o trecho:

      _Super:Método() para _Super:DelItem()

      Mas não sei se a classe DbTree é diferente ou está marcada como final, pois ela não identifica o atributo que eu incluo e não sobrescreve os métodos, se possível faça um teste com o código acima por favor.

      Obrigado

      Excluir
    5. Renato, tem um "gatinho" na dbTree que não permite a herança direta. Então, depois de muito tentar, resolvi pera herança indireta. Exemplo no blog.

      Excluir