Pulo do gato para criar telas em AdvPL

No artigo de hoje, vou mostrar um macete para a criação de antigas Dialogs em AdvPL.

O PO-UI já está no mercado e sempre com inovações, mas as vezes precisamos criar uma Dialog simples e rápida.

Tendo essa premissa, vejo muitas pessoas me perguntando, como eu crio Dialogs tão rapidamente e posicionando os componentes corretamente na tela.

Pois bem jovens, existem duas dicas para isso, e hoje vou mostrar as duas, a primeira usando FWLayer e a segunda fazendo cálculos matemáticos.

Usando FWLayer

Nessa você deve separar a Dialog em “sub containers”, onde você pode usar a classe FWLayer e utilizar comandos como AddLine e AddColumn para fazer o controle da tela.

Então imaginamos uma tela, onde em cima quero exibir um texto de título e exibir alguns botões. E abaixo, no corpo, quero mostrar uma grid com grupos de produtos.

Para isso, será criado duas seções na minha Layer, uma chamada TIT (de título) e outra chamada COR (de corpo).

Depois na TIT, irei adicionar 3 colunas, uma que conterá o título com 50%, uma vazia que conterá 40% do espaço e uma com o botão de sair que terá o restante de 10%.

Já na COR, irei adicionar apenas 1 coluna contendo 100% do espaço. Agora em cada uma dessas columns, irei buscar o Panel, e ai irei vincular os objetos a esses painéis. Abaixo um print do resultado:

Exemplo usando FWLayer

E abaixo, o código fonte do cenário acima:

//Bibliotecas
#Include "TOTVS.ch"

/*/{Protheus.doc} User Function zPulo1
Demonstração de como usar a FWLayer
@type Function
@author Atilio
@since 17/01/2021
@version 1.0
/*/

User Function zPulo1()
    Local aArea := GetArea()

    fMontaTela()

    RestArea(aArea)
Return

Static Function fMontaTela()
	Local nLargBtn      := 50
	//Objetos e componentes
	Private oDlgPulo
	Private oFwLayer
	Private oPanTitulo
	Private oPanGrid
	//Cabeçalho
	Private oSayModulo, cSayModulo := 'TST'
    Private oSayTitulo, cSayTitulo := 'Pulo do Gato na Montagem de Dialogs'
    Private oSaySubTit, cSaySubTit := 'Exemplo usando FWLayer'
	//Tamanho da janela
    Private aSize := MsAdvSize(.F.)
	Private nJanLarg := aSize[5]
	Private nJanAltu := aSize[6]
	//Fontes
    Private cFontUti    := "Tahoma"
    Private oFontMod    := TFont():New(cFontUti, , -38)
    Private oFontSub    := TFont():New(cFontUti, , -20)
    Private oFontSubN   := TFont():New(cFontUti, , -20, , .T.)
    Private oFontBtn    := TFont():New(cFontUti, , -14)
	Private oFontSay    := TFont():New(cFontUti, , -12)
    //Grid
    Private aCampos := {}
    Private cAliasTmp := "TST_" + RetCodUsr()
    Private aColunas := {}

    //Campos da Temporária
    aAdd(aCampos, { "CODIGO" , "C", TamSX3("BM_GRUPO")[1], 0 })
    aAdd(aCampos, { "DESCRI" , "C", TamSX3("BM_DESC")[1],  0 })

    //Cria a tabela temporária
    oTempTable:= FWTemporaryTable():New(cAliasTmp)
    oTempTable:SetFields( aCampos )
    oTempTable:Create()

    //Busca as colunas do browse
    aColunas := fCriaCols()

    //Popula a tabela temporária
    Processa({|| fPopula()}, "Processando...")

	//Cria a janela
	DEFINE MSDIALOG oDlgPulo TITLE "Exemplo de Pulo do Gato"  FROM 0, 0 TO nJanAltu, nJanLarg PIXEL

		//Criando a camada
		oFwLayer := FwLayer():New()
		oFwLayer:init(oDlgPulo,.F.)

		//Adicionando 3 linhas, a de título, a superior e a do calendário
		oFWLayer:addLine("TIT", 10, .F.)
		oFWLayer:addLine("COR", 90, .F.)

		//Adicionando as colunas das linhas
		oFWLayer:addCollumn("HEADERTEXT",   050, .T., "TIT")
		oFWLayer:addCollumn("BLANKBTN",     040, .T., "TIT")
		oFWLayer:addCollumn("BTNSAIR",      010, .T., "TIT")
		oFWLayer:addCollumn("COLGRID",      100, .T., "COR")

		//Criando os paineis
		oPanHeader := oFWLayer:GetColPanel("HEADERTEXT", "TIT")
        oPanSair   := oFWLayer:GetColPanel("BTNSAIR",    "TIT")
        oPanGrid   := oFWLayer:GetColPanel("COLGRID",    "COR")

		//Títulos e SubTítulos
        oSayModulo := TSay():New(004, 003, {|| cSayModulo}, oPanHeader, "", oFontMod,  , , , .T., RGB(149, 179, 215), , 200, 30, , , , , , .F., , )
        oSayTitulo := TSay():New(004, 045, {|| cSayTitulo}, oPanHeader, "", oFontSub,  , , , .T., RGB(031, 073, 125), , 200, 30, , , , , , .F., , )
        oSaySubTit := TSay():New(014, 045, {|| cSaySubTit}, oPanHeader, "", oFontSubN, , , , .T., RGB(031, 073, 125), , 300, 30, , , , , , .F., , )

		//Criando os botões
		oBtnSair := TButton():New(006, 001, "Fechar",             oPanSair, {|| oDlgPulo:End()}, nLargBtn, 018, , oFontBtn, , .T., , , , , , )

		//Cria a grid
		oGetGrid := FWBrowse():New()
        oGetGrid:SetDataTable()
        oGetGrid:SetInsert(.F.)
        oGetGrid:SetDelete(.F., { || .F. })
        oGetGrid:SetAlias(cAliasTmp)
        oGetGrid:DisableReport()
        oGetGrid:DisableFilter()
        oGetGrid:DisableConfig()
        oGetGrid:DisableReport()
        oGetGrid:DisableSeek()
        oGetGrid:DisableSaveConfig()
        oGetGrid:SetFontBrowse(oFontSay)
        oGetGrid:SetColumns(aColunas)
        oGetGrid:SetOwner(oPanGrid)
        oGetGrid:Activate()
	Activate MsDialog oDlgPulo Centered
    oTempTable:Delete()
Return

Static Function fCriaCols()
    Local nAtual   := 0 
    Local aColunas := {}
    Local aEstrut  := {}
    Local oColumn
    
    //Adicionando campos que serão mostrados na tela
    //[1] - Campo da Temporaria
    //[2] - Titulo
    //[3] - Tipo
    //[4] - Tamanho
    //[5] - Decimais
    //[6] - Máscara
    aAdd(aEstrut, {"CODIGO", "Código",                "C", TamSX3('BM_GRUPO')[01],   0, ""})
    aAdd(aEstrut, {"DESCRI", "Descrição",             "C", TamSX3('BM_DESC')[01],    0, ""})

    //Percorrendo todos os campos da estrutura
    For nAtual := 1 To Len(aEstrut)
        //Cria a coluna
        oColumn := FWBrwColumn():New()
        oColumn:SetData(&("{|| (cAliasTmp)->" + aEstrut[nAtual][1] +"}"))
        oColumn:SetTitle(aEstrut[nAtual][2])
        oColumn:SetType(aEstrut[nAtual][3])
        oColumn:SetSize(aEstrut[nAtual][4])
        oColumn:SetDecimal(aEstrut[nAtual][5])
        oColumn:SetPicture(aEstrut[nAtual][6])
        oColumn:bHeaderClick := &("{|| fOrdena('" + aEstrut[nAtual][1] + "') }")

        //Adiciona a coluna
        aAdd(aColunas, oColumn)
    Next
Return aColunas

Static Function fPopula()
    Local nAtual := 0
    Local nTotal := 0

    DbSelectArea("SBM")
    SBM->(DbSetOrder(1))
    SBM->(DbGoTop())

    //Define o tamanho da régua
    Count To nTotal
    ProcRegua(nTotal)
    SBM->(DbGoTop())

    //Enquanto houver itens
    While ! SBM->(EoF())
        //Incrementa a régua
        nAtual++
        IncProc("Adicionando registro " + cValToChar(nAtual) + " de " + cValToChar(nTotal) + "...")

        //Grava na temporária
        RecLock(cAliasTmp, .T.)
            (cAliasTmp)->CODIGO := SBM->BM_GRUPO
            (cAliasTmp)->DESCRI := SBM->BM_DESC
        (cAliasTmp)->(MsUnlock())

        SBM->(DbSkip())
    EndDo
Return

Usando Matemática

Esse segundo é mais braçal, mas funciona muito bem. Basicamente pessoal, 90% dos componentes gráficos em AdvPL, trabalham com uma escala dividida por 2 (quando se está internamente na Dialog).

Então, se criarmos uma tela com a dimensão de 200 de altura por 400 de largura, internamente nela, nós podemos declarar objetos, usando da linha 0 a 100 (metade de 200 de altura), e da coluna de 0 a 200 (metade de 400 de largura).

Com esse cenário, o centro da minha Dialog seria na linha 50 e na coluna 100. Agora basta eu pegar os componentes e fazer o cálculo da altura e largura deles, levando em conta o espaço que eu tenho na tela.

Por exemplo, um botão com 60 pixels de largura, para centralizar, eu preciso pegar a coluna do meio (nesse exemplo, 100) e subtrair metade do botão (nesse exemplo 30), então a coluna inicial do botão será 70.

Ai como é feito a matemática, se você aumentar a largura e a altura da janela (se estiverem em uma variável como nJanLarg e nJanAltu do exemplo), os componentes irão mudar sozinho também. Com essa lógica, se você quiser adicionar um componente no rodapé, basta pegar a altura da tela e subtrair a altura do componente. Se quiser adicionar um componente na direita, basta pegar a largura e subtrair. Existem inúmeras possibilidades.

Abaixo um print do exemplo acima:

Exemplo usando matemática

Abaixo o código fonte desenvolvido:

//Bibliotecas
#Include "TOTVS.ch"

/*/{Protheus.doc} User Function zPulo2
Montando uma dialog com matemática para calcular o centro da tela
@type  Function
@author Atilio
@since 17/04/2021
@version version
/*/

User Function zPulo2()
    Local bInit := {|| }
    //Fontes
    Local cFontPad    := "Tahoma"
    Local oFontBtn    := TFont():New(cFontPad, , -14)
    //Objetos
    Local oDlgPulo
    Local oBtnOK
    //Tamanho da janela
    Local nJanLarg := 400
    Local nJanAltu := 200
    Local nColMeio := (nJanLarg / 2) / 2 // Primeiro dividimos a largura por 2, para termos o tamanho interno da dialog, ai dividimos por 2 novamente para descobrir o meio - ex.: 100 pixels
    Local nLinMeio := (nJanAltu / 2) / 2 // Mesmo caso de acima
    
    //Cria a janela
    oDlgPulo := TDialog():New(0, 0, nJanAltu, nJanLarg, 'Pulo do Gato em Dialogs - Exemplo 2', , , , , CLR_BLACK, RGB(250, 250, 250), , , .T.)

        //Como a largura do botão é 60 e a altura é 18, iremos pegar a coluna do meio menos 30 e a linha do meio menos 9
        oBtnOK := TButton():New(nLinMeio - 9, nColMeio - 30, "Botão Centro",             oDlgPulo, {|| Alert("teste")}, 060, 018, , oFontBtn, , .T., , , , , , )

    //Ativa e exibe a janela
    oDlgPulo:Activate(, , , .T., {|| .T.}, , bInit )
Return

Update (28/05/2021):

A pedido do leitor Marco Nagoa, fizemos o exemplo usando matemática, com a mesma tela do exemplo usando FWLayer, segue o fonte abaixo:

//Bibliotecas
#Include "TOTVS.ch"
 
/*/{Protheus.doc} User Function zPulo2
Demonstração de como usar matemática para criar telas
@type Function
@author Atilio
@since 28/05/2021
@version 1.0
/*/
 
User Function zPulo2()
    Local aArea := GetArea()
 
    fMontaTela()
 
    RestArea(aArea)
Return
 
Static Function fMontaTela()
    Local nLargBtn      := 50
    //Objetos e componentes
    Private oPanGrid
    Private oDlgPulo
    //Cabeçalho
    Private oSayModulo, cSayModulo := 'TST'
    Private oSayTitulo, cSayTitulo := 'Pulo do Gato na Montagem de Dialogs'
    Private oSaySubTit, cSaySubTit := 'Exemplo usando Matemática'
    //Tamanho da janela
    Private aSize := MsAdvSize(.F.)
    Private nJanLarg := aSize[5]
    Private nJanAltu := aSize[6]
    //Fontes
    Private cFontUti    := "Tahoma"
    Private oFontMod    := TFont():New(cFontUti, , -38)
    Private oFontSub    := TFont():New(cFontUti, , -20)
    Private oFontSubN   := TFont():New(cFontUti, , -20, , .T.)
    Private oFontBtn    := TFont():New(cFontUti, , -14)
    Private oFontSay    := TFont():New(cFontUti, , -12)
    //Grid
    Private aCampos := {}
    Private cAliasTmp := "TST_" + RetCodUsr()
    Private aColunas := {}
 
    //Campos da Temporária
    aAdd(aCampos, { "CODIGO" , "C", TamSX3("BM_GRUPO")[1], 0 })
    aAdd(aCampos, { "DESCRI" , "C", TamSX3("BM_DESC")[1],  0 })
 
    //Cria a tabela temporária
    oTempTable:= FWTemporaryTable():New(cAliasTmp)
    oTempTable:SetFields( aCampos )
    oTempTable:Create()
 
    //Busca as colunas do browse
    aColunas := fCriaCols()
 
    //Popula a tabela temporária
    Processa({|| fPopula()}, "Processando...")
 
    //Cria a janela
    DEFINE MSDIALOG oDlgPulo TITLE "Exemplo de Pulo do Gato"  FROM 0, 0 TO nJanAltu, nJanLarg PIXEL
 
        //Títulos e SubTítulos
        oSayModulo := TSay():New(004, 003, {|| cSayModulo}, oDlgPulo, "", oFontMod,  , , , .T., RGB(149, 179, 215), , 200, 30, , , , , , .F., , )
        oSayTitulo := TSay():New(004, 045, {|| cSayTitulo}, oDlgPulo, "", oFontSub,  , , , .T., RGB(031, 073, 125), , 200, 30, , , , , , .F., , )
        oSaySubTit := TSay():New(014, 045, {|| cSaySubTit}, oDlgPulo, "", oFontSubN, , , , .T., RGB(031, 073, 125), , 300, 30, , , , , , .F., , )
 
        //Criando os botões
        oBtnSair := TButton():New(006, (nJanLarg/2-001)-((nLargBtn+2)*1), "Fechar",     oDlgPulo, {|| oDlgPulo:End()},  nLargBtn, 018, , oFontBtn, , .T., , , , , , )
 
        //Cria a grid
        oPanGrid := tPanel():New(027, 001, "", oDlgPulo, , , , RGB(000,000,000), RGB(254,254,254), (nJanLarg/2)-1, (nJanAltu/2)-3)
        oGetGrid := FWBrowse():New()
        oGetGrid:SetDataTable()
        oGetGrid:SetInsert(.F.)
        oGetGrid:SetDelete(.F., { || .F. })
        oGetGrid:SetAlias(cAliasTmp)
        oGetGrid:DisableReport()
        oGetGrid:DisableFilter()
        oGetGrid:DisableConfig()
        oGetGrid:DisableReport()
        oGetGrid:DisableSeek()
        oGetGrid:DisableSaveConfig()
        oGetGrid:SetFontBrowse(oFontSay)
        oGetGrid:SetColumns(aColunas)
        oGetGrid:SetOwner(oPanGrid)
        oGetGrid:Activate()
    Activate MsDialog oDlgPulo Centered
    oTempTable:Delete()
Return
 
Static Function fCriaCols()
    Local nAtual   := 0 
    Local aColunas := {}
    Local aEstrut  := {}
    Local oColumn
     
    //Adicionando campos que serão mostrados na tela
    //[1] - Campo da Temporaria
    //[2] - Titulo
    //[3] - Tipo
    //[4] - Tamanho
    //[5] - Decimais
    //[6] - Máscara
    aAdd(aEstrut, {"CODIGO", "Código",                "C", TamSX3('BM_GRUPO')[01],   0, ""})
    aAdd(aEstrut, {"DESCRI", "Descrição",             "C", TamSX3('BM_DESC')[01],    0, ""})
 
    //Percorrendo todos os campos da estrutura
    For nAtual := 1 To Len(aEstrut)
        //Cria a coluna
        oColumn := FWBrwColumn():New()
        oColumn:SetData(&("{|| (cAliasTmp)->" + aEstrut[nAtual][1] +"}"))
        oColumn:SetTitle(aEstrut[nAtual][2])
        oColumn:SetType(aEstrut[nAtual][3])
        oColumn:SetSize(aEstrut[nAtual][4])
        oColumn:SetDecimal(aEstrut[nAtual][5])
        oColumn:SetPicture(aEstrut[nAtual][6])
        oColumn:bHeaderClick := &("{|| fOrdena('" + aEstrut[nAtual][1] + "') }")
 
        //Adiciona a coluna
        aAdd(aColunas, oColumn)
    Next
Return aColunas
 
Static Function fPopula()
    Local nAtual := 0
    Local nTotal := 0
 
    DbSelectArea("SBM")
    SBM->(DbSetOrder(1))
    SBM->(DbGoTop())
 
    //Define o tamanho da régua
    Count To nTotal
    ProcRegua(nTotal)
    SBM->(DbGoTop())
 
    //Enquanto houver itens
    While ! SBM->(EoF())
        //Incrementa a régua
        nAtual++
        IncProc("Adicionando registro " + cValToChar(nAtual) + " de " + cValToChar(nTotal) + "...")
 
        //Grava na temporária
        RecLock(cAliasTmp, .T.)
            (cAliasTmp)->CODIGO := SBM->BM_GRUPO
            (cAliasTmp)->DESCRI := SBM->BM_DESC
        (cAliasTmp)->(MsUnlock())
 
        SBM->(DbSkip())
    EndDo
Return

Bom pessoal, por hoje é só.

Abraços e até a próxima.

Dan Atilio (Daniel Atilio)
Especialista em Engenharia de Software pela FIB. Entusiasta de soluções Open Source. E blogueiro nas horas vagas.

4 Responses

  1. Marco Nagoa - Conectar disse:

    Bacana demais, melhor seria se os dois exemplos gerassem a mesma tela. Grato Dan.

  2. crisrud disse:

    Parabéns Atílio. Muito bom!

Deixe uma resposta