Interface gráfica
Nessa parte do projeto, iremos desenvolver a interface gráfica, mas apenas no que se refere aos componentes, nenhum tipo de comportamento ou interação com o usuário será implementado.
O sistema terá duas telas, uma principal onde será exibido o resumo do processo de ordenação e outra onde será configurado o número a ser ordenado. As duas telas irão se interagir em alguns pontos (vocês verão a importância da controller quando isso ocorrer). Segue abaixo o código da tela principal. Observando que, como o foco do artigo não é interface gráfica, as telas foram projetadas de forma simplificada para facilitar o entendimento.
package br.com.gqferreira.view; import java.awt.Color; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JScrollPane; import javax.swing.JTextField; import javax.swing.JTextPane; /** * * @author Gustavo Ferreira */ public class FramePrincipal extends JFrame { private JLabel lbNumeroMaximo; private JLabel lbNumeroGerado; private JLabel lbNumeroOrdenado; private JLabel lbQtdeTrocas; private JTextField tfNumeroMaximo; private JTextField tfNumeroGerado; private JTextField tfNumeroOrdenado; private JTextField tfQtdeTrocas; private JTextPane tpInformacao; private JScrollPane spRolagemPassos; private JButton btGerarNumero; private JButton btOrdernarNumero; //Get and Set public JButton getBtGerarNumero() { return btGerarNumero; } public JButton getBtOrdernarNumero() { return btOrdernarNumero; } public JTextField getTfNumeroGerado() { return tfNumeroGerado; } public JTextField getTfNumeroMaximo() { return tfNumeroMaximo; } public JTextField getTfNumeroOrdenado() { return tfNumeroOrdenado; } public JTextField getTfQtdeTrocas() { return tfQtdeTrocas; } public JTextPane getTpInformacao() { return tpInformacao; } public JScrollPane getSpRolagemPassos() { return spRolagemPassos; } public FramePrincipal() { this.setTitle("MVC"); this.setDefaultCloseOperation(EXIT_ON_CLOSE); this.setResizable(false); this.setSize(380, 320); this.setLocationRelativeTo(null); this.setLayout(null); this.lbNumeroMaximo = new JLabel("Numero maximo"); this.lbNumeroGerado = new JLabel("Numero gerado"); this.lbNumeroOrdenado = new JLabel("Numero ordenado"); this.lbQtdeTrocas = new JLabel("Qtde de trocas"); this.tfNumeroMaximo = new JTextField("1"); this.tfNumeroMaximo.setEnabled(false); this.tfNumeroGerado = new JTextField(); this.tfNumeroGerado.setEnabled(false); this.tfNumeroOrdenado = new JTextField(); this.tfNumeroOrdenado.setEnabled(false); this.tfQtdeTrocas = new JTextField(); this.tfQtdeTrocas.setEnabled(false); this.btGerarNumero = new JButton("Gerar numero randomico"); this.btOrdernarNumero = new JButton("Ordenar numero gerado"); this.btOrdernarNumero.setEnabled(false); this.tpInformacao = new JTextPane(); this.tpInformacao.setText("Esse sistema ordena uma sequencia numerica atravez do algoritmo bolha (buble sort).\n\nEsse algoritmo realiza trocas entre um numero e o seu imediato caso este seja menor que o proximo."); //Evita que o texto do TextPane seja selecionavel this.tpInformacao.setEnabled(false); //Define a cor do texto this.tpInformacao.setDisabledTextColor(Color.BLACK); //Tira o fundo branco padrao do TextPane this.tpInformacao.setOpaque(false); this.spRolagemPassos = new JScrollPane(); this.spRolagemPassos.setBorder(null); this.spRolagemPassos.setViewportView(this.tpInformacao); this.lbNumeroMaximo.setBounds(20, 20, 150, 20); this.lbNumeroGerado.setBounds(20, 85, 150, 20); this.lbNumeroOrdenado.setBounds(20, 150, 150, 20); this.lbQtdeTrocas.setBounds(20, 215, 150, 20); this.tfNumeroMaximo.setBounds(20, 50, 100, 25); this.tfNumeroGerado.setBounds(20, 115, 100, 25); this.tfNumeroOrdenado.setBounds(20, 180, 100, 25); this.tfQtdeTrocas.setBounds(20, 245, 100, 25); this.btGerarNumero.setBounds(150, 20, 200, 25); this.btOrdernarNumero.setBounds(150,55, 200, 25); this.spRolagemPassos.setBounds(150, 90, 200, 180); this.add(lbNumeroMaximo); this.add(lbNumeroGerado); this.add(lbNumeroOrdenado); this.add(lbQtdeTrocas); this.add(tfNumeroMaximo); this.add(tfNumeroGerado); this.add(tfNumeroOrdenado); this.add(tfQtdeTrocas); this.add(btGerarNumero); this.add(btOrdernarNumero); this.add(spRolagemPassos); } }
A outra tela, como dito anteriormente, será onde se configura o número a ser ordenado, segue abaixo.
package br.com.gqferreira.view; import javax.swing.JButton; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JSlider; /** * * @author Gustavo Ferreira */ public class DialogGerarNumero extends JDialog{ private JLabel lbNumeroMaximo; private JButton btGerar; private JButton btGerarOrdenar; private JSlider slNumeroMaximo; //Get and Set public JButton getBtGerar() { return btGerar; } public JButton getBtGerarOrdenar() { return btGerarOrdenar; } public JSlider getSlNumeroMaximo() { return slNumeroMaximo; } public DialogGerarNumero() { this.setSize(300, 205); this.setLocationRelativeTo(null); this.setTitle("Gerar numero randomico"); this.setModal(true); this.setLayout(null); this.setResizable(false); this.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); this.lbNumeroMaximo = new JLabel("Numero maximo da sequencia"); this.slNumeroMaximo = new JSlider(1, 99999); this.slNumeroMaximo.setMajorTickSpacing(9999); this.slNumeroMaximo.setPaintTicks(true); this.btGerar = new JButton("Gerar numero"); this.btGerarOrdenar = new JButton("Gerar e ordenar"); this.lbNumeroMaximo.setBounds(20, 20, 200, 20); this.slNumeroMaximo.setBounds(20, 50, 245, 30); this.btGerar.setBounds(70, 90, 150, 25); this.btGerarOrdenar.setBounds(70, 125, 150, 25); this.add(lbNumeroMaximo); this.add(slNumeroMaximo); this.add(btGerar); this.add(btGerarOrdenar); } }
Controller
Dependência da controller
Agora é hora de implmentar a controller. Se você copiou as classes da interface gráfica e colou ao seu projeto e configurou o método main, deve ter notado que só a tela principal abriu e ainda sim não foi possível interarir por falta de uma classe controladora de eventos, a camada controller.
Como as duas telas irão se interagir e ambas copartilharão de alguns métodos, o correto é fazer com que elas usem a mesma controller, pois assim a interação de uma interferirá na outra.
package br.com.gqferreira.controller; import br.com.gqferreira.model.bo.OrdenacaoBO; import br.com.gqferreira.model.vo.Ordenacao; import br.com.gqferreira.view.DialogGerarNumero; import br.com.gqferreira.view.FramePrincipal; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; /** * @author Gustavo Ferreira * @see Classe que cria objeto de controle entre a camada Model e View */ public class ControllerPrincipal implements ActionListener, ChangeListener { private FramePrincipal framePrincipal; private DialogGerarNumero dialogGerarNumero; /** * Construtor
Recebe o objeto da FramePrincipal para 'observer' seu comportamento, * tratar os eventos e redirecionar para a model. * @param framePrincipal */ public ControllerPrincipal(FramePrincipal framePrincipal) { this.framePrincipal = framePrincipal; //Definindo os listeners para os botoes dessa view. this.framePrincipal.getBtGerarNumero().addActionListener(this); this.framePrincipal.getBtOrdernarNumero().addActionListener(this); } //Evento de acao, pressionar um botao ou um [Enter] em inputs @Override public void actionPerformed(ActionEvent e) { /** * Se for o pressionar do botao 'Gerar Numero' da FrameMain, instancia uma * DialogGerarNumero */ if (e.getSource() == this.framePrincipal.getBtGerarNumero()) { //Instanciando a DialogGerarNumero this.dialogGerarNumero = new DialogGerarNumero(); this.dialogGerarNumero.getSlNumeroMaximo().setValue( Integer.parseInt(this.framePrincipal.getTfNumeroMaximo().getText())); //Registrando os listeners do Dialog this.dialogGerarNumero.getBtGerar().addActionListener(this); this.dialogGerarNumero.getBtGerarOrdenar().addActionListener(this); this.dialogGerarNumero.getSlNumeroMaximo().addChangeListener(this); this.dialogGerarNumero.setVisible(true); //Destruo o Dialog this.dialogGerarNumero = null; } else if (this.dialogGerarNumero != null) { //Eventos do DialogGerarNumero if (e.getSource() == this.dialogGerarNumero.getBtGerar()) { gerarNumero(); } else if (e.getSource() == this.dialogGerarNumero.getBtGerarOrdenar()){ gerarNumero(); ordenarNumero(); } } else if (e.getSource() == this.framePrincipal.getBtOrdernarNumero()){ ordenarNumero(); } } @Override public void stateChanged(ChangeEvent e) { if (this.dialogGerarNumero != null) { /** * A medida que o Slider eh arrastado, o campo equivalente na FramePrincipal * tem seu valor alterado */ if (e.getSource() == this.dialogGerarNumero.getSlNumeroMaximo()) { this.framePrincipal.getTfNumeroMaximo().setText( String.valueOf(this.dialogGerarNumero.getSlNumeroMaximo().getValue())); } } } /** * Metodo responsavel em controlar a acao de ordenacao. Redireciona para a Model */ private void ordenarNumero(){ //Manda ordenar e recebe uma Ordenacao como resultado do processo. Ordenacao ordenacao = new OrdenacaoBO().bubbleSort( Integer.parseInt(this.framePrincipal.getTfNumeroGerado().getText())); //Atualiza a view com o resultado this.framePrincipal.getTfNumeroOrdenado().setText( String.valueOf(ordenacao.getNumeroOrdenado())); this.framePrincipal.getTfQtdeTrocas().setText( String.valueOf(ordenacao.getQtdeTrocas())); } /** * Metodo que limpa os campos que contem valores resultados de uma ordenacao */ private void limparDadosOrdenacaoAnterior() { this.framePrincipal.getTfNumeroOrdenado().setText(null); this.framePrincipal.getTfQtdeTrocas().setText(null); } /** * Metodo que gera um numero randomico e atualiza a view.
* Executa logo apos o pressionar do botao 'Gerar' do DialogGerarNumero */ private void gerarNumero() { limparDadosOrdenacaoAnterior(); //Fecha o DialogGerarNumero this.dialogGerarNumero.setVisible(false); //Atualiza o numero na FramePrincipal this.framePrincipal.getTfNumeroGerado().setText(String.valueOf((int) (Math.random() * this.dialogGerarNumero.getSlNumeroMaximo().getValue()))); //Destroi a DialogGerarNumero this.dialogGerarNumero = null; //Habilita o botao 'Ordenar' da FramePrincipal this.framePrincipal.getBtOrdernarNumero().setEnabled(true); } }
Você deve ter percebido que quando implementamos somente a camada model não havia erros de compilação e o sistema até funcionava, mostrando que a model não dependia essencialmente de mais nada para funcionar. Também deve ter percebido que quando implementou a view, não havia erros de compilação mostrando que a camada view desconhecia todo o resto e poderia ser encaixado em qualquer sistema que precisasse de uma view como aquela. Já no caso da controller, esta sim está amarrado com tudo. Sozinha a controller não é nada e dá erros de compilação na falta do resto pois, ela tem o papel de "juntar as partes". Outra coisa interessante da controller é que é ela a responsável em tratar os erros de execução, nenhuma classe da model trata os tais erros.
Segue o link para baixar o código fonte e o sistema já compilado, fiquem a vontade para comentar abaixo.
MVC com Java e desktop (parte 3) de Gustavo Ferreira é licenciado sob uma Licença Creative Commons Atribuição-CompartilhaIgual 3.0 Não Adaptada.
Código fonte
Executável
Esse artigo é dividido nas seguintes partes:
Comentarios (26) para MVC com Java e desktop (parte 3)
Prezado Gustavo,
Parabéns pelo artigo! Você conseguiu explicar de forma simples, clara e objetiva o que eu vinha procurando entender a muito tempo e com a exemplificação ficou 100% !!!
Abraços,
Augusto
Obrigado, fico feliz que tenha gostado. Sinta-se a vontade para continuar navegando pelos artigos.
Agraços,
Gustavo Ferreira.
Bom Dia, Gustavo. Tenho a seguinte dúvida, e gostaria da sua opinião/visão sobre a mesma:
- Estou desenvolvendo um sistema onde as classes envolvidas não são "Pessoas", "Pedidos", "Itens_Pedido", etc., mas sim "Editor de Textos", "Player de Vídeo", "Player de Áudio" e "Área de Exibição de Imagens". Este sistema deve permitir aos atores gerar seus próprios textos, simultaneamente gravar / reproduzir áudio / vídeo, etc...
Como seria a organização dessas classes em na arquitetura MVC? Quem seriam as classes BO, VO e Controller?
Desculpe-me pela falta de conhecimento, mas deveria eu pensar que este é o desenvolvimento de um "Framework" ???
Agradecendo qualquer esclarecimento,
atenciosamente
Augusto Cesar Nunes
Nesse caso eu imagino que "Player de Vídeo", "Editor de Texto" entre outros possam ser módulos independente, cada um com sua estrutura MVC. Se os módulos precisarem trabalhar em conjunto uns com os outros, você precisará que todos tenham uma ligação com a mesma controller para que as ações de um módulo interfira no outro.
Ou (melhor)...
Não precisam ser módulos independentes. Você pode ter uma DAO para audio (grava e lê), outra para video (grava e lê), uma para texto (grava e lê) e outra para imagens (grava e lê). Uma BO que centraliza os acessos à DAO e que resolva algum processo (conversao de formatos de audio e/ou video, redimensionamento de imagens, efeitos, restrição de acessos, login, logs, etc). Você poderá ter uma view para cada parte do sistema e uma que centralize. Olhe nessa imagem o que imaginei.
As VOs podem ser classes separadas para cada tipo de arquivo a ser salvo, como por exemplo: Audio.java contendo nome, duração, titulo, bytes[], formato, etc...
Muito bom mesmo, agora ficou muito mais claro.
Valew Gustavo.
Boa tarde Gustavo,
Parabéns pelo artigo. Só estou com uma dúvida do projeto todo, e se puder sanar agradeço.
Sabe-se que na camada model se insere as classes do modelo, e que no caso as classes tipo VO apenas tratam de valores. Ate aqui blz, a questão é se na nas classes de BO contém as regras de negócio então o trecho:
public String getNome() {
return "Sr. "+nome;
}
A regra de prefixar o valor "Sr. " antes do nome não deveria ser posta dentro de uma BO ?
Eu entendi o por que tu colocou este exemplo, obviamente para explicar o conceito get e set, mas num cenário real o mais correto seria ter essa regra em uma classe do tipo BO, correto ?
Fico no aguardo.
Misael
Gustavo,
Tenho mais 3 questão relevantes:
1) Em um sistema em que se aplique o MVC, onde (qual o nome da pasta) ficariam arquivos de recurso da aplicação tais como ícones, figuras, imagens, sons, etc ?
OBS: em uma app que tenho aqui de modelo eu criei uma pasta chamada resource para comportar tais arquivos!
2) Na web e até em alguns livros, os arquivos do tipo .jar oriundos de classes de conexão por exemplo MySql em geral são postos em uma pasta chamada lib para serem referenciados pela aplicção no caso inclusos como jar, utilizando o MVC isto estaria correto ou se deveria por em uma pasta com nomeação diferente?
3) Poderia alterar o teu modelo incluindo estas questões ?
Respondendo a primeira dúvida...
A VO é uma classe que também está no pacote model, portanto também possui alguns comportamentos de negócio. E como é uma classe que visa encapsular os atributos, deixar essa regra do "Sr" fora pode por abaixo o encapsulamento. Imagine que a classe Pessoa possa ser instanciada em várias métodos de uma BO, você vai precisar fazer o tratamento do "Sr" idêntico em todos os pontos. Isso acabaria aniquilando também o objetivo dos métodos controladores de acesso (get e set).
As classes do tipo BO devem se preocupar mais com o "algoritmo" da coisa, a engine. Mas você pode criar dois get's, um com tratamento e outro sem, assim, quando precisar alterar o tratamento, todo mundo que utilize esse get será afetado, deixando de afetar aos métodos que optarem por utilizar o get 'puro'
1) Por entender que tais recursos fazem parte da view, eu crio uma pasta de resources dentro do pacote view:
br.com.gqferreira.projectname.view.resources.[images|sounds|videos|icons]
2) Não precisa se preocupar em colocar as bibliotecas na estrutura MVC do teu projeto, até porque internamente cada biblioteca deve possuir a sua própria estrutura, o que dificultaria na hora de julgar se é model, se é controller... se é a DAO da model ou se... enfim, a biblioteca provavelmente é mista. Coloque em uma pasta chamada lib mesmo.
3) O que você sugere que possa ser alterado no modelo?
Obrigado pela atenção,
Gustavo Ferreira.
Boa tarde Gustavo,
Gostei muito desse artigo ,
muito bem explicado e consegui entender de primeira rs ..
sou iniciante e ajudou muito para poder projetar meus projetos
vlw cara ..
Aqui ainda é bom dia Diego rsrs..
Que bom que você tenha achado útil o artigo, fico contente.
Abraços!
Parabéns pelo o artigo tirou varias duvidas porem surgiu outras rsrsrs
no padrao de desenvolvimento web notei em varios tutorias e artigos que muda bastante esses padroes onde entrar como exemplo:
Util
Filter
E o controller precisa conhecer as duas interfaces!? e se uma terceira, quarta , quinta forem adicionadas?
Olá Gustavo, gostei bastante do seu artigo.
Olhá só, estou com um problema, criei 2 classes controller, 1 para controlar um login e outra pra controlar a tela de configuração de conexão com o Banco de Dados.
Quando executo o sistema, a tela de login se abre, aí eu clico no botão para abrir a tela de configuração.
Ao abrir a tela de configuração, quando vou sair dela, dou um dispose() e instancio uma nova classe da controller do login.
O problema é, quando clico em fechar ou pra sair da nova tela de login, a tela somente some, mas o sistema continua em execução.
Como posso resolver isso? Tem alguma ideia?
Obrigado
Parabéns muito bom.
Para quem se interessar, aqui tem outra aplição desktop com netbeans: http://www.memoriacache.com.br/aplicacoes-desktop-com-netbeans-swing/
Olá Gustavo.
Parabéns pelo artigo, esclareceu várias duvidas, mas ainda ficaram algumas.
Por um acaso não teria um exemplo de sistema que grave em banco e valide campos da tela para ter uma ideia não né?
Hoje eu tenho um sistema com uma tela padrão que valida de forma automática os campos, e as minhas telas de cadastro eu herdo ela.
Queria saber como fazer isso usando MVC.
Acredito que eu tendo um exemplo de um sistema que faça algumas validações e grave em banco eu consigo fazer o meu.
Obrigado.
Att,
Sidnei
Muito boa a didática, direta ao ponto e demonstra de forma muito clara. parabéns.
Artigo excelente!! Já pesquisei bastante para obter um exemplo de MVC. De todos que venho pesquisando, considerei esse, o melhor!! Obrigado!! Um abraço!
Me ajudou muito. Ótimo artigo, parabéns!
Otima explicação. Clareou muito para mim.
Olá Gustavo.
Muito obrigado por este artigo, estou começando agora com o MVC, e com esta primeira parte do seu artigo estou apreciando a utilidade deste padrão de projeto.
Muito bom e agradeço pela explicação, procurei bastante como fazer MVC e final achei e bem explícito
Parabéns. Me ajudou muito, o assunto abordado foi muito bem explicado e sanou muitas dúvidas que tinha. Muito obrigado, um grande abraço e sucesso.
Parabéns pelo artigo, depois de pesquisar muito finalmente encontrei um artigo que me fez entender claramente sobre o assunto, ótima didática. Muito obrigada!
Boa tarde Gustavo Ferreira, gostaria de poder utilizar esse artigo em um documento em PDF totalmente didático, sem fins lucrativos que estou desenvolvendo, para ajudar iniciantes assim como eu na programação em Java, utilizando o modelo MVC. Não consegui encontrar em pesquisas explicações tão boas, assim como as suas, se poderei utilizar, deixarei respectivamente a fonte do seu site e uma parte dizendo que o Artigo é de sua autoria e dos demais autores com suas respectivas fontes. Obrigado pela atenção!
Gostou do artigo? Acha que poderia ser melhor em algum ponto? Dê o seu feedback!