Desenvolvimento de uma aplicação Java SE com o MVC

Se você já programou com bibliotecas para interface com o usuário (GUI) nos últimos 10 anos, você usou um pouco o conceito de MVC (model-view-controller). O MVC foi introduzido inicialmente por Trygve Reenskaug, um desenvolvedor Smalltalk do Centro de Pesquisa da Xerox em Palo Alto em 1979, e ajuda a desacoplar o acesso aos dados e a lógica do negócio da maneira que são mostrados ao usuário.

Mais precisamente, o MVC pode ser quebrado em três elementos:

  • Model – O modelo representa os dados e as regras que governam o acesso a esses dados. Em softwares comerciais, um modelo frequentemente desempenha um papel de uma aproximação por software de um processo do mundo real.
  • View – A visão renderiza o conteúdo de um modelo. Ela especifica exatamente como os dados do modelo devem ser apresentados. Se os dados do modelo forem alterados, a visão precisa atualizar a sua apresentação da forma necessária. Isso pode ser alcançado pelo uso de um push model, no qual a própria visão registra-se com o modelo para ser notificado de mudanças, ou de um pull model, no qual a visão é responsável por chamar o modelo quando precisa recuperar os dados mais recentes.
  • Controller – O controlador traduz as interações do usuário com a visão em ações que o modelo executará. Em um cliente gráfico stand-alone, as interações do usuário podem ser cliques em botões ou seleções de menu, enquanto que em uma aplicação web, essas interações tomam forma de requisições HTTP GET e POST. Dependendo do contexto, um controlador pode também selecionar uma nova visão – por exemplo, uma página web de resultados – para ser apresentada ao usuário.

Figure1. A Common MVC Implementation

Interação entre os componentes do MVC

Essa seção analisará em uma das maneiras de implementar a Figura 1 no contexto de uma aplicação no formato Java Platform, Standard Edition 6 (Java SE 6).  Uma vez que os objetos do modelo, da visão e do controlador sejam instanciados, os seguintes passos ocorrem:

  1. A visão se registra com um ouvinte do modelo. Qualquer mudança nos dados do modelo imediatamente resulta na transmissão de uma notificação, que é recebida pela visão. Isso é um exemplo de um push model descrito anteriormente. Note que o modelo não está ciente da visão ou do controlador – ele simplesmente dispara notificações a todos os ouvintes interessados.
  2. O controlador é ligado a visão. Isso tipicamente significa que qualquer ação do usuário que é executada na visão invocará um método na classe do controlador.
  3. O controlador é fornecido como referência para o modelo.

Uma vez que o usuário interaja com a visão, as seguintes ações ocorrem:

  1. A visão reconhece uma ação vinda da interface – por exemplo, pressionar um botão ou arrastar uma barra de rolagem – ocorreu, usando um método apropriado que é registrado de modo que seja chamado cada vez que a ação ocorra.
  2. A visão chama o método apropriado no controlador.
  3. O controlador acessa o modelo, possivelmente atualizando-o de forma adequada a ação do usuário.
  4. Se o modelo for alterado, ele notifica os ouvintes interessados, com as visões, da alteração. Em algumas arquiteturas, o controlador pode ser responsável também pela atualização da visão. Isso é comum as aplicações Java baseadas na tecnologia enterprise.

A Figura 2 mostra essa interação com mais detalhes.

Figure 2. A Java SE Application Using MVC

 
Como esse artigo mencionou anteriormente, o modelo não carrega com si uma referência a visão, mas ao invés disso usa um modelo de notificações para notificar as partes interessadas de uma alteração. Uma das consequência desse design poderoso é que muitas visões podem ter o mesmo modelo associado a elas. Quando uma mudança em um modelo de dados ocorre, cada visão é notificada através de um evento de alteração de propriedade e pode atualizar a si mesmo de forma apropriada. Por exemplo, a Figura 3 mostra duas visões que usam o mesmo modelo de dado.
 

Figure 3: Multiple Views Using the Same Model

Figure 3. Multiple Views Using the Same Model

Modificando o Design do MVC

Uma implementação mais recente do design MVC coloca o controlador entre o modelo e a visão. Esse design, que é bastante comum no framework Apple Cocoa, é mostrada na Figura 4.
 

Figure 4: An MVC Design Placing the Controller Between the Model and the View

Figure 4. An MVC Design Placing the Controller Between the Model and the View

 
A diferença primária entre esse design e as versões mais tradicionais do MVC é que as notificações de mudança de estado nos objetos do modelo são comunicadas a visão através do controlador. Por isso, o controlador media o fluxo de dados entre os objetos do modelo e da visão em ambas as direções. Os objetos da visão, como sempre, usam o controlador para traduzir as ações do usuário em atualizações das propriedades no modelo. Em acréscimo, as mudanças no estado do modelo são comunicadas aos objetos da visão através dos objetos de controlador da aplicação.
Assim, quando todos os três objetos são instanciados, a visão e o modelo se registrarão com o controlador. Quando o usuário interage com a visão, os eventos são quase idênticos:

  1. A visão reconhece que uma ação da interface ocorreu, usando um método ouvinte que é registrado para ser chamado quando cada ação ocorre.
  2. A visão chama o método adequado do controlador.
  3. O controlador acessa o modelo, possivelmente atualizando ele de forma adequada a ação do usuário.
  4. Se o modelo foi alterado, ele notifica os ouvintes interessados da mudança. Porém, nesse caso, a alteração é enviada ao controlador.

Por que adotar esse design? Usando esse MVC modificado nos possibilita uma maior desacoplação do modelo e da visão. Nesse caso, o controlador pode ditar as propriedades do modelo que espera encontrar em um ou mais modelos registrados com o controlador. Além disso, podem também fornecer os métodos que afetam as propriedades do modelo para um ou mais visões que estejam registradas com ele.

Usando o MVC modificado

Essa seção do artigo mostra a você como por esse design em prática, começando com o modelo. Suponha que você queira desenhar algum texto usando um simples modelo de exibição com cinco propriedades. O exemplo de código 1 mostra um componente simples que você pode usar para criar esse modelo.
Exemplo de código 1

public class TextElementModel extends AbstractModel
{
    private String text;
    private Font font;
    private Integer x;
    private Integer y;
    private Integer opacity;
    private Integer rotation;
    /**
     * Provides the means to set or reset the model to
     * a default state
     */
    public void initDefault() {
        setOpacity(89);
        setRotation(0);
        setText("Sample Text");
        setFont(new Font("Arial", Font.BOLD, 24));
        setX(50);
        setY(50);
    }
    //  Accessors
    public String getText() {
        return text;
    }
    public void setText(String text) {
        String oldText = this.text;
        this.text = text;
        firePropertyChange(
            DefaultController.ELEMENT_TEXT_PROPERTY,
            oldText, text);
    }
    public Font getFont() {
        return font;
    }
    public void setFont(Font font) {
        Font oldFont = this.font;
        this.font = font;
        firePropertyChange(
            DefaultController.ELEMENT_FONT_PROPERTY,
            oldFont, font);
    }
    //  The remaining accessors for properties are omitted.
}

 
Note que os acessórios restantes seguem o padrão JavaBeans, embora eles sejam omitidos no Exemplo 1. Para referência, o Exemplo de código 2 mostra a classe AbstractModel, que simplesmente usa a classe javax.beans.PropertyChangeSupport para se registrar e notificar ouvintes interessados das mudanças no modelo.
Exemplo de código 2

public abstract class AbstractModel
{
    protected PropertyChangeSupport propertyChangeSupport;
    public AbstractModel()
    {
        propertyChangeSupport = new PropertyChangeSupport(this);
    }
    public void addPropertyChangeListener(PropertyChangeListener listener) {
        propertyChangeSupport.addPropertyChangeListener(listener);
    }
    public void removePropertyChangeListener(PropertyChangeListener listener) {
        propertyChangeSupport.removePropertyChangeListener(listener);
    }
    protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
        propertyChangeSupport.firePropertyChange(propertyName, oldValue, newValue);
    }
}

 
O controlador
Entre o modelo e a visão fica o controlador. Em primeiro lugar, dê uma olhada no código da super classe abstrata do controlador, como mostrado no Exemplo de código 3.
Exemplo de código 3

public abstract class AbstractController implements PropertyChangeListener {
    private ArrayList<abstractviewpanel> registeredViews;
    private ArrayList<abstractmodel> registeredModels;
    public AbstractController() {
        registeredViews = new ArrayList<abstractviewpanel>();
        registeredModels = new ArrayList<abstractmodel>();
    }
    public void addModel(AbstractModel model) {
        registeredModels.add(model);
        model.addPropertyChangeListener(this);
    }
    public void removeModel(AbstractModel model) {
        registeredModels.remove(model);
        model.removePropertyChangeListener(this);
    }
    public void addView(AbstractViewPanel view) {
        registeredViews.add(view);
    }
    public void removeView(AbstractViewPanel view) {
        registeredViews.remove(view);
    }
    //  Use this to observe property changes from registered models
    //  and propagate them on to all the views.
    public void propertyChange(PropertyChangeEvent evt) {
        for (AbstractViewPanel view: registeredViews) {
            view.modelPropertyChange(evt);
        }
    }
    /**
     * This is a convenience method that subclasses can call upon
     * to fire property changes back to the models. This method
     * uses reflection to inspect each of the model classes
     * to determine whether it is the owner of the property
     * in question. If it isn't, a NoSuchMethodException is thrown,
     * which the method ignores.
     *
     * @param propertyName = The name of the property.
     * @param newValue = An object that represents the new value
     * of the property.
     */
    protected void setModelProperty(String propertyName, Object newValue) {
        for (AbstractModel model: registeredModels) {
            try {
                Method method = model.getClass().
                    getMethod("set"+propertyName, new Class[] {
                                                      newValue.getClass()
                                                  }
                             );
                method.invoke(model, newValue);
            } catch (Exception ex) {
                //  Handle exception.
            }
        }
    }
}

 
A classe AbstractController contém dois objetos ArrayList, que são usados para manter o registro dos modelos e visões que são registrados. Note que sempre que um modelo é registrado, o controlador também registra-se como um ouvinte de mudanças das propriedades do modelo. Dessa forma, sempre que um modelo tem seu estado alterado, o método propertyChange() é chamado e o controlador passará esse evento para as visões apropriadas.
O método final, setModelProperty(), faz algumas mágicas para fazer seu trabalho. Para manter os modelos completamente desacoplados do controlador, o exemplo de código desse artigo usa a Java Reflection API. Nesse caso, quando esse método é chamado com o nome da propriedade desejada, você percorre pelos modelos registrados para determinar quais deles contém o método apropriado. Uma vez que ele é encontrado, você invoca o método usando o novo valor. Se o método não for chamado, getMethod() disparará uma exceção NoSuchMethodException, que o manipulador de exceções irá ignorar, permitindo que o loop for continue.
O Exemplo de código 4 mostra o código fonte para a classe padrão do controlador. Essa classe consiste apenas de propriedades e métodos chamados pelos ouvintes de eventos da visão.
Exemplo de código 4

public class DefaultController extends AbstractController
{
    public static final String ELEMENT_TEXT_PROPERTY = "Text";
    public static final String ELEMENT_FONT_PROPERTY = "Font";
    public static final String ELEMENT_X_PROPERTY = "X";
    public static final String ELEMENT_Y_PROPERTY = "Y";
    public static final String ELEMENT_OPACITY_PROPERTY = "Opacity";
    public static final String ELEMENT_ROTATION_PROPERTY = "Rotation";
    //  Code omitted
    public void changeElementText(String newText) {
        setModelProperty(ELEMENT_TEXT_PROPERTY, newText);
    }
    public void changeElementFont(Font newFont) {
        setModelProperty(ELEMENT_FONT_PROPERTY, newFont);
    }
    public void changeElementXPosition(int newX) {
        setModelProperty(ELEMENT_X_PROPERTY, newX);
    }
    public void changeElementYPosition(int newY) {
        setModelProperty(ELEMENT_Y_PROPERTY, newY);
    }
    public void changeElementOpacity(int newOpacity) {
        setModelProperty(ELEMENT_OPACITY_PROPERTY, newOpacity);
    }
    public void changeElementRotation(int newRotation) {
        setModelProperty(ELEMENT_ROTATION_PROPERTY, newRotation);
    }
}

 
A visão
Esse exemplo terá duas visões que exibirão os dados do modelo: uma visão de edição de propriedades e uma visão de página gráfica. As duas visões serão implementadas por um JPanel, inseridas em um JDialog ou JFrame. A caixa de diálogo permite que o usuário atualize os valores do modelo, e o painel simplesmente reflete as alterações no display de texto.
O Exemplo de código 5 mostra o código para a visão de edição de propriedades, a mais interessante das duas. A primeira seção é simplesmente devotada a inicialização dos componentes, que na sua maior parte foi gerada pela IDE do NetBeans no método initComponents(). Toda essa seção foi omitida mais está presente no código que você pode baixar. Qualquer outra inicialização que você precisar executar – nesse caso, a criação de modelos personalizados para os objetos JSpinner JSlider ou a adição de um DocumentListeners ao componente JTextField — é manipulador pelo método localInitialization().
Exemplo de código 5

public PropertiesViewPanel(DefaultController controller) {
        this.controller = controller;
        initComponents();
        localInitialization();
    }
    // ‹editor-fold defaultstate="collapsed" desc=" Local Initialization "›
    /**
     * Used to provide local initialization of Swing components
     * outside of the NetBeans automatic code generator
     */
    public void localInitialization() {
        opacitySpinner.setModel(new SpinnerNumberModel(100, 0, 100, 1));
        opacitySlider.setModel(new DefaultBoundedRangeModel(100, 0, 0, 100));
        rotationSpinner.setModel(new SpinnerNumberModel(0, -180, 180, 1));
        rotationSlider.setModel(new DefaultBoundedRangeModel(0, 0, -180, 180));
        text.getDocument().addDocumentListener(new DocumentListener() {
            public void insertUpdate(DocumentEvent e) {
                textDocumentChanged(e);
            }
            public void removeUpdate(DocumentEvent e) {
                textDocumentChanged(e);
            }
            public void changedUpdate(DocumentEvent e) {
                textDocumentChanged(e);
            }
        });
    }
    // ‹/editor-fold›

 
Note que o código gerado automaticamente pelo IDE do NetBeans está escondido no código fonte de forma que o desenvolvedor pode colapsar cada uma dessas seções quando não for necessário:

// ‹editor-fold defaultstate="collapsed" desc=" Local Initialization "›
// ‹/editor-fold›

Se você estiver usando o IDE do NetBEans, essa prática é altamente recomendada.
A segunda seção da classe PropertiesViewPanel lida exclusivamente com o modelo. No Exemplo de código 6, um método modelPropertyChange() é chamado pelo controlador sempre que o modelo reporta uma mudança de estado.
Exemplo de código 6

// ‹editor-fold defaultstate="collapsed" desc=" Model Event Handling Code "›
    public void modelPropertyChange(final PropertyChangeEvent evt) {
        if (evt.getPropertyName().equals(
                   DefaultController.ELEMENT_X_PROPERTY)) {
            String newStringValue = evt.getNewValue().toString();
            xPositionTextField.setText(newStringValue);
        } else if
           (evt.getPropertyName().equals(
                   DefaultController.ELEMENT_Y_PROPERTY)) {
            String newStringValue = evt.getNewValue().toString();
            yPositionTextField.setText(newStringValue);
        } else if
           (evt.getPropertyName().equals(
                   DefaultController.ELEMENT_OPACITY_PROPERTY)) {
            int newIntegerValue = (Integer)evt.getNewValue();
            opacitySpinner.setValue(newIntegerValue);
            opacitySlider.setValue(newIntegerValue);
        } else if
            (evt.getPropertyName().equals(
                   DefaultController.ELEMENT_ROTATION_PROPERTY)) {
            int newIntegerValue = (Integer)evt.getNewValue();
            rotationSpinner.setValue(newIntegerValue);
            rotationSlider.setValue(newIntegerValue);
        } else if
           (evt.getPropertyName().equals(
                   DefaultController.ELEMENT_TEXT_PROPERTY)) {
            String newStringValue = evt.getNewValue().toString();
            text.setText(newStringValue);
        } else if
           (evt.getPropertyName().equals(
                   DefaultController.ELEMENT_FONT_PROPERTY)) {
            Font f = (Font)evt.getNewValue();
            String fontString = f.getFontName() + " " + f.getSize();
            font.setText(fontString);
            currentFont = f;
        }
        //  Remainder of the code omitted
    }
    // ‹/editor-fold›

 
Novamente, esse código omite algumas partes do código que são similares as seções mostradas.
A parte final consiste dos ouvintes de eventos. O Exemplo de código 7 contém ouvintes que são chamados sempre que eventos da interface ocorrem, como o pressionamento no botão Change Fontou do Opacity, ou zerar o texto em qualquer um dos campos de texto. Esses são os ouvintes de eventos do Swing que os desenvolvedores mais estão familiarizados. Se estiver usando o IDE do NetBeans, você verá que o IDE automaticamente gera muitos desses métodos.
Exemplo de código 7

 // ‹editor-fold defaultstate="collapsed" desc=" GUI Event Handling Code "›
    //  Code omitted
    private void yPositionTextFieldFocusLost(java.awt.event.FocusEvent evt) {
        try {
            controller.changeElementYPosition(
                Integer.parseInt(yPositionTextField.getText()));
        } catch (Exception e) {
            //  Handle exception.
        }
    }
    private void yPositionTextFieldActionPerformed(java.awt.event.ActionEvent evt) {
        try {
            controller.changeElementYPosition(
                Integer.parseInt(yPositionTextField.getText()));
        } catch (Exception e) {
            //  Handle exception.
        }
    }
    //  Code omitted -- code for xPosition
    //  is nearly the same as for yPosition.
    private void changeFontButtonActionPerformed(java.awt.event.ActionEvent evt) {
        JFontChooserDialog fontChooser = new
            JFontChooserDialog((Dialog)this.getTopLevelAncestor());
        fontChooser.setSelectedFont(currentFont);
        fontChooser.setVisible(true);
        Font returnedFont = fontChooser.getSelectedFont();
        if (returnedFont != null) {
            controller.changeElementFont(returnedFont);
        }
    }
    private void opacitySliderStateChanged(javax.swing.event.ChangeEvent evt) {
        controller.changeElementOpacity((int)opacitySlider.getValue());
    }
    private void rotationSliderStateChanged(javax.swing.event.ChangeEvent evt) {
        controller.changeElementRotation((int)rotationSlider.getValue());
    }
    private void opacitySpinnerStateChanged(javax.swing.event.ChangeEvent evt) {
        controller.changeElementOpacity((Integer)opacitySpinner.getValue());
    }
    private void rotationSpinnerStateChanged(javax.swing.event.ChangeEvent evt) {
        controller.changeElementRotation((Integer)rotationSpinner.getValue());
    }
    private void textDocumentChanged(DocumentEvent evt) {
        Document document = evt.getDocument();
        try {
            controller.changeElementText(document.getText(0,
            document.getLength()));
        } catch (BadLocationException ex) {
            //  Handle exception.
        }
    }
    // ‹/editor-fold›

Problemas com o Design da aplicação

Uma vez que a aplicação esteja sendo executada, você imediatamente se deparará com um problema. Considere a seguinte cadeia de eventos:

  1. Um dos componentes do Swing das visões recebe uma mudança, presumivelmente vinda da ação do usuário.
  2. O método apropriado do controlador é chamado.
  3. O modelo é atualizado e notifica o controlador da alteração da propriedade.
  4. A visão recebe um evento de alteração do controlador e tenta zerar o valores dos componentes do Swing equivalentes.
  5. O método apropriado do controlador é chamado, e o modelo é atualizado novamente.

Nesse ponto, um desses três cenários pode ocorrer, dependendo de qual componente do Swing você está usando e de quão robusto seu modelo é:

  • O componente do Swing que disparou a primeira alteração recusa-se a executar a segunda atualização, ao notar que o estado da propriedade não pode ser atualizada enquanto está no processo de notificação dos ouvintes da alteração inicial. Isso ocorre principalmente quando você usa os componentes de texto do Swing.
  • O modelo observa que o valor da segunda atualização coincide com a primeira,e recusa-se a enviar uma notificação de atualização. Isso é sempre uma prática segura de programação, e ocorre automaticamente se você usar a classe PropertyChangeSupport fornecida no pacote java.beans. Porém, não evita que o modelo receba atualizações redundantes.
  • Nenhuma medida de  salvaguarda é colocada no modelo ou no componente Swing, e o programa entra em loop infinito.

Esses problemas ocorrem porque os componentes Swing são autônomos. Por exemplo, o que acontece se o usuário pressiona a seta pra cima do componente JSpinner no PropertiesViewPanel, incrementando o valor dele em um? Após esse valor ser atualizado, um método ouvinte que está esperando por alterações nesse valor é chamado,  opacitySpinnerStateChanged(), que por sua vez chama o controlador e então atualiza a propriedade adequada do modelo.
Com um design tradicional do MVC, a visão deve ainda conter o valor anterior, e a alteração no modelo deve atualizar a visão para o valor atual. Porém,  não há necessidade de atualizar o componente Swing porque ele já se auto atualizou para o valor correto – ele fez isso antes que um evento fosse passado ao controlador.
Como podemos contornar isso? Uma maneira é escrever um mecanismo que diga ao modelo ou ao controlador para não propagar uma notificação de alteração sob essas circunstâncias, mas isso não é uma boa idéia. Lembre que mais de uma visão pode estar esperando por alterações no modelo. Se você desligar o envio de notificações para o modelo, nenhum outro ouvinte, incluindo outras visões, serão notificadas. Além disso, outros componentes da mesma visão podem depender dessas notificações.
Idealmente, cada componente Swing deve estar ciente de seu valor atual e o valor que a visão está tentando dar a ele. Se eles coincidem, nenhum notificação será enviada. Porém, alguns componentes Swing incluem essa lógica, e outros não. Uma solução possível é checar o valor a ser alterado que chegar ao modelo com o valor armazenado no componente Swing. Se eles forem idênticos, não haverá necessidade de alterar o valor do componente Swing.
O Exemplo de código 8 mostra uma atualização do método modelPropertyChange() para demonstrar essa abordagem.
Exemplo de código 8

public void modelPropertyChange(final PropertyChangeEvent evt) {
        if (evt.getPropertyName().equals(DefaultController.ELEMENT_X_PROPERTY)) {
            String newStringValue = evt.getNewValue().toString();
            if (!xPositionTextField.getText().equals(newStringValue))
                xPositionTextField.setText(newStringValue);
        }
        //  Remaining code omitted
    }

 
O exemplo final, que usa duas visões, é mostrado na figura 5. A outra visão faz uso das bibliotecas do Java 2D para exibir o texto, que está fora do escopo desse artigo. Porém, o código fonte é relativamente fácil de entender e está incluído no código fonte disponível para download.

Figure 5. Both Views Attached to a Single Model

Figure 5. Both Views Attached to a Single Model

Componentes comuns do Swing

Como esse artigo mostrou, parte da inicialização de cada delegação ou controlador requer que você ative vários eventos de componentes Swing. Porém, componentes Swing individuais podem frequentemente gerar múltiplos tipos de eventos. A Tabela abaixo apresenta alguns método comuns.
Tabela 1. Métodos de escuta comuns do Swing

Evento a ser monitorado Método do Swing que escuta o evento
JButton pressed;JCheckBox pressed;JRadioButton pressed;JToggleButtontoggled. JButton.addActionListener(java.awt.event.ActionListener)
JSpinner value changed. JSpinner.addChangeListener(javax.swing.event.ChangeListener)
JTextField,JFormattedTextField,JPasswordField orJTextArea character(s) are added, changed, or deleted. JTextField.getDocument().addDocumentListener(javax.swing.event.DocumentListener)
JTextField return button is pressed, or focus switches to another component. JTextField.addActionListener(java.awt.event.ActionListener)
JComboBox new entry is selected. from list. JComboBox.addActionListener(java.awt.event.ActionListener)
JComboBox editor (typically a text field) characters are added, changed, or deleted. JTextField editor = ((JTextField)comboBox.getEditor().getEditorComponent());
editor.getDocument().addDocumentListener(javax.swing.event.DocumentListener)
JComboBox return button is pressed. JComboBox.addActionListener(java.awt.event.ActionListener)
JComboBox pop-up menu is about to become visible, invisible, or be cancelled without a selection. JComboBox.addPopupMenuListener(javax.swing.event.PopupMenuListener)
JList new entry is selected. JList.addListSelectionListener(javax.swing.event.ListSelectionListener)
JTabbedPane new tab is selected. JTabbedPane.addChangeListener(javax.swing.event.ChangeListener)
JScrollBar adjustment is made. JScrollBar.addAdjustmentListener(java.awt.event.AdjustmentListener)
JScrollPaneadjustment is made. JScrollPane.getHorizontalScrollBar().addAdjustmentListener(java.awt.event.AdjustmentListener)
JScrollPane.getVerticalScrollBar().addAdjustmentListener(java.awt.event.AdjustmentListener)
JPopupMenu is about to become visible, invisible, or be cancelled without a selection. JPopupMenu.addPopupMenuListener(javax.swing.event.PopupMenuListener)
JMenu is about to be become visible or invisible, or be cancelled without a selection. JMenu.addMenuListener(javax.swing.event.MenuListener)
JMenuItem (in standard or pop-up menu) is selected. JMenuItem.addActionListener(java.awt.event.ActionListener)
JSlider value is changed.
JSlider.addChangeListener(javax.swing.event.ChangeListener)

Conclusão

O MVC é um dos pilares de qualquer ferramenta de programação de interfaces gráficas. Esse artigo mostrou como implementar uma variação do design MVC usando o Java SE e as bibliotecas do Swing. Além disso, foram mostrados alguns problemas comuns que os programadores enfrentam ao usar o MVC, assim como exibidos alguns componentes Swing que qualquer programador Java pode usar ao criar uma visão.

Para mais informações

Traduzido de