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

标签 java swing model-view-controller

我在使用 Swing 开发桌面应用程序时遇到的最大问题是我完成申请时得到的意大利面。
我来自 MVC 的 PHP 背景,在那里我有一个简单的方法来实现依赖注入(inject),并且我可以在需要时访问模型层中的任何地方,我只是构建了所需的类。
但是在 Java 中,它对我来说有点复杂,因为 Swing 组件基本上是做事的小模型,问题是,我如何处理它以获得一个漂亮且非意大利面条式的设计。
我所说的意大利面设计?
对我来说,意大利面条式设计有时会导致我在我的组件中需要访问另一个组件,或者我需要访问我没有构建的某个地方。为此,我需要将这些实例从组件传递到组件,这意味着我为这些对象创建访问权限,这些对象永远不会使用它并且只是被用作中间人来传递实例。基本上是我问自己的一点。
让我们看看这个虚拟示例:
您有组件 Map .该组件应该为您绘制您所在国家/地区的 map ,并绘制实体(实体可以只是一些城市标签、军事基地、雷达目标数据以及 map 可以真正显示的任何内容)。因此,在您的模型层中,您可能在某处定义了一个名为 EntitiyManager 的类。其中包含一个实体列表,每个实体都是继承 MapEntity 的东西。 .那么你需要做什么才能绘制这些实体呢?
方法一
通行证EntityManagerMap并从 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
            }
        }
    }
}
方法二
通行证EntityManagerMap和每个 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);
    }
}
方法 1 的问题:它又长又乱,根本没有条理,让你的绘画方法太大而难以通过。即使您可以将其拆分为方法。
方法 2 的问题: EntityManager不是 View ,它不应该绘制任何东西,它应该只用于算法、数据管理等操作。
但总的来说,我不喜欢我通过了 EntityManager 的事实。像这样的观点,它只是不适合我。
现在假设我必须创建一个函数,使我能够测量 map 上的距离。我需要一个 mouseEventListener并且该鼠标事件监听器需要访问它可以将输入传递到的某个地方。我需要一个地方来管理那个距离测量。如果我想创建一个小窗口组件来显示我测量了多少公里,另一方面我想在 map 上显示我测量的虚线,这意味着我需要在两个组件中使用测量距离,这两个是相互关联的。
我打算阅读 Spring这个周末,看看它如何帮助我。
每次遇到的另一个问题是,我真的不知道如何让我的 Controller 正确地将输入传递给模型,我真的找不到在 Swing 中设计 Controller 的好方法,因为组件是如何设计的.所以对我来说,输入管理也很困惑,只会让我的程序变得一团糟。
那么设计完美 Swing 应用程序的好方法是什么?
更新
我最大的问题是当我有一个 View 并且当当前 View 中的某些内容发生更改时我必须更新另一个 View 。
假设我们有一个 Map看法。 map View 是您所在国家/地区的 map 。当您移动鼠标时,会为该 View 调用一个事件 mouseMoved或类似的东西,然后你会得到该 map 中鼠标的 X 和 Y。现在,我们要做的是
  • 转换 X , Y映射在该 View 的模型中完成的纬度和经度,
  • 我们还有另一个 View ,名为 BottomInformation它基本上是框架底部的一个栏,它显示信息。我们需要更新 BottomInformation查看以显示我们得到的这些经度和纬度。

  • 这意味着 Map需要访问 BottomInformation .如果没有意大利面,我该怎么做?是否有与尝试 MVP 和 MVC 不同的方法?

    最佳答案

    当我启动一个复杂的 MVC Swing 应用程序时,我也会遇到那些“意大利面”。
    这是我如何管理它。但这是我自己的做法,也许你不会同意我的看法!
    如果有人不同意我的话,请告诉我。我不是专业人士,只是想帮忙。 :)

    提醒

    首先,“Boris The Spider”已经向您介绍了良好的 MVC/MVP 原则。我只会记住你的基础:

    MVC Schema

    这意味着您的 Controller 需要一个 直接访问模型和查看对象。 查看 可以访问 模型对象。 该模型单独工作。这意味着如果你改变你的模型(改变使用的 API,改变算法,...), View 并不关心,它只是通过一些方法来显示它。

    Controller 必须监听用户操作(按钮、字段等)并根据此更改模型数据。

    该 View 只能显示模型中的数据。没有其他的。

    如何实现

    实例化对象

    首先,我创建模型和 View 对象。

    public static void main(String[] args) {
        EntityManager entManager = new EntityManager();
        Map mapView = new Map(entManager);
    }
    

    您创建对象,通过构造函数参数传递每个对象所需的引用。模型总是最先创建的,因为它是独立的。

    然后,您创建 View ,该 View 需要模型来获取数据值。

    在创建所有 GUI 对象后,我在 View 中创建 Controller 。这样,我通过一个方法将它们附加到它们的监听器 attachListeners() .

    绘图实体

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

    因为您的示例需要绘制所有组件,所以 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();
           }
       }
    }
    


    假设您有一个带有一些 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);
        }
    }
    

    更新 View

    1) 从用户操作改变 View

    通常, Controller 会在出现界面事件时使用react。 Controller 的工作是告诉模型改变,同时,调用“加载”方法更新 View 。

    假设您有 2 次浏览,MapEntityDetails .你有一个 Controller ,叫做 MapListenerEntityDetailsController .它们都具有相应的 View 对象作为属性。他们从那里的 View 中捕获所有用户操作。
    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
        }
    }
    

    我经常做的事情是在辅助 View 中添加对主 View 的引用。我的意思是 Map是主框架,有时会显示第二个 View 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.
            }
        }
    }
    

    2) 模型自动转换

    当您的模型自行更改时,您的 View 必须得到通知。您的 Controller 位于它们之间,是从模型接收通知的对象说“嘿,我改变了,让我们更新 View 吧!”

    这就是为什么你应该编码一些 fireXXXChange() Model 对象中的方法。他们会告诉听众他们必须做一些工作。

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

    因为暴露起来比较复杂,我就在how to manage MVC with Swing上给大家放这个Oracle教程.

    我真的希望它不会混淆,并且我对您有所帮助!
    解决意大利面条式代码的最佳方法是记录并查看最适合您需要编码的每个应用程序,因为每种情况都不同。

    文件:
    - javaworld.com:MVC meets Swing
    - 系统间链接:MVC pattern implemented with Swing
    - oracle.com:Java application design with MVC

    关于Java Swing - 如何在模型和 View 之间创建最佳通信,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38980669/

    相关文章:

    java - 如何通过java j2ee中的 Controller 设置外键?

    java - Android4.4 无法处理带有 "vnd.android-dir/mms-sms"的短信 Intent

    java - 如何快速学习 Drools 或其他规则引擎

    java - 如何使用 Mockito 验证带有枚举参数的方法调用?

    Java Swing : How to prevent system copy, 剪切、粘贴操作?

    java - Java 7 上 JCombobox 的类型安全

    java - Jersey 多种产品

    java - JTable 未在 java 中显示

    c# - Compact Framework - 是否有可用的 MVC 框架/库?

    java - 使用 JavaFx 应用 MVC