当使用RMI调用中的值修改其DefaultListModel时,JavaJList有时会变为空白
我见过很多这样的问题,但我仍然不明白我的实现出了什么问题
我正在尝试使用RMI构建一个客户机-服务器应用程序来实现通信;它应该成为一款游戏,但现在我只关注基本的客户端界面操作,比如登录、玩家列表等
在客户端,我有一个swing GUI,其中显示了服务器上当前注册的玩家列表。每次新玩家在服务器上注册时,服务器都会将注册玩家的完整列表发送给所有注册的客户端。然后,客户端必须清除其播放器列表内容,并用接收到的值再次填充
接收到的列表是IPlayer
的数组,它是Player
类导出的远程接口。要更新客户端上的JList
,我使用下面的updatePlayerList()
方法:
class BrowseScreen extends Screen implements ActionListener, MouseListener, ListSelectionListener {
private JList<String> playerList;
...
@Override
protected void init() {
setLayout(new BorderLayout());
...
playerList = new JList<String>();
playerList.setModel(new DefaultListModel<String>());
JScrollPane playerScroll = new JScrollPane(playerList);
playerScroll.setBorder(BorderFactory.createTitledBorder(null, "Players", TitledBorder.CENTER, TitledBorder.DEFAULT_POSITION));
add(playerList, BorderLayout.EAST);
...
}
public void updatePlayerList(IPlayer[] list) {
try {
DefaultListModel<String> model = (DefaultListModel<String>) playerList.getModel();
model.clear();
for (IPlayer p : list) {
model.addElement(p.getName()); // remote method invocation
}
System.out.println(model); // added to check the content of the list model
} catch (RemoteException e) {
// TODO auto-generated catch block
e.printStackTrace();
}
}
...
}
(注意:Screen
只是一个JPanel
扩展,我用它来定义应用程序中的不同屏幕,比如登录、房间列表等。)
当我尝试打开多个客户机时,发生的情况是,在随机客户机上,列表完全变为空白,每次我使用其中一个客户机登录/退出时,JList
的更新和其他一些(仍然是随机的)客户机可能会以空列表结束
列表模型应该用新数据正确更新,因为我在控制台上看到了正确的输出
我读了一些关于“JList
不更新”问题的答案,最常见的答案是模型更新应该在EDT线程中执行。我不确定我做得是否正确,但我有一个主要问题:
public static void main(String[] args) throws RemoteException {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
Client client = new Client();
GuiClient gui = new GuiClient(client);
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}
(注意:GuiClient
对象使用对Client
的引用来执行涉及与服务器交互的复杂操作。所有用户界面组件,如显示前一个Screen
的JFrame
,都由GuiClient
管理)
我希望我能很好地解释情况(我对stackoverflow是新手);我的项目现在有很多课程,我不能在这里给出它的全貌
非常感谢您的帮助:)
编辑:使用SwingWorker
执行RMI调用解决了问题
这就是我改变updatePlayerList()
方法的方式:
public void updatePlayerList(IPlayer[] list) {
new ReadPlayerList(list, playerList).execute();
}
这是ReadPlayerList
类,它扩展了SwingWorker
:
public class ReadPlayerList extends SwingWorker<Void, String> {
private IPlayer[] list;
private JList<String> playerList;
private boolean listCleared;
public ReadPlayerList(IPlayer[] list, JList<String> playerList) {
this.list = list;
this.playerList = playerList;
listCleared = false;
}
@Override
protected Void doInBackground() throws Exception {
for (IPlayer p : list) {
publish(p.getName());
}
return null;
}
@Override
protected void process(List<String> chunks) {
DefaultListModel<String> model = (DefaultListModel<String>) playerList.getModel();
if (!listCleared) {
model.clear();
listCleared = true;
}
for (String name : chunks) {
model.addElement(name);
}
}
}
这解决了JList无法显示其内容的问题。现在的挑战是确保这个更新操作始终是原子的。我想我必须编写某种“线程安全JList”扩展,但这是另一个故事:)
# 1 楼答案
您有一个涉及客户机-服务器通信的复杂系统,您会看到随机的、不可预测的错误。总之,你看到了线程问题的所有典型症状
是的,您正在Swing线程上启动GUI,但这可能不是问题的根源。很可能(我不得不猜测),这是因为您没有使用RMI通信正确处理线程。你是不是在用一个SwingWorker来做这件事?如果我是你,我会这样做,这样你就可以将线程背景中的通信代码与Swing事件线程隔离开来,这样你就可以轻松地让通信代码与EDT上的Swing代码进行通信
如果你需要更具体的帮助,你需要展示更多,最好是发布一个尽可能接近minimal example program的程序