How to work with swing with multiple classes

2019-01-15 10:20发布

问题:

I just want to know some thing about the swing 1) How to use MVC model in swing? 2)Say i Have an Main window and i need to do the menu as separate class ,all the component as separate class and .which will be the best method to integrate it

回答1:

OK, this is called answering with overkill, so sorry about that, but here's a quick example I've whipped up that tries to use a simple MVC pattern to do a trivial thing: press a button and change the text in a JTextField. It's overkill because you could do the same thing in just a few lines of code, but it does illustrate some MVC in separate files and how the model controls the State. Please ask questions if anything is confusing!

The main class that puts all together and gets things started:

import javax.swing.*;

public class SwingMvcTest {
   private static void createAndShowUI() {

      // create the model/view/control and connect them together
      MvcModel model = new MvcModel();
      MvcView view = new MvcView(model);
      MvcControl control = new MvcControl(model);
      view.setGuiControl(control);

      // EDIT: added menu capability
      McvMenu menu = new McvMenu(control);

      // create the GUI to display the view
      JFrame frame = new JFrame("MVC");
      frame.getContentPane().add(view.getMainPanel()); // add view here
      frame.setJMenuBar(menu.getMenuBar()); // edit: added menu capability
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.pack();
      frame.setLocationRelativeTo(null);
      frame.setVisible(true);
   }

   // call Swing code in a thread-safe manner per the tutorials
   public static void main(String[] args) {
      java.awt.EventQueue.invokeLater(new Runnable() {
         public void run() {
            createAndShowUI();
         }
      });
   }
}

The view class:

import java.awt.*;
import java.awt.event.*;
import java.beans.*;
import javax.swing.*;

public class MvcView {
   private MvcControl control;
   private JTextField stateField = new JTextField(10);
   private JPanel mainPanel = new JPanel(); // holds the main GUI and its components

   public MvcView(MvcModel model) {
      // add a property change listener to the model to listen and 
      // respond to changes in the model's state
      model.addPropertyChangeListener(new PropertyChangeListener() {
         public void propertyChange(PropertyChangeEvent evt) {
            // if the state change is the one we're interested in...
            if (evt.getPropertyName().equals(MvcModel.STATE_PROP_NAME)) {
               stateField.setText(evt.getNewValue().toString()); // show it in the GUI
            }
         }
      });
      JButton startButton = new JButton("Start");
      startButton.addActionListener(new ActionListener() {
         // all the buttons do is call methods of the control
         public void actionPerformed(ActionEvent e) {
            if (control != null) {
               control.startButtonActionPerformed(e); // e.g., here
            }
         }
      });
      JButton endButton = new JButton("End");
      endButton.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            if (control != null) {
               control.endButtonActionPerformed(e); // e.g., and here
            }
         }
      });

      // make our GUI pretty
      int gap = 10;
      JPanel buttonPanel = new JPanel(new GridLayout(1, 0, gap, 0));
      buttonPanel.add(startButton);
      buttonPanel.add(endButton);

      JPanel statePanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0));
      statePanel.add(new JLabel("State:"));
      statePanel.add(Box.createHorizontalStrut(gap));
      statePanel.add(stateField);

      mainPanel.setBorder(BorderFactory.createEmptyBorder(gap, gap, gap, gap));
      mainPanel.setLayout(new BorderLayout(gap, gap));
      mainPanel.add(buttonPanel, BorderLayout.CENTER);
      mainPanel.add(statePanel, BorderLayout.PAGE_END);
   }

   // set the control for this view
   public void setGuiControl(MvcControl control) {
      this.control = control;
   }

   // get the main gui and its components for display
   public JComponent getMainPanel() {
      return mainPanel;
   }

}

The Control:

import java.awt.event.ActionEvent;

public class MvcControl {
   private MvcModel model;

   public MvcControl(MvcModel model) {
      this.model = model;
   }

   // all this simplistic control does is change the state of the model, that's it
   public void startButtonActionPerformed(ActionEvent ae) {
      model.setState(State.START);
   }

   public void endButtonActionPerformed(ActionEvent ae) {
      model.setState(State.END);
   }
}

The model uses a PropertyChangeSupport object to allow other objects (in this situation the View) to listen for changes in state. So the model is in effect our "observable" while the view is the "observer"

import java.beans.*;

public class MvcModel {
   public static final String STATE_PROP_NAME = "State";
   private PropertyChangeSupport pcSupport = new PropertyChangeSupport(this);
   private State state = State.NO_STATE;

   public void setState(State state) {
      State oldState = this.state;
      this.state = state;
      // notify all listeners that the state property has changed
      pcSupport.firePropertyChange(STATE_PROP_NAME, oldState, state);
   }

   public State getState() {
      return state;
   }

   public String getStateText() {
      return state.getText();
   }

   // allow addition of listeners or observers
   public void addPropertyChangeListener(PropertyChangeListener listener) {
      pcSupport.addPropertyChangeListener(listener);
   }

}

A simple enum, State, to encapsulate the concept of state:

public enum State {
   NO_STATE("No State"), START("Start"), END("End");
   private String text;

   private State(String text) {
      this.text = text;
   }

   @Override
   public String toString() {
      return text;
   }

   public String getText() {
      return text;
   }
}

edit: I see you mentioned menu as well, so I've added menu support with the addition of this class and the addition of several lines in the SwingMcvTest class. Note that because of separation of code, it was trivial to make this change to the GUI since all the menu needs to do is call control methods. It needs to know nothing of the model or the view:

import java.awt.event.ActionEvent;
import javax.swing.*;

public class McvMenu {
   private JMenuBar menuBar = new JMenuBar();
   private MvcControl control;

   @SuppressWarnings("serial")
   public McvMenu(MvcControl cntrl) {
      this.control = cntrl;

      JMenu menu = new JMenu("Change State");
      menu.add(new JMenuItem(new AbstractAction("Start") {
         public void actionPerformed(ActionEvent ae) {
            if (control != null) {
               control.startButtonActionPerformed(ae);
            }
         }
      }));
      menu.add(new JMenuItem(new AbstractAction("End") {
         public void actionPerformed(ActionEvent ae) {
            if (control != null) {
               control.endButtonActionPerformed(ae);
            }
         }
      }));

      menuBar.add(menu);
   }

   public JMenuBar getMenuBar() {
      return menuBar;
   }
}

God that's a lot of code to do a trivial bit of chit! I nominate myself and my code for this week's stackoverflow Rube Goldberg award.



回答2:

Swing has build in mechanism that simplifies MVC implementation. It has Actions framework. Class that is responsible on building view should care about instantiation of the JComponent subclasses and placing them onto panels. Each component that should react on the user's activity should have corresponding Action (b.setAction(myAction)). I typically create package com.myapp.actions and put all actions there. Sometimes I create abstract action as well but it is application specific. Actions allow you to separate the logic from presentation layer. Think about action as a entry point to "Model".

Typical application has more controls than actions. Some controls reuse the same action. For example you can save file by typing Ctrl-S, clicking menu item or toolbar button, using context menu etc. But all theses controls will invoke the same action SaveFileAction.

Concerning to your second question there are 2 different ways. First is based on inheritance. There are people that extend JFrame when they need frame and implement all layout into this special class.

Other approach is to create a set of utility methods that generate the layout. I personally prefer this one. I think that inheritance should be used if you really need a subclass of something for example when you wish to override one of super class methods (e.g. paint())

I hope my description helps. Good luck.