【问题标题】:How should child GUI components access their parents (with MVC)?子 GUI 组件应该如何访问它们的父组件(使用 MVC)?
【发布时间】:2012-02-18 02:36:39
【问题描述】:

假设我正在构建一个 Java Swing GUI,我有一个框架,其中包含一个面板,另一个面板包含一个按钮。 (假设面板是可重复使用的,所以我将它们制成了单独的类。)

Frame → FirstPanel → SecondPanel → Button

实际上,children 行可能更复杂,但我只是想让这个示例保持简单。

如果我希望按钮控制其父组件之一(例如调整框架大小),在两个不一定直接位于另一个内部的 GUI 类之间实现功能的最佳方法是什么?

我不喜欢将getParent() 方法串在一起的想法,或者将Frame 的实例一直向下传递到其子级以便可以从SecondPanel 访问的想法。基本上,我不想以一种或另一种方式将我的课程菊花链式连接在一起。

这是一个按钮应该更新模型而不是直接更新父组件的实例吗?然后通知父模型的更改并相应地更新自己?

我整理了一个小例子,应该可以自己编译和运行来说明我的问题。这是一个 JPanel 中的两个 JButton,另一个 JPanel 中,一个 JFrame 中。按钮控制 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 行),以便面板可以与模型通信。

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

【问题讨论】:

    标签: java model-view-controller swing user-interface


    【解决方案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;
            }
        }
    }
    

    【讨论】:

    • 这是一个有趣的方法。我必须更仔细地考虑我的具体情况。
    【解决方案2】:

    如果您希望您的类保持解耦,您可以添加一个 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;
        }
    }
    

    然后,您拥有将所有类链接到一个单独位置的代码,并且它可以独立于您的控制器和视图实现而变化。

    HTH,

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-02-28
      • 2016-10-05
      • 2019-12-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-12-24
      • 2017-07-08
      相关资源
      最近更新 更多