简体   繁体   English

如何在Java Swing应用程序中管理控制器的视图更新

[英]How to manage view updates from controllers in a Java Swing app

I'm finding that writing good OO code with Swing is incredibly hard. 我发现用Swing编写好的OO代码非常难。 My problem is essentially that I have a view (a JPanel) that has action listeners. 我的问题基本上是我有一个有动作监听器的视图(JPanel)。 The action listeners figure out which button was clicked and call the appropriate controller method. 动作侦听器确定单击了哪个按钮并调用适当的控制器方法。 The issue is that this controller method needs to update another view. 问题是此控制器方法需要更新另一个视图。 So the issue I'm having is I have views being passed all over the place to controllers. 所以我遇到的问题是我将视图传递给控制器​​。 Here's an example. 这是一个例子。

public class MyView extends JPanel implements ActionListener {
  private final MyController controller = new MyController();

  @Override public void actionPerformed(ActionEvent e) {
    this.controller.updateOtherView();
  }
}

This is essentially what I want, but this is what ends up happening. 这基本上就是我想要的,但这就是最终发生的事情。

public class MyView extends JPanel implements ActionListener {
  private MyController controller = new MyController();
  private OtherView otherView;

  public MyView(MyOtherView otherView) {
    this.otherView = otherView;
  }

  @Override public void actionPerformed(ActionEvent e) {
    this.controller.updateOtherView(otherView);
  }
}

And you can see that as the number of views that need to be updated increase and the number of classes that look like this increase, the views are essentially global variables and the code becomes complex and unclear. 您可以看到,随着需要更新的视图数量的增加以及看起来像这样的类数量的增加,视图本质上是全局变量,代码变得复杂且不清楚。 Another problem I'm running into is this other view usually isn't directly passed into MyView, but it has to go through the parents of MyView to get to MyView, which really just bugs me. 我遇到的另一个问题是这个其他视图通常没有直接传递到MyView,但它必须通过MyView的父级才能进入MyView,这真的只是让我烦恼。

For a real example of this, lets say I have a Menu and this MyView. 对于一个真实的例子,假设我有一个菜单和这个MyView。 MyView has a play button, which plays some music for a while and it disables (greys out) the play button until the music is finished. MyView有一个播放按钮,可播放一段时间的音乐,并禁用(灰显)播放按钮,直到音乐结束。 If I have a menu option called play, now I need to access the other views play button so I can grey it out. 如果我有一个名为play的菜单选项,现在我需要访问其他视图播放按钮,这样我就可以将它变灰了。 How can I do this without this annoying passing of views everywhere? 如果没有这种烦人的意见传递,我该怎么做呢? Although there might be specific solutions for this problem, I'm looking for something that will solve this view access problem in the general case. 虽然可能有针对此问题的特定解决方案,但我正在寻找能够解决一般情况下此视图访问问题的方法。

I'm not sure at all how to fix this. 我根本不确定如何解决这个问题。 I'm kind of using MVC pattern terminology at the moment without using the MVC pattern, which may or may not necessary. 我现在正在使用MVC模式术语而不使用MVC模式,这可能是也可能不是必需的。 Any help is appreciated. 任何帮助表示赞赏。

One solution: simply have the controller update the model. 一种解决方案:只需让控制器更新模型即可。 Then listeners attached to the model would update the views. 然后附加到模型的侦听器将更新视图。 You can also have the JMenuItems and corresponding JButtons share the same Action, and thereby when you disable the Action it will disable all buttons/menus/etc that use that Action. 您还可以让JMenuItems和相应的JButton共享相同的Action,因此当您禁用Action时,它将禁用使用该Action的所有按钮/菜单/等。

For example, the main class: 例如,主类:

import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class MvcExample {

   private static void createAndShowGui() {
      MyView view = new MyView();
      MyMenuBar menuBar = new MyMenuBar();
      MyModel model = new MyModel();
      MyControl control = new MyControl(model);
      control.addProgressMonitor(view);
      control.addView(view);
      control.addView(menuBar);

      model.setState(MyState.STOP);

      JFrame frame = new JFrame("MVC Example");
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.getContentPane().add(view.getMainPanel());
      frame.setJMenuBar(menuBar.getMenuBar());
      frame.pack();
      frame.setLocationByPlatform(true);
      frame.setVisible(true);

   }

   public static void main(String[] args) {
      SwingUtilities.invokeLater(new Runnable() {
         public void run() {
            createAndShowGui();
         }
      });
   }

   private static final byte[] DATA_ARRAY = { 0x43, 0x6f, 0x70, 0x79, 0x72,
         0x69, 0x67, 0x68, 0x74, 0x20, 0x46, 0x75, 0x62, 0x61, 0x72, 0x61,
         0x62, 0x6c, 0x65, 0x2c, 0x20, 0x30, 0x36, 0x2f, 0x31, 0x36, 0x2f,
         0x32, 0x30, 0x31, 0x32, 0x2e, 0x20, 0x46, 0x75, 0x62, 0x61, 0x72,
         0x61, 0x62, 0x6c, 0x65, 0x20, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x21 };

}

The Control: 控制:

import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;

import javax.swing.AbstractAction;

@SuppressWarnings("serial")
public class MyControl {
   private MyModel model;
   private PlayAction playAction = new PlayAction();
   private PauseAction pauseAction = new PauseAction();
   private StopAction stopAction = new StopAction();
   private List<MyProgressMonitor> progMonitorList = new ArrayList<MyProgressMonitor>();

   public MyControl(MyModel model) {
      this.model = model;

      model.addPropertyChangeListener(new MyPropChangeListener());
   }

   public void addProgressMonitor(MyProgressMonitor progMonitor) {
      progMonitorList.add(progMonitor);
   }

   public void addView(MySetActions setActions) {
      setActions.setPlayAction(playAction);
      setActions.setPauseAction(pauseAction);
      setActions.setStopAction(stopAction);
   }

   private class MyPropChangeListener implements PropertyChangeListener {
      @Override
      public void propertyChange(PropertyChangeEvent pcEvt) {
         if (MyState.class.getName().equals(pcEvt.getPropertyName())) {
            MyState state = (MyState) pcEvt.getNewValue();

            if (state == MyState.PLAY) {
               playAction.setEnabled(false);
               pauseAction.setEnabled(true);
               stopAction.setEnabled(true);
            } else if (state == MyState.PAUSE) {
               playAction.setEnabled(true);
               pauseAction.setEnabled(false);
               stopAction.setEnabled(true);
            } else if (state == MyState.STOP) {
               playAction.setEnabled(true);
               pauseAction.setEnabled(false);
               stopAction.setEnabled(false);
            }
         }
         if (MyModel.PROGRESS.equals(pcEvt.getPropertyName())) {
            for (MyProgressMonitor progMonitor : progMonitorList) {
               int progress = (Integer) pcEvt.getNewValue();
               progMonitor.setProgress(progress);
            }            
         }
      }
   }

   private class PlayAction extends AbstractAction {
      public PlayAction() {
         super("Play");
         putValue(MNEMONIC_KEY, KeyEvent.VK_P);
      }

      @Override
      public void actionPerformed(ActionEvent e) {
         model.play();
      }
   }

   private class StopAction extends AbstractAction {
      public StopAction() {
         super("Stop");
         putValue(MNEMONIC_KEY, KeyEvent.VK_S);
      }

      @Override
      public void actionPerformed(ActionEvent e) {
         model.stop();
      }
   }
   private class PauseAction extends AbstractAction {
      public PauseAction() {
         super("Pause");
         putValue(MNEMONIC_KEY, KeyEvent.VK_A);
      }

      @Override
      public void actionPerformed(ActionEvent e) {
         model.pause();
      }
   }
}

A State Enum: 国家枚举:

public enum MyState {
   PLAY, STOP, PAUSE
}

One of the view interfaces: 其中一个视图界面:

import javax.swing.Action;

public interface MySetActions {

   void setPlayAction(Action playAction);
   void setPauseAction(Action pauseAction);
   void setStopAction(Action stopAction);
}

Another view interface: 另一个视图界面:

public interface MyProgressMonitor {
   void setProgress(int progress);
}

The main GUI View: 主GUI视图:

import java.awt.BorderLayout;
import java.awt.GridLayout;

import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JProgressBar;

public class MyView implements MySetActions, MyProgressMonitor {
   private JButton playButton = new JButton();
   private JButton stopButton = new JButton();
   private JButton pauseButton = new JButton();
   private JPanel mainPanel = new JPanel();
   private JProgressBar progressBar = new JProgressBar();

   public MyView() {
      progressBar.setBorderPainted(true);

      JPanel btnPanel = new JPanel(new GridLayout(1, 0, 5, 0));
      btnPanel.add(playButton);
      btnPanel.add(pauseButton);
      btnPanel.add(stopButton);

      mainPanel.setLayout(new BorderLayout(0, 5));
      mainPanel.setBorder(BorderFactory.createEmptyBorder(5, 15, 5, 15));
      mainPanel.add(btnPanel, BorderLayout.CENTER);
      mainPanel.add(progressBar, BorderLayout.PAGE_END);
   }

   @Override
   public void setPlayAction(Action playAction) {
      playButton.setAction(playAction);
   }

   @Override
   public void setStopAction(Action stopAction) {
      stopButton.setAction(stopAction);
   }

   @Override
   public void setPauseAction(Action pauseAction) {
      pauseButton.setAction(pauseAction);
   }

   @Override
   public void setProgress(int progress) {
      progressBar.setValue(progress);
   }

   public JComponent getMainPanel() {
      return mainPanel;
   }

}

The menu bar portion of the View: View的菜单栏部分:

import java.awt.event.KeyEvent;
import javax.swing.Action;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;

public class MyMenuBar implements MySetActions {
   private JMenuItem playMenItem = new JMenuItem();
   private JMenuItem pauseMenuItem = new JMenuItem();
   private JMenuItem stopMenItem = new JMenuItem();
   private JMenuBar menuBar = new JMenuBar();

   public MyMenuBar() {
      JMenu menu = new JMenu("Main Menu");
      menu.setMnemonic(KeyEvent.VK_M);
      menu.add(playMenItem);
      menu.add(pauseMenuItem);
      menu.add(stopMenItem);
      menuBar.add(menu);
   }

   public JMenuBar getMenuBar() {
      return menuBar;
   }

   @Override
   public void setPlayAction(Action playAction) {
      playMenItem.setAction(playAction);
   }

   @Override
   public void setStopAction(Action stopAction) {
      stopMenItem.setAction(stopAction);
   }

   @Override
   public void setPauseAction(Action pauseAction) {
      pauseMenuItem.setAction(pauseAction);
   }

}

And finally, the model: 最后,模型:

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeListener;
import javax.swing.Timer;
import javax.swing.event.SwingPropertyChangeSupport;

public class MyModel {
   public final static String PROGRESS = "progress";
   protected static final int MAX_PROGRESS = 100; 
   private MyState state = null;
   private SwingPropertyChangeSupport pcSupport = new SwingPropertyChangeSupport(
         this);
   private Timer timer;
   private int progress = 0;

   public MyState getState() {
      return state;
   }

   public void setState(MyState state) {
      MyState oldValue = this.state;
      MyState newValue = state;
      this.state = newValue;
      pcSupport.firePropertyChange(MyState.class.getName(), oldValue, newValue);
   }

   public int getProgress() {
      return progress;
   }

   public void setProgress(int progress) {
      Integer oldValue = this.progress;
      Integer newValue = progress;
      this.progress = newValue;
      pcSupport.firePropertyChange(PROGRESS, oldValue, newValue);
   }

   public void play() {
      MyState oldState = getState();
      setState(MyState.PLAY);

      if (oldState == MyState.PAUSE) {
         if (timer != null) {
            timer.start();
            return;
         }
      }
      int timerDelay = 50;
      // simulate playing ....
      timer = new Timer(timerDelay, new ActionListener() {
         int timerProgress = 0;

         @Override
         public void actionPerformed(ActionEvent actEvt) {
            timerProgress++;
            setProgress(timerProgress);
            if (timerProgress >= MAX_PROGRESS) {
               setProgress(0);
               MyModel.this.stop();
            }
         }
      });
      timer.start();
   }

   public void pause() {
      setState(MyState.PAUSE);
      if (timer != null && timer.isRunning()) {
         timer.stop();
      }
   }

   public void stop() {
      setState(MyState.STOP);
      setProgress(0);
      if (timer != null && timer.isRunning()) {
         timer.stop();
      }
      timer = null;
   }

   public void addPropertyChangeListener(PropertyChangeListener listener) {
      pcSupport.addPropertyChangeListener(listener);
   }

   public void removePropertyChangeListener(PropertyChangeListener listener) {
      pcSupport.removePropertyChangeListener(listener);
   }
}

Please ask if any of this is the least bit confusing. 请问一下这是否有点混乱。

In such situation I tend to use Singletons. 在这种情况下,我倾向于使用单身人士。 Of course this depends on the uniqueness of your views. 当然,这取决于您的观点的独特性。 I usually have Singletons for my "windows" (JFrames) so I can navigate from those to whatever children I need by using getters. 我通常为我的“窗户”(JFrames)拥有单身人士,所以我可以通过使用getter从那些导航到我需要的任何孩子。 However this might not be the best idea in very complex situations. 然而,在非常复杂的情况下,这可能不是最好的主意。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM