引言
這部分我們就開始設計這個山寨版的qq了,首先最開始的就是需要一個登錄界面,當輸入的用戶名密碼正確之后,就跳轉到登錄成功的界面,我們這里登錄成功之后設計的是顯示該用戶好友界面,這一串我認為是一個整體,所以就放在了一起來寫,可能會造成本文比較長。
首先我們來看一下登錄界面
登錄界面
我們設計的登錄界面如圖所示
分析界面
這個界面可以分為三個大的部分,北部的一張圖片,qq2003全新體驗Q人類,中部的QQ號碼,手機號碼和Email登錄部分,以及下面的三個按鈕,中間的QQ號碼Label,號碼輸入框,清除號碼按鈕,QQ密碼Label,密碼輸入框,忘記密碼Label,以及隱身登錄,記住密碼Checkbox,加上申請密碼保護按鈕,這九個可以用一個3×3的網絡布局來做
而QQ號碼,手機號碼和Email登錄這三個可以切換的Label可以用JTabbedPane
界面編程
首先北部組件的那張圖片可以是一個JLabel,然后把圖片作為參數傳入即可
jLabel = new JLabel(new ImageIcon("image/tou.gif"));
然后將其加入到JFrame的北部
this.add(jLabel, "North");
然后南部組件的三個按鈕可以是將三個JButton放在一個JPanel里
jPanel = new JPanel();
jPanelButton1 = new JButton(new ImageIcon("image/denglu.gif"));
jPanelButton1.addActionListener(this);
jPanelButton2 = new JButton(new ImageIcon("image/quxiao.gif"));
jPanelButton3 = new JButton(new ImageIcon("image/xiangdao.gif"));
jPanel.add(jPanelButton1);
jPanel.add(jPanelButton2);
jPanel.add(jPanelButton3);
this.add(jPanel, "South");
中部的組件比較復雜,主要要實現可以切換標簽頁
首先我們把3×3網格里的東西做出來
jPanel1 = new JPanel(new GridLayout(3, 3));
jPanel1_JLabel1 = new JLabel("QQ號碼", JLabel.CENTER);
jPanel1_JLabel2 = new JLabel("QQ密碼", JLabel.CENTER);
jPanel1_JLabel3 = new JLabel("忘記密碼");
jPanel1_JLabel4 = new JLabel("申請密碼保護");
jPanel1_JButton = new JButton(new ImageIcon("image/clear.gif"));
jPanel1_UserNameField = new JTextField();
jPanel1_PasswordField = new JPasswordField();
jPanel1_CheckBox1 = new JCheckBox("隱身登錄");
jPanel1_CheckBox2 = new JCheckBox("記住密碼");
jPanel1.add(jPanel1_JLabel1);
jPanel1.add(jPanel1_UserNameField);
jPanel1.add(jPanel1_JButton);
jPanel1.add(jPanel1_JLabel2);
jPanel1.add(jPanel1_PasswordField);
jPanel1.add(jPanel1_JLabel3);
jPanel1.add(jPanel1_CheckBox1);
jPanel1.add(jPanel1_CheckBox2);
jPanel1.add(jPanel1_JLabel4);
然后我們除了QQ號碼這個標簽頁已經做好了之外(jPanel1),我們把后面兩個標簽頁(手機號碼和Email相應的標簽頁也做好)
jPanel2 = new JPanel();
jPanel3 = new JPanel();
最后我們創建一個JTabbedPane把這三個JPanel放進去即可
jTabbedPane = new JTabbedPane();
jTabbedPane.add("QQ號碼", jPanel1);
jTabbedPane.add("手機號碼", jPanel2);
jTabbedPane.add("Email", jPanel3);
this.add(jTabbedPane, "Center");
至此登錄界面就做好了,接下來就是QQ好友界面
QQ好友界面
我們設計的QQ好友界面如下圖所示
分析界面
這個界面大概可以分為三個部分,最上面的我的好友,可以是一個Button,連着中間的好友列表,好友列表要實現向下拉的功能,可以用一個JScrollPane實現,最下面是兩個Button,可以放在一個2×1的網格布局里
//用於放我的好友這個界面的東西,布局為BorderLayout
friendPanel1=new JPanel(new BorderLayout());
//用於顯示好友列表
friendPanel2=new JPanel(new GridLayout(50, 1, 4, 4));
//用於放陌生人、黑名單按鈕
friendPanel3=new JPanel(new GridLayout(2,1));
//將我的好友按鈕放入friendPanel1
friendPanel_Button1=new JButton("我的好友");
friendPanel1.add(friendPanel_Button1,"North");
//將好友列表放入friendPanel1
//給好友列表初始化,用一個JLabel數組放好友列表,每一個好友是一個JLabel
JLabel[] friendsList;
friendsList=new JLabel[50];
for (int i = 0; i < friendsList.length; i++) {
friendsList[i]=new JLabel(i+1+"",new ImageIcon("image/mm.jpg"),JLabel.LEFT);
//將每個JLabel放入friendPanel2
friendPanel2.add(friendsList[i]);
}
//將friendPanel2放入JScrollPane
friendPanel_jScrollPane=new JScrollPane(friendPanel2);
//將好友列表也就是JScrollPane放入friendPanel1
friendPanel1.add(friendPanel_jScrollPane,"Center");
//將南部的兩個按鈕放入friendPanel1
friendPanel_Button2=new JButton("陌生人");
friendPanel_Button3=new JButton("黑名單");
friendPanel3.add(friendPanel_Button2);
friendPanel3.add(friendPanel_Button3);
friendPanel1.add(friendPanel3,"South");
陌生人界面
另外我們想實現的是當點擊陌生人按鈕時能切換到陌生人界面
如何才能實現呢,一個簡單的方式就是我們仿照好友界面也要定義這么多的組件
//用於放陌生人這個界面的東西,布局為BorderLayout
msrPanel1=new JPanel(new BorderLayout());
首先北部現在變為了兩個按鈕的網格部分的Panel
msrPanel3=new JPanel(new GridLayout(2,1));
//將這個Panel放入msrPanel1
msrPanel_Button1=new JButton("我的好友");
msrPanel_Button2=new JButton("陌生人");
msrPanel3.add(msrPanel_Button1);
msrPanel3.add(msrPanel_Button2);
msrPanel1.add(msrPanel3,"North");
中部依舊是一個JScrollPane用於顯示陌生人列表
msrPanel2=new JPanel(new GridLayout(20, 1, 4, 4));
JLabel[] msrList=new JLabel[20];
for (int i = 0; i < msrList.length; i++) {
msrList[i]=new JLabel(i+1+"",new ImageIcon("image/mm.jpg"),JLabel.LEFT);
msrPanel2.add(msrList[i]);
}
msrPanel_jScrollPane=new JScrollPane(msrPanel2);
msrPanel1.add(msrPanel_jScrollPane,"Center");
最后是南部的一個黑名單按鈕
msrPanel_Button3=new JButton("黑名單");
msrPanel1.add(msrPanel_Button3,"South");
到這里為止我們遇到了一個困難,就是如何在這兩個界面中來回切換呢
這里我們要將JFrame本身的布局設置為CardLayout
cl=new CardLayout();
this.setLayout(cl);
然后將我的好友,陌生人加入到這個卡片布局中
this.add(friendPanel1,"1");
this.add(msrPanel1,"2");
然后我們要在我的好友界面監聽陌生人這個按鈕,當按下的時候我們切換到陌生人界面
也要在陌生人界面中監聽我的好友這個按鈕,當按下時我們切換到我的好友界面
if (e.getSource()==friendPanel_Button2) {
cl.show(this.getContentPane(), "2");
}
if (e.getSource()==msrPanel_Button1) {
cl.show(this.getContentPane(), "1");
}
實現鼠標放在好友上面異色顯示
也是用到鼠標的監聽事件,Entered事件Exited事件
public void mouseEntered(MouseEvent e) {
// TODO Auto-generated method stub
JLabel jLabel=(JLabel) e.getSource();
jLabel.setForeground(Color.red);
}
public void mouseExited(MouseEvent e) {
// TODO Auto-generated method stub
JLabel jLabel=(JLabel) e.getSource();
jLabel.setForeground(Color.black);
}
登錄驗證
接下來我們要實現登錄界面輸入用戶名和密碼,通過驗證用戶名和密碼來達到用戶登錄驗證的功能,所以我們需要一個服務器端來做這件事情。
首先是服務器的界面,服務器端的界面設計如下,我們僅僅需要通過GUI開啟和關閉服務器即可
服務器界面
分析界面
界面的設計代碼也很簡單,就是用一個JPanel把兩個button給裝起來即可,然后再構造函數中去初始化這些組件
JPanel jPanel;
JButton jButton1, jButton2;
public MyServerFrame() {
jPanel = new JPanel();
jButton1 = new JButton("啟動服務器");
jButton1.addActionListener(this);
jButton2 = new JButton("關閉服務器");
jPanel.add(jButton1);
jPanel.add(jButton2);
this.add(jPanel);
this.setSize(300, 200);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setVisible(true);
}
服務器GUI中的button添加監聽事件
jButton1.addActionListener(this);
public void actionPerformed(ActionEvent e) {
if (e.getSource()==jButton1) {
new MyQQServer();
}
}
服務器的GUI設計好了,那么接下來我們真正的來編寫服務器端的代碼,客戶端要向服務器端發送用戶名和密碼信息,這里服務器和客戶端的通信通過對象流的方式傳遞對象信息,所以首先我們得有對象流傳送對象信息的先備知識,這里穿插一下這個知識的講解
對象流
服務器端接收
public class MyServer {
public MyServer()
{
System.out.println("在3456端口監聽,,");
ServerSocket ss=new ServerSocket(3456);
Socket s=ss.accept();
//以對象流的形式讀取
ObjectInputStream ois=new ObjectInputStream(s.getInputStream());
User u=(User)ois.readObject();
//輸出
System.out.println(u.getName()+u.getPass());
}
}
客戶端發送
public class MyClient {
public MyClient()
{
Socket s=new Socket("127.0.0.1",3456);
//通過ObjectOutputStream給服務器傳送對象
ObjectOutputStream oos=new ObjectOutputStream(s.getOutputStream());
User u=new User();
u.setName("桑陽");
u.setPass("123");
oos.writeObject(u);
}
}
其中User類我們在服務器和客戶端中都要建立,那么我們選擇建立在一個名叫common的包中,該類要實現序列化接口
User類
public class User implements java.io.Serializable{
private String name;
private String pass;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPass() {
return pass;
}
public void setPass(String pass) {
this.pass = pass;
}
}
有了對象流的概念之后,我們就可以真正的來編寫用戶驗證的代碼了
登錄驗證
用戶驗證的邏輯就是在登錄界面點擊了登錄按鈕,要將用戶名框中的信息和密碼框中的信息組成一個User類,用對象流的方法傳遞到服務器端,進行驗證。所以首先需要響應按鈕的點擊事件
響應登錄點擊事件
響應登錄點擊事件的邏輯就是,點擊了登錄按鈕,會去新建一個QQClientUser類,該類中有一個驗證用戶的方法,而該方法實際上會去調用QQClientConServer類中的sendLoginInfoToServer。
public class QqClientUser {
public boolean checkUser(User u)
{
return new QqClientConServer().sendLoginInfoToServer(u);
}
}
在QQClientConServer中我們建立與服務器的連接,在我們把用戶對象發送到服務器之后,就開始等待服務器端發回來的結果,結果由信息類型表示,當信息類型為1的時候,表示驗證通過,這里我們需要定義一個信息類型類MessageType.class,由於這個也是客戶端和服務器端要公用的,所以也放在common包下,注意這個是個接口
public interface MessageType {
public String message_success="1";
public String message_failure="2";
public String message_common_message="3";
public String message_require_friendOnline="4";
public String message_return_friendOnline="5";
}
然后需要建立message類,用於創建服務器和客戶端之間傳送的對象
public class Message implements Serializable {
private String mesType;
private String sender;
private String getter;
private String content;
private String sentTime;
//一大堆set、get方法
}
於是在服務端驗證,如果用戶密碼正確,就返回messageType為1
if (user.getPasswdString().equals("123456")) {
message.setMesType("1");
oosStream.writeObject(message);
//單開一個線程,讓該線程與一個客戶端保持通信
ServerToClientThread serverToClientThread=new ServerToClientThread(socket);
ManageClientThread.addClientThread(user.getUserID(), serverToClientThread);
serverToClientThread.start();
//並通知所有的其他用戶
serverToClientThread.notifyOther(user.getUserID());
}else {
message.setMesType("2");
oosStream.writeObject(message);
socket.close();
}
public class QqClientConServer {
//發送第一次請求
public boolean sendLoginInfoToServer(Object o)
{
boolean b=false;
Socket s=new Socket("127.0.0.1", 9999);
ObjectOutputStream oos=new ObjectOutputStream(s.getOutputStream());
oos.writeObject(o);
ObjectInputStream ois=new ObjectInputStream(s.getInputStream());
Message ms=(Message)ois.readObject();
if(ms.getMesType().equals("1"))
{
b=true;
}
return b;
}
}
public void actionPerformed(ActionEvent arg0) {
//如果用戶點擊登錄
if(arg0.getSource()==jp1_jb1)
{
QqClientUser qqClientUser=new QqClientUser();
User u=new User();
u.setUserId(jp2_jtf.getText().trim()); //trim是出去開始或結尾的空格
u.setPasswd(new String(jp2_jpf.getPassword()));
if(qqClientUser.checkUser(u))
{
new QqFriendList();
//關閉掉登錄界面
this.dispose();
}else{
JOptionPane.showMessageDialog(this, "用戶名密碼錯誤");
}
}
}
服務器端
public class MyQQServer {
public MyQQServer(){
ServerSocket ss=new ServerSocket(9999);
while (true) {
Socket socket=ss.accept();
//接收客戶端發來的信息
ObjectInputStream oiStream=new ObjectInputStream(socket.getInputStream());
User user=(User) oiStream.readObject();
System.out.println("接收到用戶名:"+user.getUserID()+"密碼:"+user.getPasswdString());
Message message=new Message();
ObjectOutputStream oosStream=new ObjectOutputStream(socket.getOutputStream());
if (user.getPasswdString().equals("123456")) {
message.setMesType("1");
oosStream.writeObject(message);
//單開一個線程,讓該線程與一個客戶端保持通信
ServerToClientThread serverToClientThread=new ServerToClientThread(socket);
ManageClientThread.addClientThread(user.getUserID(), serverToClientThread);
serverToClientThread.start();
//並通知所有的其他用戶
serverToClientThread.notifyOther(user.getUserID());
}else {
message.setMesType("2");
oosStream.writeObject(message);
socket.close();
}
System.out.println("服務器啟動完畢");
}
}