Resposta rápida
- O que é
MenuDefé aStatic Functionque devolve oaRotina, o array que o browse usa para montar o menu de opções.- Onde mora o comando
ADD OPTIONnão é função, é um#xcommanddeFWMBrowse.ch— incluído automaticamente porFWMVCDef.ch.- O array
- 6 posições por linha — título, ação, um
0fixo, operação, acesso, desabilita-menu. - Operação
- duas escalas diferentes no mesmo include —
MODEL_OPERATION_VIEWvale 1,OP_VISUALIZARvale 2. - Precisa de include próprio?
- Não.
#Include "FWMVCDef.ch"já trazFWMBrowse.chjunto.
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:
#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ção | Vem de | Conteúdo |
|---|---|---|
| 1 | TITLE | Texto exibido no menu |
| 2 | ACTION | Nome da função ou VIEWDEF.<fonte> a executar |
| 3 | — | 0 fixo, gravado pelo comando |
| 4 | OPERATION | Número da operação (ver as duas escalas, abaixo) |
| 5 | ACCESS | Nível de acesso exigido |
| 6 | DISABLE MENU | Opcional — 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:
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 aRotinaRepare 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:
Function FWLoadView(cProgram)
Local oObjeto
If "." $ cProgram
cProgram := SubStr(cProgram,At(".",cProgram)+1)
EndIf
oObjeto := &("STATICCALL("+cProgram+",ViewDef,'"+cProgram+"')")
Return oObjetoFWLoadView 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:
#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#DEFINE OP_PESQUISAR 1
#DEFINE OP_VISUALIZAR 2
#DEFINE OP_INCLUIR 3
#DEFINE OP_ALTERAR 4
#DEFINE OP_EXCLUIR 5
#DEFINE OP_IMPRIMIR 8Incluir, 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
TDNtdn.totvs.com
TDN — AdvPL utilizando MVC
Continue por aqui
- 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.