所以我正在为学校编写一个boid模拟程序。我的程序支持这些boid的多个不同组,它们不会与其他组聚集在一起,它们都有不同的设置,当创建新部落时,我会在程序的主GUI中添加一个BoxPanel,这些BoxPanel有一个设置按钮,可以打开一个带有组设置的新框架。
当我启动程序并添加代码中所有预先定义的部落时,这非常有效。现在,我在GUI中制作了一个新的部分,让我们制作这些boid的新组,并在模拟运行时添加它们,这就是问题开始的时候
出于某种奇怪的原因,它在模拟中添加了正确设置的组,但不会将BoxPanels添加到主GUI中。它使我模拟侧面的整个设置栏完全消失。我对此进行了测试,如果我在计算线程的开头添加部落,它会做同样的事情,所以这似乎是多个线程和swing的问题。有什么想法是什么导致的,或者如何解决这个问题吗?我对此完全感到困惑。
tl;dr: 当我还没有启动线程时,下面添加部落的代码运行良好,但如果我在启动线程后尝试使用它,optionPanel将显示为空
以下是将BoxPanels添加到主gui的代码:
def addTribe(tribe: Tribe) = {
tribeFrames += new TribeSettingFrame(tribe)
tribeBoxPanels += new TribeBoxPanel(tribe)
this.refcontents
}
private def refcontents = {
top.optionPanel.contents.clear()
top.optionPanel.contents += new BoxPanel(Orientation.Vertical) {
tribeBoxPanels.foreach(contents += _.tribeBoxPanel)
}
top.optionPanel.contents += new BoxPanel(Orientation.Horizontal) {
contents += top.addTribeButton
}
top.optionPanel.contents += new BoxPanel(Orientation.Horizontal) {
contents += top.vectorDebugButton
}
}
new Thread(BoidSimulation).start()
哦,我测试了它是否真的通过打印出内容的大小来添加它应该添加的内容,并且所有内容都很匹配,它只是不会绘制它们。
编辑:经过一番挖掘,这似乎真的是一件从线程更新swing的事情。很多地方建议使用SwingWorker,但从我收集的信息来看,我认为它不适合我的程序,因为它是一个连续的模拟,我必须在每帧中不断制作新的SwingWorkers。
EDIT2:尝试从线程调用方法,如下所示:
SwingUtilities.invokeLater(new Runnable() {
override def run() {
GUI2D.addTribe(tribe)
}
});
没什么区别我开始认为这是我如何使用TribeBoxPanel和TribeSettingFrame的问题。这些对象都只包含一个返回所需BoxPanel或Frame的方法。这个实现不好吗?如果是这样,创建动态BoxPanels和Frames的更好方法是什么
Swing不是线程安全的。
跟我重复一遍。
摆动不是线程安全的。
听到合唱了吗?Swing不是线程安全的有官方文档。
还有一个非常简单的解决方法。
SwingUtilities.invokeLater(new Runnable() {
@Override public void run() {
// your stuff
}
});
在Scala中,这被支持为:
Swing.invokeLater(/* your stuff */)
首先应该让UI线程处理所有UI操作。简单的方法应该是遵循Scala代码:
Swing.onEDT { GUI2D.addTribe(tribe) }
但正如你已经指出的,这并不能解决你的问题。我遇到了一个非常类似的问题,我只更改了Swing.Label
的文本内容,有时它就消失了。
事实证明,它只是在文本太长而无法在标签最初为自己保留的区域内显示时才消失。因此,解决问题的一种方法是在创建optionPanel时给它一个更大的初始大小。
Swing.onEDT { top.optionPanel.preferredSize = new Dimension(width,height) }
我不太确定这是否必须在第一次绘制组件之前(在调用Frame.open()之前)设置。