php editor Strawberry introduces to you the Java Q&A on synchronization issues between GraphStream's View and JTextField in this article. When using the GraphStream library, you sometimes encounter challenges with data synchronization between View and JTextField. Through this article, you will learn how to solve this problem and make your Java programs smoother and more efficient.
I have posted my issue at https://github.com/graphstream/gs-ui-swing/issues/19#issue-2109865450. But since the last answer on this repository was posted on June 10, 2021, and other questions were posted without any replies, I'm not sure if anyone is still paying attention to the questions there. That's why I'm re-asking my question here.
I created a jframe consisting of a view and two jtextfields:
The view shows five nodes: four are just landmarks and are not expected to be moved by the user ("fixed_*"), and one will be moved by the user ("unfixed"). Two jtextfields display the coordinates of "unpinned" nodes. Both view and jtextfield must be in sync with each other. In fact, when the user moves the "unpinned" node in the view, both jtextfields must update accordingly:
Conversely, when the user modifies the coordinates in one of the jtextfields, the view must also update accordingly:
Here are four test cases:
Test cases 1, 2 and 3 work fine, but test case 4 does not work. In fact, in test case 4, once the user moves the "unpinned" node in the view, modifications to the coordinates in one of the jtextfields do not update the view.
I'm trying to analyze the difference between the execution of test cases 3 and 4. To do this, I printed the name of the current thread at various places in the code. I see that modifications via jtextfield are run on thread "awt-eventqueue-0" (swing's event dispatching thread, no?), while modifications via view are run on thread "thread-0". In my implementation, "thread-0" is the thread on which I run graphstream's pump loop, waiting for events that occur in graphstream's viewer thread and copying them back within "thread-0". From my understanding of the graphstream documentation:
Did I fully understand the documentation?
In my implementation, I chose to access the graphstream's graph from another thread outside of the swing thread. So, I deduce from the previously run test cases 3 and 4:
I'm under the impression that I'm doing the wrong thing with all of these threads. Can you help me?
I try to make a minimal working example (mwe) to reproduce my problem. The following is the content of the java source file 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; } } }
This is the maven pom file pom.xml used to build the executable jar containing all dependencies:
<?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>
To use these two files, just create a folder mwe/, put nodesynctest.java into mwe/src/main/java/mwe/ and put pom.xml into mwe/, and then in mwe/ run
mvn compiled assembly: single
and
java -jar target/mwe-0.0.1-snapshot-jar-with-dependency.jar
This is the complete project folder: mwe.zip
After some debugging, I finally found the problem. I just need to change the line:
unfixedgraphstreamnode.setattribute("xy", myunfixednode.x, myunfixednode.y);
Pass two lines:
unfixedgraphstreamnode.setattribute("x", myunfixednode.x); unfixedgraphstreamnode.setattribute("y", myunfixednode.y);
Get everything working properly.
Why
unfixedgraphstreamnode.setattribute("xy", myunfixednode.x, myunfixednode.y);
Not working is still a mystery to me. In fact, the documentation for https://www.php.cn/link/c305a250710e95cf6bad18c18a1c02f4 and https://www.php.cn/link/7c097a5ed40a8d91afd49026dd3b1062 says we can use xy
attribute to set the coordinates of the node. But the use of attributes xyz
is also encouraged. So I tried changing the code to:
unfixedGraphStreamNode.setAttribute("xyz", myUnfixedNode.x, myUnfixedNode.y, 0.0);
并且它有效!我将在项目的 github 存储库上发布问题。
The above is the detailed content of Synchronization issues between GraphStream's View and JTextField. For more information, please follow other related articles on the PHP Chinese website!