窗体
进入聊天室界面(panel里面,label,textbox,button):
聊天界面(flowLayoutPanel(聊天面板)):
文档大纲(panel设置顶层(登录界面),聊天界面在底层)
步骤:设置进入聊天室→输入聊天→右边自己发送的消息→左边别人发的消息
MyClient.cs(进入聊天室类)
internal class MyClient
{// 定义委托类型public delegate void UpdatLabelHandle(string str = "");// 声明委托变量public UpdatLabelHandle LabelInfo;// 接受和发送消息 创建连接对象写在异步里面Thread thConnect; // 连接服务器的线程TcpClient client; // 全局客户端对象public bool IsConnect;// 是否连接成功Thread receiveThread;// 接收消息的线程Thread sendThread;// 发送消息的线程Thread updateChatThread;// 更新聊天室ui的线程public MyClient() {thConnect = new Thread(ConnetServer);thConnect.Start();}public void ConnetServer(){client = new TcpClient();// 开启一个异步的连接// 参数1 ip地址// 2 端口号// 3 回调函数 看一判断是否连接成功// 4 传递回调函数的参数client.BeginConnect(IPAddress.Parse("192.168.107.72"),3333,requestCallBack,client);float num = 0;while (IsConnect == false){// 证明没有连接成功num += 0.1f;if (LabelInfo != null) LabelInfo(); // 不传参数的目的if (num >= 10f){return;//超时连接 10s连接不上就连接失败}Thread.Sleep(100);}if (IsConnect==true)// {NetworkStream stream = client.GetStream();// 在此处开启分线程接收发送消息,更新uisendThread = new Thread(sendHandle);sendThread.Start(stream);receiveThread = new Thread(receiveHandle);receiveThread.Start(stream);updateChatThread = new Thread(updateHandle);updateChatThread.Start();}}// 把消息保存队列中// 可以存储数据结合,先进先出的特点,如果先添加一个你好,你好可以通过方法先取出来// Queue 队列 先进先出例如买饭// 数组 先进后出的 进电梯public Queue<string> SendQueue = new Queue<string>();// 发消息public void sendHandle(object obj){NetworkStream stream = obj as NetworkStream;try{while (IsConnect){if (SendQueue.Count>0)// 发短消息不为空 如果把窗体里面发消息文本内容取到此处{string msg = SendQueue.Dequeue();// 取出先放进去的数据byte[] bs = Encoding.UTF8.GetBytes(msg);stream.Write(bs,0,bs.Length);}}}catch(Exception ex){Console.WriteLine("send"+ex.Message);}}public Queue<string> receiveQueue = new Queue<string>();// 接受消息public void receiveHandle(object obj){NetworkStream stream = obj as NetworkStream;try{while (IsConnect){byte[] bs = new byte[1024];int length = stream.Read(bs, 0, bs.Length);string s = Encoding.UTF8.GetString(bs, 0, length);receiveQueue.Enqueue(s);}}catch (Exception e){Console.WriteLine("receive"+e.Message);}}// 定义委托类型 接受UpdateChatUI方法public delegate void updateChatHandle(string s, bool a = false);// 定义委托变脸public updateChatHandle F1;// 更新uipublic void updateHandle(){while (true){if (F1!=null&& receiveQueue.Count>0){F1(receiveQueue.Dequeue(), false);}}}// IAsyncResult 异步结果的类// BeginConnect 的回调函数 不管成功与否都执行public void requestCallBack(IAsyncResult ar){TcpClient t = ar.AsyncState as TcpClient;// 通过AsyncState异步状态属性获取参数if (t.Connected) // 如果连接成功了{IsConnect = true;LabelInfo("连接成功");t.EndConnect(ar); // 结束挂起的状态 }else{// 连接失败 LabelInfo("连接失败");}LabelInfo = null;}public void Stop(){if (IsConnect){IsConnect = false;if (client!=null){client.Close();client = null;}// 把线程终端if (thConnect!=null){thConnect.Abort();// 终止线程}if (sendThread != null){sendThread.Abort();}if (receiveThread != null){receiveThread.Abort();}if (updateChatThread!= null){updateChatThread.Abort();}}}
}
ItemRight.cs(右边信息类)
public class ItemLeft:Panel
{// 聊天气泡 label和圆形的头像// 消息内容 和父窗体的宽度public ItemLeft(string msg, int parentWidth){this.Font = new Font("楷体", 18);// 设置气泡宽度this.Width = parentWidth - 20 - 6;PictureBox pic = new PictureBox();pic.Image = Image.FromFile("D:\\李克课件\\Csharp\\网络通信\\6.13聊天室服务器\\02 聊天室客户端\\大爱仙尊.jpg");pic.Width = 60;pic.Height = 60;pic.SizeMode = PictureBoxSizeMode.StretchImage;// 把图片压缩以适应盒子大小pic.Location = new Point(10, 10);// 右边头像的位置// 设置头像圆形 通过绘制绘制圆形GraphicsPath gp = new GraphicsPath();// 创建一个绘制线对象gp.AddEllipse(pic.ClientRectangle);Region re = new Region(gp);// 绘制的椭圆生成一个区域图片pic.Region = re;// 把区域图片赋值给picthis.Controls.Add(pic);// 绘制label// 计算msg宽度和高度Graphics g = this.CreateGraphics();// 创建绘制对象int exceptWidth = this.Width - 200; // 期望宽度// MeasureString 测量指定这个字符串的长度或者宽度// 参数1 测量的字符串// 2 指定字符串// 3 一行盼望的宽度float width = g.MeasureString(msg, this.Font, exceptWidth).Width;float height = g.MeasureString(msg, this.Font, exceptWidth).Height;Label l = new Label();l.Text = msg;l.BackColor = Color.Green;l.Location = new Point(80, 10);l.Width = (int)width;l.Height = (int)height;this.Controls.Add(l);// 更新panel的高度if ((int)height + 20 < 80){this.Height = 80;}else{this.Height = (int)height + 20;}//re.Dispose();// 释放资源//gp.Dispose();}
}
ItemRight.cs(左边聊天框)
// 聊天气泡 label和圆形的头像// 消息内容 和父窗体的宽度public ItemLeft(string msg, int parentWidth){this.Font = new Font("楷体", 18);// 设置气泡宽度this.Width = parentWidth - 20 - 6;PictureBox pic = new PictureBox();pic.Image = Image.FromFile("D:\\李克课件\\Csharp\\网络通信\\6.13聊天室服务器\\02 聊天室客户端\\大爱仙尊.jpg");pic.Width = 60;pic.Height = 60;pic.SizeMode = PictureBoxSizeMode.StretchImage;// 把图片压缩以适应盒子大小pic.Location = new Point(10, 10);// 右边头像的位置// 设置头像圆形 通过绘制绘制圆形GraphicsPath gp = new GraphicsPath();// 创建一个绘制线对象gp.AddEllipse(pic.ClientRectangle);Region re = new Region(gp);// 绘制的椭圆生成一个区域图片pic.Region = re;// 把区域图片赋值给picthis.Controls.Add(pic);// 绘制label// 计算msg宽度和高度Graphics g = this.CreateGraphics();// 创建绘制对象int exceptWidth = this.Width - 200; // 期望宽度// MeasureString 测量指定这个字符串的长度或者宽度// 参数1 测量的字符串// 2 指定字符串// 3 一行盼望的宽度float width = g.MeasureString(msg, this.Font, exceptWidth).Width;float height = g.MeasureString(msg, this.Font, exceptWidth).Height;Label l = new Label();l.Text = msg;l.BackColor = Color.Green;l.Location = new Point(80, 10);l.Width = (int)width;l.Height = (int)height;this.Controls.Add(l);// 更新panel的高度if ((int)height + 20 < 80){this.Height = 80;}else{this.Height = (int)height + 20;}//re.Dispose();// 释放资源//gp.Dispose();}
}
窗体代码
public partial class Form1 : Form
{Timer timer;// 定时器bool isRunning = false;// 开关MyClient client;public Form1(){InitializeComponent();this.flowLayoutPanel1.AutoScroll = true;timer = new Timer(){Interval = 100, // 时间间隔};timer.Tick += (send, arg) =>{isRunning = false;timer.Stop();};}// 进入聊天室按钮方法private void button1_Click(object sender, EventArgs e){if (!string.IsNullOrEmpty(textBox1.Text)){// 开始连接服务器 封装一个自定义客户端类client = new MyClient(); // 给client委托赋值updateLabelclient.LabelInfo = updateLabel;client.F1 = UpdateChatUI;// 把方法赋值给f1变量}else{MessageBox.Show("请输入你的名字");}}public List<string> list1 = new List<string>() { "拼命加载中", "拼命加载中.", "拼命加载中..", "拼命加载中..." };int index = 0;// 封装一个更新label的方法public void updateLabel(string str){this.Invoke((Action)(() =>{if (string.IsNullOrEmpty(str))// 正在连接中{label1.Text = list1[index];index++;if (index == list1.Count) index = 0;}else // 证明连接有结果时候{this.label1.Text = str;// 需要判断如果连接成功了 需要进入聊天室if (client.IsConnect){// 登录成功 现实聊天界面nullthis.Controls.Remove(this.panel1);this.Text = this.textBox1.Text;// 修改窗体标题}}}));}// 发送消息的按钮的方法// 1 给服务器发送消息,封装MyClient.cs文件中// 2 更新聊天界面,封装到form1.cs文件中,如果MyClient.cs需要使用把封装更新UI传递过去// 使用委托// 3 聊天界面 自定义控件区分到底是谁的消息private void button2_Click(object sender, EventArgs e){if (isRunning){return;}isRunning = true;timer.Start();Console.WriteLine("111");string msg = this.textBox2.Text.Trim();if (msg.Length != 0){// 开始服务器发送消息 封装MyClient.cs文件中msg = this.Text + "说:" + msg;// 把msg发送队列client.SendQueue.Enqueue(msg);//添加数据到队列里面// 更新UIUpdateChatUI(msg,true);// 再次输入this.textBox2.Text = "";}}// 展示聊天室// 参数1 是消息内容// 参数2 是否是自己发的消息public void UpdateChatUI(string msg,bool isSelf){this.Invoke((Action)(() =>{Panel item = null;if (isSelf) // 显示在右边{item = new ItemRight(msg, flowLayoutPanel1.Width);}else // 别人发的消息 显示左边{item = new ItemLeft(msg, flowLayoutPanel1.Width);}// 显示在flowlayoutpanel上,this.flowLayoutPanel1.Controls.Add(item);// 让flowLayoutPanel1 滚动到最下面this.flowLayoutPanel1.VerticalScroll.Value = this.flowLayoutPanel1.VerticalScroll.Maximum;}));}private void Form1_FormClosing(object sender, FormClosingEventArgs e){if (client != null){client.Stop();client = null;}}// 进入聊天室private void textBox1_KeyDown(object sender, KeyEventArgs e){if (e.KeyCode ==Keys.Enter){// 点击了Ennter键button1_Click(null, null);}}private void textBox2_KeyDown(object sender, KeyEventArgs e){if (e.KeyCode == Keys.Enter){// 点击了Ennter键button2_Click(null, null);}}
}