如何在 Java Swing 应用程序中管理来自控制器的视图更新 [英] How to manage view updates from controllers in a Java Swing app

查看:23
本文介绍了如何在 Java Swing 应用程序中管理来自控制器的视图更新的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我发现用 Swing 编写好的 OO 代码非常困难.我的问题本质上是我有一个具有动作侦听器的视图(一个 JPanel).动作侦听器确定单击了哪个按钮并调用适当的控制器方法.问题是这个控制器方法需要更新另一个视图.所以我遇到的问题是我的视图到处传递给控制器​​.这是一个例子.

I'm finding that writing good OO code with Swing is incredibly hard. My problem is essentially that I have a view (a JPanel) that has action listeners. 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);
  }
}

并且可以看到,随着需要更新的视图数量的增加和看起来像这样的类的数量增加,视图本质上是全局变量,代码变得复杂和不清楚.我遇到的另一个问题是这个其他视图通常不会直接传递到 MyView,但它必须通过 MyView 的父视图才能到达 MyView,这真的让我很烦恼.

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 有一个播放按钮,它会播放一些音乐一段时间并禁用(变灰)播放按钮,直到音乐结束.如果我有一个名为播放的菜单选项,现在我需要访问其他视图播放按钮,以便我可以将其灰显.如果没有这种烦人的观点传递,我怎么能做到这一点?尽管可能有针对此问题的特定解决方案,但我正在寻找可以在一般情况下解决此视图访问问题的方法.

For a real example of this, lets say I have a Menu and this 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. If I have a menu option called play, now I need to access the other views play button so I can grey it out. 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.

我完全不知道如何解决这个问题.我目前正在使用 MVC 模式术语而不使用 MVC 模式,这可能是必要的,也可能不是.任何帮助表示赞赏.

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. Any help is appreciated.

推荐答案

一个解决方案:只需让控制器更新模型.然后附加到模型的侦听器将更新视图.您还可以让 JMenuItems 和相应的 JButtons 共享相同的 Action,因此当您禁用 Action 时,它将禁用使用该 Action 的所有按钮/菜单/等.

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.

例如主类:

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 };

}

控制:

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();
      }
   }
}

状态枚举:

public enum MyState {
   PLAY, STOP, PAUSE
}

其中一个视图接口:

import javax.swing.Action;

public interface MySetActions {

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

另一个视图界面:

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

主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;
   }

}

视图的菜单栏部分:

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);
   }

}

最后是模型:

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.

这篇关于如何在 Java Swing 应用程序中管理来自控制器的视图更新的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆