儿童GUI组件如何访问其父母(使用MVC)?
假设我正在构建一个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行),以便面板可以与模型进行通信。
我觉得在这里发生了一些效率低下的问题(并且类变得过于紧密)。如果我的问题不清楚,请告诉我。
如果你想让你的类保持分离,你可以添加一个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,
在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;
}
}
}
这是一个有趣的方法。我必须更仔细地考虑我的特殊情况。 – Knave 2012-02-18 18:18:07