Como implementar ActionListener em uma estrutura MVC - Swing

Boa tarde pessoal tudo bem ?

Estou aqui fazendo uns testes com action em swing e me bateu uma dúvida:

Minha classe:

package view;

import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JRootPane;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

import com.birosoft.liquid.LiquidLookAndFeel;

import controller.infra.msg.MensagensEnum;

public class MainView extends JFrame implements ActionListener{
	public JMenuItem menuTelaDois;
	/**
	 * Contrutor da classe.
	 */
	public MainView(){
		alteraLayout();
		setSize(new Dimension(500,500));
		
		setJMenuBar(criaMenubar());
		
		setLocationRelativeTo(null);
		setVisible(true);
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	}
	
	/**
	 * Método responsável por setar o layout no estilo MAC.
	 */
	private void alteraLayout(){
		try {
			setUndecorated(true);
			getRootPane().setWindowDecorationStyle(JRootPane.FRAME);
			UIManager.setLookAndFeel(new com.birosoft.liquid.LiquidLookAndFeel());
			LiquidLookAndFeel.setLiquidDecorations(true,"mac");
		    LiquidLookAndFeel.setShowTableGrids(true);
		    LiquidLookAndFeel.setStipples(false);
			SwingUtilities.updateComponentTreeUI(this);
		} catch (Exception e) {
			JOptionPane.showMessageDialog(null, MensagensEnum.ERRO_LAYOUT, "ERRO", JOptionPane.ERROR);
		}
	}
	
	public JMenuBar criaMenubar() {
		
		JMenuBar menuBar  = new JMenuBar();
		JMenu menuArquivo = new JMenu("Arquivo");
		
		JMenu menuNovo  = new JMenu("Novo");
		JMenuItem menuTelaUm = new JMenuItem("Layout Um", new ImageIcon("src/view/images/logo.png"));
		
		menuTelaDois = new JMenuItem("Layout Dois", KeyEvent.VK_L);
		menuTelaDois.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_L, ActionEvent.CTRL_MASK));
		
		menuTelaDois.addActionListener(this);
		

		JMenuItem menuTelaTres = new JMenuItem("Layout Três");
		menuNovo.add(menuTelaUm);
		menuNovo.add(menuTelaDois);
		menuNovo.add(menuTelaTres);

		JMenuItem menuAbrir = new JMenuItem("Abrir...");
		JMenuItem menuSair  = new JMenuItem("Sair");
		
		menuArquivo.add(menuNovo);
		menuArquivo.add(menuAbrir);
		menuArquivo.addSeparator();
		menuArquivo.add(menuSair);
		
		menuBar.add(menuArquivo);
		return menuBar;	
	}
	
	public void actionPerformed(ActionEvent e){
		if (e.getSource() == menuTelaDois ){
			System.out.println("Entrou no metodo da ação");
		}
	}
	
	public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, UnsupportedLookAndFeelException {
		MainView main = new MainView();
	}
}

Preste atenção neste detalhe:

		menuTelaDois = new JMenuItem("Layout Dois", KeyEvent.VK_L);
		menuTelaDois.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_L, ActionEvent.CTRL_MASK));
		menuTelaDois.addActionListener(this);

E no metodo que eu implementei:

	public void actionPerformed(ActionEvent e){
		if (e.getSource() == menuTelaPraxy ){
			System.out.println("Entrou no metodo da ação");
		}
	}

tudo bem até aqui.

A minha grande dúvida é a seguinte, em uma estrutura MVC, como eu posso implementar este metodo em uma classe controller, para que eu não tenha que instanciar nada da camada de controle aqui dentro do metodo que está na view ?

Não sei se fui claro:S se alguem tiver dúvida por favor pergunte :stuck_out_tongue:

Bom pessoal, pensei e pensei e cheguei a esta conclusão:

package view;

import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JRootPane;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;

import com.birosoft.liquid.LiquidLookAndFeel;

import controller.infra.msg.MensagensEnum;

public class MainView extends JFrame{
	
	private JMenuItem menuTelaDois;
	
	/**
	 * Contrutor da classe.
	 */
	public MainView(){
		alteraLayout();
		setSize(new Dimension(500,500));
		
		setJMenuBar(criaMenubar());
		
		setLocationRelativeTo(null);
		setVisible(true);
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	}
	
	/**
	 * Método responsável por setar o layout no estilo MAC.
	 */
	private void alteraLayout(){
		try {
			setUndecorated(true);
			getRootPane().setWindowDecorationStyle(JRootPane.FRAME);
			UIManager.setLookAndFeel(new com.birosoft.liquid.LiquidLookAndFeel());
			LiquidLookAndFeel.setLiquidDecorations(true,"mac");
		    LiquidLookAndFeel.setShowTableGrids(true);
		    LiquidLookAndFeel.setStipples(false);
			SwingUtilities.updateComponentTreeUI(this);
		} catch (Exception e) {
			JOptionPane.showMessageDialog(null, MensagensEnum.ERRO_LAYOUT, "ERRO", JOptionPane.ERROR);
		}
	}
	
	public JMenuBar criaMenubar() {
		
		JMenuBar menuBar  = new JMenuBar();
		JMenu menuArquivo = new JMenu("Arquivo");
		
		
		JMenu menuNovo  = new JMenu("Novo");
		JMenuItem menuTelaUm = new JMenuItem("Layout Um", new ImageIcon("src/view/images/logo.png"));
		
		menuTelaDois = new JMenuItem("Layout Dois");
		menuTelaDois.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_L, ActionEvent.CTRL_MASK));
		//menuTelaPraxy.addActionListener(this);
		

		JMenuItem menuTelaTres = new JMenuItem("Layout Três");
		menuNovo.add(menuTelaUm);
		menuNovo.add(menuTelaDois);
		menuNovo.add(menuTelaTres);

		JMenuItem menuAbrir = new JMenuItem("Abrir...");
		JMenuItem menuSair  = new JMenuItem("Sair");
		
		menuArquivo.add(menuNovo);
		menuArquivo.add(menuAbrir);
		menuArquivo.addSeparator();
		menuArquivo.add(menuSair);
		
		menuBar.add(menuArquivo);
		return menuBar;	
	}
	
/*	public void actionPerformed(ActionEvent e){
		if (e.getSource() == menuTelaPraxy ){
			System.out.println("Entrou no metodo da ação");
		}
	}*/
	
	public void setMenuTelaDois(ActionListener al){
		menuTelaDois.addActionListener(al);
	}
}

Classe Controller:

package controller;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import view.MainView;

public class Controller{

	public Controller() {
		// TODO Auto-generated constructor stub
	}
	
	public static void main(String[] args) {
		
		ActionListener testeAction = new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				System.out.println("Entrou no metodo da ação");				
			}
		};
		
		MainView teste = new MainView();
		teste.setMenuTelaDois(testeAction);
		

		
	}
}

Vamos la, primeiro eu criei o atributo:

private JMenuItem menuTelaDois;

então fiz o setter dele:

	public void setMenuTelaDois(ActionListener al){
		menuTelaDois.addActionListener(al);
	}

Na controle fiz um ActionListener com a ação que eu queria e setei no meu menu…

O que vcs achão está pode ser a melhor solução ?

Alchemist,
Tudo bem?

Padrão meu esse…
Para cada menu, eu crio uma ação separada. Isso se explicita com o uso de uma classe privada, implementando a interface ActionListener.
Por exemplo…

JButton buttonCadastrar = new JButton( "Cadastrar" );
        buttonCadastrar.addActionListener( new ButtonCadastrar() );
JButton buttonAlterar = new JButton( "Alterar" );
        buttonAlterar.addActionListener( new ButtonAlterar() );
JButton buttonRemover= new JButton( "Remover" );
        buttonRemover.addActionListener( new ButtonRemover() );

private class ButtonCadastrar implements ActionListener {
   public void actionPerformed( ActionEvent e ) {
   
       //Faz tudo o que eu quero referente ao botão cadastrar.
   }
}

private class ButtonAlterar implements ActionListener {
   public void actionPerformed( ActionEvent e ) {
   
       //Faz tudo o que eu quero referente ao botão alterar.
   }
}

private class ButtonRemover implements ActionListener {
   public void actionPerformed( ActionEvent e ) {
   
       //Faz tudo o que eu quero referente ao botão remover.
   }
}

Aí você pergunta… Mas porquê uma classe separada pra cada ação? Não vai ficar muito grande o código?
O uso de uma classe para cada ação é pelo fato de que meu código vai ficar separado (ou seja, cada ação de cada botão em seu específico lugar) e limpo, pois não mistura nada com nada!

Tente algo parecido!
Abraços!

Opa tudo bem Nicolas ?

Então achei a sua ideia muito boa, então eu fiz está classe:

package controller;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JMenuItem;

import view.PainelCentralView;

public class MenuController implements ActionListener{

	PainelCentralView painelCentralView;
	
	public MenuController(PainelCentralView painelCentralView) {
		this.painelCentralView = painelCentralView;
	}
	
	public void actionPerformed(ActionEvent e) {
		JMenuItem item = (JMenuItem) e.getSource();
		
		if(item.getText().equals("Layout UM"))
			System.out.println("UM");
		
		if(item.getText().equals("Layout DOIS"))
			System.out.println("DOIS");
		
		if (item.getText().equals("Layout TRÊS"))
			System.out.println("Três");
			
	}
}

O objetivo seria instanciar uma outra classe de controle dentro de cada if onde no construtor dela eu vou passar a classe PainelCentralView que extend ao JPanel, que seria o meu painel central. Dentro desta nova classe de controle eu vou trabalhar com as telas de cada menu…

O que vc achou desta classe ai ? Uma boa forma de definir tarefas ? to precisando de opniões pessoal.

[quote=Alchemist]Opa tudo bem Nicolas ?

Então achei a sua ideia muito boa, então eu fiz está classe:

package controller;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JMenuItem;

import view.PainelCentralView;

public class MenuController implements ActionListener{

	PainelCentralView painelCentralView;
	
	public MenuController(PainelCentralView painelCentralView) {
		this.painelCentralView = painelCentralView;
	}
	
	public void actionPerformed(ActionEvent e) {
		JMenuItem item = (JMenuItem) e.getSource();
		
		if(item.getText().equals("Layout UM"))
			System.out.println("UM");
		
		if(item.getText().equals("Layout DOIS"))
			System.out.println("DOIS");
		
		if (item.getText().equals("Layout TRÊS"))
			System.out.println("Três");
			
	}
}

O objetivo seria instanciar uma outra classe de controle dentro de cada if onde no construtor dela eu vou passar a classe PainelCentralView que extend ao JPanel, que seria o meu painel central. Dentro desta nova classe de controle eu vou trabalhar com as telas de cada menu…

O que vc achou desta classe ai ? Uma boa forma de definir tarefas ? to precisando de opniões pessoal.[/quote]

Eu não gostei dessa solução, não.
Classes Controller são classes que servem para comunicar a VIEW com o MODEL, e não pra tratar de eventos da VIEW.
Além do mais, em sua classe, você está tratando tudo junto, nesse ponto:

public void actionPerformed(ActionEvent e) {
		JMenuItem item = (JMenuItem) e.getSource();
		
		if(item.getText().equals("Layout UM"))
			System.out.println("UM");
		
		if(item.getText().equals("Layout DOIS"))
			System.out.println("DOIS");
		
		if (item.getText().equals("Layout TRÊS"))
			System.out.println("Três");
			
	}

E se o seu código for muito grande, você concorda que ficará muito bagunçado? ^^

Já que você quer tratar eventos da VIEW (Cliques em botões, cliques em menus), crie classes privadas na própria VIEW que tratem disso, como fiz acima.

JMenu meuMenu = new JMenu();

JMenuItem item;
          item = new JMenuItem( "Cadastrar Clientes" );
          item.addActionListener( new CadastrarClientes() );
          meuMenu.add( item );

          item = new JMenuItem( "Cadastrar Produtos" );
          item.addActionListener( new CadastrarProdutos() );
          meuMenu.add( item );

          item = new JMenuItem( "Gerar Relatórios" );
          item.addActionListener( new GerarRelatorios() );
          meuMenu.add( item );

// Todas as Forms declaradas abaixo são somente exemplos intuitivos.

private class CadastrarClientes implements ActionListener {
   public void actionPerformed( ActionEvent e ) {

      FormCadastrarClientes fcc = new FormCadastrarClientes();
                            fcc.setVisible( true );
                            fcc.dispose();
   }
}

private class CadastrarProdutos implements ActionListener {
   public void actionPerformed( ActionEvent e ) {

      FormCadastrarProdutos fcp = new FormCadastrarProdutos();
                            fcp.setVisible( true );
                            fcp.dispose();
   }
}
private class GerarRelatorios implements ActionListener {
   public void actionPerformed( ActionEvent e ) {

      FormGerarRelatorios fgr = new FormGerarRelatorios();
                          fgr.setVisible( true );
                          fgr.dispose();
   }
}

Vê o que acha, e me dê suas opiniões também!
Abraços!

Hum, acho que vc n pegou bem a ideia…

hum eu tinha pensado assim:

public  void actionPerformed(ActionEvent e) {  
         JMenuItem item = (JMenuItem) e.getSource();  
           
         if(item.getText().equals("Layout UM"))
            CadastroClienteController = new CadastroClienteController(painelCentralView);
             
           
         if(item.getText().equals("Layout DOIS"))  
             CadastroProdutosView = new CadastroProdutosView(painalCentralView);
           
         if (item.getText().equals("Layout TRÊS"))  
            CalculoFinanceiroController = new CalculoFinanceiroController(painelCentralView);
               
     } 

Onde está classe só falaria quem vai fazer o que, e as controller ou views fazeriam todo o resto…

Por exemplo esta MenuController chama dentro dela a CadastroProdutosView, que irá criar dentro dela a tela de cadastroProdutos usando este painel que eu passei…

Deste modo que vc apresentou, como eu enviaria o painel principal para moder modelar dentro das classes, por exemplo a FormCadastrarCliente ? Como eu passaria o painel principal nela?

E por exemplo dentro desta mesma classe “FormCadastrarCliente” em algum momento eu vou ter que chamar uma classe de ação onde está classe chamaria uma controle certo? Não seria a mesma coisa deste caso ?

Em relação ao codigo ser grande, eu usaria este tipo de modelagem apenas no menu…

O que vc acha do que eu falei ? Cara n sei pq mais eu n discordo de vc viu rsrsrs mais fala ai…

[quote=Alchemist]Hum, acho que vc n pegou bem a ideia…

hum eu tinha pensado assim:

public  void actionPerformed(ActionEvent e) {  
         JMenuItem item = (JMenuItem) e.getSource();  
           
         if(item.getText().equals("Layout UM"))
            CadastroClienteController = new CadastroClienteController(painelCentralView);
             
           
         if(item.getText().equals("Layout DOIS"))  
             CadastroProdutosController = new CadastroProdutosController(painalCentralView);
           
         if (item.getText().equals("Layout TRÊS"))  
            CalculoFinanceiroController = new CalculoFinanceiroController(painelCentralView);
               
     } 

Onde está classe só falaria quem vai fazer o que, e as controller fazeriam todo o resto…

Deste modo que vc apresentou, como eu enviaria o painel principal para moder modelar dentro das classes, por exemplo a FormCadastrarCliente ? Como eu passaria o painel principal nela?

E por exemplo dentro desta mesma classe “FormCadastrarCliente” em algum momento eu vou ter que chamar uma classe de ação onde está classe chamaria uma controle certo? Não seria a mesma coisa deste caso ?

Em relação ao codigo ser grande, eu usaria este tipo de modelagem apenas no menu…

O que vc acha do que eu falei ? Cara n sei pq mais eu n discordo de vc viu rsrsrs mais fala ai…

[/quote]

Hmm… Você tá fazendo algo como um Front-Controller? É o que tá aparentando rs
Não estou dizendo que o que você está fazendo está errado, mas sim que eu não usaria dessa maneira, e sim da maneira que escrevi. rs

Ficaria assim, da maneira que apresentei:

JMenu meuMenu = new JMenu();  
   
JMenuItem item;  
          item = new JMenuItem( "Cadastrar Clientes" );  
          item.addActionListener( new CadastrarClientes( this ) );  //<------------------------
          meuMenu.add( item ); 

private  class CadastrarClientes implements ActionListener {  
   
   private MeuFormPrincipal mfp;
 
   // Estou passando o formulário pai pelo construtor da minha classe!
   public CadastrarClientes( MeuFormPrincipal mfp ) { this.mfp = mfp; } 
  
   public void actionPerformed( ActionEvent e ) {  
  
      FormCadastrarClientes fcc = new FormCadastrarClientes( mfp ); // <-----
                            fcc.setVisible( true );  
                            fcc.dispose();  
   }  
} 

Sacou?

#modo viagem total: [color=green]ON[/color]
Pensando na modelagem de Front-Controller, você poderia fazer um Front geral pra sua aplicação toda… Ficaria bacana! :smiley:
#modo viagem total: [color=red]OFF[/color]

Como ainda não consegui pensar em um Front-Controller pra aplicações desktop, eu continuo fazendo da maneira que fiz nas minhas postagens, tanto para eventos de Botões, Menus e outros ^^

Abraços!

hahaha eu que vc não está falando que eu estou errado, estamos debatendo para abrir ideias :stuck_out_tongue:

Eu entendi o que vc fez, basicamente é a mesma coisa que eu fiz só que em uma classe para cada ação, o que me soa meio estranho é fazer uma classe e ação que chame apenas um View:

este exmplo mesmo seu:

private  class CadastrarClientes implements ActionListener {    
      
    private MeuFormPrincipal mfp;  
   
    // Estou passando o formulário pai pelo construtor da minha classe!  
    public CadastrarClientes( MeuFormPrincipal mfp ) { this.mfp = mfp; }   
     
    public void actionPerformed( ActionEvent e ) {    
     
       FormCadastrarClientes fcc = new FormCadastrarClientes( mfp ); // <-----  
                             fcc.setVisible( true );    
                             fcc.dispose();    
    }    
 }  

Basicamente ela nunca fará mais do que instanciar a View, onde está viu terá mais classes de ação certo ?

Se ei tiver um sistema com 100 telas de cadastro onde cada tela existe 3 botões, salvar, deletar e alterar… como vc irá tratar as ações dos botões das 100 telas ? uma classe de ação para cada botão ?

[quote=Alchemist]este exmplo mesmo seu:

private  class CadastrarClientes implements ActionListener {    
      
    private MeuFormPrincipal mfp;  
   
    // Estou passando o formulário pai pelo construtor da minha classe!  
    public CadastrarClientes( MeuFormPrincipal mfp ) { this.mfp = mfp; }   
     
    public void actionPerformed( ActionEvent e ) {    
     
       FormCadastrarClientes fcc = new FormCadastrarClientes( mfp ); // <-----  
                             fcc.setVisible( true );    
                             fcc.dispose();    
    }    
 }  

Basicamente ela nunca fará mais do que instanciar a View, onde está viu terá mais classes de ação certo ?

Se ei tiver um sistema com 100 telas de cadastro onde cada tela existe 3 botões, salvar, deletar e alterar… como vc irá tratar as ações dos botões das 100 telas ? uma classe de ação para cada botão ?[/quote]

Nem sempre!
Digamos que eu esteja em uma tela de Gerenciamento de Contatos (CRUD Básico), por exemplo, onde nela exista uma JTable que imprime os dados, um botão de Cadastrar, um de Alterar, um de Excluir, um de Importar, um de Exportar e outro de Fechar.
Para cada, botão, uma classe de Ação. Em meus Eventos dos botões, haverão métodos para chamar o TableModel, validar se o cara selecionou uma linha, validar de ele escrever o nome certo de algum parâmetro, saca? Por exemplo…

private class ButtonCadastrar implements ActionListener {
   public void actionPerformed( ActionEvent e ) {
      
      WinCadastroContatos wcc = new WinCadastroContatos();
                          wcc.showDialog();
      // Uma ação de pegar o objeto criado no formulário de cadastro e 
      //passá-lo ao TableModel para adição tanto na JTable quanto no BD.

      Map<String, Object> contato = wcc.requisitarObjeto();
      contatosTableModel.inserirRegistro( contato ); 
      JOptionPane.showMessageDialog( null,  "Cadastro efetuado com sucesso." );
   }
}
private class ButtonAlterar implements ActionListener {
   public void actionPerformed( ActionEvent e ) {
   
      // Valida se o usuário selecionou algum contato para alterar, altera os dados do contato e tudo o mais.
      if ( tableContatos.getSelectedRow() > -1 ) {
         
         Map<String, Object> contato = tableModel.requisitarRegistro( tableContatos.getSelectedRow() );
         WinCadastroContatos wcc = new WinCadastroContatos();
                             wcc.showDialog();

         contato = wcc.requisitarObjeto();
         contatosTableModel.alterarRegistro( tableContatos.getSelectedRow(), contato ); 
         JOptionPane.showMessageDialog( null,  "Cadastro alterado com sucesso." );
      } else {
         JOptionPane.showMessageDialog( null,  "Selecione ao menos um contato para alterar!" );
      }
   }
}
// E por aí vai...
}

Cada classe de Ação permitirá uma chamada ao meu Front-Controller ( no caso, o TableModel que controla a requisição do usuário e repassa aos controllers específicos ) e validações, chamadas de tela e tudo o mais!

Faria uma tela de Cadastro recursiva, onde teria um painel de botões em comum, e todas as minhas telas de cadastro herdariam desta tela recursiva, a tela-base. Criaria na classe Base métodos de ação para cada botão como abstratos, obrigando cada filho a implementá-los, onde cada telinha implementaria a ação do modo que quisesse. Algo assim:

public abstract class WinBase implements JDialog {
   
   private JButton buttonCadastrar;
   public WinBase() {
      
      buttonCadastrar = new JButton( "Cadastrar" );
      buttonCadastrar.addActionListener( new ButtonCadastrar() );
   }
   // Método abstrato para o botão cadastrar;
   public abstract void buttonCadastrar_onClick( ActionEvent e );
   private class ButtonCadastrar implements ActionListener {
      public void actionPerformed( ActionEvent e ) {
         buttonCadastrar_onClick(e); //<--- Chama o meu método abstrato, que será implementado na filha!
      }
   }
}

public class WinCadastro implements WinBase {

   @Override
   public void buttonCadastrar_onClick( ActionEvent e ) {
      JOptionPane.showMessageDialog( null, "Aqui está o evento para a tela WinCadastro!" );
   }
}

Seria assim :smiley:
O que acha?

É Nicolas, antes de postar o ultimo post eu já tinha pensado bastante, e depois de vc postar este pensei mais um pouco rsrsrs Vc já tinha me convencido, realmente está opção de criar as classes de ação é melhor e mais limpa sem falar o fato de facilitar a manutenção. Estou fazendo um projetinho aqui de testes vou implementar desta forma e ver como eu vou me comportar :stuck_out_tongue:

Bom valeu a paciência ai :stuck_out_tongue: