前段时间要求做一个项目,项目比较简单,项目要求是:1、从客户端读取指定目录、格式(.xml)的文件,然后传给服务器;2、做成客户端形式。
client端写好了,server端的代码是从网上借鉴的,后来才做成窗口形式。功能不是很完美,有些bug尚未解决,希望大家多多指正。下面我会把主要代码贴出来给大家看看。
client端的编写过程:
首先是设计界面,界面也比较简洁,一个主窗口、一个功能配置窗口
这个说明一下,这个窗口样式是采用SynthLookAndFeel的方式。用到了开源的beautyeye样式,大家可以在网上搜索下载。
这里有个注意的地方:beautyeye默认的样式看起来相当漂亮了,但是可能不太符合我的要求,所以根据作者的文档修改了下
//隐藏“设置”按钮 UIManager.put("RootPane.setupButtonVisible", false); //设置本属性将改变窗口边框样式定义 BeautyEyeLNFHelper.frameBorderStyle = BeautyEyeLNFHelper.FrameBorderStyle.translucencySmallShadow; //BeautyEyeLNFHelper.frameBorderStyle = BeautyEyeLNFHelper.FrameBorderStyle.translucencyAppleLike;//默认样式 //加载Beauty Eye的外观,如果不需要其他设置的话,直接写这句话就OK了,其他都可以不用写 BeautyEyeLNFHelper.launchBeautyEyeLNF(); //关闭窗口在不活动时的半透明效果 BeautyEyeLNFHelper.translucencyAtFrameInactive = false;
上面代码加到布局之前。
还有种方法可以加载,效果跟上面差不多,区别在于上面的判断了系统兼容问题,可以处理更多的系统,如linux。
try { // 使用配置文件创建窗口皮肤 SynthLookAndFeel synth = new SynthLookAndFeel(); /** //加载自定义皮肤文件 InputStream is = clazz.getResourceAsStream("window.xml"); if (is == null) { System.err.println("Unable to find theme configuration"); System.exit(0); } synth.load(is, clazz); */ //UIManager.put("swing.boldMetal", Boolean.FALSE);//修改皮肤 //UIManager.setLookAndFeel(synth);//自定义风格,需要编写皮肤文件。如上面的window.xml然后加载 //UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");//自带的其他风格 //UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");//Windows风格 //UIManager.setLookAndFeel("com.sun.java.swing.plaf.motif.MotifLookAndFeel") ; //Mac风格 //UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel") ;//Java默认风格 dowsLookAndFeel"); UIManager.setLookAndFeel("org.jb2011.lnf.beautyeye.BeautyEyeLookAndFeelWin");//BeautyEye风格 this.init();//初始化主窗口 } catch (Exception e) { e.printStackTrace(); System.exit(0);//报错程序退出 }
自定义皮肤文件window.xml代码,仅供参考,本项目没有使用到,网上的教程比较少,做的比较难看,所以使用现有的beautyeye皮肤样式,如果大家喜欢折腾可以试试:
<?xml version="1.0" encoding="UTF-8"?> <synth> <style id="default"> <font name="Aharoni" size="12" /> <!-- <state> <color value="#FFFFFF" type="BACKGROUND" /> <color value="#FFFFFF" type="TEXT_FOREGROUND" /> </state> --> </style> <bind style="default" type="region" key=".*" /> <style id="border"> <opaque value="true" /> <state> <color value="#0692fa" type="BACKGROUND" /> <color value="#0692fa" type="TEXT_FOREGROUND" /> <insets top="5" left="5" bottom="5" right="5" /> </state> </style> <bind style="border" type="region" key="Border" /> <style id="menubar"> <opaque value="true" /> <state> <color value="#eeeeee" type="BACKGROUND" /> <color value="#000000" type="TEXT_FOREGROUND" /> </state> </style> <bind style="menubar" type="region" key="MenuBar" /> <style id="menuitem"> <opaque value="true" /> <state> <color value="#eeeeee" type="BACKGROUND" /> <color value="#000000" type="TEXT_FOREGROUND" /> </state> </style> <bind style="menuitem" type="region" key="MenuItem" /> <style id="textfield"> <opaque value="true" /> <state> <color value="#eeeeee" type="BACKGROUND" /> <color value="#000000" type="TEXT_FOREGROUND" /> <insets top="5" left="5" bottom="15" right="5" /> </state> </style> <bind style="textfield" type="region" key="Textfield" /> <style id="label"> <opaque value="true" /> <state> <color value="#eeeeee" type="BACKGROUND" /> <color value="#000000" type="TEXT_FOREGROUND" /> <insets top="5" left="20" bottom="5" right="5" /> </state> </style> <bind style="label" type="region" key="JLabel" /> <style id="optionpane"> <opaque value="true" /> <state> <color value="#eeeeee" type="BACKGROUND" /> <color value="#000000" type="TEXT_FOREGROUND" /> <insets top="5" left="20" bottom="5" right="5" /> </state> </style> <bind style="optionpane" type="region" key="JOptionPane" /> <style id="button"> <opaque value="true"></opaque> <state> <insets top="5" left="5" bottom="5" right="5" /> <color type="BACKGROUND" value="#0092fe" /> <color type="TEXT_FOREGROUND" value="#FFFFFF" /> </state> <state value="MOUSE_OVER"> <insets top="5" left="10" bottom="5" right="5" /> <color type="TEXT_FOREGROUND" value="#c0c0c0" /> </state> <state value="PRESSED"> <insets top="5" left="5" bottom="5" right="5" /> <color type="TEXT_FOREGROUND" value="#c0c0c0" /> </state> <state value="DISABLED"> <insets top="5" left="5" bottom="5" right="5" /> <color type="TEXT_FOREGROUND" value="#777777" /> </state> <property key="Button.margin" type="insets" value="5 5 5 5" /> </style> <bind style="button" type="region" key="Button" /> </synth>
布局类的代码如下:
ImageIcon icon=new ImageIcon(clazz.getResource("icon.jpg")); mainFrame = new JFrame("设备状态监控配置"); mainFrame.setIconImage(icon.getImage());//设置窗口的icon mainFrame.setSize(508, 536); mainFrame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);//关闭不是直接程序退出,只是界面隐藏 JComponent bar = (JComponent) ((JLayeredPane) mainFrame.getRootPane().getComponents()[1]).getComponent(1); JButton closeBtn = (JButton) bar.getComponent(1);// 获取关闭按钮 closeBtn.setToolTipText("最小化到托盘");//修改关闭按钮默认的ToolTipText if (SystemTray.isSupported()) {//判断系统是否支持托盘功能 this.minTray();//最小化到托盘 } //Toolkit toolkit = Toolkit.getDefaultToolkit();这个我写成了全局变量,这里贴出来给大家看 //屏幕中间显示 Dimension screenSize = toolkit.getScreenSize(); Dimension jfSize = mainFrame.getSize(); int x = screenSize.width / 2 - jfSize.width / 2; int y = screenSize.height / 2 - jfSize.height / 2; mainFrame.setLocation(x, y); mainFrame.setLayout(null);//设置Layout为null JPanel p1 = new JPanel(new BorderLayout()); Border border = BorderFactory.createTitledBorder(new LineBorder(Color.LIGHT_GRAY), "运行状态", 1, 0, new Font("宋体", Font.PLAIN, 12));//设置边框,可以对比图 JPanel statusPanel = new JPanel(); statusPanel.setLayout(new GridLayout(9, 2, 0, 5));//设置GridLayout布局9横2纵,上下5px的间隔 JLabel jb0 = new JLabel("服务器IP:", JLabel.LEFT); //...(省略其他) //初始化label,当显示文本框用 label0 = new JLabel(); //设置边框颜色 label0.setBorder(new LineBorder(Color.LIGHT_GRAY)); //...(省略其他) //初始化文本框的值 label2.setText("未连接"); label3.setText("未获取"); label4.setText("未启动"); label6.setText("0"); //...(省略其他) //添加到panelstatusPanel.add(jb0);statusPanel.add(label0); //...(省略其他) //初始化按钮 optionsButton = new JButton("配置选项"); runButton = new JButton("启 动"); stopButton = new JButton("暂 停"); aboutButton = new JButton("关 于"); //如果有需要可以修改按钮的默认颜色 //optionsButton.setUI(new BEButtonUI().setNormalColor(BEButtonUI.NormalColor.lightBlue)); //设置按钮的icon optionsButton.setIcon(new ImageIcon(clazz.getResource("configure.png"))); //...(省略其他) //设置按钮的大小、鼠标手势 optionsButton.setPreferredSize(new Dimension(90, 30)); optionsButton.setCursor(new Cursor(Cursor.HAND_CURSOR)); //...(省略其他) //按钮所在的panel JPanel panel = new JPanel(); //添加按钮 panel.add(optionsButton); ...(省略其他) //用于显示错误信息的panel JPanel errerPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0)); errerLabel = new JLabel();//用于显示错误信息的label //设置字体颜色、长度、高度 //...(省略) errerPanel.add(errerLabel);//主panel,把之前两个panel加进去 JPanel content=new JPanel(new BorderLayout()); content.add(statusPanel, BorderLayout.CENTER); content.add(errerPanel, BorderLayout.SOUTH); content.setBorder(border); p1.add(content, BorderLayout.CENTER); p1.add(panel, BorderLayout.SOUTH); p1.setBounds(0, 0, 498, 500);//设置显示区域 mainFrame.add(p1); mainFrame.setResizable(false); mainFrame.setVisible(true);
这样基本可以实现主界面的样式。这里讲讲两个按钮(runButton、stopButton)的实现。大家自动swing有一个主线程,大家可以打印试试Thread.currentThread().getName()看看。因为本程序要使用socket程序会一直阻塞,使得其他布局或功能没有办法完成。可以通过这样解决:
runButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { //...省略其他代码 new Thread() {//new一个线程,这样程序可以继续往下执行,及label2等设置文本能顺利执行 public void run() { flag = true;sendMessage();// 执行方法,用到socket,会网络阻塞 } }.start(); label2.setText("正在连接..."); label3.setText(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); label4.setText("运行正常"); } });
程序设计的时候有一个暂停的功能,思路:通过全局变量来 flag(boolean类型) 控制,当运行的时候给flag赋值为true,暂停的时候为false;看代码:
stopButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { //...省略其他代码 label4.setText("暂停运行"); flag = false; } });
下面是 sendMessage() 的代码:
try { String string=null; while (flag) { if (socket == null) { socket=new Socket(LINKIP, LINKPORT); } // 输出流,立即刷新 PrintWriter os = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"), true); // 输入流 BufferedReader is = new BufferedReader(new InputStreamReader(socket.getInputStream(),"UTF-8")); string = this.readSSLXML(path);//从地址为path的地方读取xml文件,使用了XMLConfiguration(commons-configuration-1.9.jar,依赖包commons-lang-2.3.jar以下),可以搜索下载,然后返回string类型的值 Thread.sleep(TIMESPACING);//睡眠指定时间 os.println(string);// 往Server写值 count++;//统计 String msg=null; try { if ((msg=is.readLine()) != null) { successCount++;//统计 //其他处理(省略) } } catch (IOException e) { socket=new Socket(LINKIP, LINKPORT);//异常处理,重新建立连接 } System.out.println("服务器接收:"+ successCount + "条,未发送:" + errorCount + "条"); } } catch (Exception e) { exceptionHandler();//异常处理 }
相当于递归调用,重复20次,设置flag=false 停止运行。并在屏幕右下角弹出提示框,图标闪动,播放警示音提醒用户,跟QQ功能相似。下面是 exceptionHandler() 的代码:
private void exceptionHandler() { if (trySendMessage >= TRY_COUNT) {// 重试次数 flag = false; errerLabel.setIcon(new ImageIcon(clazz.getResource("error.png"))); errerLabel.setText("连接失败,请检查服务器状态,网络连接状态,配置文件路径及格式!"); runButton.setEnabled(true); optionsButton.setEnabled(true); stopButton.setEnabled(false); runButton.setCursor(new Cursor(Cursor.HAND_CURSOR)); // 屏幕右下角提示框 TipWindow tw = new TipWindow(); tw.init(); tw.run(); while (!flag) { try {// 系统托盘闪动 trayIcon.setImage(toolkit.createImage("")); toolkit.beep();// 警示音 Thread.sleep(400); Image image = toolkit.createImage(clazz.getResource("icon.jpg")); trayIcon.setImage(image); Thread.sleep(400); } catch (Exception e) { e.printStackTrace(); } } return; } trySendMessage++; System.err.println("连接失败,正在重试第" + trySendMessage + "次..."); sendMessage();//再次调用sendMessage()方法 }
下面是右下角提示框的代码:
/** * 屏幕右下角提示框 * * @author admin * */ class TipWindow { private Dimension dim; private int x, y, width=300, height=180; private Insets screenInsets; private JDialog jd; private void init() { jd = new JDialog(mainFrame, "设备状态读取程序运行异常提示"); jd.setSize(width, height); dim = toolkit.getScreenSize(); screenInsets = toolkit.getScreenInsets(jd .getGraphicsConfiguration()); x = (int) (dim.getWidth() - width); y = (int) (dim.getHeight() - screenInsets.bottom); // 提示内容 JLabel info=new JLabel("设备状态读取程序运行异常,可能遇到下列问题:",JLabel.LEFT); info.setFont(new Font("微软雅黑", Font.PLAIN, 12)); JTextArea textArea = new JTextArea("1、网络未连接或中断\n2、服务器未开启或挂起\n3、配置状态文件不存在或格式错误\n4、尝试重新启动程序"); textArea.setEditable(false); textArea.setLineWrap(true);// 自动换行 textArea.setPreferredSize(new Dimension(250, 120));// 分割组件的宽度 //设置文本颜色、字体 textArea.setForeground(Color.RED); textArea.setSelectionColor(Color.WHITE); textArea.setSelectedTextColor(Color.RED); textArea.setBackground(Color.WHITE); textArea.setFont(new Font("微软雅黑", Font.PLAIN, 12)); JPanel center=new JPanel(); center.add(info,BorderLayout.NORTH); center.add(textArea,BorderLayout.CENTER); JPanel main = new JPanel(new BorderLayout(10,0)); main.add(center, BorderLayout.CENTER); jd.add(main); jd.setAlwaysOnTop(true); jd.setUndecorated(true); jd.setResizable(false); jd.setVisible(true); } // 显示--渐渐滑入的效果 private void run() { for (int i = 0; i <= jd.getHeight(); i += 10) { try { jd.setLocation(x, y - i); Thread.sleep(5); } catch (InterruptedException ex) { } } } }
程序不希望直接关闭,所以用到了最小化到托盘的功能,这里使用了自定义的JPopupMenu ,但是它不能直接加到TrayIcon(只允许添加PopupMenu)里面,需要处理一下,具体看代码
// 最小到系统托盘 private void minTray() { try { systemTray = SystemTray.getSystemTray();//获取系统托盘 // 右击时添加的菜单项 Image openImage = toolkit.createImage(clazz.getResource("house.png"));//图标 openItem = new JMenuItem("显示主窗口", new ImageIcon(openImage));//初始化,使用JMenuItem的好处是可以添加图标,样式更美观 openItem.setCursor(new Cursor(Cursor.HAND_CURSOR)); ...(省略其他) //按钮的功能实现 openItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent event) { mainFrame.setVisible(true); } }); ...(省略其他) //退出按钮功能 exitItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent event) { if (confirm())System.exit(0);//程序退出,有个确认功能,也很简单,两个按钮,返回true或者false。true关闭,false忽略 } }); // 弹出式菜单,即选中右击时弹出的菜单 final JPopupMenu popupMenu = new JPopupMenu(); popupMenu.setPreferredSize(new Dimension(120, 165));//设置右键弹出来的菜单大小 popupMenu.add(openItem); popupMenu.addSeparator();//分割线 popupMenu.add(startItem); popupMenu.add(stopItem); popupMenu.addSeparator(); popupMenu.add(aboutItem); popupMenu.addSeparator(); popupMenu.add(exitItem); Image image = toolkit.createImage(clazz.getResource("icon.jpg")); trayIcon = new TrayIcon(image, "设备状态读取程序", null);// 实例化托盘图标 trayIcon.setImageAutoSize(true); trayIcon.addMouseListener(new MouseAdapter() { public void mouseReleased(MouseEvent e) { if (e.getButton() == MouseEvent.BUTTON3) {//右键 //之前是没有这句话的,第一次点的时候因为popupMenu 还没有初始化, //因为不知道大小,显示的话就挨到屏幕最底部,而不是从鼠标点击的point开始,所以这样处理了一下 int y = (int) (e.getY() - (popupMenu.getHeight() == 0 ? 165 : popupMenu.getHeight())); popupMenu.setLocation(e.getX(), y);//设置显示开始的point popupMenu.setInvoker(popupMenu);//加载菜单 popupMenu.setVisible(true); } } public void mouseClicked(MouseEvent mouse) { if (mouse.getButton() == MouseEvent.BUTTON1) {//左键 mainFrame.setVisible(true); } } }); systemTray.add(trayIcon);//把图标显示到系统托盘 } catch (Exception ex) { ex.printStackTrace(); } }
基本功能就这些,大家可以参考跟指正。
本人的处女作,写的不好望大家见谅!谢谢