C#聊天室客户端完整③

news2024/12/24 11:22:59

窗体

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1832114.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL Explain 关键字详解

概述 explain 关键字可以模拟执行 sql 查询语句&#xff0c;输出执行计划&#xff0c;分析查询语句的执行性能 使用方式如下&#xff1a;explain sql explain select * from t1执行计划各字段含义 1. id 如果 id 序号相同&#xff0c;从上往下执行如果 id 序号不同&#…

项目实施文档(Word原件项目直接套用)

软件实施方案 二、 项目介绍 三、 项目实施 四、 项目实施计划 五、 人员培训 六、 项目验收 七、 售后服务 八、 项目保障措施 获取方式&#xff1a;本文末个人名片直接获取。

关于el-date-picker组件,如何隐藏时间组件底部清空按钮

工作中可能会遇到el-date-picker组件隐藏时间组件底部清空按钮 分为两种 &#xff1a; 如果你想要实现全部的el-date-picker的清空隐藏 和 某一个页面的el-date-picker的清空隐藏 1 全局隐藏 步骤1&#xff1a;在element-ui.scss中添加如下代码&#xff1a; .el-picker-pane…

Excel VLOOKUP 使用记录

Excel VLOOKUP 使用记录 VLOOKUP简单使用 VLOOKUP(lookup_value,table_array,col_index_num,[range-lookup]) 下面是excel对VLOOKUP 的解释 lookup_value&#xff08;查找值&#xff09;&#xff1a;要匹配查找的值 table_array&#xff08;数据表&#xff09;&#xff1…

想上币的项目方怎么去选择交易所

在区块链和加密货币蓬勃发展的今天&#xff0c;许多项目方都渴望通过交易所上线其代币&#xff0c;以扩大影响力、提升流动性和市场认可度。然而&#xff0c;选择合适的交易所并非易事&#xff0c;它关乎项目的未来发展和市场地位。那么&#xff0c;对于有上币意向的项目来说&a…

从0进入微服务需要了解的基础知识

文章目录 系统架构演化过程为什么要了解系统架构的演化过程技术发展认知技术选型与创新 演变过程单体架构分层-分布式集群微服务 分布式\集群\微服务 微服务中的核心要素-拆分原则项目拆分与复杂度微服务的拆分维度有哪些小结 微服务中的核心要素服务化进行拆分后一定是微服务&…

Flink 窗口函数

一、Window 概述 Flink 流式计算是一种被设计用于处理无限数据集的数据处理引擎&#xff0c;而无限数据集是指一种不断增长的本质上无限的数据集&#xff0c;而 window 是一种切割无线数据为有限块进行处理的手段。 二、Window 分类 Window 可以分为两类&#xff1a; Count…

利用Python语言调用讯飞星火认知大模型接口实战指南

什么是API接口 API&#xff08;应用程序编程接口&#xff09;是一组规则&#xff0c;允许不同的软件系统相互通信。通过API&#xff0c;开发者可以访问外部系统的功能和数据&#xff0c;而无需了解其内部实现。 API接口就像一座桥梁&#xff0c;连接应用程序和服务。例如&…

车企高管组团“出道”,汽车营销已经Next level了?

汽车进入了“卷”老板、“卷”高管的时代&#xff01; 谁能想到&#xff0c;雷军凭一己之力&#xff0c;在一定程度上重塑了汽车的竞争策略。价格战之外&#xff0c;车市又开启了流量之战。 云略曾在《雷军20天吸粉500w&#xff01;……》一文中&#xff0c;提到继雷军之后&…

敏捷开发时代,彻底结束了

最近&#xff0c;我收到一位读者的私信&#xff0c;他最近“内耗”得非常厉害&#xff0c;他可能一时兴起把我的私信当作了吐槽箱。 他们公司一直实行敏捷的管理模式&#xff0c;复盘发现了一个问题&#xff1a;发布与迭代具有强相关性&#xff0c;一个迭代就发布一次&#xf…

网络安全 DVWA通关指南 SQL Injection(SQL注入)

DVWA SQL Injection 文章目录 DVWA SQL InjectionLowMediumHighImpossible SQL注入漏洞基本原理 Web应用程序对用户输入的数据校验处理不严或者根本没有校验&#xff0c;致使用户可以拼接执行SQL命令。 可能导致数据泄露或数据破坏&#xff0c;缺乏可审计性&#xff0c;甚至导致…

RockChip Android12 Settings一级菜单

一:概述 在之前的文章中对Android8.1 Settings的流程进行了说明,本章将针对Android12 Settings一级菜单的加载逻辑进行详细说明,Settings版本之间的差异不是很大,有兴趣的同学可自行学习,本文不在做赘述。 Android8.1 Settings说明:RockChip Android8.1 Settings-CSDN博…

浏览器开发公司Brave 将自己的搜索结果与其 Leo AI 助手集成

Brave Software是一家开发浏览器的公司&#xff0c;其主要产品是Brave浏览器。Brave浏览器基于Chromium项目开发&#xff0c;具有高性能和隐私保护的特点。此外&#xff0c;Brave浏览器还提供了“off record”模式&#xff0c;允许用户在不记录浏览历史的情况下使用浏览器。关于…

Cisco Packet Tracer实验(五)不同vlan间的通信简单配置

1&#xff0e;单臂路由(图) 环境&#xff1a;一台路由器&#xff0c;一台二层交换机&#xff0c;两台pc机 单臂路由&#xff08;Single Arm Routing&#xff09;是指在网络架构中&#xff0c;只有一个物理接口&#xff08;单臂&#xff09;连接到路由器三层交换机&#xff0c;而…

电脑微信聊天记录监控要怎么做?找谁找?

电脑微信聊天记录的监控通常涉及到使用特定的监控软件&#xff0c;这些软件设计用于企业管理和网络监控&#xff0c;以确保工作场所的通信安全和提高工作效率。以下是进行电脑微信聊天记录监控的一般步骤和建议&#xff1a; 如何进行监控&#xff1a; 1.明确目的与合法性&…

计算机组成原理之存储器(二)

文章目录 随机读写存储器RAM静态MOS存储单元与存储芯片动态MOS存储单元与存储芯片 半导体存储器逻辑设计存储器的读写以及刷新存储器的读写动态存储芯片的刷新 随机读写存储器RAM 静态MOS存储单元与存储芯片 静态RAM用半导体管的导通和截止来记忆&#xff0c;只要不掉电&#x…

Transformer中的Self-Attention和Multi-Head Attention

2017 Google 在Computation and Language发表 当时主要针对于自然语言处理&#xff08;之前的RNN模型记忆长度有限且无法并行化&#xff0c;只有计算完ti时刻后的数据才能计算ti1时刻的数据&#xff0c;但Transformer都可以做到&#xff09; 文章提出Self-Attention概念&…

python学习笔记-06

函数进阶 1.无参数无返回值&#xff1a;这类函数往往用于提示信息打印 2.无参数有返回值&#xff1a;这类函数往往用于数据采集过程中 3.有参数有返回值&#xff1a;这类函数一般是计算型的 4.有参数无返回值&#xff1a;这类函数多用于设置某些不需要返回值的参数设置1.局部变…

实验2:RIPv2的配置

由于RIPv1是有类别的路由协议,路由更新不携带子网信息,不支持不连续子网、VLSM、手工汇总和验证等&#xff0c;本书重点讨论RIPv2。 1、实验目的 通过本实验可以掌握&#xff1a; RIPv1和 RIPv2的区别。在路由器上启动RIPv2路由进程。激活参与RIPv2路由协议的接口。auto-sum…

一个提问高下立见?国产AI大模型冲上扣子广场PK

以“国产GPTs”出名的扣子&#xff0c;做出了GPT没有的功能。 6月12日&#xff0c;字节跳动旗下的AI应用开发平台“扣子”&#xff08;Coze国内版&#xff09;悄悄上线了新功能“模型广场”。 扣子是AI应用开发平台&#xff0c;无论用户是否有编程基础&#xff0c;都可以在扣子…