Olá pessoal…
Na aula de hoje vou mostrar como criar uma Modelo 2 em MVC (Cadastro com pai e filho, mas utilizando 1 única tabela, como pedido de compras – SC7).
Usando Autumn Code Maker
Caso você queira gerar o código de maneira rápida, nós temos essa opção no Autumn Code Maker, onde você pode gerar o código prw em poucos minutos.
Se tiver interesse, acesse o artigo Como criar uma tela Modelo 2 em MVC em poucos passos.
Ou se preferir, na aba Artigo Original, tem o conteúdo original de 2017 desse post.
Artigo Original
Update – 14/12/2020:
Foi criado um novo exemplo, mais atual, sobre a utilização do conceito de Modelo 2 em MVC, veja acessando o seguinte link: https://terminaldeinformacao.com/2020/12/14/novo-exemplo-de-modelo-2-em-mvc-2020/
Postagem Original:
Abaixo o código fonte.
//Bibliotecas
#Include 'Protheus.ch'
#Include 'FWMVCDef.ch'
//Variáveis Estáticas
Static cTitulo := "Tabelas Genéricas"
/*/{Protheus.doc} zModel2
Exemplo de Modelo 2 para cadastro de SX5
@author Atilio
@since 14/01/2017
@version 1.0
@return Nil, Função não tem retorno
@example
u_zModel2()
@obs Para o erro na exclusão - FORMCANDEL da CC2, abra a SX9, e onde tiver '12'+CC2_COD, substitua por '12'+CC2_CODMUN
/*/
User Function zModel2()
Local aArea := GetArea()
Local oBrowse
Local cFunBkp := FunName()
SetFunName("zModel2")
//Cria um browse para a SX5, filtrando somente a tabela 00 (cabeçalho das tabelas
oBrowse := FWMBrowse():New()
oBrowse:SetAlias("SX5")
oBrowse:SetDescription(cTitulo)
oBrowse:SetFilterDefault("SX5->X5_TABELA == '00'")
oBrowse:Activate()
SetFunName(cFunBkp)
RestArea(aArea)
Return Nil
/*---------------------------------------------------------------------*
| Func: MenuDef |
| Autor: Daniel Atilio |
| Data: 14/01/2017 |
| Desc: Criação do menu MVC |
*---------------------------------------------------------------------*/
Static Function MenuDef()
Local aRot := {}
//Adicionando opções
ADD OPTION aRot TITLE 'Visualizar' ACTION 'VIEWDEF.zModel2' OPERATION MODEL_OPERATION_VIEW ACCESS 0 //OPERATION 1
ADD OPTION aRot TITLE 'Incluir' ACTION 'VIEWDEF.zModel2' OPERATION MODEL_OPERATION_INSERT ACCESS 0 //OPERATION 3
ADD OPTION aRot TITLE 'Alterar' ACTION 'VIEWDEF.zModel2' OPERATION MODEL_OPERATION_UPDATE ACCESS 0 //OPERATION 4
ADD OPTION aRot TITLE 'Excluir' ACTION 'VIEWDEF.zModel2' OPERATION MODEL_OPERATION_DELETE ACCESS 0 //OPERATION 5
Return aRot
/*---------------------------------------------------------------------*
| Func: ModelDef |
| Autor: Daniel Atilio |
| Data: 14/01/2017 |
| Desc: Criação do modelo de dados MVC |
*---------------------------------------------------------------------*/
Static Function ModelDef()
Local oModel := Nil
Local oStTmp := FWFormModelStruct():New()
Local oStFilho := FWFormStruct(1, 'SX5')
Local bVldPos := {|| u_zVldX5Tab()}
Local bVldCom := {|| u_zSaveMd2()}
Local aSX5Rel := {}
//Adiciona a tabela na estrutura temporária
oStTmp:AddTable('SX5', {'X5_FILIAL', 'X5_CHAVE', 'X5_DESCRI'}, "Cabecalho SX5")
//Adiciona o campo de Filial
oStTmp:AddField(;
"Filial",; // [01] C Titulo do campo
"Filial",; // [02] C ToolTip do campo
"X5_FILIAL",; // [03] C Id do Field
"C",; // [04] C Tipo do campo
TamSX3("X5_FILIAL")[1],; // [05] N Tamanho do campo
0,; // [06] N Decimal do campo
Nil,; // [07] B Code-block de validação do campo
Nil,; // [08] B Code-block de validação When do campo
{},; // [09] A Lista de valores permitido do campo
.F.,; // [10] L Indica se o campo tem preenchimento obrigatório
FwBuildFeature( STRUCT_FEATURE_INIPAD, "Iif(!INCLUI,SX5->X5_FILIAL,FWxFilial('SX5'))" ),; // [11] B Code-block de inicializacao do campo
.T.,; // [12] L Indica se trata-se de um campo chave
.F.,; // [13] L Indica se o campo pode receber valor em uma operação de update.
.F.) // [14] L Indica se o campo é virtual
//Adiciona o campo de Código da Tabela
oStTmp:AddField(;
"Tabela",; // [01] C Titulo do campo
"Tabela",; // [02] C ToolTip do campo
"X5_CHAVE",; // [03] C Id do Field
"C",; // [04] C Tipo do campo
TamSX3("X5_TABELA")[1],; // [05] N Tamanho do campo
0,; // [06] N Decimal do campo
Nil,; // [07] B Code-block de validação do campo
Nil,; // [08] B Code-block de validação When do campo
{},; // [09] A Lista de valores permitido do campo
.T.,; // [10] L Indica se o campo tem preenchimento obrigatório
FwBuildFeature( STRUCT_FEATURE_INIPAD, "Iif(!INCLUI,SX5->X5_CHAVE,'')" ),; // [11] B Code-block de inicializacao do campo
.T.,; // [12] L Indica se trata-se de um campo chave
.F.,; // [13] L Indica se o campo pode receber valor em uma operação de update.
.F.) // [14] L Indica se o campo é virtual
//Adiciona o campo de Descrição
oStTmp:AddField(;
"Descricao",; // [01] C Titulo do campo
"Descricao",; // [02] C ToolTip do campo
"X5_DESCRI",; // [03] C Id do Field
"C",; // [04] C Tipo do campo
TamSX3("X5_DESCRI")[1],; // [05] N Tamanho do campo
0,; // [06] N Decimal do campo
Nil,; // [07] B Code-block de validação do campo
Nil,; // [08] B Code-block de validação When do campo
{},; // [09] A Lista de valores permitido do campo
.T.,; // [10] L Indica se o campo tem preenchimento obrigatório
FwBuildFeature( STRUCT_FEATURE_INIPAD, "Iif(!INCLUI,SX5->X5_DESCRI,'')" ),; // [11] B Code-block de inicializacao do campo
.F.,; // [12] L Indica se trata-se de um campo chave
.F.,; // [13] L Indica se o campo pode receber valor em uma operação de update.
.F.) // [14] L Indica se o campo é virtual
//Setando as propriedades na grid, o inicializador da Filial e Tabela, para não dar mensagem de coluna vazia
oStFilho:SetProperty('X5_FILIAL', MODEL_FIELD_INIT, FwBuildFeature(STRUCT_FEATURE_INIPAD, '"*"'))
oStFilho:SetProperty('X5_TABELA', MODEL_FIELD_INIT, FwBuildFeature(STRUCT_FEATURE_INIPAD, '"*"'))
//Criando o FormModel, adicionando o Cabeçalho e Grid
oModel := MPFormModel():New("zModel2M", , bVldPos, bVldCom)
oModel:AddFields("FORMCAB",/*cOwner*/,oStTmp)
oModel:AddGrid('SX5DETAIL','FORMCAB',oStFilho)
//Adiciona o relacionamento de Filho, Pai
aAdd(aSX5Rel, {'X5_FILIAL', 'Iif(!INCLUI, SX5->X5_FILIAL, FWxFilial("SX5"))'} )
aAdd(aSX5Rel, {'X5_TABELA', 'Iif(!INCLUI, SX5->X5_CHAVE, "")'} )
//Criando o relacionamento
oModel:SetRelation('SX5DETAIL', aSX5Rel, SX5->(IndexKey(1)))
//Setando o campo único da grid para não ter repetição
oModel:GetModel('SX5DETAIL'):SetUniqueLine({"X5_CHAVE"})
//Setando outras informações do Modelo de Dados
oModel:SetDescription("Modelo de Dados do Cadastro "+cTitulo)
oModel:SetPrimaryKey({})
oModel:GetModel("FORMCAB"):SetDescription("Formulário do Cadastro "+cTitulo)
Return oModel
/*---------------------------------------------------------------------*
| Func: ViewDef |
| Autor: Daniel Atilio |
| Data: 14/01/2017 |
| Desc: Criação da visão MVC |
*---------------------------------------------------------------------*/
Static Function ViewDef()
Local oModel := FWLoadModel("zModel2")
Local oStTmp := FWFormViewStruct():New()
Local oStFilho := FWFormStruct(2, 'SX5')
Local oView := Nil
//Adicionando o campo Chave para ser exibido
oStTmp:AddField(;
"X5_CHAVE",; // [01] C Nome do Campo
"01",; // [02] C Ordem
"Tabela",; // [03] C Titulo do campo
X3Descric('X5_TABELA'),; // [04] C Descricao do campo
Nil,; // [05] A Array com Help
"C",; // [06] C Tipo do campo
X3Picture("X5_TABELA"),; // [07] C Picture
Nil,; // [08] B Bloco de PictTre Var
Nil,; // [09] C Consulta F3
Iif(INCLUI, .T., .F.),; // [10] L Indica se o campo é alteravel
Nil,; // [11] C Pasta do campo
Nil,; // [12] C Agrupamento do campo
Nil,; // [13] A Lista de valores permitido do campo (Combo)
Nil,; // [14] N Tamanho maximo da maior opção do combo
Nil,; // [15] C Inicializador de Browse
Nil,; // [16] L Indica se o campo é virtual
Nil,; // [17] C Picture Variavel
Nil) // [18] L Indica pulo de linha após o campo
oStTmp:AddField(;
"X5_DESCRI",; // [01] C Nome do Campo
"02",; // [02] C Ordem
"Descricao",; // [03] C Titulo do campo
X3Descric('X5_DESCRI'),; // [04] C Descricao do campo
Nil,; // [05] A Array com Help
"C",; // [06] C Tipo do campo
X3Picture("X5_DESCRI"),; // [07] C Picture
Nil,; // [08] B Bloco de PictTre Var
Nil,; // [09] C Consulta F3
.T.,; // [10] L Indica se o campo é alteravel
Nil,; // [11] C Pasta do campo
Nil,; // [12] C Agrupamento do campo
Nil,; // [13] A Lista de valores permitido do campo (Combo)
Nil,; // [14] N Tamanho maximo da maior opção do combo
Nil,; // [15] C Inicializador de Browse
Nil,; // [16] L Indica se o campo é virtual
Nil,; // [17] C Picture Variavel
Nil) // [18] L Indica pulo de linha após o campo
//Criando a view que será o retorno da função e setando o modelo da rotina
oView := FWFormView():New()
oView:SetModel(oModel)
oView:AddField("VIEW_CAB", oStTmp, "FORMCAB")
oView:AddGrid('VIEW_SX5',oStFilho,'SX5DETAIL')
//Setando o dimensionamento de tamanho
oView:CreateHorizontalBox('CABEC',30)
oView:CreateHorizontalBox('GRID',70)
//Amarrando a view com as box
oView:SetOwnerView('VIEW_CAB','CABEC')
oView:SetOwnerView('VIEW_SX5','GRID')
//Habilitando título
oView:EnableTitleView('VIEW_CAB','Cabeçalho - Tabela Genérica')
oView:EnableTitleView('VIEW_SX5','Itens - Tabela Genérica')
//Tratativa padrão para fechar a tela
oView:SetCloseOnOk({||.T.})
//Remove os campos de Filial e Tabela da Grid
oStFilho:RemoveField('X5_FILIAL')
oStFilho:RemoveField('X5_TABELA')
Return oView
/*/{Protheus.doc} zVldX5Tab
Função chamada na validação do botão Confirmar, para verificar se já existe a tabela digitada
@type function
@author Atilio
@since 14/01/2017
@version 1.0
@return lRet, .T. se pode prosseguir e .F. se deve barrar
/*/
User Function zVldX5Tab()
Local aArea := GetArea()
Local lRet := .T.
Local oModelDad := FWModelActive()
Local cFilSX5 := oModelDad:GetValue('FORMCAB', 'X5_FILIAL')
Local cCodigo := SubStr(oModelDad:GetValue('FORMCAB', 'X5_CHAVE'), 1, TamSX3('X5_TABELA')[01])
Local nOpc := oModelDad:GetOperation()
//Se for Inclusão
If nOpc == MODEL_OPERATION_INSERT
DbSelectArea('SX5')
SX5->(DbSetOrder(1)) //X5_FILIAL + X5_TABELA + X5_CHAVE
//Se conseguir posicionar, tabela já existe
If SX5->(DbSeek(cFilSX5 + '00' + cCodigo))
Aviso('Atenção', 'Esse código de tabela já existe!', {'OK'}, 02)
lRet := .F.
EndIf
EndIf
RestArea(aArea)
Return lRet
/*/{Protheus.doc} zSaveMd2
Função desenvolvida para salvar os dados do Modelo 2
@type function
@author Atilio
@since 14/01/2017
@version 1.0
/*/
User Function zSaveMd2()
Local aArea := GetArea()
Local lRet := .T.
Local oModelDad := FWModelActive()
Local cFilSX5 := oModelDad:GetValue('FORMCAB', 'X5_FILIAL')
Local cCodigo := SubStr(oModelDad:GetValue('FORMCAB', 'X5_CHAVE'), 1, TamSX3('X5_TABELA')[01])
Local cDescri := oModelDad:GetValue('FORMCAB', 'X5_DESCRI')
Local nOpc := oModelDad:GetOperation()
Local oModelGrid := oModelDad:GetModel('SX5DETAIL')
Local aHeadAux := oModelGrid:aHeader
Local aColsAux := oModelGrid:aCols
Local nPosChave := aScan(aHeadAux, {|x| AllTrim(Upper(x[2])) == AllTrim("X5_CHAVE")})
Local nPosDescPt := aScan(aHeadAux, {|x| AllTrim(Upper(x[2])) == AllTrim("X5_DESCRI")})
Local nPosDescSp := aScan(aHeadAux, {|x| AllTrim(Upper(x[2])) == AllTrim("X5_DESCSPA")})
Local nPosDescEn := aScan(aHeadAux, {|x| AllTrim(Upper(x[2])) == AllTrim("X5_DESCENG")})
Local nAtual := 0
DbSelectArea('SX5')
SX5->(DbSetOrder(1)) //X5_FILIAL + X5_TABELA + X5_CHAVE
//Se for Inclusão
If nOpc == MODEL_OPERATION_INSERT
//Cria o registro na tabela 00 (Cabeçalho de tabelas)
RecLock('SX5', .T.)
X5_FILIAL := cFilSX5
X5_TABELA := '00'
X5_CHAVE := cCodigo
X5_DESCRI := cDescri
X5_DESCSPA := cDescri
X5_DESCENG := cDescri
SX5->(MsUnlock())
//Percorre as linhas da grid
For nAtual := 1 To Len(aColsAux)
//Se a linha não estiver excluída, inclui o registro
If ! aColsAux[nAtual][Len(aHeadAux)+1]
RecLock('SX5', .T.)
X5_FILIAL := cFilSX5
X5_TABELA := cCodigo
X5_CHAVE := aColsAux[nAtual][nPosChave]
X5_DESCRI := aColsAux[nAtual][nPosDescPt]
X5_DESCSPA := aColsAux[nAtual][nPosDescSp]
X5_DESCENG := aColsAux[nAtual][nPosDescEn]
SX5->(MsUnlock())
EndIf
Next
//Se for Alteração
ElseIf nOpc == MODEL_OPERATION_UPDATE
//Se conseguir posicionar, altera a descrição digitada
If SX5->(DbSeek(cFilSX5 + '00' + cCodigo))
RecLock('SX5', .F.)
X5_DESCRI := cDescri
X5_DESCSPA := cDescri
X5_DESCENG := cDescri
SX5->(MsUnlock())
EndIf
//Percorre o acols
For nAtual := 1 To Len(aColsAux)
//Se a linha estiver excluída
If aColsAux[nAtual][Len(aHeadAux)+1]
//Se conseguir posicionar, exclui o registro
If SX5->(DbSeek(cFilSX5 + cCodigo + aColsAux[nAtual][nPosChave]))
RecLock('SX5', .F.)
DbDelete()
SX5->(MsUnlock())
EndIf
Else
//Se conseguir posicionar no registro, será alteração
If SX5->(DbSeek(cFilSX5 + cCodigo + aColsAux[nAtual][nPosChave]))
RecLock('SX5', .F.)
//Senão, será inclusão
Else
RecLock('SX5', .T.)
X5_FILIAL := cFilSX5
X5_TABELA := cCodigo
X5_CHAVE := aColsAux[nAtual][nPosChave]
EndIf
X5_DESCRI := aColsAux[nAtual][nPosDescPt]
X5_DESCSPA := aColsAux[nAtual][nPosDescSp]
X5_DESCENG := aColsAux[nAtual][nPosDescEn]
SX5->(MsUnlock())
EndIf
Next
//Se for Exclusão
ElseIf nOpc == MODEL_OPERATION_DELETE
//Se conseguir posicionar, exclui o registro
If SX5->(DbSeek(cFilSX5 + '00' + cCodigo))
RecLock('SX5', .F.)
DbDelete()
SX5->(MsUnlock())
EndIf
//Percorre a grid
For nAtual := 1 To Len(aColsAux)
//Se conseguir posicionar, exclui o registro
If SX5->(DbSeek(cFilSX5 + cCodigo + aColsAux[nAtual][nPosChave]))
RecLock('SX5', .F.)
DbDelete()
SX5->(MsUnlock())
EndIf
Next
EndIf
//Se não for inclusão, volta o INCLUI para .T. (bug ao utilizar a Exclusão, antes da Inclusão)
If nOpc != MODEL_OPERATION_INSERT
INCLUI := .T.
EndIf
RestArea(aArea)
Return lRet
Bom pessoal, por hoje é só.
Abraços e até a próxima.
Olá, DAN_ATILIO
Tentei alterar o seu fonte para uma tabela SZ2, estou com uma mensagem de erro e não encontro nenhuma referência, será que pode me ajudar?
“error in parameter FWFormField: The model FORMCAB contains no reference to the field Z2_CODPRO ”
No meu FORMCAB, não tenho o campo Z2_CODPRO. Ele fica no SZ2DETAIL.
Boa tarde.
Na sua oStTmp você não adicionou esse campo não?
Mande o fonte para eu poder dar uma olhada, talvez identifico algo.
Bom dia,
Copiei e colei seu código fonte, para poder entender melhor. Não fiz nenhuma alteração. Mas ele não mostra grid. Abre uma tela de cadastro normal.
Poderia me ajudar?
Obrigada,
Lis Oliveira
Boa noite Lis, tudo bem?
Que estranho, poderia me mandar o prw por e-Mail através da página de Contato?
Obrigado.
no que eu fiz tambem nao apareceu a grid…
Boa noite Breno.
Por favor, me mande o prw para eu conferir aqui, lembrando que:
– O nome do prw tem que ser o mesmo da User Function (zModel2.prw)
– Tente executar diretamente do menu, sem usar o Fórmulas
Fico no aguardo.
Abraços.
Amigo, olá, eu não estou conseguindo salvar, diz que preciso preencher os campos obrigatorios, eu já tirei todos os campos da tabela de obrigatorio, eu preencho e ainda assim não consigo :/
Boa noite Breno, tudo bem?
No exemplo da SX5 igual o que disponibilizei, funciona?
Abraços.
Precisava colocar a função no Menu. Chamando de fórmulas dava este problema.
Porque você precisou criar as funções para salvar? O MVC não faz isso sozinho?
Boa noite Lis, tudo bem?
Sim salva, se você olhar as vídeo aulas antigas eu “não fiz esse macete”.
Porém, para Modelo 1 (1 tabela) e Modelo 3 (2 tabelas) o MVC se comporta normalmente, porém para Modelo 2 (1 tabela com cabeçalho e filho), ele não se comporta adequadamente, então tem que fazer manualmente o salvamento.
Abraços.
Segui exatamente os passos mas na hora de salvar o grid, não estou conseguindo acessar o aCols na parte :
Local aColsAux := oModelGrid:aCols
Boa tarde Guilherme, tudo bem?
Nas versões novas do Protheus, alternaram o modo de usar o aCols antigo. Para acessar você deve usar o método SetUseOldGrid ( https://terminaldeinformacao.com/knowledgebase/metodo-setuseoldgrid/ ).
Ou utilizar os métodos padrões novos como GoLine e GetValue ( https://terminaldeinformacao.com/knowledgebase/metodo-goline/ ).
Abraços.
Olá Dan_Atilio !
Alterei o seu fonte porém quando compilo estou com um erro de: argumento #8, parâmetrocLookUperro, previstoC,B->L on FWFORMVIEWSTRUCT:ADDFIELD(FWFORMVIEWSTRUCT.PRW) 23/10/2018 18:06:50 line : 162, teria como verificar para mim ? Obrigado!
Olá Elmer.
Veja na estrutura dos campos, como está o F3 deles, algum pode estar incorreto.
Abraços.
Atilio, boa tarde!
É possivel ao inves do cabeçalho ser uma grid tambem?
Como se fosse o cadastro de formação de preço.
Obrigado
Bom dia.
Sim é possível sim, dê uma olhada no exemplo da Modelo X, nele tem duas grids com SetRelation.
Grande abraço.
Boa tarde,
Preciso fazer um modelo 2 com uma só tabela, igual ao exemplo passado, porem a linha “Local aColsAux := oModelGrid:aCols”, o acols está vindo vazio.
Gostaria de saber se alguem tem um fonte com esse mesmo exemplo só que atualizado.
Desde já agradeço.
Obs.:
Só achei aqui esse exemplo de modelos2 com uma só tabela. Parabéns ao Terminal de Informação.
Leonardo Lourenço
Boa tarde, tudo bem?
Leonardo, para usar o aCols da forma antiga, você deve usar o método SetUseOldGrid.
Exemplo: https://terminaldeinformacao.com/knowledgebase/metodo-setuseoldgrid/
Bom dia , o comando oStFilho:SetProperty(‘ZZZ_DATA’, MODEL_FIELD_INIT, FwBuildFeature(STRUCT_FEATURE_INIPAD, ‘”*”‘)) para campo tipo data não funciona poderia me passar como devo proceder?
Bom dia Paulo, tudo bem?
Para a parte de Enchoice, o comando funciona normalmente, segue 3 exemplos (um com a data atual onde roda o AppServer, outro com a data logada no sistema, e outra com o dia primeiro de novembro):
Static Function ModelDef() Local oStruct := FWFormStruct(1, "ZD1") Local oModel //Deixe um dos 3 abaixo para inicializar o campo data //oStruct:SetProperty('ZD1_DTFORM', MODEL_FIELD_INIT, FwBuildFeature(STRUCT_FEATURE_INIPAD, 'Date()')) //oStruct:SetProperty('ZD1_DTFORM', MODEL_FIELD_INIT, FwBuildFeature(STRUCT_FEATURE_INIPAD, 'dDataBase')) //oStruct:SetProperty('ZD1_DTFORM', MODEL_FIELD_INIT, FwBuildFeature(STRUCT_FEATURE_INIPAD, 'sToD("20221101")')) //Resto do seu ModelDefJá para Grids, eu nunca precisei inicializar o campo Data, então não sei dizer se ele irá respeitar os comandos acima. Caso não respeite, ai uma alternativa é interceptar as novas linhas da grid, e você dar manualmente um SetValue ou FWFldPut nelas com a data.
Abraços.