Resposta rápida
- O que monta
- a ViewDef é a tela — só posiciona campos e grids que o ModelDef já definiu.
- Sequência real
- FWLoadModel, FWFormStruct(2,…), FWFormView():New(), SetModel, AddField/AddGrid, CreateHorizontalBox, SetOwnerView.
- Decide alguma regra?
- Não. Validação e gravação são do modelo; a view não valida nada.
- Erro mais comum
- componente sem SetOwnerView existe, mas não aparece na tela.
- Box é obrigatório?
- Não. Uma view com um único AddField pode não ter box nenhum.
Consulta rápida7 seções
A ViewDef é a terceira das três Static Function de nome obrigatório que o
framework MVC procura num fonte — ao lado de MenuDef e ModelDef. Ela monta
a interface, mas não guarda estado nenhum sobre o que é válido ou o que grava.
Todo esse trabalho já foi feito por ModelDef antes de a ViewDef sequer ser
chamada. O que ela faz é pegar o modelo pronto e responder uma pergunta
diferente: onde, na tela, cada pedaço dele aparece.
Como o modelo chega até a view
O primeiro passo de uma ViewDef quase sempre é FWLoadModel(<fonte>). O
corpo dessa função é curto — duas linhas — e explica por que o argumento
precisa ser exatamente o nome do fonte, não o nome de uma rotina nem o ID do
modelo: ela monta uma string e executa STATICCALL(<fonte>, ModelDef) com
&(). É macro-execução pura. Se ModelDef não existir com esse nome exato
no fonte informado, a chamada falha.
FWLoadView faz o mesmo truque, mais uma etapa que passa despercebida: antes
de montar o STATICCALL, ela corta tudo antes do primeiro ponto do
parâmetro recebido. É esse corte que faz ACTION "VIEWDEF.PGV010" funcionar
— o framework recebe VIEWDEF.PGV010, descarta o VIEWDEF. e chama
STATICCALL(PGV010, ViewDef, 'PGV010'). Repare no terceiro argumento: o
nome do programa é passado como parâmetro para ViewDef. Quase nenhum fonte
declara esse parâmetro na assinatura da própria Static Function ViewDef()
— ele chega, mas ninguém o recebe, porque ADVPL não reclama de argumento
sobrando.
A view lê o dicionário de novo
Dentro da ViewDef, a estrutura não é reaproveitada da ModelDef. Ela nasce
de uma segunda chamada a FWFormStruct, agora com o primeiro parâmetro 2
em vez de 1, para o MESMO alias que o modelo já leu:
Local oStruct := FWFormStruct(2, "Z50")Modelo e tela leem o dicionário cada um para si — não existe conversão de
uma estrutura na outra. É por isso que remover um campo da estrutura de view
(com RemoveField) tira o campo da tela sem tirar do modelo: ele continua
sendo gravado, só deixa de ser editável.
O terceiro parâmetro do AddField é quem une os dois lados
Depois de FWFormView():New() e SetModel(oModel), o componente entra com
AddField:
oView:AddField("VIEW_Z50", oStruct, "Z50MASTER")A doc do próprio método é explícita sobre o primeiro parâmetro: ele
representa o ID criado no modelo que esse campo de tela vai representar —
ou um ID novo, e se for um ID novo é necessário passar o ID de vínculo no
terceiro parâmetro. Ou seja: o ID da view e o ID do modelo podem ser
diferentes, e é justamente o terceiro argumento — cLinkID — que faz a
ligação entre os dois quando eles não coincidem. O quarto parâmetro,
bValid, é documentado como uso interno exclusivo: não é um gancho para
código de quem escreve o fonte.
O SetOwnerView que falta não reclama; o errado, sim
Os boxes têm a mesma assinatura nos dois eixos
oView:CreateHorizontalBox("TELA", 100)A assinatura completa é createHorizontalBox(cID, nPercHeight, cIDOwner, lFixPixel, cIDFolder, cIDSheet). O detalhe pouco notado é que
createVerticalBox usa a MESMA assinatura, com o MESMO nome de segundo
parâmetro: nPercHeight. O fonte do framework não troca o nome para
“largura” quando o box é vertical — a nomenclatura oficial fica como está,
mesmo divergindo do eixo real. lFixPixel é quem muda o significado do
número: de percentual do espaço disponível para pixels fixos.
CreateHorizontalBox é, disparado, o mais comum dos dois — a maioria das
telas MVC se resolve empilhando faixas horizontais, uma embaixo da outra.
CreateVerticalBox aparece quando a divisão precisa ser lado a lado, caso
bem mais raro num cadastro comum.
Quando o box é dispensável
Uma view não precisa de nenhum box. Uma ViewDef de entidade única pode
ficar em cinco linhas — FWLoadModel, FWFormView():New(),
FWFormStruct(2,...), SetModel e um único AddField —, sem
CreateHorizontalBox e sem SetOwnerView. Com um único AddField e nada
mais, o próprio componente principal ocupa a tela inteira: não há disputa
de espaço para o box resolver. O box só se torna necessário quando existe
mais de um componente para dividir o espaço — um cabeçalho e um grid, por
exemplo.
Abas e outros métodos com comportamento verificado
CreateFolder(cIdFolder, cIDOwner) seguido de addSheet cria a estrutura de
abas. O comportamento tem duas faces, e as duas são reais: se o dono passado
já for um campo de formulário com abas vindas do dicionário, a chamada não
cria outra pasta por cima; se for um campo sem abas nenhuma, a chamada
dispara uma exceção de classe. Vale testar o owner antes de assumir que
CreateFolder sempre funciona.
Três métodos a mais valem citação pela assinatura verificada:
addIncrementField(cIDView, cField) numera automaticamente um campo do grid
sem envolver o modelo; addUserButton(cTitle, cResource, bBloco) acrescenta
um botão — o exemplo do próprio fonte do framework escreve AddUserBotton,
com erro de digitação, mas o método real que compila é addUserButton; e
SetCloseOnOk(bBlock) recebe um bloco que tem a view como parâmetro e
precisa devolver verdadeiro para fechar a janela ou falso para mantê-la
aberta — a decisão de fechar sai da view, não do modelo.
Se a tela abre vazia e o modelo parece certo, fale com a gente.
Referências
TDNtdn.totvs.com
TDN — FWFormView
Continue por aqui
- Vídeo
ProtheusReferência técnica
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.