一个 Java Swing MVC DEMO

Tue Feb 25 2020 on java mvc

一个 Java Swing MVC DEMO

MVC 模式是Model-View-Controller的简称,下面是一个计数器的例子。

比如下面的代码,为了增加一点复杂性我们的Model中有一个定时任务每隔一秒会将time属性+1,现在我们需要设计我们的 View 来展现Model中的数据。可以看到我们的Model继承了Listenable类,这个类给我们提供了两个重要的接口:

方法名说明
void emit(String eventName)在其子类可以使用这个函数来释放一个事件
void on(String eventName, Runnable handler)在其子类外部可以使用这个函数监听一个事件

注意Listenable类需要自己来编写,文末给出了实现,他非常简短却能大幅度的降低你的程序复杂性,你可以直接将他粘贴到你的工程中来使用。

其实他的用法很简单,不要被吓到了,比如下面这个Model在 time++后就释放了一个叫做“time++”的事件。

package demo;

import java.util.Timer;
import java.util.TimerTask;

public class Model extends Listenable {
    private int count;
    private int time;

    public Model() {
        new Timer().schedule(new Task(), 1000, 1000);
    }

    public void addCount() {
        count++;
    }

    public int getCount() {
        return count;
    }

    public int getTime() {
        return time;
    }

    private class Task extends TimerTask {
        @Override
        public void run() {
            time++;
            emit("time++");
        }
    }
}

释放这个事件后,你就可以使用下面这样的格式来监听事件的发生,每当“time++”,do something 就会执行。比较类似TimerTask

Model model = new Model();
model.on("time++", () -> {
    // do something...
});

接着我们定义一个View继承JPanel类,然后添加两个JLabel和一个JButton,并且设计一些常用的方法。

package demo;

import javax.swing.*;

public class View extends JPanel {
    private JLabel label;
    private JButton button;

    public View() {
        label = new JLabel("");
        button = new JButton("add");
        add(label);
        add(button);
    }

    public JButton getButton() {
        return button;
    }

    public void setLabelText(String text) {
        label.setText(text);
    }

}

下面就是连接ViewModelController了,可以看到我们定义了initViewinitController两个类来初始化。在initController方法中我们给View中的按钮添加了监听事件,当按钮被点击时就会被Controller监听到,然后根据按钮的意义来改变Model中的数据。MVC 的结构如下图

image.png

ModelController接收命令和数据。 它存储这些数据并更新ViewViewModel提供的数据呈现给用户。Controller接受用户的输入,并将其转换为ModelView

但是有些时候Model需要主动改变View那我们怎样实现呢?还记得刚才我们在Model中 emit 的“time++”事件吗?我们只需要在Controller中监听这个事件并且做出响应即可,这样的好处是避免了在Model中持有View,因为如果没有监听机制那么Model只能自己来调用View的方法来更新视图。那么就需要拿到View的引用。

package demo;

public class Controller {
    private Model model;
    private View view;

    public Controller(Model model, View view) {
        this.model = model;
        this.view = view;
        initView();
    }

    public void initView() {
        view.setTextLabelText(model.getCount() + "");
    }

    public void initController() {
        model.on("time++", () -> {
            view.setTimeLabelText(model.getTime() + "");
        });
        model.on("time++", () -> {
            System.out.println(model.getTime());
        });
        view.getButton().addActionListener(e -> {
            model.addCount();
            view.setTextLabelText(model.getCount() + "");
        });
        // 上面写法等价下面写法,上面是匿名函数写法
        // view.getButton().addActionListener(this::onButtonClocked);
    }

    // private void onButtonClocked(ActionEvent actionEvent) {
    //     model.addCount();
    //     view.setLabelText(model.getCount() + "");
    // }
}

接着我们通过main函数启动我们的 swing 应用:

package demo;

import javax.swing.*;

public class Main {
    public static void main(String[] args) {
        View view = new View();
        Model model = new Model();
        Controller controller = new Controller(model, view);
        controller.initController();
        createWindow(view, "demo");
    }

    private static void createWindow(JPanel panel, String title) {
        JFrame frame = new JFrame(title);
        frame.setContentPane(panel);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(200, 200);
        frame.setVisible(true);
    }
}

我们现在给出Listenable类的实现:

package demo;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Map;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

public class Listenable {
    private Map<String, List<Runnable>> handlers = new ConcurrentHashMap<>();

    public void on(String eventName, Runnable handler) {
        List<Runnable> list = handlers.get(eventName);
        if (list != null) {
            list.add(handler);
        } else {
            list = Collections.synchronizedList(new ArrayList<>());
            list.add(handler);
            handlers.put(eventName, list);
        }
    }

    protected void emit(String eventName) {
        List<Runnable> list = handlers.get(eventName);
        if (list != null) {
            list.forEach(Runnable::run);
        }
    }
}

Related

Comments

comments powered by Disqus