首页 > 编程语言 >C#聊天室客户端完整③

C#聊天室客户端完整③

时间:2024-06-17 21:28:37浏览次数:22  
标签:聊天室 C# pic client msg new null public 客户端

窗体

进入聊天室界面(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);
        }
    }
}

天才是百分之九十九的汗水加百分之一的灵感

标签:聊天室,C#,pic,client,msg,new,null,public,客户端
From: https://blog.csdn.net/weixin_73535261/article/details/139752471

相关文章

  • C语言---------深入理解指针
    目录一、字符指针二、指针数组:三、数组指针:1、定义:2、&数组名和数组名区别:3、数组指针的使用:四、数组参数,指针参数:1、一维数组传参:2、二维数组传参:3、一级指针传参:4、二级指针传参:五、函数指针:1、定义:2、函数名和&函数名:3、函数指针的调用:六、函数指针数组:七......
  • Angular 18+ 高级教程 – Memory leak, unsubscribe, onDestroy
    何谓 MemoryLeak?Angular是SPA(Single-pageapplication)框架,用来开发SPA。SPA最大的特点就是它不刷新页面,不刷新就容易造成memoryleak。举个例子:有一个页面A,我们写了一个setInterval执行一些代码(比如autoplay幻灯片)。当用户离开页面A去页面B时,传统网......
  • vscode error: ‘for‘ loop initial declarations are only allowed in C99 mode解决
    在tasks.json的args里加上-std=c99{"version":"2.0.0","tasks":[{"type":"shell","label":"C/C++:g++.exe生成活动文件","command"......
  • VSCode远程开发配置SSH密钥免密登录
    VSCode远程开发配置SSH密钥免密登录ssh-key-deploy为开源软件,嫌弃报毒,请勿使用ssh-key-deploy为开源软件,嫌弃报毒,请勿使用ssh-key-deploy为开源软件,嫌弃报毒,请勿使用下载ssh-key-deploySSH密钥生成部署工具ssh-key-deployGitHub仓库:https://github.com/ikay666/ssh-key-dep......
  • FinalReference 如何使 GC 过程变得拖拖拉拉
    本文基于OpenJDK17进行讨论,垃圾回收器为ZGC。提示:为了方便大家索引,特将在上篇文章《以ZGC为例,谈一谈JVM是如何实现Reference语义的》中讨论的众多主题独立出来。FinalReference对于我们来说是一种比较陌生的Reference类型,因为我们好像在各大中间件以及JDK中......
  • 使用python脚本玩转古早TCAD软件(待更新)
    前言TCAD(TechnologyComputerAidedDesign),虽然原名中没有与半导体器件有关的词汇,但这种软件便是半导体工艺模拟及器件模拟的工具,可以说是EDA软件的一种。TCAD软件同其他EDA软件一样,底层需要复杂的数学模型和数物模型支撑,能大幅减少半导体制造的研发成本,为新型半导体器件提供初......
  • LeetCode 2268. Minimum Number of Keypresses
    原题链接在这里:https://leetcode.com/problems/minimum-number-of-keypresses/description/题目:Youhaveakeypadwith 9 buttons,numberedfrom 1 to 9,eachmappedtolowercaseEnglishletters.Youcanchoosewhichcharacterseachbuttonismatchedtoaslong......
  • 埃氏筛+欧拉筛 (c++)
    求出从2到n的素数埃氏筛方法:筛2的倍数,3的倍数,4的倍数......时间复杂度:O(n·loglogn)缺点:一个数筛了多次,比如6会被2筛,被3筛,被6筛,浪费时间下面的代码中,f是是否是素数的标记数组,N是要筛的个数f[1]=1;for(inti=2;i*i<=N;i++)if(f[i]==0){for(intj=i+i;......
  • Reflective Journal Final
    1.WhenIfirstventuredintotherealmofdigitalmultimodalcreation,Iinitiallybelievedthatcreationwassolelyconfinedtowords,relyingsolelyonvocabularyandgrammartoconveyinformation.However,asIdelveddeeperintomystudies,Igraduall......
  • c++万能头文件
    一、问题出现c/C++使用首先就是要开头头文件的引用,没有写头文件的程序基本都不会成功运行得到想要的结果,因为每个程序基本都避免不了一定的输入与输出,而输入与输出却在头文件#include/#include<stdio.h>中大量的库函数扑面而来,随之产生了一个很令人头疼的问题,每一种类型的函......