窗体
进入聊天室界面(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();
// 在此处开启分线程接收发送消息,更新ui
sendThread = 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;
// 更新ui
public 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;// 把区域图片赋值给pic
this.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;// 把区域图片赋值给pic
this.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委托赋值updateLabel
client.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)
{
// 登录成功 现实聊天界面null
this.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);//添加数据到队列里面
// 更新UI
UpdateChatUI(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);
}
}
}