GraphStream 的 View 和 JTextField 之间的同步问题

问题描述 投票:0回答:1

我已经在https://github.com/graphstream/gs-ui-swing/issues/19#issue-2109865450中发布了我的问题。但由于此存储库上的最后一个答案已于 2021 年 6 月 10 日发布,并且其他问题已发布,但没有任何回复,我不确定是否仍然有人在关注那里的问题。这就是为什么我在这里重新问我的问题。

我创建了一个由一个View和两个JTextField组成的Jframe:

视图显示五个节点:四个只是地标,预计不会被用户移动(“fixed_*”),还有一个将被用户移动(“unfixed”)。两个 JTextField 显示“未固定”节点的坐标。 View 和 JTextField 都必须彼此同步。事实上,当用户移动视图中的“未固定”节点时,两个 JTextField 必须相应更新:

相反,当用户修改 JTextField 之一中的坐标时,视图也必须相应更新:

这里有四个测试用例:

  • 测试用例 1:用户运行应用程序并移动视图中的“未固定”节点。
  • 测试用例 2:用户运行应用程序并修改 JTextField 之一中的坐标。
  • 测试用例 3:用户运行应用程序,修改其中一个 JTextField 中的坐标,然后移动视图中的“未固定”节点。
  • 测试用例 4:用户运行应用程序,移动视图中的“未固定”节点,然后修改其中一个 JTextField 中的坐标。

测试用例 1、2 和 3 工作正常,但测试用例 4 不起作用。实际上,在测试用例 4 中,一旦用户移动了视图中的“未固定”节点,那么对其中一个 JTextField 中的坐标的修改不会更新视图。

我尝试分析测试用例 3 和 4 的执行之间有何不同。为此,我在代码的不同位置打印了当前线程的名称。我看到通过 JTextField 的修改是在线程“AWT-EventQueue-0”(Swing 的事件调度线程,不是吗?)上运行的,而通过 View 的修改是在线程“Thread-0”上运行的。在我的实现中,“Thread-0”是我运行 GraphStream 的泵送循环的线程,等待 GraphStream 的查看器线程中发生的事件并将它们复制回“Thread-0”内。根据我对 GraphStream 文档的理解:

  • GraphStream 的查看器始终运行在 Swing 的事件调度线程 (EDT) 中,
  • 与 GraphStream 的查看器关联的 GraphStream 的 Graph 可以从 EDT 或另一个线程访问,具体取决于所使用的 GraphStream 的 ThreadingModel,
  • 从另一个线程访问 GraphStream 的图比 EDT 更强大,因为它允许在图上与 GraphStream 的查看器并行运行算法。

我是否充分理解文档?

在我的实现中,我选择从 Swing 线程之外的另一个线程访问 GraphStream 的 Graph。因此,我从之前运行的测试用例 3 和 4 中推断出:

  • 从 EDT 更新 GraphStream 的视图不会阻止未来从“Thread-0”更新 GraphStream 的视图(测试用例 3),
  • 但是“Thread-0”中 GraphStream 视图的更新会阻止 EDT 中 GraphStream 视图的任何未来更新(测试用例 4)。

我的印象是我对所有这些线程都做错了。你能帮我一下吗?

我尝试制作一个最小工作示例(MWE)来重现我的问题。以下是 Java 源文件 NodeSyncTest.java 的内容:

package mwe;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Map;

import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingConstants;

import org.apache.commons.lang3.math.NumberUtils;
import org.graphstream.graph.Graph;
import org.graphstream.graph.Node;
import org.graphstream.graph.implementations.MultiGraph;
import org.graphstream.ui.graphicGraph.GraphicGraph;
import org.graphstream.ui.graphicGraph.GraphicNode;
import org.graphstream.ui.swing_viewer.SwingViewer;
import org.graphstream.ui.view.View;
import org.graphstream.ui.view.Viewer;
import org.graphstream.ui.view.ViewerListener;
import org.graphstream.ui.view.ViewerPipe;

class NodeSyncTest {
    
    public static void main(String[] args) {
        System.out.println("NodeSyncTest.main : " + Thread.currentThread().getName());
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                System.out.println("SwingUtilities.invokeLater.Runnable.run : " + Thread.currentThread().getName());
                new NodeSyncTest();
            }
        });
    }
    
    NodeSyncTest() {
                
        Map<String, MyNode> myNodes = Map.of(
                "fixed_top_left", new MyNode(-2, 2),
                "fixed_top_right", new MyNode(2, 2),
                "fixed_bottom_left", new MyNode(-2, -2),
                "fixed_bottom_right", new MyNode(2, -2),
                "unfixed", new MyNode(0, 0)
            );

        GraphStreamControl graphStreamControl = new GraphStreamControl(myNodes);
        
        JTextFieldsControl jTextFieldsControl = new JTextFieldsControl(myNodes);
        
        graphStreamControl.jTextFieldsControl = jTextFieldsControl;
        jTextFieldsControl.graphStreamControl = graphStreamControl;
        
        graphStreamControl.fillGraphStreamGraph();
        
        JFrame mainDialog = new JFrame();
        
        mainDialog.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        mainDialog.setSize(300, 300);

        mainDialog.getContentPane().add((Component) graphStreamControl.view, BorderLayout.CENTER);
        mainDialog.getContentPane().add(jTextFieldsControl.panel, BorderLayout.SOUTH);
        
        mainDialog.setLocationRelativeTo(null);
        
        mainDialog.setVisible(true);
        
        graphStreamControl.startPumpLoop();
    }
    
     class GraphStreamControl {
         
         Map<String, MyNode> myNodes;
         
         MyNode myUnfixedNode;
         
         Graph graphStreamGraph;
         
         Viewer viewer;
         
         View view;
         
         ViewerPipe viewerPipe;
         
         JTextFieldsControl jTextFieldsControl;
        
         GraphStreamControl(Map<String, MyNode> myNodes) {
             
            this.myNodes = myNodes;
            
            myUnfixedNode = myNodes.get("unfixed");
                        
            System.setProperty("org.graphstream.ui", "swing");
            
            graphStreamGraph = new MultiGraph("");
            
            viewer = new SwingViewer(graphStreamGraph, Viewer.ThreadingModel.GRAPH_IN_ANOTHER_THREAD);
            viewer.disableAutoLayout();
            view = viewer.addDefaultView(false);
            viewerPipe = viewer.newViewerPipe();
            viewerPipe.addSink(graphStreamGraph);
            
            viewerPipe.addViewerListener(new ViewerListener() {

                @Override
                public void viewClosed(String viewName) {}

                @Override
                public void buttonPushed(String id) {}

                @Override
                public void buttonReleased(String id) {
                    System.out.println("ViewerListener.buttonReleased : " + Thread.currentThread().getName());
                    if ("unfixed".equals(id)) {
                        GraphicGraph graphicGraph = viewer.getGraphicGraph();
                        GraphicNode unfixedGraphStreamNode = (GraphicNode) graphicGraph.getNode("unfixed");
                        myUnfixedNode.x = unfixedGraphStreamNode.getX();
                        myUnfixedNode.y = unfixedGraphStreamNode.getY();
                        jTextFieldsControl.update();
                    }
                }

                @Override
                public void mouseOver(String id) {}

                @Override
                public void mouseLeft(String id) {}

            });

        }
         
        public void fillGraphStreamGraph() {
            for (var entry : myNodes.entrySet()) {
                String nodeId = entry.getKey();
                MyNode myNode = entry.getValue();
                Node graphStreamNode = graphStreamGraph.addNode(nodeId);
                graphStreamNode.setAttribute("xy", myNode.x, myNode.y);
                graphStreamNode.setAttribute("ui.label", nodeId);
                graphStreamNode.setAttribute("ui.style", "text-alignment: under;");
            }
            
        }

        void startPumpLoop() {
            new Thread(() -> {
                System.out.println("GraphStreamControl.startPumpLoop : " + Thread.currentThread().getName());
                while (true) {
                    try {
                        viewerPipe.blockingPump();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }

        void update() {
            GraphicGraph graphicGraph = viewer.getGraphicGraph();
            GraphicNode unfixedGraphStreamNode = (GraphicNode) graphicGraph.getNode("unfixed");
            unfixedGraphStreamNode.setAttribute("xy", myUnfixedNode.x, myUnfixedNode.y);    
        }
        
    }
     
    class JTextFieldsControl {
        
        Map<String, MyNode> myNodes;
        
        MyNode myUnfixedNode;
        
        JPanel panel;
        
        JTextField xTextField;
        
        JTextField yTextField;
        
        GraphStreamControl graphStreamControl;
        
        JTextFieldsControl(Map<String, MyNode> myNodes) {
            
            this.myNodes = myNodes;
            
            myUnfixedNode = myNodes.get("unfixed");
                                    
            panel = new JPanel(new GridLayout(1, 4));
            
            JLabel xLabel = new JLabel("X:", SwingConstants.RIGHT);
            xLabel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
            panel.add(xLabel);
            xTextField = new JTextField(3);
            xTextField.setHorizontalAlignment(SwingConstants.RIGHT);
            xTextField.setText(Double.toString(myUnfixedNode.x));
            xTextField.getCaret().setDot(0);
            xTextField.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent event) {
                    System.out.println("JTextFieldsControl - actionPerformed on xTextField : " + Thread.currentThread().getName());
                    String xNodeString = xTextField.getText();
                    double xNodeDouble = NumberUtils.toDouble(xNodeString);
                    myUnfixedNode.x = xNodeDouble;
                    graphStreamControl.update();
                    
                }
            });
            panel.add(xTextField);
            
            JLabel yLabel = new JLabel("Y:", SwingConstants.RIGHT);
            yLabel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
            panel.add(yLabel);
            yTextField = new JTextField(3);
            yTextField.setHorizontalAlignment(SwingConstants.RIGHT);
            yTextField.setText(Double.toString(myUnfixedNode.y));
            yTextField.getCaret().setDot(0);
            yTextField.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent event) {
                    System.out.println("JTextFieldsControl - actionPerformed on yTextField : " + Thread.currentThread().getName());
                    String yNodeString = yTextField.getText();
                    double yNodeDouble = NumberUtils.toDouble(yNodeString);
                    myUnfixedNode.y = yNodeDouble;
                    graphStreamControl.update();
                }
            });
            panel.add(yTextField);
        }
        
        void update() {
            String xNodeString = Double.toString(myUnfixedNode.x);
            xTextField.setText(xNodeString);
            xTextField.getCaret().setDot(0);
            String yNodeString = Double.toString(myUnfixedNode.y);
            yTextField.setText(yNodeString);
            yTextField.getCaret().setDot(0);
        }
    }
    
    class MyNode {
        double x;
        double y;
        MyNode(double x, double y) {
            this.x = x;
            this.y = y;
        }
    }
    
}

这是 Maven POM 文件 pom.xml 之一,用于构建包含所有依赖项的可执行 JAR:

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>mwe</groupId>
    <artifactId>mwe</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <name>MWE</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.14.0</version>
        </dependency>
        <dependency>
            <groupId>org.graphstream</groupId>
            <artifactId>gs-core</artifactId>
            <version>2.0</version>
        </dependency>
        <dependency>
            <groupId>org.graphstream</groupId>
            <artifactId>gs-ui-swing</artifactId>
            <version>2.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <!-- Build an executable JAR -->
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.1.0</version>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                            <!-- here we specify that we want to use the main
                            method within the App class -->
                            <mainClass>mwe.NodeSyncTest</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>mwe.NodeSyncTest</mainClass>
                        </manifest>
                    </archive>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

要使用这两个文件,只需创建一个文件夹 mwe/,将 NodeSyncTest.java 放入 mwe/src/main/java/mwe/ 并将 pom.xml 放入 mwe/,然后在 mwe/ 中运行

mvn compile assembly:single

java -jar target/mwe-0.0.1-SNAPSHOT-jar-with-dependencies.jar

这是完整的项目文件夹:MWE.zip

java swing thread-safety graphstream
1个回答
0
投票

经过一番调试,终于找到了问题出在哪里。我只需要更换线路:

unfixedGraphStreamNode.setAttribute("xy", myUnfixedNode.x, myUnfixedNode.y);      

通过两行:

unfixedGraphStreamNode.setAttribute("x", myUnfixedNode.x);
unfixedGraphStreamNode.setAttribute("y", myUnfixedNode.y);

让一切顺利。

为什么

unfixedGraphStreamNode.setAttribute("xy", myUnfixedNode.x, myUnfixedNode.y);

不起作用对我来说仍然是个谜。事实上,https://graphstream-project.org/doc/Tutorials/Graph-Visualization/https://graphstream-project.org/doc/FAQ/Attributes/Is-there-a-list的文档-of-attributes-with-a-predefined-meaning-for-the-graph-viewer/ 表示我们可以使用

xy
属性来设置节点的坐标。但也鼓励使用属性
xyz
。因此,我尝试将代码更改为:

unfixedGraphStreamNode.setAttribute("xyz", myUnfixedNode.x, myUnfixedNode.y, 0.0);

而且它有效!我将在项目的 GitHub 存储库上发布问题。

© www.soinside.com 2019 - 2024. All rights reserved.