Java Swing - 如何在模型和视图之间创建最佳通信

Art*_*545 5 java model-view-controller swing

我使用Swing开发桌面应用程序时遇到的最大问题是我在完成应用程序时产生的意大利面条.

我来自MVC的PHP背景,我有一种简单的方法来实现依赖注入,当我需要时,我只能访问模型层中的任何地方,我只是构建了所需的类.

但是在Java中对我来说有点复杂,因为Swing组件基本上都是做事情的小模型,问题是,我如何处理它以提供一个漂亮的非意大利面设计.

我称之为意大利面设计?

对我来说,意大利面条设计是我有时会达到这样的程度,在我的组件中,我需要访问另一个组件,或者我需要访问我没有构建的某个地方.为此,我需要将这些实例从组件传递给组件,这意味着我为那些永远不会使用它的组件创建了这些对象的访问权限,并且只是在中间用作传递实例的人.基本上是我问自己的一点.

让我们看看这个虚拟的例子:

你有这个组件Map.该组件应该为您绘制您所在国家/地区的地图,并绘制实体(实体可以只是某个城市标签,军事基地,雷达目标数据以及地图可以显示的任何内容).因此,在您的模型层中,您可能在某处定义了一个名为的类EntitiyManager,其中包含实体列表,每个实体都是继承的MapEntity.那么为了绘制这些实体你需要做些什么呢?

方法1

通过EntityManager对Map和检索数据EntityManager得出:

public class Map extends JPanel {

    /**
     * Entity manager used to store and manipulate entities
     */
    private EntityManager manager;

    public Map(EntityManager manager) {
        this.manager = manager;
    }

    // SOME CODE HERE ALOT OF CODE

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);

        Graphics2D g2d = (Graphics2D) g;

        for (Entity e : manager.getEntities()) {
            if (!e.isVisible()) {
                continue;
            }

            if (e instanceof CityLabel) {
                // TO DRAW FOR CITY LABEL
            } else if (e instanceof MilitaryBase) {
                // TO DRAW FOR MILITARY BASE
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

方法2

传递EntityManager给Map每个Entity人都有自己的绘图方法:

public class Map extends JPanel {

    /**
     * Entity manager used to store and manipulate entities
     */
    private EntityManager manager;

    public Map(EntityManager manager) {
        this.manager = manager;
    }

    // SOME CODE HERE ALOT OF CODE

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);

        Graphics2D g2d = (Graphics2D) g;
        manager.render(g2d);
    }
}
Run Code Online (Sandbox Code Playgroud)

方法1的问题:它很长而且很混乱,根本没有组织,使你的绘画方法太大而且难以通过.即使您可以将其拆分为方法.

方法2的问题: EntityManager不是一个视图,它不应该绘制任何东西,它应该只用于算法,数据管理等操作.

但总的来说,我不喜欢这样的事实,即我把这样的事情传递EntityManager给了视图,它对我来说并不好看.

现在让我们说我必须创建一个能够测量地图上距离的函数.我需要一个mouseEventListener鼠标事件监听器需要访问它可以传递输入的地方.我需要一个地方来管理这个距离测量.如果我想创建一个显示我测量了多少公里的小窗口组件怎么办?另一方面我想在地图上显示我测量的虚线,这意味着我需要在两个组件中使用测量距离,这两个彼此相关.

我计划在本周末阅读有关Spring的内容,看看它对我有何帮助.

每次遇到的另一个问题是,我真的不知道如何让我的控制器正确地将输入传递给模型,我真的找不到在Swing中设计控制器的好方法,因为组件是如何设计的.所以对我来说输入管理也令人困惑,只是让我在我的程序中弄得一团糟.

那么设计完美Swing应用程序的好方法是什么?

更新

我最大的问题是当我有一个视图时,我必须在当前视图中更改某些内容时更新另一个视图.

假设我们有一个Map观点.地图视图是您所在国家/地区的地图.移动鼠标时,会得到一个为该视图调用的事件mouseMoved或类似的事件,然后在该Map中获得鼠标的X和Y. 现在,我们要做的是

  1. 转换X,Y映射在该视图的模型中完成的纬度和经度,
  2. 我们有另一个被命名的视图,BottomInformation它基本上是框架底部的一个条形图,它显示了信息.我们需要更新BottomInformation视图以显示我们获得的经度和纬度.

这意味着Map需要访问BottomInformation.没有意大利面,我该如何正确地做到这一点?尝试MVP和MVC有不同的方法吗?

Kap*_*ash 5

当我开始一个复杂的MVC Swing应用程序时,我也遇到了那些"意大利面条".以下是我管理它的方式.但这是我自己的方式,也许你不同意我的看法! 如果有人不同意我的任何言论,请告诉我.我不是专业人士,只是想帮忙.:)


提醒

首先,'Boris The Spider'已经为您介绍了良好的MVC/MVP原则.我会记住你的基础:

MVC架构

这意味着您的控制器需要直接访问模型和查看对象.该视图可以访问模型对象.该模型单独工作.这意味着如果您更改模型(更改所使用的API,更改算法,......),视图并不关心,它只是通过几种方法显示它.

控制器必须监听用户操作(按钮,字段等)并根据此更改模型数据.

该视图仅能够显示模型中的数据.没有其他的.


如何实施

实例化对象

首先,我创建模型和视图对象.

public static void main(String[] args) {
    EntityManager entManager = new EntityManager();
    Map mapView = new Map(entManager);
}
Run Code Online (Sandbox Code Playgroud)

您可以创建对象,通过构造函数参数传递每个对象所需的引用.该模型始终是第一个创建的,因为它是独立的.

然后,创建视图,该模型需要模型来获取数据值.

在创建所有GUI对象后,我在视图中创建控制器.这样,我通过方法将它们附加到他们的监听器attachListeners().

绘图实体

我的方法是视图需要每个实体类型的方法.例如,drawCity()方法,然后drawRoad()或drawOilStation().因为视图是唯一必须知道如何绘制它们的对象.模型不必处理它.

因为你的例子需要绘制所有组件,所以paintComponents()方法会非常混乱,是的.在我看来,你的"方法1"是最好的方法.

@Override
public void paintComponent(Graphics g) {
   super.paintComponent(g);

  Graphics2D g2d = (Graphics2D) g;

   for (Entity e : manager.getEntities()) {
       if (!e.isVisible()) {
           continue;
       }

       if (e instanceof CityLabel) {
           drawCity();
       } else if (e instanceof MilitaryBase) {
           drawMilitaryBase();
       }
   }
}
Run Code Online (Sandbox Code Playgroud)

让我们假设你有一个带有一些JList和JPanel的GUI.您的加载方法(读取从模型中显示的数据)将单独调用,每个方法从模型中获取所需的数据.

例如 :

public void createInterface(){
    //Instanciate some Swing components

    fillPhoneList();
    updateNamePanel();
}

private void fillPhoneList(){
    List<PhoneNumber> phoneList = model.getPhoneList();
    for(PhoneNumber phone : phoneList){
         phoneListView.add(phone);
    }
}
Run Code Online (Sandbox Code Playgroud)

更新视图

1)从用户操作更改视图

通常,控制器在出现界面事件时作出反应.控制器的工作是告诉模型改变,同时更新调用"加载"方法的视图.

假设您有2个视图,Map并且EntityDetails.你有一个控制器,称为MapListener和EntityDetailsController.它们都具有相应的视图对象作为属性.他们从那里查看所有用户操作.

public class MapListener implements ActionListener{
    private Map mapView;

    @Override
    public void actionPerformed(ActionEvent e){
        //Doing some things
    }
}

public class EntityDetailsListener implements ActionListener{
    private EntityDetails entityView;

    @Override
    public void actionPerformed(ActionEvent e){
        //Doing some things
    }
}
Run Code Online (Sandbox Code Playgroud)

我经常做的事情是在辅助视图中添加对主视图的引用.我的意思是那Map是主框架,它有时会显示第二个视图EntityDetails.所以我Map在EntityDetailsListener必要时添加一个引用.

public class EntityDetailsListener implements ActionListener{
    private EntityDetails entityView;
    private Map mapView;
    @Override
    public void actionPerformed(ActionEvent e){
        //Doing some things
        if (e.getSource() == changeNameButton){
            //For exemple, if you change the name of the entity on the EntityDetails view.
            mapView.updateEntity(this); //calling the update method in the Map class.
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

2)模型自动转换

当您的模型自行更改时,必须通知您的视图.您的控制器位于它们之间,是从模型接收通知的对象,说"嘿,我改变了,让我们更新视图!"

这就是你应该fireXXXChange()在Model对象中编写一些方法的原因.他们会告诉听众他们必须做一些工作.

因此,您需要将侦听器传递给模型对象.因为你在其他任何东西之前创建它,你必须通过像这样的方法传递它addMapListener(MapListener listener).然后,您可以在数据更改时通知此控制器.

因为公开它非常复杂,所以我只想让你学习如何使用Swing管理MVC的 Oracle教程.


我真的希望它不会混淆,我帮助了你一点点!解决意大利面条代码的最佳方法是记录并查看编码所需的每个应用程序的最佳方法,因为每种情况都不同.

文档: - javaworld.com:MVC 遇到Swing - 链接系统间:使用Swing实现的MVC模式 - oracle.com:使用MVC进行Java应用程序设计