簡體   English   中英

子GUI組件應如何(通過MVC)訪問其父項?

[英]How should child GUI components access their parents (with MVC)?

假設我正在構建Java Swing GUI,並且有一個框架,其中包含一個面板,該面板包含另一個面板,該面板包含一個按鈕。 (假設這些面板是可重用的,因此我將它們划分為單獨的類。)

Frame → FirstPanel → SecondPanel → Button

實際上,孩子的隊伍可能更復雜,但是我只想保持這個示例簡單。

如果我希望按鈕控制其父組件之一(例如,調整框架的大小),那么在兩個不一定直接位於另一個GUI類之間的GUI類之間實現功能的最佳方法是什么?

我不喜歡將getParent()方法串在一起,或者將Frame實例一直向下傳遞到其子級,以便可以從SecondPanel進行訪問的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行),以便面板可以與模型進行通信。

我覺得這里發生了一些效率低下的事情(並且這些類變得太緊密了)。 如果我的問題不清楚,請告訴我。

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

如果您想讓類保持解耦,則可以添加一個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,

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM