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):
Já 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.