Java和GUI—根据MVC模式,ActionListener属于哪里?
我目前正在编写一个模板Java应用程序,不知何故,如果我想完全遵循MVC模式,我不确定ActionListeners属于哪里 这个例子是基于Swing的,但它不是关于框架,而是关于Java中MVC的基本概念,使用任何框架来创建GUI 我从一个包含JFrame和JButton的绝对简单的应用程序开始(处理框架,然后关闭应用程序)。跟踪此帖子的代码。没什么特别的,只是想弄清楚我们在说什么。我还没有从模型开始,因为这个问题太困扰我了 类似的问题已经不止一个了,比如:Java和GUI—根据MVC模式,ActionListener属于哪里?,java,swing,user-interface,model-view-controller,awt,Java,Swing,User Interface,Model View Controller,Awt,我目前正在编写一个模板Java应用程序,不知何故,如果我想完全遵循MVC模式,我不确定ActionListeners属于哪里 这个例子是基于Swing的,但它不是关于框架,而是关于Java中MVC的基本概念,使用任何框架来创建GUI 我从一个包含JFrame和JButton的绝对简单的应用程序开始(处理框架,然后关闭应用程序)。跟踪此帖子的代码。没什么特别的,只是想弄清楚我们在说什么。我还没有从模型开始,因为这个问题太困扰我了 类似的问题已经不止一个了,比如: 但他们中没有一个是真正令人满意
但他们中没有一个是真正令人满意的,因为我想知道两件事:
- 将所有ActionListener放在一个单独的包中是否合理?
- 我希望这样做是为了视图和控制器的可读性,特别是当有很多侦听器时
- 如果监听器不是控制器中的子类,我将如何从ActionListener中执行控制器函数?(跟进问题)
杰森
控制器:
package controller;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import view.MainView;
public class MainController
{
MainView mainView = new MainView();
public MainController()
{
this.initViewActionListeners();
}
private void initViewActionListeners()
{
mainView.initButtons(new CloseListener());
}
public class CloseListener implements ActionListener
{
@Override
public void actionPerformed(ActionEvent e)
{
mainView.dispose();
}
}
}
视图:
它们与控件关联,但不必是控件的直接部分。例如,请参阅下面发布的代码,我正准备回答另一个问题,一个关于匿名内部类和耦合的问题,这里我给出了所有按钮的匿名内部操作(当然是ActionListeners),然后使用这些操作更改GUI状态。GUI(控件)的任何侦听器都将收到此更改的通知,然后可以相应地进行操作
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;
import javax.swing.event.SwingPropertyChangeSupport;
public class AnonymousInnerEg2 {
private static void createAndShowUI() {
GuiModel2 model = new GuiModel2();
GuiPanel2 guiPanel = new GuiPanel2();
GuiControl2 guiControl = new GuiControl2();
guiControl.setGuiPanel(guiPanel);
guiControl.setGuiModel(model);
try {
guiControl.init();
} catch (GuiException2 e) {
e.printStackTrace();
System.exit(-1);
}
JFrame frame = new JFrame("AnonymousInnerEg");
frame.getContentPane().add(guiPanel);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
createAndShowUI();
}
});
}
}
enum GuiState {
BASE("Base"), START("Start"), END("End");
private String name;
private GuiState(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
class GuiModel2 {
public static final String STATE = "state";
private SwingPropertyChangeSupport support = new SwingPropertyChangeSupport(this);
private GuiState state = GuiState.BASE;
public GuiState getState() {
return state;
}
public void setState(GuiState state) {
GuiState oldValue = this.state;
GuiState newValue = state;
this.state = state;
support.firePropertyChange(STATE, oldValue, newValue);
}
public void addPropertyChangeListener(PropertyChangeListener l) {
support.addPropertyChangeListener(l);
}
public void removePropertyChangeListener(PropertyChangeListener l) {
support.removePropertyChangeListener(l);
}
}
@SuppressWarnings("serial")
class GuiPanel2 extends JPanel {
public static final String STATE = "state";
private String state = GuiState.BASE.getName();
private JLabel stateField = new JLabel("", SwingConstants.CENTER);
public GuiPanel2() {
JPanel btnPanel = new JPanel(new GridLayout(1, 0, 5, 0));
for (final GuiState guiState : GuiState.values()) {
btnPanel.add(new JButton(new AbstractAction(guiState.getName()) {
{
int mnemonic = (int) getValue(NAME).toString().charAt(0);
putValue(MNEMONIC_KEY, mnemonic);
}
@Override
public void actionPerformed(ActionEvent e) {
String name = getValue(NAME).toString();
setState(name);
}
}));
}
setLayout(new BorderLayout());
add(stateField, BorderLayout.PAGE_START);
add(btnPanel, BorderLayout.CENTER);
}
public String getState() {
return state;
}
public void setState(String state) {
String oldValue = this.state;
String newValue = state;
this.state = state;
firePropertyChange(STATE, oldValue, newValue);
}
public void setStateField(String name) {
stateField.setText(name);
}
}
class GuiControl2 {
private GuiPanel2 guiPanel;
private GuiModel2 model;
private boolean allOK = false;
public void setGuiPanel(GuiPanel2 guiPanel) {
this.guiPanel = guiPanel;
guiPanel.addPropertyChangeListener(GuiPanel2.STATE,
new GuiPanelStateListener());
}
public void init() throws GuiException2 {
if (model == null) {
throw new GuiException2("Model is null");
}
if (guiPanel == null) {
throw new GuiException2("GuiPanel is null");
}
allOK = true;
guiPanel.setStateField(model.getState().getName());
}
public void setGuiModel(GuiModel2 model) {
this.model = model;
model.addPropertyChangeListener(new ModelListener());
}
private class GuiPanelStateListener implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (!allOK) {
return;
}
if (GuiPanel2.STATE.equals(evt.getPropertyName())) {
String text = guiPanel.getState();
model.setState(GuiState.valueOf(text.toUpperCase()));
}
}
}
private class ModelListener implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (!allOK) {
return;
}
if (GuiModel2.STATE.equals(evt.getPropertyName())) {
GuiState state = (GuiState) evt.getNewValue();
guiPanel.setStateField(state.getName());
}
}
}
}
@SuppressWarnings("serial")
class GuiException2 extends Exception {
public GuiException2() {
super();
}
public GuiException2(String message) {
super(message);
}
}
但请注意:我不是专业的程序员,甚至不是受过大学培训的程序员,所以请仅将此作为我的观点。这是一个很难用Swing回答的问题,因为Swing不是纯MVC实现,视图和控制器是混合的 从技术上讲,模型和控制器应该能够交互,控制器和视图应该能够交互,但视图和模型永远不应该交互,这显然不是Swing的工作方式,但这是另一个争论 另一个问题是,您真的不想向任何人公开UI组件,控制器不应该关心某些操作是如何发生的,只是它们可以 这将建议视图维护附加到UI控件的
ActionListener
s。然后,视图应提醒控制器发生了某种操作。为此,您可以使用另一个由视图管理的ActionListener
,控制器订阅该视图
更好的是,我会有一个专用的视图侦听器,它描述这个视图可能产生的操作,例如
public interface MainViewListener {
public void didPerformClose(MainView mainView);
}
然后,控制器将通过此侦听器订阅视图,当(在本例中)按下关闭按钮时,视图将调用didPerformClose
即使在这个示例中,我也会尝试创建一个“主视图”接口,它描述了任何实现都保证提供的属性(setter和getter)和操作(侦听器/回调),然后您不关心这些操作是如何发生的,只关心当它们发生时,您应该做些什么
在每个级别上,您都想问问自己,为另一个实例更改任何元素(更改模型、控制器或视图)有多容易?如果您发现自己必须解耦代码,那么您就有问题了。通过接口进行通信,并尝试减少各层之间的耦合量以及各层对其他层的了解量,使其仅维持契约
已更新…
让我们以这个为例
public interface MainViewListener {
public void didPerformClose(MainView mainView);
}
实际上有两个视图(不考虑实际对话框),即凭据视图和登录视图,是的,正如您将看到的,它们是不同的
认证视图
credentials视图负责收集要进行身份验证的详细信息、用户名和密码。它将向控制器提供信息,让其知道这些凭据何时已更改,因为控制器可能希望采取一些操作,例如启用“登录”按钮
视图还希望知道何时将要进行身份验证,因为它希望禁用其字段,因此用户无法在进行身份验证时更新视图,同样,它还需要知道身份验证何时失败或成功,因为它需要针对这些可能发生的情况采取措施
public interface CredentialsView {
public String getUserName();
public char[] getPassword();
public void willAuthenticate();
public void authenticationFailed();
public void authenticationSucceeded();
public void setCredentialsViewController(CredentialsViewController listener);
}
public interface CredentialsViewController {
public void credientialsDidChange(CredentialsView view);
}
证书窗格
CredentialsPane
是CredentialsView
的物理实现,它实现合同,但管理其自身的内部状态。合同的管理方式与控制人无关,它只关心合同是否得到维护
public class CredentialsPane extends JPanel implements CredentialsView {
private CredentialsViewController controller;
private JTextField userNameField;
private JPasswordField passwordField;
public CredentialsPane(CredentialsViewController controller) {
setCredentialsViewController(controller);
setLayout(new GridBagLayout());
userNameField = new JTextField(20);
passwordField = new JPasswordField(20);
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.insets = new Insets(2, 2, 2, 2);
gbc.anchor = GridBagConstraints.EAST;
add(new JLabel("Username: "), gbc);
gbc.gridy++;
add(new JLabel("Password: "), gbc);
gbc.gridx = 1;
gbc.gridy = 0;
gbc.anchor = GridBagConstraints.WEST;
gbc.fill = GridBagConstraints.HORIZONTAL;
add(userNameField, gbc);
gbc.gridy++;
add(passwordField, gbc);
DocumentListener listener = new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
getCredentialsViewController().credientialsDidChange(CredentialsPane.this);
}
@Override
public void removeUpdate(DocumentEvent e) {
getCredentialsViewController().credientialsDidChange(CredentialsPane.this);
}
@Override
public void changedUpdate(DocumentEvent e) {
getCredentialsViewController().credientialsDidChange(CredentialsPane.this);
}
};
userNameField.getDocument().addDocumentListener(listener);
passwordField.getDocument().addDocumentListener(listener);
}
@Override
public CredentialsViewController getCredentialsViewController() {
return controller;
}
@Override
public String getUserName() {
return userNameField.getText();
}
@Override
public char[] getPassword() {
return passwordField.getPassword();
}
@Override
public void willAuthenticate() {
userNameField.setEnabled(false);
passwordField.setEnabled(false);
}
@Override
public void authenticationFailed() {
userNameField.setEnabled(true);
passwordField.setEnabled(true);
userNameField.requestFocusInWindow();
userNameField.selectAll();
JOptionPane.showMessageDialog(this, "Authentication has failed", "Error", JOptionPane.ERROR_MESSAGE);
}
@Override
public void authenticationSucceeded() {
// Really don't care, but you might want to stop animation, for example...
}
public void setCredentialsViewController(CredentialsViewController controller){
this.controller = controller;
}
}
登录视图
LoginView
负责管理CredentialsView
,但也负责通知LoginView控制器
何时应该进行身份验证,或者如果用户通过某种方式取消了该过程
同样地,LoginViewController
将告诉视图何时进行身份验证,以及身份验证是否失败或成功
public interface LoginView {
public CredentialsView getCredentialsView();
public void willAuthenticate();
public void authenticationFailed();
public void authenticationSucceeded();
public void dismissView();
public LoginViewController getLoginViewController();
}
public interface LoginViewController {
public void authenticationWasRequested(LoginView view);
public void loginWasCancelled(LoginView view);
}
罗金潘
LoginPane
有点特殊,它充当LoginViewController
的视图,但也充当credentials视图的控制器
public static class LoginPane extends JPanel implements LoginView, CredentialsViewController {
private LoginViewController controller;
private CredentialsPane credientialsView;
private JButton btnAuthenticate;
private JButton btnCancel;
private boolean wasAuthenticated;
public LoginPane(LoginViewController controller) {
setLoginViewController(controller);
setLayout(new BorderLayout());
setBorder(new EmptyBorder(8, 8, 8, 8));
btnAuthenticate = new JButton("Login");
btnCancel = new JButton("Cancel");
JPanel buttons = new JPanel();
buttons.add(btnAuthenticate);
buttons.add(btnCancel);
add(buttons, BorderLayout.SOUTH);
credientialsView = new CredentialsPane(this);
add(credientialsView);
btnAuthenticate.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
getLoginViewController().authenticationWasRequested(LoginPane.this);
}
});
btnCancel.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
getLoginViewController().loginWasCancelled(LoginPane.this);
// I did think about calling dispose here,
// but's not really the the job of the cancel button to decide what should happen here...
}
});
validateCreientials();
}
public static boolean showLoginDialog(LoginViewController controller) {
final LoginPane pane = new LoginPane(controller);
JDialog dialog = new JDialog();
dialog.setTitle("Login");
dialog.setModal(true);
dialog.add(pane);
dialog.pack();
dialog.setLocationRelativeTo(null);
dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
dialog.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
pane.getLoginViewController().loginWasCancelled(pane);
}
});
dialog.setVisible(true);
return pane.wasAuthenticated();
}
public boolean wasAuthenticated() {
return wasAuthenticated;
}
public void validateCreientials() {
CredentialsView view = getCredentialsView();
String userName = view.getUserName();
char[] password = view.getPassword();
if ((userName != null && userName.trim().length() > 0) && (password != null && password.length > 0)) {
btnAuthenticate.setEnabled(true);
} else {
btnAuthenticate.setEnabled(false);
}
}
@Override
public void dismissView() {
SwingUtilities.windowForComponent(this).dispose();
}
@Override
public CredentialsView getCredentialsView() {
return credientialsView;
}
@Override
public void willAuthenticate() {
getCredentialsView().willAuthenticate();
btnAuthenticate.setEnabled(false);
}
@Override
public void authenticationFailed() {
getCredentialsView().authenticationFailed();
validateCreientials();
wasAuthenticated = false;
}
@Override
public void authenticationSucceeded() {
getCredentialsView().authenticationSucceeded();
validateCreientials();
wasAuthenticated = true;
}
public LoginViewController getLoginViewController() {
return controller;
}
public void setLoginViewController(LoginViewController controller) {
this.controller = controller;
}
@Override
public void credientialsDidChange(CredentialsView view) {
validateCreientials();
}
}
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.EmptyBorder;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import sun.net.www.protocol.http.HttpURLConnection;
public class Test {
protected static final Random AUTHENTICATION_ORACLE = new Random();
public static void main(String[] args) {
new Test();
}
public interface CredentialsView {
public String getUserName();
public char[] getPassword();
public void willAuthenticate();
public void authenticationFailed();
public void authenticationSucceeded();
public CredentialsViewController getCredentialsViewController();
}
public interface CredentialsViewController {
public void credientialsDidChange(CredentialsView view);
}
public interface LoginView {
public CredentialsView getCredentialsView();
public void willAuthenticate();
public void authenticationFailed();
public void authenticationSucceeded();
public void dismissView();
public LoginViewController getLoginViewController();
}
public interface LoginViewController {
public void authenticationWasRequested(LoginView view);
public void loginWasCancelled(LoginView view);
}
public Test() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
LoginViewController controller = new LoginViewController() {
@Override
public void authenticationWasRequested(LoginView view) {
view.willAuthenticate();
LoginAuthenticator authenticator = new LoginAuthenticator(view);
authenticator.authenticate();
}
@Override
public void loginWasCancelled(LoginView view) {
view.dismissView();
}
};
if (LoginPane.showLoginDialog(controller)) {
System.out.println("You shell pass");
} else {
System.out.println("You shell not pass");
}
System.exit(0);
}
});
}
public class LoginAuthenticator {
private LoginView view;
public LoginAuthenticator(LoginView view) {
this.view = view;
}
public void authenticate() {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
}
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
if (AUTHENTICATION_ORACLE.nextBoolean()) {
view.authenticationSucceeded();
view.dismissView();
} else {
view.authenticationFailed();
}
}
});
}
});
t.start();
}
}
public static class LoginPane extends JPanel implements LoginView, CredentialsViewController {
private LoginViewController controller;
private CredentialsPane credientialsView;
private JButton btnAuthenticate;
private JButton btnCancel;
private boolean wasAuthenticated;
public LoginPane(LoginViewController controller) {
setLoginViewController(controller);
setLayout(new BorderLayout());
setBorder(new EmptyBorder(8, 8, 8, 8));
btnAuthenticate = new JButton("Login");
btnCancel = new JButton("Cancel");
JPanel buttons = new JPanel();
buttons.add(btnAuthenticate);
buttons.add(btnCancel);
add(buttons, BorderLayout.SOUTH);
credientialsView = new CredentialsPane(this);
add(credientialsView);
btnAuthenticate.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
getLoginViewController().authenticationWasRequested(LoginPane.this);
}
});
btnCancel.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
getLoginViewController().loginWasCancelled(LoginPane.this);
// I did think about calling dispose here,
// but's not really the the job of the cancel button to decide what should happen here...
}
});
validateCreientials();
}
public static boolean showLoginDialog(LoginViewController controller) {
final LoginPane pane = new LoginPane(controller);
JDialog dialog = new JDialog();
dialog.setTitle("Login");
dialog.setModal(true);
dialog.add(pane);
dialog.pack();
dialog.setLocationRelativeTo(null);
dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
dialog.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
pane.getLoginViewController().loginWasCancelled(pane);
}
});
dialog.setVisible(true);
return pane.wasAuthenticated();
}
public boolean wasAuthenticated() {
return wasAuthenticated;
}
public void validateCreientials() {
CredentialsView view = getCredentialsView();
String userName = view.getUserName();
char[] password = view.getPassword();
if ((userName != null && userName.trim().length() > 0) && (password != null && password.length > 0)) {
btnAuthenticate.setEnabled(true);
} else {
btnAuthenticate.setEnabled(false);
}
}
@Override
public void dismissView() {
SwingUtilities.windowForComponent(this).dispose();
}
@Override
public CredentialsView getCredentialsView() {
return credientialsView;
}
@Override
public void willAuthenticate() {
getCredentialsView().willAuthenticate();
btnAuthenticate.setEnabled(false);
}
@Override
public void authenticationFailed() {
getCredentialsView().authenticationFailed();
validateCreientials();
wasAuthenticated = false;
}
@Override
public void authenticationSucceeded() {
getCredentialsView().authenticationSucceeded();
validateCreientials();
wasAuthenticated = true;
}
public LoginViewController getLoginViewController() {
return controller;
}
public void setLoginViewController(LoginViewController controller) {
this.controller = controller;
}
@Override
public void credientialsDidChange(CredentialsView view) {
validateCreientials();
}
}
public static class CredentialsPane extends JPanel implements CredentialsView {
private CredentialsViewController controller;
private JTextField userNameField;
private JPasswordField passwordField;
public CredentialsPane(CredentialsViewController controller) {
setCredentialsViewController(controller);
setLayout(new GridBagLayout());
userNameField = new JTextField(20);
passwordField = new JPasswordField(20);
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.insets = new Insets(2, 2, 2, 2);
gbc.anchor = GridBagConstraints.EAST;
add(new JLabel("Username: "), gbc);
gbc.gridy++;
add(new JLabel("Password: "), gbc);
gbc.gridx = 1;
gbc.gridy = 0;
gbc.anchor = GridBagConstraints.WEST;
gbc.fill = GridBagConstraints.HORIZONTAL;
add(userNameField, gbc);
gbc.gridy++;
add(passwordField, gbc);
DocumentListener listener = new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
getCredentialsViewController().credientialsDidChange(CredentialsPane.this);
}
@Override
public void removeUpdate(DocumentEvent e) {
getCredentialsViewController().credientialsDidChange(CredentialsPane.this);
}
@Override
public void changedUpdate(DocumentEvent e) {
getCredentialsViewController().credientialsDidChange(CredentialsPane.this);
}
};
userNameField.getDocument().addDocumentListener(listener);
passwordField.getDocument().addDocumentListener(listener);
}
@Override
public CredentialsViewController getCredentialsViewController() {
return controller;
}
@Override
public String getUserName() {
return userNameField.getText();
}
@Override
public char[] getPassword() {
return passwordField.getPassword();
}
@Override
public void willAuthenticate() {
userNameField.setEnabled(false);
passwordField.setEnabled(false);
}
@Override
public void authenticationFailed() {
userNameField.setEnabled(true);
passwordField.setEnabled(true);
userNameField.requestFocusInWindow();
userNameField.selectAll();
JOptionPane.showMessageDialog(this, "Authentication has failed", "Error", JOptionPane.ERROR_MESSAGE);
}
@Override
public void authenticationSucceeded() {
// Really don't care, but you might want to stop animation, for example...
}
public void setCredentialsViewController(CredentialsViewController controller) {
this.controller = controller;
}
}
}
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Ripples implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Ripples());
}
private Animation animation;
private DrawingPanel drawingPanel;
private RipplesModel model;
public Ripples() {
model = new RipplesModel();
}
@Override
public void run() {
JFrame frame = new JFrame("Ripples");
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent event) {
stopAnimation();
frame.dispose();
System.exit(0);
}
});
drawingPanel = new DrawingPanel(model);
frame.add(drawingPanel, BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
animation = new Animation(this, model);
new Thread(animation).start();
}
public void repaint() {
drawingPanel.repaint();
}
private void stopAnimation() {
if (animation != null) {
animation.setRunning(false);
}
}
public class DrawingPanel extends JPanel {
private static final long serialVersionUID = 1L;
private RipplesModel model;
public DrawingPanel(RipplesModel model) {
this.model = model;
setBackground(Color.BLACK);
setPreferredSize(new Dimension(500, 500));
addMouseListener(new RipplesListener(model));
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setStroke(new BasicStroke(5f));
List<Circle> circles = model.getCircles();
for (Circle circle : circles) {
Point p = circle.getCenter();
int radius = circle.getRadius();
g2.setColor(circle.getColor());
g2.drawOval(p.x - radius, p.y - radius,
2 * radius, 2 * radius);
}
}
}
public class RipplesListener extends MouseAdapter {
private Random random;
private RipplesModel model;
public RipplesListener(RipplesModel model) {
this.model = model;
this.random = new Random();
}
@Override
public void mousePressed(MouseEvent event) {
model.addCircle(new Circle(event.getPoint(),
createColor()));
}
private Color createColor() {
int r = random.nextInt(128) + 128;
int g = random.nextInt(128) + 128;
int b = random.nextInt(128) + 128;
return new Color(r, g, b);
}
}
public class Animation implements Runnable {
private volatile boolean running;
private Ripples frame;
private RipplesModel model;
public Animation(Ripples frame, RipplesModel model) {
this.frame = frame;
this.model = model;
this.running = true;
}
@Override
public void run() {
while (running) {
sleep(20L);
incrementRadius();
repaint();
}
}
private void incrementRadius() {
List<Circle> circles = model.getCircles();
for (Circle circle : circles) {
circle.incrementRadius();
}
}
private void sleep(long delay) {
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void repaint() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
frame.repaint();
}
});
}
public synchronized void setRunning(boolean running) {
this.running = running;
}
}
public class RipplesModel {
private List<Circle> circles;
public RipplesModel() {
this.circles = new ArrayList<>();
}
public void addCircle(Circle circle) {
this.circles.add(circle);
}
public List<Circle> getCircles() {
return circles;
}
}
public class Circle {
private int radius;
private final Color color;
private final Point center;
public Circle(Point center, Color color) {
this.center = center;
this.color = color;
this.radius = 10;
}
public void incrementRadius() {
radius = (++radius > 200) ? 10 : radius;
}
public Color getColor() {
return color;
}
public int getRadius() {
return radius;
}
public Point getCenter() {
return center;
}
}
}