swing编写client端及多线程server端之client端

前段时间要求做一个项目,项目比较简单,项目要求是: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();
	}
}


基本功能就这些,大家可以参考跟指正。
本人的处女作,写的不好望大家见谅!谢谢


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值