Pragiva — Tecnologia na prática

MenuDef: as opções de uma rotina MVC

MenuDef declara o aRotina do browse MVC: seis posições fixas, uma única ação repetida cinco vezes, e duas escalas de operação que não são a mesma coisa.

Protheus Referência técnica 5 min de leitura

Assista o vídeo

Resposta rápida

O que é
MenuDef é a Static Function que devolve o aRotina, o array que o browse usa para montar o menu de opções.
Onde mora o comando
ADD OPTION não é função, é um #xcommand de FWMBrowse.ch — incluído automaticamente por FWMVCDef.ch.
O array
6 posições por linha — título, ação, um 0 fixo, operação, acesso, desabilita-menu.
Operação
duas escalas diferentes no mesmo include — MODEL_OPERATION_VIEW vale 1, OP_VISUALIZAR vale 2.
Precisa de include próprio?
Não. #Include "FWMVCDef.ch" já traz FWMBrowse.ch junto.
Consulta rápida5 seções

MenuDef é uma das três funções de nome obrigatório que o framework MVC procura, ao lado de ModelDef e ViewDef. A responsabilidade dela é estreita: dizer que opções aparecem no menu do browse e para onde cada uma manda a execução. Nada nela decide o que grava — isso é trabalho do modelo.

O comando que grava seis posições

ADD OPTION parece um comando de linguagem, mas é macro de compilação. É um #xcommand que expande literalmente para um Aadd:

ADVPL / TLPP
#xcommand ADD OPTION <aRotina> ;
 TITLE <cTitle> ;
 ACTION <cAction> ;
 OPERATION <nOperation> ;
 ACCESS <nAccess> ;
 [ <lDisable: DISABLE MENU> ] ;
 =>;
 Aadd( <aRotina>, { <cTitle>, <cAction>, 0, <nOperation>, <nAccess>, [<.lDisable.>]})

Ler a expansão resolve uma dúvida comum: por que aRotina tem seis posições se a cláusula só menciona quatro palavras-chave. Está no corpo do Aadd — a terceira posição é o número 0, escrito pelo próprio comando.

PosiçãoVem deConteúdo
1TITLETexto exibido no menu
2ACTIONNome da função ou VIEWDEF.<fonte> a executar
3—0 fixo, gravado pelo comando
4OPERATIONNúmero da operação (ver as duas escalas, abaixo)
5ACCESSNível de acesso exigido
6DISABLE MENUOpcional — desabilita a opção sem removê-la

TITLE, ACTION, OPERATION e ACCESS são obrigatórias; só o bloco entre colchetes, DISABLE MENU, é opcional.

Cinco opções, uma ação só

No PGV010.prw, o fonte de exemplo deste site, MenuDef declara cinco linhas:

ADVPL / TLPP
Static Function MenuDef()

 Local aRotina := {}

 ADD OPTION aRotina TITLE "Pesquisar" ACTION "AxPesqui" OPERATION 1 ACCESS 0
 ADD OPTION aRotina TITLE "Visualizar" ACTION "VIEWDEF.PGV010" OPERATION 2 ACCESS 0
 ADD OPTION aRotina TITLE "Incluir" ACTION "VIEWDEF.PGV010" OPERATION 3 ACCESS 0
 ADD OPTION aRotina TITLE "Alterar" ACTION "VIEWDEF.PGV010" OPERATION 4 ACCESS 0
 ADD OPTION aRotina TITLE "Excluir" ACTION "VIEWDEF.PGV010" OPERATION 5 ACCESS 0

Return aRotina

Repare no padrão: visualizar, incluir, alterar e excluir apontam para a mesma string, "VIEWDEF.PGV010". O que muda entre as quatro é só o número do OPERATION. A função que carrega essa ação é FWLoadView, e o corpo dela explica por que o prefixo VIEWDEF. funciona sem nenhuma lógica especial de roteamento:

ADVPL / TLPP
Function FWLoadView(cProgram)
Local oObjeto
If "." $ cProgram
 cProgram := SubStr(cProgram,At(".",cProgram)+1)
EndIf
oObjeto := &("STATICCALL("+cProgram+",ViewDef,'"+cProgram+"')")
Return oObjeto

FWLoadView procura um ponto na string recebida e, se encontrar, descarta tudo o que vem antes dele. "VIEWDEF.PGV010" vira "PGV010", que é o nome real do fonte passado para STATICCALL. O texto VIEWDEF na frente não é um comando reconhecido por palavra — é só o prefixo que qualquer string precisa ter para o corte fazer sentido; a função cortaria da mesma forma diante de outro texto antes do ponto. É essa mesma macro-execução, STATICCALL(<fonte>, ViewDef,...), que explica por que ViewDef precisa se chamar exatamente assim e por que o argumento é o nome do fonte, nunca o nome da rotina.

A opção “Pesquisar” quebra o padrão de propósito: sua ACTION é "AxPesqui", função de framework pronta para consulta, não "VIEWDEF.PGV010". Pesquisar não abre a view; só lista.

Duas escalas de operação, no mesmo include

Este é o ponto que mais engana quem lê aRotina de cabeça. O número que vai na quarta posição de cada opção não é o mesmo número que o modelo devolve quando alguém chama GetOperation(). As duas famílias de constante moram no mesmo FWMVCDef.ch:

ADVPL / TLPP
#DEFINE MODEL_OPERATION_VIEW 1
#DEFINE MODEL_OPERATION_INSERT 3
#DEFINE MODEL_OPERATION_UPDATE 4
#DEFINE MODEL_OPERATION_DELETE 5
#DEFINE MODEL_OPERATION_ONLYUPDATE 6
ADVPL / TLPP
#DEFINE OP_PESQUISAR 1
#DEFINE OP_VISUALIZAR 2
#DEFINE OP_INCLUIR 3
#DEFINE OP_ALTERAR 4
#DEFINE OP_EXCLUIR 5
#DEFINE OP_IMPRIMIR 8

Incluir, alterar e excluir coincidem nas duas famílias (3, 4 e 5), o que disfarça o problema até alguém testar visualizar — a única operação em que as escalas divergem.

A confusão piora porque módulos do próprio pacote definem constantes MODEL_OPERATION_* locais, em includes próprios do módulo, e essas colidem entre si: uma declara MODEL_OPERATION_GERAP valendo 6 — o mesmo número de MODEL_OPERATION_ONLYUPDATE no framework — e outras duas declaram MODEL_OPERATION_COPY e MODEL_OPERATION_CONTABILIZAR, ambas valendo 9. Nenhuma dessas três é constante do FWMVCDef.ch; são convenções locais de quem escreveu aquele módulo, reaproveitando o prefixo MODEL_OPERATION_ sem reaproveitar o valor. Antes de usar uma constante desse nome fora do próprio módulo em que ela apareceu, confira em qual include ela está definida.

Entre as duas famílias, é a MODEL_OPERATION_* que decide, dentro do modelo, o que uma rotina faz com o registro atual — a mais consultada por código de validação. Na prática, UPDATE, INSERT e DELETE dominam esse uso, porque gravação e exclusão concentram a maior parte da regra de negócio; VIEW aparece bem menos, já que visualizar raramente precisa de uma trava própria no modelo.

O que existe e o que não existe

FWMVCMenu(<fonte>) existe e é chamada sempre com um único argumento — o nome do fonte. A implementação dela não é pública, então não há como afirmar aqui o que ela devolve, só que ela é chamada dessa forma. Duas armadilhas de nome: FWMenuDef não existe, em forma nenhuma. E não existe AddOption como função de menu MVC — o que monta o aRotina é o #xcommand ADD OPTION, sem parênteses de chamada. O nome AddOption existe, mas como método de uma classe de outro módulo, sem relação com o menu de uma rotina MVC; procurá-lo esperando a API de menu leva ao lugar errado.

ADD OPTION também não compila em TLPP — toda opção de menu de uma rotina MVC ainda passa por esse #xcommand em AdvPL, mesmo num fonte que usa TLPP para o resto do código. O material de curso costuma explicar isso por incompatibilidade do pré-processador com o #xcommand; essa causa não está verificada aqui, só o efeito: nenhuma tela MVC monta o próprio menu fora de AdvPL.

Como o browse encontra o MenuDef

Nada disso funciona sozinho: o browse precisa saber de que fonte ler o MenuDef. É o papel de SetMenuDef, chamado pela User Function principal antes de Activate. Ela recebe o nome do fonte — o mesmo argumento que alimenta FWLoadModel e FWLoadView — e não o nome de nenhuma rotina interna. Um SetMenuDef apontando para o fonte errado carrega, em silêncio, o aRotina de outra tela: o browse sobe, mas as opções que aparecem não são as que aquele MenuDef específico declarou. É o mesmo cuidado de nome de fonte que atravessa ModelDef e ViewDef — a diferença é que aqui o sintoma não é um erro de compilação, é um menu com a opção errada.


Se uma opção de menu não abre a rotina que deveria e ninguém acha o motivo, fale com a gente.

Referências

  1. TDNtdn.totvs.com

    TDN — AdvPL utilizando MVC

Continue por aqui

  1. Vídeo

    ProtheusReferência técnica

    ViewDef: a tela de uma rotina MVC

    ViewDef não valida nem grava: só recebe o modelo pronto e diz onde cada componente aparece. O que acontece sem SetOwnerView e quando o box não é preciso.

    4 min de leituraAssista o vídeo