2012-02-18 86 views
0

假设我正在构建一个Java Swing GUI,并且我有一个框架,其中包含一个面板,其中包含另一个面板,其中包含一个按钮。 (假设板是可重复使用的,所以我让他们到单独的类。)儿童GUI组件如何访问其父母(使用MVC)?

Frame → FirstPanel → SecondPanel → Button 

在现实中,孩子们的线可能更加复杂,但我只是想保持这个例子简单。

如果我想让按钮控制其父组件之一(例如,调整框架的大小),在两个GUI类之间实现功能的最佳方式是什么?

我不喜欢将getParent()方法连接在一起,或者将Frame的实例一直传递给其子节点,以便可以从SecondPanel访问它。基本上,我不想以这种或那种方式将我的课程串联起来。

这是一个实例,其中按钮应该更新模型,而不是直接更新父组件?然后父母被通知模型的变化并相应地更新自己?

我已经汇集了一个应该自行编译和运行的小例子来说明我的问题。这是JPanel中的两个JButton,另一个JPanel中的JFrame中的两个JButton。按钮控制JFrame的大小。

import java.awt.Color; 
import java.awt.Dimension; 
import java.awt.event.ActionEvent; 
import java.awt.event.ActionListener; 
import java.beans.PropertyChangeEvent; 
import java.beans.PropertyChangeListener; 
import java.util.ArrayList; 
import javax.swing.BorderFactory; 
import javax.swing.JButton; 
import javax.swing.JFrame; 
import javax.swing.JPanel; 

public class MVCExample 
{ 
    public static void main(String[] args) 
    { 
     Model model = new Model(); 

     Controller ctrl = new Controller(); 
     ctrl.registerModel(model); 

     View view = new View(ctrl); 
     view.setVisible(true); 

     model.init(); 
    } 

    /** 
    * Model class 
    */ 
    static class Model 
    { 
     private ArrayList<PropertyChangeListener> listeners = 
       new ArrayList<PropertyChangeListener>(); 

     private Dimension windowSize; 

     public Dimension getWindowSize(){ return windowSize; } 

     public void setWindowSize(Dimension windowSize) 
     { 
      if(!windowSize.equals(getWindowSize())) 
      { 
       firePropertyChangeEvent(getWindowSize(), windowSize); 
       this.windowSize = windowSize; 
      } 
     } 

     public void init() 
     { 
      setWindowSize(new Dimension(400, 400)); 
     } 

     public void addListener(PropertyChangeListener listener) 
     { 
      listeners.add(listener); 
     } 

     public void firePropertyChangeEvent(Object oldValue, Object newValue) 
     { 
      for(PropertyChangeListener listener : listeners) 
      { 
       listener.propertyChange(new PropertyChangeEvent(
         this, null, oldValue, newValue)); 
      } 
     } 
    } 

    /** 
    * Controller class 
    */ 
    static class Controller implements PropertyChangeListener 
    { 
     private Model model; 
     private View view; 

     public void registerModel(Model model) 
     { 
      this.model = model; 
      model.addListener(this); 
     } 

     public void registerView(View view) 
     { 
      this.view = view; 
     } 

     // Called from view 
     public void updateWindowSize(Dimension windowSize) 
     { 
      model.setWindowSize(windowSize); 
     } 

     // Called from model 
     public void propertyChange(PropertyChangeEvent pce) 
     { 
      view.processEvent(pce); 
     } 
    } 

    /** 
    * View classes 
    */ 
    static class View extends JFrame 
    { 
     public View(Controller ctrl) 
     { 
      super("JFrame"); 

      ctrl.registerView(this); 
      setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
      getContentPane().add(new FirstPanel(ctrl)); 
      pack(); 
     } 

     public void processEvent(PropertyChangeEvent pce) 
     { 
      setPreferredSize((Dimension)pce.getNewValue()); 
      pack(); 
     } 
    } 

    static class FirstPanel extends JPanel 
    { 
     public FirstPanel(Controller ctrl) 
     { 
      setBorder(BorderFactory.createTitledBorder(
        BorderFactory.createLineBorder(
        Color.RED, 2), "First Panel")); 

      add(new SecondPanel(ctrl)); 
     } 
    } 

    static class SecondPanel extends JPanel 
    { 
     private Controller controller; 
     private JButton smallButton = new JButton("400x400"); 
     private JButton largeButton = new JButton("800x800"); 

     public SecondPanel(Controller ctrl) 
     { 
      this.controller = ctrl; 
      setBorder(BorderFactory.createTitledBorder(
        BorderFactory.createLineBorder(
        Color.BLUE, 2), "Second Panel")); 

      add(smallButton); 
      add(largeButton); 

      smallButton.addActionListener(new ActionListener() 
      { 
       public void actionPerformed(ActionEvent ae) 
       { 
        controller.updateWindowSize(new Dimension(400, 400)); 
       } 
      }); 

      largeButton.addActionListener(new ActionListener() 
      { 
       public void actionPerformed(ActionEvent ae) 
       { 
        controller.updateWindowSize(new Dimension(800, 800)); 
       } 
      }); 
     } 
    } 
} 

我不喜欢的是,控制器需要存在于JFrame中,以便帧可以注册自己接收事件。但是控制器必须一直传递到SecondPanel(第112,131和143行),以便面板可以与模型进行通信。

我觉得在这里发生了一些效率低下的问题(并且类变得过于紧密)。如果我的问题不清楚,请告诉我。

回答

0

如果你想让你的类保持分离,你可以添加一个ViewFactory来处理所有片段的链接。像这样的东西可能会奏效:

static interface ViewFactory { 
    View makeView(Controller c); 
} 

static class DefaultViewFactory implements ViewFactory { 
    public View makeView(Controller c) { 
     Button b = new Button(); 
     b.addActionListener(new ActionListener() { 
      public void actionPerformed(ActionEvent e) { 
       c.updateWindowSize(new Dimension(800, 600)); 
     }); 
     Panel2 p2 = new Panel2(); 
     p2.add(b); 
     Panel1 p1 = new Panel1(); 
     p1.add(p2); 
     View v = new View(); 
     v.add(p1); 
     return v; 
    } 
} 

然后,你必须在一个单独的地方所有的类链接在一起的代码,它可以在你的控制器,你的View实现独立变化。

HTH,

1

在Swing中,所述控制器和视图通常属于UI代理,并且该模型是分开的。视图可以构建复杂的组件层次结构来表示模型,并且控制器根据需要监听它们。该组件仅用于将两部分连接在一起的各种簿记。

因此,例如,在组合框中,JCombobox是您设置UI和模型的地方。 ComboboxUI组合组成组合框的组件 - 渲染器或编辑器和按钮,以及弹出和列表 - 并提供布局和可能的自定义渲染。这是查看逻辑。它还会监听所有这些组件并根据需要修改模型。这是控制器级别。对模型所做的更改会通过事件触发组件。

因此,就你而言,没有理由视图代码无法构建整个组件层次结构。我会让该模型为改变其属性的按钮提供动作,然后让视图监听该属性更改并调整窗口大小:

class View implements PropertyChangeListener { 
    JFrame frame; 

    View(Model model) { 
     model.addPropertyChangeListener(this); 

     frame = new JFrame(); 

     List<Action> actions = model.getActions(); 

     JPanel panel = new JPanel(); 
     panel.setLayout(new GridLayout(1, actions.size())); 

     for(Action action : actions) { 
      panel.add(new JButton(action)); 
     } 

     frame.getContentPane().add(panel); 
     frame.pack(); 
     frame.setVisible(true); 
    } 

    public void propertyChange(PropertyChangeEvent evt) { 
     frame.setSize((Dimension)evt.getNewValue()) 
    } 
} 

class Model { 
    List<Action> actions = new ArrayList<Action>(); 
    Dimension dimension; 

    Model() { 
     actions.add(new DimensionAction(400, 400)); 
     actions.add(new DimensionAction(800, 800)); 
    } 

    List<Action> getActions() { 
     return Collections.unmodifiableList(actions); 
    } 

    void setDimension(Dimension newDimension) { 
     Dimension oldDimension = this.dimension; 
     this.dimension = newDimension; 

     firePropertyChange("dimension", oldDimension, newDimension); 
    } 

    ... Property change support ... 

    class DimensionAction extends AbstractAction { 
     Dimension dimension; 

     DimensionAction(int width, int height) { 
      super(width + "x" + height); 
      this.dimension = new Dimension(width, height); 
     } 

     @Override 
     public void actionPerformed(ActionEvent e) { 
      Model.this.dimension = dimension; 
     } 
    } 
} 
+0

这是一个有趣的方法。我必须更仔细地考虑我的特殊情况。 – Knave 2012-02-18 18:18:07