写在前面:
上周末抽点时间把自己写的一个简单 Socket 聊天程序的和记录了一下,周二终于等来毕业前考的软考证书,然后接下来就是在加班的日子度过了,今天正好周五,打算把客户端的详细设计和 Common 模块记录一下,因为这个周末开始就要去忙其他东西了。
设计:
客户端设计主要分成两个部分,分别是 socket 通讯模块设计和 UI 相关设计。
客户端 socket 通讯设计:
这里的设计其实跟服务端的设计差不多,不同的是服务端是接收心跳包,而客户端是发送心跳包,由于客户端只与一个服务端进行通讯(客户端之间的通讯也是由服务端进行分发的),所以这里只使用了一个大小为 2 的线程池去处理这两件事(newFixedThreadPool(2)),对应的处理类分别是 ReceiveListener、KeepAliveDog,其中 ReceiveListener 在初始化的时候传入一个 Callback 作为客户端收到服务端的消息的回调,Callback 的默认实现是 DefaultCallback,DefaultCallback 根据不同的事件通过 HF 分发给不同 Handler 去处理,而 ClientHolder 则是存储当前客户端信息,设计如下:
Socket 通讯模块具体实现:
[Client.java]
Client 是客户端连接服务端的入口,创建 Client 需要指定一个 Callback 作为客户端接收服务端消息时的回调,然后由 Client 的 start() 方法启动对服务端的监听(ReceiveListener),当 ReceiveListener 接收到服务端发来的数据时,调用回调(Callback)的 doWork() 方法去处理;同时 Client 中还需要发送心跳包来通知服务端自己还在连接着服务端,发心跳包由 Client 中 keepAlive() 启动,由 KeepAliveDog 实现;这两个步骤由一个固定大小为 2 为线程池 newFixedThreadPool(2) 去执行,可能这里使用一个 newFixedThreadPool(1) 和 newScheduledThreadPool(1) 去处理更合理,因为心跳包是定时发的,服务端就是这样实现的(这个后续调整),Client 的具体代码如下(这里暴露了另外两个方法用于获取 socket 和当前 socket 所属的用户):
- 1
- /**
- 2 * 客户端
- 3 * @author yaolin
- 4 *
- 5 */
- 6 public class Client {
- 7 8 private final Socket socket;
- 9 private String from;
- 10 private final ExecutorService pool;
- 11 private final Callback callback;
- 12 13 public Client(Callback callback) throws IOException {
- 14 this.socket = new Socket(ConstantValue.SERVER_IP, ConstantValue.SERVER_PORT);
- 15 this.pool = Executors.newFixedThreadPool(2);
- 16 this.callback = callback;
- 17
- }
- 18 19 public void start() {
- 20 pool.execute(new ReceiveListener(socket, callback));
- 21
- }
- 22 23 public void keepAlive(String from) {
- 24 this.from = from;
- 25 pool.execute(new KeepAliveDog(socket, from));
- 26
- }
- 27 28 public Socket getSocket() {
- 29
- return socket;
- 30
- }
- 31 32 public String getFrom() {
- 33
- return from;
- 34
- }
- 35
- }
[KeepAliveDog.java]
客户端在与服务端建立连接之后(该程序中是指登陆成功之后,因为登陆成功之后客户端的 socket 才会被服务端的 SocketHolder 管理),需要每个一段时间就给服务端发送心跳包告诉服务端自己还在跟服务端保持联系,不然服务端会在一段时间之后将没有交互的 socket 丢弃(详见服务端那篇博客),KeepAliveDog 的代码实现如下(后期可能会调整为 newScheduledThreadPool(1),所以这里的代码也会调整):
- 1
- /**
- 2 * KeepAliveDog : tell Server this client is running;
- 3 *
- 4 * @author yaolin
- 5 */
- 6 public class KeepAliveDog implements Runnable {
- 7 8 private final Socket socket;
- 9 private final String from;
- 10 11 public KeepAliveDog(Socket socket, String from) {
- 12 this.socket = socket;
- 13 this.from = from;
- 14
- }
- 15 16@Override 17 public void run() {
- 18
- while (socket != null && !socket.isClosed()) {
- 19
- try {
- 20 21 PrintWriter out = new PrintWriter(socket.getOutputStream());
- 22 AliveMessage message = new AliveMessage();
- 23 message.setFrom(from);
- 24 out.println(JSON.toJSON(message));
- 25 out.flush();
- 26 27 Thread.sleep(ConstantValue.KEEP_ALIVE_PERIOD * 1000);
- 28 29
- } catch(Exception e) {
- 30 LoggerUtil.error("Client send message failed !" + e.getMessage(), e);
- 31
- }
- 32
- }
- 33
- }
- 34
- }
[ReceiveListener.java]
Client 的 start() 方法启动对服务端的监听由 ReceiveListener 实现,ReceiveListener 接收到服务端的消息之后会回调 Callback 的 doWork() 方法,让回调去处理具体的业务逻辑,所以 ReceiveListener 只负责监听服务端的消息,具体的处理由 Callback 负责,这里需要提一下的是当消息类型是文件类型的时候会睡眠配置执行的间隔时间,这样 Callback 中的 doWork 才能对读取来至服务端的文件流,而不是直接进入下一次循环,这里的设计跟服务端是类似的。ReceiveListener 的具体实现代码如下:
- 1 public class ReceiveListener implements Runnable {
- 2 3 private final Socket socket;
- 4 private final Callback callback;
- 5 6 public ReceiveListener(Socket socket, Callback callback) {
- 7 this.socket = socket;
- 8 this.callback = callback;
- 9
- }
- 10 11@Override 12 public void run() {
- 13
- if (socket != null) {
- 14
- while (!socket.isClosed()) {
- 15
- try {
- 16 InputStream is = socket.getInputStream();
- 17 String line = null;
- 18 StringBuffer sb = null;
- 19 20
- if (is.available() > 0) {
- 21 22 BufferedReader bufr = new BufferedReader(new InputStreamReader(is));
- 23 sb = new StringBuffer();
- 24
- while (is.available() > 0 && (line = bufr.readLine()) != null) {
- 25 sb.append(line);
- 26
- }
- 27 LoggerUtil.trach("RECEIVE [" + sb.toString() + "] AT " + new Date());
- 28 29 callback.doWork(socket, sb.toString());
- 30 BaseMessage message = JSON.parseObject(sb.toString(), BaseMessage.class);
- 31
- if (message.getType() == MessageType.FILE) {
- 32 // PAUSE TO RECEIVE FILE
- 33 LoggerUtil.trach("CLIENT:PAUSE TO RECEIVE FILE");
- 34 Thread.sleep(ConstantValue.MESSAGE_PERIOD);
- 35
- }
- 36
- } else {
- 37 Thread.sleep(ConstantValue.MESSAGE_PERIOD);
- 38
- }
- 39
- } catch(Exception e) {
- 40 LoggerUtil.error("Client send message failed !" + e.getMessage(), e);
- 41
- }
- 42
- }
- 43
- }
- 44
- }
- 45 46
- }
[Callback.java、DefaultCallback.java]
从上面可以看出 Client 对消息的处理是 Callback 回调,其 Callback 只是一个接口,所有 Callback 实现该接口根据自己的需要对消息进行相应地处理,这里 Callback 默认的实现是 DefaultCallback,DefaultCallback 只对三种消息进行处理,分别是聊天消息、文件消息、返回消息。对于聊天消息,DefaultCallback 将通过 UI 中的 Router 路由获取到相应的界面(详见下面的 UI 设计),然后将消息展现在对应的聊天框中;对于文件消息,DefaultCallback 则是将文件写入到配置中指定的路径中(这里没有通过用户的允许就接收文件,这种设计不是很友好,目前先这样); 对于返回消息,DefaultCallback 会根据返回消息中的 KEY 叫给不同的 Handler 去处理。具体代码如下:
- 1 public interface Callback {
- 2 public void doWork(Socket server, Object data);
- 3
- }
- 1 public class DefaultCallback implements Callback {
- 2 3@Override 4 public void doWork(Socket server, Object data) {
- 5
- if (data != null) {
- 6 BaseMessage message = JSON.parseObject(data.toString(), BaseMessage.class);
- 7
- switch (message.getType()) {
- 8
- case MessageType.CHAT:
- 9 handleChatMessage(data);
- 10
- break;
- 11
- case MessageType.FILE:
- 12 handleFileMessage(server, data);
- 13
- break;
- 14
- case MessageType.RETURN:
- 15 handleReturnMessage(data);
- 16
- break;
- 17
- }
- 18
- }
- 19
- }
- 20 21 private void handleChatMessage(Object data) {
- 22 ChatMessage m = JSON.parseObject(data.toString(), ChatMessage.class);
- 23 String tabKey = m.getFrom(); // FROM
- 24 JComponent comp = Router.getView(ChatRoomView.class).getComponent(ChatRoomView.CHATTABBED);
- 25
- if (comp instanceof JTabbedPane) {
- 26 JTabbedPane tab = (JTabbedPane) comp;
- 27 int index = tab.indexOfTab(tabKey);
- 28
- if (index == -1) {
- 29 tab.addTab(tabKey, ResultHolder.get(tabKey).getScrollPane());
- 30
- }
- 31 JTextArea textArea = ResultHolder.get(tabKey).getTextArea();
- 32 textArea.setText(new StringBuffer() 33.append(textArea.getText()).append(System.lineSeparator()).append(System.lineSeparator()) 34.append(" [").append(m.getOwner()).append("] : ").append(System.lineSeparator()) 35.append(m.getContent()) 36.toString());
- 37 // SCROLL TO BOTTOM
- 38 textArea.setCaretPosition(textArea.getText().length());
- 39
- }
- 40
- }
- 41 42 private void handleFileMessage(Socket server, Object data) {
- 43 FileMessage message = JSON.parseObject(data.toString(), FileMessage.class);
- 44
- if (message.getSize() > 0) {
- 45 OutputStream os = null;
- 46
- try {
- 47
- if (server != null) {
- 48 InputStream is = server.getInputStream();
- 49 File dir = new File(ConstantValue.CLIENT_RECEIVE_DIR);
- 50
- if (!dir.exists()) {
- 51 dir.mkdirs();
- 52
- }
- 53 os = new FileOutputStream(54 new File(PathUtil.combination(ConstantValue.CLIENT_RECEIVE_DIR, new Date().getTime() + message.getName())));
- 55 int total = 0;
- 56
- while (!server.isClosed()) {
- 57
- if (is.available() > 0) {
- 58 byte[] buff = new byte[ConstantValue.BUFF_SIZE];
- 59 int len = -1;
- 60
- while (is.available() > 0 && (len = is.read(buff)) != -1) {
- 61 os.write(buff, 0, len);
- 62 total += len;
- 63 LoggerUtil.debug("RECEIVE BUFF [" + len + "]");
- 64
- }
- 65 os.flush();
- 66
- if (total >= message.getSize()) {
- 67 LoggerUtil.info("RECEIVE BUFF [OK]");
- 68
- break;
- 69
- }
- 70
- }
- 71
- }
- 72
- }
- 73
- } catch(Exception e) {
- 74 LoggerUtil.error("Receive file failed ! " + e.getMessage(), e);
- 75
- } finally {
- 76
- if (os != null) {
- 77
- try {
- 78 os.close();
- 79
- } catch(Exception ignore) {
- 80
- }
- 81 os = null;
- 82
- }
- 83
- }
- 84
- }
- 85
- }
- 86 87 private void handleReturnMessage(Object data) {
- 88 ReturnMessage m = JSON.parseObject(data.toString(), ReturnMessage.class);
- 89
- if (StringUtil.isNotEmpty(m.getKey())) {
- 90
- switch (m.getKey()) {
- 91
- case Key.NOTIFY:
- // Notify client to update usr list
- 92 HF.getHandler(Key.NOTIFY).handle(data);
- 93
- break;
- 94
- case Key.LOGIN:
- 95 HF.getHandler(Key.LOGIN).handle(data);
- 96
- break;
- 97
- case Key.REGISTER:
- 98 HF.getHandler(Key.REGISTER).handle(data);
- 99
- break;
- 100
- case Key.LISTUSER:
- 101 HF.getHandler(Key.LISTUSER).handle(data);
- 102
- break;
- 103
- case Key.TIP:
- 104 HF.getHandler(Key.TIP).handle(data);
- 105
- break;
- 106
- }
- 107
- }
- 108
- }
- 109
- }
[Handler.java、HF.java、ListUserHdl.java...]
Handler 组件负责对服务端返回消息类型的消息进行处理,DefaultCallback 根据不同的 KEY 将消息分发给不同的 Handler 进行处理,这里也算一套简单的工厂组件吧,跟服务端处理接收到的数据设计是类似的,完整的类图如下:
下面给出这一块的代码,为了缩小篇幅,将所有 Handler 实现的代码收起来。
- 1 public interface Handler {
- 2 public Object handle(Object obj);
- 3
- }
- 1 public class HF {
- 2 3 public static Handler getHandler(String key) {
- 4
- switch (key) {
- 5
- case Key.NOTIFY:
- 6
- return new NotifyHdl();
- 7
- case Key.LOGIN:
- 8
- return new LoginHdl();
- 9
- case Key.REGISTER:
- 10
- return new RegisterHdl();
- 11
- case Key.LISTUSER:
- 12
- return new ListUserHdl();
- 13
- case Key.TIP:
- 14
- return new TipHdl();
- 15
- }
- 16
- return null;
- 17
- }
- 18
- }
View Code
- 1 public class ListUserHdl implements Handler {
- 2 3@Override 4 public Object handle(Object obj) {
- 5
- if (obj != null) {
- 6
- try {
- 7 ReturnMessage rm = JSON.parseObject(obj.toString(), ReturnMessage.class);
- 8
- if (rm.isSuccess() && rm.getContent() != null) {
- 9 ClientListUserDTO dto = JSON.parseObject(rm.getContent().toString(), ClientListUserDTO.class);
- 10 JComponent comp = Router.getView(ChatRoomView.class).getComponent(ChatRoomView.LISTUSRLIST);
- 11
- if (comp instanceof JList) {
- 12@SuppressWarnings("unchecked") //
- 13 JList listUsrList = (JList) comp;
- 14 List listUser = new LinkedList();
- 15 listUser.addAll(dto.getListUser());
- 16 Collections.sort(listUser);
- 17 listUser.add(0, ConstantValue.TO_ALL);
- 18 listUsrList.setListData(listUser.toArray(new String[] {}));
- 19
- }
- 20
- }
- 21
- } catch(Exception e) {
- 22 LoggerUtil.error("Handle listUsr failed! " + e.getMessage(), e);
- 23
- }
- 24
- }
- 25
- return null;
- 26
- }
- 27 28
- }
View Code
- 1 public class LoginHdl implements Handler {
- 2 3@Override 4 public Object handle(Object obj) {
- 5
- if (obj != null) {
- 6
- try {
- 7 ReturnMessage rm = JSON.parseObject(obj.toString(), ReturnMessage.class);
- 8
- if (rm.isSuccess()) {
- 9 Router.getView(RegisterAndLoginView.class).trash();
- 10 Router.getView(ChatRoomView.class).create().display();
- 11 ClientHolder.getClient().keepAlive(rm.getTo()); // KEEP...
- 12
- } else {
- 13 Container container = Router.getView(RegisterAndLoginView.class).container();
- 14
- if (container != null) {
- 15 // show error
- 16 JOptionPane.showMessageDialog(container, rm.getMessage());
- 17
- }
- 18
- }
- 19
- } catch(Exception e) {
- 20 LoggerUtil.error("Handle login failed! " + e.getMessage(), e);
- 21
- }
- 22
- }
- 23
- return null;
- 24
- }
- 25 26
- }
View Code
- 1 public class NotifyHdl implements Handler {
- 2 3@Override 4 public Object handle(Object obj) {
- 5
- if (obj != null) {
- 6
- try {
- 7 ReturnMessage rm = JSON.parseObject(obj.toString(), ReturnMessage.class);
- 8
- if (rm.isSuccess() && rm.getContent() != null) {
- 9 ClientNotifyDTO dto = JSON.parseObject(rm.getContent().toString(), ClientNotifyDTO.class);
- 10 JComponent comp = Router.getView(ChatRoomView.class).getComponent(ChatRoomView.LISTUSRLIST);
- 11
- if (comp instanceof JList) {
- 12@SuppressWarnings("unchecked") //
- 13 JList listUsrList = (JList) comp;
- 14 List listUser = modelToList(listUsrList.getModel());
- 15
- if (dto.isFlag()) {
- 16
- if (!listUser.contains(dto.getUsername())) {
- 17 listUser.add(dto.getUsername());
- 18 listUser.remove(ConstantValue.TO_ALL);
- 19 Collections.sort(listUser);
- 20 listUser.add(0, ConstantValue.TO_ALL);
- 21
- }
- 22
- } else {
- 23 listUser.remove(dto.getUsername());
- 24
- }
- 25 listUsrList.setListData(listUser.toArray(new String[] {}));
- 26
- }
- 27
- }
- 28
- } catch(Exception e) {
- 29 LoggerUtil.error("Handle nofity failed! " + e.getMessage(), e);
- 30
- }
- 31
- }
- 32
- return null;
- 33
- }
- 34 35 private List modelToList(ListModel listModel) {
- 36 List list = new LinkedList();
- 37
- if (listModel != null) {
- 38
- for (int i = 0; i < listModel.getSize(); i++) {
- 39 list.add(listModel.getElementAt(i));
- 40
- }
- 41
- }
- 42
- return list;
- 43
- }
- 44
- }
View Code
- 1 public class RegisterHdl implements Handler {
- 2 3@Override 4 public Object handle(Object obj) {
- 5
- if (obj != null) {
- 6
- try {
- 7 ReturnMessage rm = JSON.parseObject(obj.toString(), ReturnMessage.class);
- 8 Container container = Router.getView(RegisterAndLoginView.class).container();
- 9
- if (container != null) {
- 10
- if (rm.isSuccess()) {
- 11 JOptionPane.showMessageDialog(container, rm.getContent());
- 12
- } else {
- 13 JOptionPane.showMessageDialog(container, rm.getMessage());
- 14
- }
- 15
- }
- 16
- } catch(Exception e) {
- 17 LoggerUtil.error("Handle register failed! " + e.getMessage(), e);
- 18
- }
- 19
- }
- 20
- return null;
- 21
- }
- 22 23
- }
View Code
- 1 public class TipHdl implements Handler {
- 2 3@Override 4 public Object handle(Object obj) {
- 5
- if (obj != null) {
- 6
- try {
- 7 ReturnMessage m = JSON.parseObject(obj.toString(), ReturnMessage.class);
- 8
- if (m.isSuccess() && m.getContent() != null) {
- 9 String tabKey = m.getFrom();
- 10 String tip = m.getContent().toString();
- 11 JComponent comp = Router.getView(ChatRoomView.class).getComponent(ChatRoomView.CHATTABBED);
- 12
- if (comp instanceof JTabbedPane) {
- 13 JTabbedPane tab = (JTabbedPane) comp;
- 14 int index = tab.indexOfTab(tabKey);
- 15
- if (index == -1) {
- 16 tab.addTab(tabKey, ResultHolder.get(tabKey).getScrollPane());
- 17
- }
- 18 JTextArea textArea = ResultHolder.get(tabKey).getTextArea();
- 19 textArea.setText(new StringBuffer() 20.append(textArea.getText()).append(System.lineSeparator()).append(System.lineSeparator()) 21.append(" [").append(m.getOwner()).append("] : ").append(System.lineSeparator()) 22.append(tip) 23.toString());
- 24 // SCROLL TO BOTTOM
- 25 textArea.setCaretPosition(textArea.getText().length());
- 26
- }
- 27
- }
- 28
- } catch(Exception e) {
- 29 LoggerUtil.error("Handle tip failed! " + e.getMessage(), e);
- 30
- }
- 31
- }
- 32
- return null;
- 33
- }
- 34 35
- }
对于 Socket 通讯模块还有一个类,那就是 ClientHolder,这个类用于存储当前 Client,跟服务端的 SocketHolder 是类似的。
- 1
- /**
- 2 * @author yaolin
- 3 */
- 4 public class ClientHolder {
- 5 6 public static Client client;
- 7 8 public static Client getClient() {
- 9
- return client;
- 10
- }
- 11 12 public static void setClient(Client client) {
- 13 ClientHolder.client = client;
- 14
- }
- 15
- }
UI 模块具体实现:
上面记录了 socket 通讯模块的设计,接下来记录一下 UI 的设计模块,我不打算自己写 UI,毕竟自己写出来的太丑了,所以后期可能会叫同学或朋友帮忙敲一下,所以我将 UI 的事件处理都交由 Action 去处理,将 UI 设计和事件响应简单分离,所有 UI 继承 JFrame 并实现 View 接口,上面的 Handler 实现类通过 Router 获取(存在则直接返回,不存在则创建并存储)指定的 UI,View 中提供了 UI 的创建 create()、获取 container()、获取 UI 中的组件 getComponent(),显示 display(),回收 trash();ResultWrapper 和 ResultHolder 只是为了创建和存储聊天选项卡。设计如下:
[Router.java、View.java]
所有 UI 继承 JFrame 并实现 View 接口,Handler 实现类通过 Router 获取(存在则直接返回,不存在则创建并存储)指定的 UI,View 中提供了 UI 的创建 create()、获取 container()、获取 UI 中的组件 getComponent(),显示 display(),回收 trash(),具体实现如下:
- 1
- /**
- 2 * View 路由
- 3 * @author yaolin
- 4 */
- 5 public class Router {
- 6 7 private static Map listRoute = new HashMap();
- 8 9 public static View getView(Class clazz) {
- 10 View v = listRoute.get(clazz.getName());
- 11
- if (v == null) {
- 12
- try {
- 13 v = (View) Class.forName(clazz.getName()).newInstance();
- 14 listRoute.put(clazz.getName(), v);
- 15
- } catch(Exception e) {
- 16 LoggerUtil.error("Create view failed! " + e.getMessage(), e);
- 17
- }
- 18
- }
- 19
- return v;
- 20
- }
- 21
- }
- 1
- /**
- 2 * 所有界面的规范接口
- 3 * @author yaolin
- 4 *
- 5 */
- 6 public interface View {
- 7 8
- /**
- 9 *
- 10 */
- 11 public View create();
- 12 13
- /**
- 14 *
- 15 */
- 16 public Container container();
- 17 18
- /**
- 19 * @param key
- 20 */
- 21 public JComponent getComponent(String key);
- 22 23
- /**
- 24 *
- 25 */
- 26 public void display();
- 27 28
- /**
- 29 *
- 30 */
- 31 public void trash();
- 32 33
- }
[RegisterAndLoginView.java、ChatRoomView.java]
由于不想自己写 UI,我这里只是简单的写了两个 UI 界面,分别是注册和登陆界面、聊天界面,这里给出两个丑丑的界面:
注册登录界面
聊天界面
下面给出这两个这界面的具体代码:
- 1
- /**
- 2 * 注册、登陆
- 3 * @author yaolin
- 4 */
- 5 public class RegisterAndLoginView extends JFrame implements View {
- 6 7 private static final long serialVersionUID = 6322088074312546736L;
- 8 private final RegisterAndLoginAction action = new RegisterAndLoginAction();
- 9 10 private static boolean CREATE = false;
- 11 12@Override 13 public View create() {
- 14
- if (!CREATE) {
- 15 init();
- 16 CREATE = true;
- 17
- }
- 18
- return this;
- 19
- }
- 20 21 public Container container() {
- 22 create();
- 23
- return getContentPane();
- 24
- }
- 25 26@Override 27 public JComponent getComponent(String key) {
- 28
- return null;
- 29
- }
- 30 31@Override 32 public void display() {
- 33 setVisible(true);
- 34
- }
- 35 36@Override 37 public void trash() {
- 38 dispose();
- 39
- }
- 40 41 42 private void init() {
- 43 // Attribute
- 44 setSize(500, 300);
- 45 setResizable(false);
- 46 setLocationRelativeTo(null);
- 47 48 // Container
- 49 JPanel panel = new JPanel();
- 50 panel.setLayout(null);
- 51 52 // Component
- 53 // username
- 54 JLabel lbUsername = new JLabel(I18N.TEXT_USERNAME);
- 55 lbUsername.setBounds(100, 80, 200, 30);
- 56 final JTextField tfUsername = new JTextField();
- 57 tfUsername.setBounds(150, 80, 230, 30);
- 58 panel.add(lbUsername);
- 59 panel.add(tfUsername);
- 60 // passsword
- 61 JLabel lbPassword = new JLabel(I18N.TEXT_PASSWORD);
- 62 lbPassword.setBounds(100, 120, 200, 30);
- 63 final JPasswordField pfPassword = new JPasswordField();
- 64 pfPassword.setBounds(150, 120, 230, 30);
- 65 panel.add(lbPassword);
- 66 panel.add(pfPassword);
- 67 // btnRegister
- 68 JButton btnRegister = new JButton(I18N.BTN_REGISTER);
- 69 btnRegister.setBounds(100, 175, 80, 30);
- 70 // btnLogin
- 71 final JButton btnLogin = new JButton(I18N.BTN_LOGIN);
- 72 btnLogin.setBounds(200, 175, 80, 30);
- 73 // btnCancel
- 74 JButton btnExit = new JButton(I18N.BTN_EXIT);
- 75 btnExit.setBounds(300, 175, 80, 30);
- 76 panel.add(btnRegister);
- 77 panel.add(btnLogin);
- 78 panel.add(btnExit);
- 79 80 // Event
- 81 pfPassword.addKeyListener(new KeyAdapter() {
- 82 public void keyPressed(final KeyEvent e) {
- 83
- if (e.getKeyCode() == KeyEvent.VK_ENTER) 84 btnLogin.doClick();
- 85
- }
- 86
- }); // end of addKeyListener
- 87 88 btnRegister.addActionListener(new ActionListener() {
- 89 public void actionPerformed(final ActionEvent e) {
- 90
- if (StringUtil.isEmpty(tfUsername.getText()) 91 || StringUtil.isEmpty(new String(pfPassword.getPassword()))) {
- 92 JOptionPane.showMessageDialog(getContentPane(), I18N.INFO_REGISTER_EMPTY_DATA);
- 93
- return;
- 94
- }
- 95 action.handleRegister(tfUsername.getText(), new String(pfPassword.getPassword()));
- 96
- }
- 97
- }); // end of addActionListener
- 98 99 btnLogin.addActionListener(new ActionListener() {
- 100 public void actionPerformed(final ActionEvent e) {
- 101
- if (StringUtil.isEmpty(tfUsername.getText()) 102 || StringUtil.isEmpty(new String(pfPassword.getPassword()))) {
- 103 JOptionPane.showMessageDialog(getContentPane(), I18N.INFO_LOGIN_EMPTY_DATA);
- 104
- return;
- 105
- }
- 106 action.handleLogin(tfUsername.getText(), new String(pfPassword.getPassword()));
- 107
- }
- 108
- }); // end of addActionListener
- 109 110 btnExit.addActionListener(new ActionListener() {
- 111 public void actionPerformed(final ActionEvent e) {
- 112 System.exit(0);
- 113
- }
- 114
- }); // end of addActionListener
- 115 116 getContentPane().add(panel);
- 117 setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
- 118
- }
- 119
- }
- 1
- /**
- 2 * Client 聊天窗口
- 3 *
- 4 * @author yaolin
- 5 */
- 6 public class ChatRoomView extends JFrame implements View {
- 7 8 private static final long serialVersionUID = -4515831172899054818L;
- 9 10 public static final String LISTUSRLIST = "LISTUSRLIST";
- 11 public static final String CHATTABBED = "CHATTABBED";
- 12 13 private static boolean CREATE = false;
- 14 private ChatRoomAction action = new ChatRoomAction();
- 15 16 private JList listUsrList = null;
- 17 private JTabbedPane chatTabbed = null;
- 18 19@Override 20 public View create() {
- 21
- if (!CREATE) {
- 22 init();
- 23 CREATE = true;
- 24
- }
- 25
- return this;
- 26
- }
- 27 28 public Container container() {
- 29 create();
- 30
- return getContentPane();
- 31
- }
- 32 33@Override 34 public JComponent getComponent(String key) {
- 35 create();
- 36
- switch (key) {
- 37
- case LISTUSRLIST:
- 38
- return listUsrList;
- 39
- case CHATTABBED:
- 40
- return chatTabbed;
- 41
- }
- 42
- return null;
- 43
- }
- 44 45@Override 46 public void display() {
- 47 setVisible(true);
- 48
- }
- 49 50@Override 51 public void trash() {
- 52 dispose();
- 53
- }
- 54 55 public void init() {
- 56 setTitle(I18N.TEXT_APP_NAME);
- 57 setSize(800, 600);
- 58 setResizable(false);
- 59 setLocationRelativeTo(null);
- 60 61 setLayout(new BorderLayout());
- 62 add(createChatPanel(), BorderLayout.CENTER);
- 63 add(createUsrListView(), BorderLayout.EAST);
- 64 65 setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
- 66
- }
- 67 68 private JComponent createChatPanel() {
- 69 // FILE SELECTOR
- 70 final JFileChooser fileChooser = new JFileChooser();
- 71 72 JPanel panel = new JPanel(new BorderLayout());
- 73 // CENTER
- 74 chatTabbed = new JTabbedPane();
- 75 chatTabbed.addTab(ConstantValue.TO_ALL, ResultHolder.get(ConstantValue.TO_ALL).getScrollPane());
- 76 panel.add(chatTabbed, BorderLayout.CENTER);
- 77 78 // SOUTH
- 79 JPanel south = new JPanel(new BorderLayout());
- 80 // SOUTH - FILE
- 81 JPanel middle = new JPanel(new BorderLayout());
- 82 middle.add(new JLabel(), BorderLayout.CENTER); // JUST FOR PADDING
- 83 JButton btnUpload = new JButton(I18N.BTN_SEND_FILE);
- 84 middle.add(btnUpload, BorderLayout.EAST);
- 85 south.add(middle, BorderLayout.NORTH);
- 86 // SOUTH - TEXTAREA
- 87 final JTextArea taSend = new JTextArea();
- 88 taSend.setCaretColor(Color.BLUE);
- 89 taSend.setMargin(new Insets(10, 10, 10, 10));
- 90 taSend.setRows(10);
- 91 south.add(taSend, BorderLayout.CENTER);
- 92 // SOUTH - BTN
- 93 JPanel bottom = new JPanel(new BorderLayout());
- 94 bottom.add(new JLabel(), BorderLayout.CENTER); // JUST FOR PADDING
- 95 JButton btnSend = new JButton(I18N.BTN_SEND);
- 96 bottom.add(btnSend, BorderLayout.EAST);
- 97 98 south.add(bottom, BorderLayout.SOUTH);
- 99 100 btnUpload.addActionListener(new ActionListener() {
- 101 public void actionPerformed(final ActionEvent e) {
- 102
- if (!ConstantValue.TO_ALL.equals(chatTabbed.getTitleAt(chatTabbed.getSelectedIndex()))) {
- 103 int returnVal = fileChooser.showOpenDialog(ChatRoomView.this);
- 104
- if (returnVal == JFileChooser.APPROVE_OPTION) {
- 105 File file = fileChooser.getSelectedFile();
- 106 action.upload(chatTabbed.getTitleAt(chatTabbed.getSelectedIndex()), file);
- 107
- }
- 108
- } else {
- 109 JOptionPane.showMessageDialog(getContentPane(), I18N.INFO_FILE_TO_ALL_ERROR);
- 110
- }
- 111
- }
- 112
- });
- 113 114 btnSend.addActionListener(new ActionListener() {
- 115 public void actionPerformed(final ActionEvent e) {
- 116
- if (StringUtil.isNotEmpty(taSend.getText())) {
- 117 action.send(chatTabbed.getTitleAt(chatTabbed.getSelectedIndex()), taSend.getText());
- 118 taSend.setText(null);
- 119
- }
- 120
- }
- 121
- });
- 122 123 panel.add(south, BorderLayout.SOUTH);
- 124
- return panel;
- 125
- }
- 126 127 private JComponent createUsrListView() {
- 128 listUsrList = new JList();
- 129 listUsrList.setBorder(new LineBorder(Color.BLUE));
- 130 listUsrList.setListData(new String[] {
- ConstantValue.TO_ALL
- });
- 131 listUsrList.setFixedCellWidth(200);
- 132 listUsrList.setFixedCellHeight(30);
- 133 listUsrList.addListSelectionListener(new ListSelectionListener() {
- 134@Override 135 public void valueChanged(ListSelectionEvent e) { // chat to
- 136
- if (chatTabbed.indexOfTab(listUsrList.getSelectedValue()) == -1 137 && listUsrList.getSelectedV
来源: