Как дочерние компоненты GUI должны обращаться к своим родителям (с MVC)? - PullRequest
0 голосов
/ 18 февраля 2012

Допустим, я создаю Java Swing GUI, и у меня есть фрейм, который содержит панель, содержащую другую панель, которая содержит кнопку.(Предположим, что панели можно использовать многократно, поэтому я разделил их на отдельные классы.)

Frame → FirstPanel → SecondPanel → Button

На самом деле линия детей может быть более сложной, но я просто хочу сохранить этот пример простым.

Если я хочу, чтобы кнопка управляла одним из своих родительских компонентов (например, изменяет размер фрейма), каков наилучший способ реализации функциональности между двумя классами графического интерфейса, которые не обязательно находятся один непосредственно внутри другого?

Мне не нравится идея связать вместе getParent() методы или передать экземпляр Frame через дочерние элементы так, чтобы к нему можно было обращаться из SecondPanel.По сути, я не хочу, чтобы мои классы были последовательно соединены так или иначе.

Это экземпляр, в котором кнопка должна обновлять модель, а не родительский компонент напрямую?Затем родитель получает уведомление об изменении модели и обновляется соответствующим образом?

Я собрал небольшой пример, который должен компилироваться и запускаться самостоятельно, чтобы проиллюстрировать мою проблему.Это два JButton в JPanel, в другом 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), чтобы панель могла взаимодействовать с моделью.

Мне кажется, что здесь происходит что-то неэффективное(и классы становятся слишком тесно связанными).Дайте мне знать, если моя проблема не ясна.

Ответы [ 2 ]

1 голос
/ 18 февраля 2012

В Swing контроллер и представление обычно принадлежат делегату пользовательского интерфейса, а модель является отдельной. Представление может построить сложную иерархию компонентов для представления модели, и контроллер прислушивается к ним по мере необходимости. Компонент просто используется для различной бухгалтерии, которая связывает воедино две части.

Так, например, в комбинированном ящике JCombobox - это место, где вы устанавливаете пользовательский интерфейс и модель. 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 голосов
/ 18 февраля 2012

Если вы хотите, чтобы ваши классы оставались разделенными, вы можете добавить 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.

НТН,

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...