文章目录
一、前言
C#程序设计实战练习项目,做一个类似于QQ的软件,程序参考明日科技出版的《C#项目开发入门实战》第一章:Q友,做自己的QQ。
众所周知,学编程实践大于纯论理学习。为巩固和练习C#程序设计在编写Winform窗体应用程序
方面的开发与应用,着手做了这样一个练习,实战中确实遇到了很多坑,也学到了很多,回顾开发过程,记录心得,写下这篇博客。
参考教程视频:https://www.bilibili.com/video/BV1EA411v7HT?p=27
教程参考资源:https://pan.baidu.com/s/1m8SzKBw3laiC3up71TLcfQ 提取码:61eb
练习参考资源:https://pan.baidu.com/s/1WQzzSLdZ9PN0VhyeA2XdPA 提取码:6666
练习成果演示:https://www.bilibili.com/video/BV1Yg41137qU/
本文同步发表在我的个人博客网站上:https://www.sunguoqi.com/2021/05/30/SunTalk/
二、项目介绍
QQ作为一款即时通信软件,于1999年2月推出,目前以发展了22年,拥有海量用户和灵活强大的功能。本次实验仅仅做了最基本的四个模块的练习,分别是用户登录界面
,用户账号注册界面
,主界面
,聊天界面
.
(虽然是练习,但还是想让软件拥有自己的特色,所以将书中的Q友改成了SunTalk,也更改了窗体背景,头像,控件颜色位置等属性)
三、开发技术
查了下,腾讯QQ客户端采用 Microsoft Visual C++
开发; 服务器端软件采用Linux gcc
开发 ;数据库采用MySql
数据库。腾讯QQ采用标准的TCP/IP协议
为通信协议。腾讯QQ客户端之间的消息传送也采用了UDP模式。
本次练习是在windows
操作系统下进行的,使用的是C#编程语言
,数据库采用的是MS SQL Server 2019
,集成开发环境使用的是Visual Studio 2019
。
主要涉及了以下内容:
Form
窗体关键属性、方法和事件的应用;- 如何触发窗体和控件的时间;
ListView
控件和ImageLis
t组件的结合使用;- 数据库及数据表的建立与管理;
- 使用
C#
操作SQL server
数据库;Timer
定时器组件的使用;- 如何判断是否按下了
<Enter>
键;- 自定义最小化和关闭按钮。
四、程序缺陷
本次练习主要实现了登录,注册,发消息这三个功能,而且只是单机的,确实比较鸡肋,但仍能学到很多东西。
五、前车之鉴
在起初练习这个项目的时候,使用的框架是.NET Core
框架。然而做到后面的时候,发现.NET Core
框架存在个严重的bug,不支持上下文菜单组件,和工具栏控件等。这使得本次练习不能继续完美的进行下去了,翻阅了很多论坛,得到了一句真理,开发winform
窗体应用程序还是老老实实的用.NET FrameWork
框架吧。
.NET Core
框架无法直接更改成.NET Framework
框架,所以,我重头来了一遍。。。
然而当我设计完登录窗体,问题又出现了,运行调试,设计好的控件错位了???
于是又查了各大论坛,发现.NET FrameWork
框架确实存在这个问题,笔记本电脑的显示屏分辨率缩放通常被放大到125%或者150%来适应电脑屏幕,而使用.NET FrameWork
框架设计窗体似乎必须在分辨率缩放为100%的时候才能保证控件不发生错位(啊这,为什么用.NET core
框架不会)但是相应的VS2019
上的字体就比较模糊了,目前确实没找到什么好的解决办法,就硬着头皮继续做了下来。。。。。。。
六、正式开始
SunTalk
软件业务流程:用户——>软件登录——>是否注册(未注册进入注册页面)——>登录验证——>主窗体——>双击头像——>聊天窗体。
七、数据库设计
后续功能的实现都需要操作数据库,所以设计数据库是第一要义,设计数据库之前,我们应该知道我们需要怎样的数据,进而需要设计怎样的表格,数据库的设计和程序窗体的设计应该是并行的,但为了后面调用的方便,还是先将数据库的设计好。
1、创建数据库
右键对象资源管理器
下的数据库
,选择新建,为数据库起个名字,点击确定。
右键db_SunTalk
下的表,选择新建表,表的设计分别如下。
值得注意的是,以下各表中的ID字段列属性的标识规范(是标识)需要设计成(是),表示增量和标识种子分别为1。
记得在初次更改标识规范时,出现过无法更改的现象,这时需要在工具
下拉菜单的选项
中取消阻止保存要求重新创建表的更改
复选框的对勾。
2、数据表设计
2.1 tb_User(用户信息表)
2.2 tb_Friend(好友信息表)
2.3 tb_Message(消息表)
2.4 tb_MessageType(消息类型表)
2.5 tb_FriendLimit(添加好友条件表)
3、视图设计
新建查询然后输入以下代码,执行创建消息视图。
CREATE VIEW [dbo].[v_Message]
AS
SELECT DISTINCT
dbo.tb_Message.ID, dbo.tb_Message.FromUserID, dbo.tb_Message.ToUserID,
dbo.tb_Message.Message, dbo.tb_Message.MessageTypeID, dbo.tb_Message.MessageState,
dbo.tb_Message.MessageTime, dbo.tb_User.NickName
FROM dbo.tb_Message INNER JOIN
dbo.tb_User ON dbo.tb_Message.FromUserID = dbo.tb_User.ID
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
八、创建项目
1、选择 .NET Framework 框架
打开visual studio 2019
,创建新项目。这里记得使用.NET Framework
框架。
2、以100%缩放比例重启VS
创建后的界面如下,可能窗口布局会不一样,这没有关系,不过较为显著的是本显示窗体缩放比例已设置为125%,使用100%缩放比例重新启动visual Studio 帮我决定
提示,这个要选择使用100%缩放比例重新启动Visual Studio
,点击蓝色的超链接即可,否则的话在缩放比例为125%或者150%下设计的窗体,运行调试后控件会错位。
九、用户登录窗体
1、登录窗体布局设计
本次练习参考腾讯QQ的登录页面,窗体设计本身很复杂,需要设计各种图标,不过可以偷个懒,腾讯QQ的界面改改,作为SunTalk窗体的背景,在背景的基础上添加相应控件。
实现上图效果,首先我们将form1
窗体NAME
更改成Frm_Login
,以便在实现代码中调用。(其他窗体也要相应的更改哦,尤其是名字
)
Backgroundimage
:添加登录背景图片FormBorderStyle
:NoneStartPosition
:CenterScreenText
:SunTalk登录Size
:(根据背景拖动窗体就可以了)
2、窗体控件填充
下面我们分析下该登录窗体需要哪些控件,这里头像显示部分的功能没有实现,共包含了以下八个控件。
(各控件最重要的部分其实是名字,后续功能实现需要通过名字对其进行调用,控件的其他属性这里只列出部分,可自定义设置,比如字体颜色,字体大小,控件位置,控件背景色等等)
- 1、
TextBox
Name
:txtID
BorderStyle
:None
- 2、
TextBox
Name
:txtPwd
BorderStyle
:None
- 3、
CheckBox
Name
:cboxRemember
Text
:记住密码
- 4、
CheckBox
Name
:cboxAutoLogin
Text
:自动登录
- 5、
Linklabel
Name
:linklblReg
Text
:注册账号
- 6、
PictureBox
Name
:pboxLogin
BackColor
:Transparent
- 7、
PictureBox
Name
:pboxMin
BackColor
:Transparent
- 8、
PictureBox
Name
:pboxClose
BackColor
:Transparent
3、登录窗体功能实现
选中窗体,在空白区域(无其他控件的地方)双击,或者右键查看代码进入代码编辑区。
3.1 便于测试
为了方便测试登录窗体,实现点击安全登陆按钮可以代开主窗体的操作,我们应该事先在数据库中添加一条用户数据。
这样我们输入账号即可测试记住密码,打开主窗体等功能。
3.2 判断账号密码格式
首先我们需要编写一个函数ValidateInput
来判断用户输入账号和密码的格式问题。
规则是:账号和密码不应为空,并且账号ID据库里设计的是int类型,最大数据范围是65535。
private bool ValidateInput()
{
if (txtID.Text.Trim() == "") //登录账号
{
MessageBox.Show("请输入登录账号", "登录提示", MessageBoxButtons.OK,
MessageBoxIcon.Information);
txtID.Focus(); //使登录账号文本框获得鼠标焦点
return false;
}
else if(int.Parse(txtID.Text.Trim())>65535)
{
MessageBox.Show("请输入正确的登录账号", "登录提示", MessageBoxButtons.OK,
MessageBoxIcon.Information);
txtID.Focus(); //使登录账号文本框获得鼠标焦点
return false;
}
else if (txtID.Text.Length>5 && txtPwd.Text.Trim() == "")//密码
{
MessageBox.Show("请输入密码", "登录提示", MessageBoxButtons.OK,
MessageBoxIcon.Information);
txtPwd.Focus(); //使密码文本框获得鼠标焦点
return false;
}
return true;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
3.3 账号类型必须是数字类型
账号类型必须是数字类型,为此,我们需要限定此文本框的输入,选中txtID
账号登录文本框,在事件中找到KeyPress
事件,双击该事件,进入改事件的代码编辑区。输入以下代码。
值得注意的是,第一行代码和大括号在我们双击该事件的时候自动生成,我们只需添加大括号内的实现代码。(其余的代码块同理
)
private void txtID_KeyPress(object sender, KeyPressEventArgs e)
{
//判断是否为数字
if (char.IsDigit(e.KeyChar) || (e.KeyChar == '\r') || (e.KeyChar == '\b'))
e.Handled = false; //在控件中显示该字符
else
e.Handled = true; //取消在控件中显示该字符
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
3.4 实现用户登录
实现用户登录,我们首先需要在数据库中查询是否存在此账户,然后判断账号和密码是否正确,若正确则进入SunTalk主界面。
为此我们需要建立此程序与数据库之间的连接。
3.4.1 创建DataOperator类
在项目资源管理器下右键项目文件,在右键菜单中选择添加,为程序添加一个DataOperator
类,此类实现了本程序与本地数据库的连接。实现代码如下。
在DataOperator
类的代码编辑区,我们首先引用两个命名空间,这是操作数据库必要的。
using System.Data;
using System.Data.SqlClient;
- 1
- 2
3.4.2 连接数据库
在公共代码编辑区添加如下代码连接到本地数据库,这里的需要更改成自己的数据库账号和密码。
//数据库连接字符串
private static string connString = @"Data Source=LAPTOP-KQ506P5I;Database=db_SunTalk;User ID=sa;Pwd=!!!!!!!;";
//数据库连接对象
public static SqlConnection connection = new SqlConnection(connString);
- 1
- 2
- 3
- 4
3.4.3 执行查询,返回列
定义一个ExecSQL
方法来查询数据库,并返回查询结果结果中的第一行第一列。
public int ExecSQL(string sql)
{
SqlCommand command = new SqlCommand(sql, connection); //指定要执行的SQL语句
if (connection.State == ConnectionState.Closed) //如果当前数据库连接处于关闭状态
connection.Open(); //打开数据库连接
int num = Convert.ToInt32(command.ExecuteScalar()); //执行查询
connection.Close(); //关闭数据库连接
return num; //返回结果中的第一行第一列
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
3.4.4 返回结果,返回行数
定义一个ExecSQLResult
方法来查询数据库,并返回受影响的行数。
public int ExecSQLResult(string sql)
{
SqlCommand command = new SqlCommand(sql, connection); //指定要执行的SQL语句
if (connection.State == ConnectionState.Closed) //如果当前数据库连接处于关闭状态
connection.Open(); //打开数据库连接
int result = command.ExecuteNonQuery(); //执行SQL语句
connection.Close(); //关闭数据库连接
return result; //返回受影响的行数
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
3.4.5 创建Publicclass类
为程序添加一个Publicclass
类,在该类中定义一个静态变量来记录loginID
用来记录用户登录账号。
public static int loginID;
- 1
3.4.6 创建Publicclass类的实例对象
在Frm_Login
的公共变量或方法的代码编辑区创建用户账号的的实例对象。
DataOperator dataOper = new DataOperator();
- 1
3.4.7 触发安全登录窗体
选中安全登录控件,双击进入该控件的click
点击事件,该事件通过查询tb_User
数据表中是否存在相匹配的账户来实现用户登录功能,实现代码如下。
private void pboxLogin_Click(object sender, EventArgs e)
{
if (ValidateInput()) //调用自定义方法验证用户输入
{
string sql = "select count(*) from tb_User where ID=" + int.Parse(txtID.Text.Trim())
+ " and Pwd = '" + txtPwd.Text.Trim() + "'"; //定义查询SQL语句
int num = dataOper.ExecSQL(sql); //数据库查询
if (num == 1) //验证通过
{
PublicClass.loginID = int.Parse(txtID.Text.Trim()); //设置登录的用户号码
if (cboxRemember.Checked)
{
dataOper.ExecSQLResult("update tb_User set Remember=1 where ID=" +
int.Parse(txtID.Text.Trim())); //记住密码
if (cboxAutoLogin.Checked)
dataOper.ExecSQLResult("update tb_User set AutoLogin=1 where ID=" +
int.Parse(txtID.Text.Trim())); //自动登录
}
else
{
dataOper.ExecSQLResult("update tb_User set Remember=0 where ID=" +
int.Parse(txtID.Text.Trim()));
dataOper.ExecSQLResult("update tb_User set AutoLogin=0 where ID=" +
int.Parse(txtID.Text.Trim()));
}
dataOper.ExecSQLResult("update tb_User set Flag=1 where ID=" +
int.Parse(txtID.Text.Trim())); //设置在线状态
Frm_Main frmMain = new Frm_Main(); //创建主窗体对象
frmMain.Show(); //显示主窗体
this.Visible = false; //隐藏登录主窗体
}
else
{
MessageBox.Show("输入的用户名或密码有误!", "登录提示", MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
3.5 添加Frm_Main窗体
编写完上面的代码,不出意外的话,会报错,原因在这行代码。
Frm_Main frmMain = new Frm_Main(); //创建主窗体对象
- 1
是的,在创建主窗体实例对象时,我们应该保证此窗体时真实存在的,于是,我们必须事先添加此窗体。
在项目资源管理器下右键项目文件,在右键菜单中选择添加,为程序添加一个form窗体
,并将改窗体的Name
设置成Frm_Main
3.6 按下回车键自动登录
如何实现当账号和密码都输入完成后,不点击安全登录空间,而是按下Enter
键便实现登录功能呢?
我们将txtPwd
控件的KeyPress
事件和Enter
键相关联即可。实现代码如下。
private void txtPwd_KeyPress(object sender, KeyPressEventArgs e)
{
if (e.KeyChar == '\r') //判断是否按下回车键
pboxLogin_Click(sender, e); //使登录按钮获得鼠标焦点
}
- 1
- 2
- 3
- 4
- 5
6、实现记住密码和自动登录功能
6.1 自动登录的逻辑
要实现自动登录,必须先记住密码。为此,需要在记住密码下添加如下逻辑。
选中cboxRemember
控件,双击即可进入CheckedChanged
事件代码编辑区编写如下代码。
private void cboxRemember_CheckedChanged(object sender, EventArgs e)
{
if (!cboxRemember.Checked) //判断记住密码文本框为未选中状态
cboxAutoLogin.Checked = false; //自动登录设置为未选中
}
- 1
- 2
- 3
- 4
- 5
6.2 判断数据表中自动登录字段
当我们第一次登录软件的时候,如果勾选了记住密码,当我们成功登录后,自动登录的字段值就会从默认的0变成1,提交修改到数据库中。
当我们第二次登录时,我们需要判断数据表中的自动登录字段,于是我们需要在DataOperator
类中添加一个GetDataSet
方法
public DataSet GetDataSet(string sql)
{
SqlDataAdapter sqlda = new SqlDataAdapter(sql, connection); //指定要执行的SQL语句
DataSet ds = new DataSet(); //创建数据集对象
sqlda.Fill(ds); //填充数据集
return ds; //返回数据集
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
6.3 实时检测账号,自动填充密码
当用户设置了记住密码,则在用户输入账号时,对账号进行实时检测,如果在数据库中检测到有匹配记录,则对登录密码自动填充。
此方法需要引用System.Data
命名空间,于是在程序在上方,需要添加如下代码。
using System.Data;
- 1
事件实现代码如下。
private void txtID_TextChanged(object sender, EventArgs e)
{
ValidateInput();
//根据号码查询其密码、记住密码和自动登录字段的值
string sql = "select Pwd,Remember,AutoLogin from tb_User where ID=" +int.Parse(txtID.Text.Trim()) + "";
DataSet ds = dataOper.GetDataSet(sql); //查询结果存储到数据集中
if (ds.Tables[0].Rows.Count > 0) //判断是否存在该用户
{
if (Convert.ToInt32(ds.Tables[0].Rows[0][1]) == 1) //判断是否记住密码
{
cboxRemember.Checked = true; //记录密码复选框选中
txtPwd.Text = ds.Tables[0].Rows[0][0].ToString(); //自动输入密码
if (Convert.ToInt32(ds.Tables[0].Rows[0][2]) == 1) //判断是否自动登录
{
cboxAutoLogin.Checked = true; //自动登录复选框选中
pboxLogin_Click(sender, e); //自动登录
}
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
7、打开注册账号窗体
同打开主界面窗体一样,在我们想要打开账号注册窗体时,我们应保证其事先存在,同添加主窗体一样,添加账号注册窗体,并将该窗体的Name属性更改成Frm_Register
选中注册账号控件双击,进入此控件的点击事件代码编辑区,添加如下代码,实现注册窗体的打开。
private void linklblReg_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
Frm_Register frmRegister = new Frm_Register(); //创建申请账号对象
frmRegister.Show(); //显示申请账号窗体
}
- 1
- 2
- 3
- 4
- 5
8、窗体最小化
双击pboxMin
控件,编写其点击事件代码,如下。
private void pboxMin_Click(object sender, EventArgs e)
{
this.WindowState = FormWindowState.Minimized; //设置窗体最小化
}
- 1
- 2
- 3
- 4
9、关闭应用
双击pboxClose
控件,编写其点击事件代码,如下。
private void pboxClose_Click(object sender, EventArgs e)
{
Application.ExitThread(); //退出当前应用程序
}
- 1
- 2
- 3
- 4
十、账号注册
通过单击注册账号超链接即可打开申请账号窗体,其中包含了必填资料和选填资料。
1、注册窗体布局设计
注册窗体的设计没有添加背景采用的是纯控件组合。
2、注册窗体控件填充
字体大小颜色等其他属性没有列出,可自己尝试。
- 1、
PictureBox
Name
:picLogo
BackColor
:Transparent
Image
:(自定义个图片)
SizeMode
:StretchImage
- 2、
GroupBox
Name
:grpBaseInfo
BackColor
:Transparent
text
:注册基本资料
- 3、
GroupBox
Name
:grpDetails
BackColor
:Transparent
text
:详细资料(选填)
- 4、
Label
Name
:lblNickName
Text
:昵称
- 5、
Label
Name
:lblAge
Text
:年龄
- 6、
Label
Name
:lblSex
Text
:性别
- 7、
Label
Name
:lblPwd
Text
:密码
- 8、
Label
Name
:lblPwdAgain
Text
:重复密码
- 9、
TextBox
Name
:txtNickName
- 10、
TextBox
Name
:txtAge
- 11、
RadioButton
Name
:rbtnMale
Text
:男
- 12、
RadioButton
Name
:rbtnFemale
Text
:女
- 13、
TextBox
Name
:txtPwd
- 14、
TextBox
Name
:txtPwdAgain
BackColor
:Transparent
- 15、
Label
Name
:lblName
Text
:真实姓名
- 16、
Label
Name
:lblStar
Text
:星座
- 17、
Label
Name
:lblBloodType
Text
:血型
- 18、
TextBox
Name
:txtName
- 19、
ComboBox
Name
:cboxStar
DropDownStyle
:DropDownlist
Items
:白羊座…
- 20、
ComboBox
Name
:cboxBloodType
DropDownStyle
:DropDownlist
Items
:A型…
- 21、
Button
Name
:btnRegister
Text
:注册
- 22、
Button
Name
:btncancel
Text
:取消
3、注册窗体功能实现
3.1 星座和血型的默认设置
将“星座”和“血型”下拉选择框默认选项设置为第一项(索引为0),触发Frm_Register
窗体的Load
事件,双击窗体空白处进入代码编辑区,编写如下代码即可实现此功设置。
private void Frm_Register_Load(object sender, EventArgs e)
{
cboxStar.SelectedIndex = cboxBloodType.SelectedIndex = 0; //设置星座和血型的默认值
}
- 1
- 2
- 3
- 4
3.2 创建数据库操作类的对象
实现申请账号功能时,需要像数据库里添加数据,所以需要创建DataOperator对象。
在Frm_Register
的公共变量和方法编辑区编写如下代码。
DataOperator dataOper = new DataOperator();
- 1
3.3 实现账号注册
触发注册按钮的点击事件,双击注册按钮即可,在事件编辑区编写如下代码该事件首先验证用户输入,如果条件都满足,则将用户输入的信息添加到tb_User
表中。并获得新注册的账号。
private void btnRegister_Click(object sender, EventArgs e)
{
if (txtNickName.Text.Trim() == "" || txtNickName.Text.Length > 20)//验证昵称
{
MessageBox.Show("昵称输入有误!", "提示", MessageBoxButtons.OK,
MessageBoxIcon.Information);
txtNickName.Focus();
return;
}
if (txtAge.Text.Trim() == "") //验证年龄
{
MessageBox.Show("请输入年龄!", "提示", MessageBoxButtons.OK,
MessageBoxIcon.Information);
txtAge.Focus();
return;
}
if (!rbtnMale.Checked && !rbtnFemale.Checked) //验证性别
{
MessageBox.Show("请选择性别!", "提示", MessageBoxButtons.OK,
MessageBoxIcon.Information);
lblSex.Focus();
return;
}
if (txtPwd.Text.Trim() == "") //验证密码
{
MessageBox.Show("请输入密码!", "提示", MessageBoxButtons.OK,
MessageBoxIcon.Information);
txtPwd.Focus();
return;
}
if (txtPwdAgain.Text.Trim() == "") //验证确认密码
{
MessageBox.Show("请输入确认密码!", "提示", MessageBoxButtons.OK,
MessageBoxIcon.Information);
txtPwdAgain.Focus();
return;
}
if (txtPwd.Text.Trim() != txtPwdAgain.Text.Trim()) //验证两次密码是否一致
{
MessageBox.Show("两次输入的密码不一样!", "提示", MessageBoxButtons.OK,
MessageBoxIcon.Information);
txtPwdAgain.Focus();
return;
}
int myQQNum = 0; //QQ号码
string message; //弹出的消息
string sex = rbtnMale.Checked ? rbtnMale.Text : rbtnFemale.Text; //获得选中的性别
string sql = string.Format("insert into tb_User (Pwd, NickName, Sex, Age, Name, Star, BloodType) values('{0}', '{1}', '{2}',{3},'{4}','{5}','{6}'); select @@Identity from tb_User",txtPwd.Text.Trim(), txtNickName.Text.Trim(), sex, int.Parse(txtAge.Text.Trim()), txtName.Text.Trim(), cboxStar.Text, cboxBloodType.Text);
SqlCommand command = new SqlCommand(sql, DataOperator.connection);//指定要执行的SQL语句
DataOperator.connection.Open(); //打开数据库连接
int result = command.ExecuteNonQuery(); //执行SQL语句
if (result == 1) //判断是否成功
{
sql = "select SCOPE_IDENTITY() from tb_User"; //查询新增加的记录的标识号
command = new SqlCommand(sql, DataOperator.connection); //执行查询
myQQNum = Convert.ToInt32(command.ExecuteScalar()); //获取最新增加的账号
message = string.Format("注册成功!你的SunTalk号码是" + myQQNum);
}
else
{
message = "注册失败,请重试!";
}
DataOperator.connection.Close(); //关闭数据库连接
MessageBox.Show(message, "注册结果", MessageBoxButtons.OK, MessageBoxIcon.Information);
this.Close(); //关闭当前窗体
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
3.4 取消注册
如果用户点击取消,则关闭该注册窗口。,双击取消按钮,编写取消按钮点击事件的实现代码,如下。
private void btnCancel_Click(object sender, EventArgs e)
{
this.Close();
}
- 1
- 2
- 3
- 4
十一、主窗体
主窗体主要显示当前用户的个人信息(头像,昵称,账号、个性签名)好友列表(头像,昵称、是否在线)和快捷工具栏,用户可以通过双击某个好友,与其进行聊天。
1、主窗体布局设计
本窗体设计用了背景做了填充,也可以不用。
2、主窗体控件填充
2.1添加组件
控件是指在窗体上可以看到的对象,而组件则在窗体中看不到。
Frm_Main主要用了两种组件,分别是ImageLIst和Timer,其中ImageList组件用于存储图像列表,Timer组件用来作为定时器,Frm_Main窗体中用到了3个ImageList组件和3个Timer组件。如上面的截图所示。
三个组件的作用分别是提供大头像列表、小头像列表和聊天消息列表(这个可以不加),组件属性可参考下图,注意更改Name属性,以便后续代码中调用。
Timer组件需要添加三个,分别是tmMessage、tmADDFriend、tmChat属性分别如下。
- 1、
Timer
Name
:tmMessage
Enabled
:True
Interval
:2000
- 2、
Timer
Name
:tmAddFriend
Enabled
:False
Interval
:1000
- 3、
Timer
Name
:tmChat
Enabled
:False
Interval
:500
2.2 添加控件
- 1、
PictureBox
Name
:pboxHead
BackColor
:Transparent
SizeMode
:Zoom
- 2、
Label
Name
:lblName
Text
:
AutoSize
:True
- 3、
TextBox
Name
:txtSign
BorderStyle
:None
- 4、
ListView
Name
:lvFriend
LargeImageList
:imglistHead
Groups
:添加两个分组,Header属性分别为“我的好友”“陌生人”
MultiSelect
:False
SmallImageList
:imglistSmallHead
StateImageList
:imglistSmallHead
- 6、
PictureBox
Name
:pboxMin
BackColor
:Transparent
- 7、
PictureBox
Name
:pboxClose
BackColor
:Transparent
2.3 设计工具栏
如上图中的5所示,我们需要在主窗体处添加一个工具栏。首先我们向主窗体 中添加toolStrip
控件,并修改其Name
属性为tsOperation
先将控件的toolStrip
的Dock
属性设置为Bottom
。
添加5个Button
按钮,分别设置其属性。
- 1、
Name
:tsbtnInfo
Image
:(自定义)
Text
:个人信息
- 2、
Name
:tsbtnSearchFriend
Image
:(自定义)
Text
:查找
(为了突出查找按钮,可以设置成既显示图片又显示文字,方法是:右键该控件设置DisplayStyle
属性为ImageAndText
)
- 3、
Name
:tsbUpdateFriendList
Image
:(自定义)
Text
:更新好友列表
- 4、
Name
:tsbtnMessageReading
Image
:(自定义)
Text
:系统消息
- 5、
Name
:tsbtExit
Image
:(自定义)
Text
:退出
2.4 设计快捷菜单
首先我们像主窗体中添加ContextImageList
控件,将其Name属性修改成cmsFriendKList
在请在此处输入处添加三个按钮,右键即可设置其属性,后续可在代码中调用。
-
小头像
Name
:tsmenuViewVisible
:False -
添加好友
Name
:tsmenuAddVisible
:False -
删除
Name
:tsmenuDelVisible
:False
3、主窗体功能实现
3.1 添加应用及一些必要变量
在Frm_Main
的命名空间引用区域添加如下代码
using System.Data;
using System.Data.SqlClient;
using System.Media;
- 1
- 2
- 3
在Frm_Main
的公共代码编辑区添加如下代码
int fromUserID; //消息发送者
int friendHeadID; //发消息好友的头像ID
int messageImageIndex = 0; //工具栏中的消息图标的索引
public static string nickName = ""; //自己的昵称
public static string strFlag = "[离线]";
DataOperator dataOper = new DataOperator();
- 1
- 2
- 3
- 4
- 5
- 6
3.2 加载用户相关信息
3.2.1 数据库查询
窗体加载时,从数据库中获取用户的好友信息,因此需要在DataOperator
类中添加GetDataReader
方法来执行Sql查询。
public SqlDataReader GetDataReader(string sql)
{
SqlCommand command = new SqlCommand(sql, connection);//指定要执行的SQL语句
if (connection.State == ConnectionState.Open)//如果当前数据连接处于打开状态
connection.Close(); //关闭数据库连接
connection.Open();//打开数据库连接
SqlDataReader datareader = command.ExecuteReader();//生成SqlDataReader
return datareader;//返回SqlDataReader
} }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
3.2.2 显示用户信息
切换到Frm_Main
代码页,在公共变量编辑区添加如下代码来显示用户的头像,昵称,账号等信息。
public void ShowInfo()
{
int headID = 0; //头像索引
//获取当前用户的昵称、头像
string sql = "select NickName, HeadID,Sign from tb_User where ID=" + PublicClass.loginID + "";
SqlDataReader dataReader = dataOper.GetDataReader(sql); //执行查询操作
if (dataReader.Read()) //读取查询结果
{
if (!(dataReader["NickName"] is DBNull)) //判断NickName不为空
{
nickName = dataReader["NickName"].ToString(); //记录自己的昵称
}
headID = Convert.ToInt32(dataReader["HeadID"]); //记录自己的头像ID
txtSign.Text = dataReader["Sign"].ToString(); //显示个性签名
}
dataReader.Close(); //关闭读取器
DataOperator.connection.Close(); //关闭数据库连接
this.Text = PublicClass.loginID.ToString(); //设置窗体标题为当前用户账号
pboxHead.Image = imglistHead.Images[headID]; //显示用户头像
lblName.Text = nickName + "(" + PublicClass.loginID + ")"; //显示昵称及账号
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
3.2.3 显示好友信息
添加如下代码,在好友列表中显示好友头像,昵称和是否在线等信息。
private void ShowFriendList()
{
lvFriend.Items.Clear(); //清空原来的列表
//定义查找好友的SQL语句
string sql = "select FriendID,NickName,HeadID,Flag from tb_User,tb_Friend where tb_Friend.HostID=" + PublicClass.loginID + " and tb_User.ID=tb_Friend.FriendID";
SqlDataReader dataReader = dataOper.GetDataReader(sql); //执行查询
int i = lvFriend.Items.Count; //记录添加到ListView中的项索引
while (dataReader.Read()) //循环添加好友列表
{
if(dataReader["Flag"].ToString()=="0")
strFlag = "[离线]";
else
strFlag = "[在线]";
string strTemp = dataReader["NickName"].ToString(); //记录好友昵称
//对好友昵称进行处理
string strFriendName=strTemp;
if (strTemp.Length < 9)
strFriendName = strTemp.PadLeft(9, ' ');
else
strFriendName = (strTemp.Substring(0, 2) + "...").PadLeft(9, ' ');
//向ListView中添加项,Name:好友ID,值:昵称,要显示的头像
lvFriend.Items.Add(dataReader["FriendID"].ToString(), strFriendName + strFlag,
(int)dataReader["HeadID"]);
lvFriend.Items[i].Group = lvFriend.Groups[0]; //设置项的分组为我的好友
i++; //临时变量加1
}
dataReader.Close(); //关闭读取器
DataOperator.connection.Close(); //关闭数据库连接
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
3.2.4 默认加载
添加如下代码,当用户进入主界面时,默认显示用户的个人信息和好友列表以及默认头像等。
private void Frm_Main_Load(object sender, EventArgs e){ tsbtnMessageReading.Image = imglistMessage.Images[0]; //工具栏的消息图标 ShowInfo(); //显示个人信息 ShowFriendList(); //显示好友列表}
- 1
3.3 工具栏按钮功能实现
3.3.1 个人信息
private void Frm_Main_Load(object sender, EventArgs e)
{
tsbtnMessageReading.Image = imglistMessage.Images[0]; //工具栏的消息图标
ShowInfo(); //显示个人信息
ShowFriendList(); //显示好友列表
}
- 1
- 2
- 3
- 4
- 5
- 6
3.3.2 查找好友
private void tsbtnInfo_Click(object sender, EventArgs e)
{
Frm_EditInfo frmInfo = new Frm_EditInfo(); //创建个人信息窗体对象
frmInfo.Show(); //显示个人信息窗体
}
- 1
- 2
- 3
- 4
- 5
3.3.3 显示好友列表
private void tsbtnSearchFriend_Click(object sender, EventArgs e)
{
Frm_AddFriend frmAddFriend = new Frm_AddFriend(); //创建查找好友窗体对象
frmAddFriend.Show(); //显示查找好友窗体
}
- 1
- 2
- 3
- 4
- 5
3.3.4 显示系统消息
private void tsbtnMessageReading_Click(object sender, EventArgs e)
{
tmAddFriend.Stop(); //停止消息提醒定时器
messageImageIndex = 0; //头像恢复正常
//显示正常的系统消息提醒图标
tsbtnMessageReading.Image = imglistMessage.Images[messageImageIndex];
Frm_Remind frmRemind = new Frm_Remind(); //创建系统消息窗体对象
frmRemind.Show(); //显示系统消息窗体
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
3.3.5 退出当前程序
private void tsbtnExit_Click(object sender, EventArgs e)
{
DialogResult result = MessageBox.Show("确实要退出吗?", "确认", MessageBoxButtons.YesNo,
MessageBoxIcon.Question); //弹出确定对话框
if (result == DialogResult.Yes) //如果单击是按钮
{
Application.ExitThread(); //退出当前程序
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
3.4 双击打开聊天窗体
首先定义一些必要的变量。
public int friendID=0; //当前聊天的好友号码
public string nickName; //当前聊天的好友昵称
public int headID; //当前聊天的好友头像ID
DataOperator dataOper = new DataOperator(); //创建数据操作类的对象
- 1
- 2
- 3
- 4
触发lvFriend
控件的MouseDoubleClick事件,编写如下代码,实现双击头像打开聊天窗体功能。
Frm_Chat frmChat;//聊天窗体对象
//双击打开聊天窗体
private void lvFriend_MouseDoubleClick(object sender, MouseEventArgs e)
{
if (lvFriend.SelectedItems.Count > 0)//判断是否有选中项
{
if (frmChat == null)//判断聊天窗体对象是否为空
{
frmChat = new Frm_Chat();//创建聊天窗体对象
//记录聊天的账号
frmChat.friendID = Convert.ToInt32(lvFriend.SelectedItems[0].Name);
frmChat.nickName = dataOper.GetDataSet("select NickName from tb_User where ID=" + frmChat.friendID).Tables[0].Rows[0][0].ToString();//记录昵称
frmChat.headID = Convert.ToInt32(dataOper.GetDataSet("select HeadID from tb_User where ID=" + frmChat.friendID).Tables[0].Rows[0][0])+1;//记录头像ID
frmChat.ShowDialog();//以对话框显示聊天窗体对象
frmChat = null;//将聊天窗体对象设置为空
}
if (tmChat.Enabled == true)//如果聊天定时器处于可用状态
{
tmChat.Stop();//停止聊天定时器
lvFriend.SelectedItems[0].ImageIndex = friendHeadID;//将选中项的头像显示为正常状态
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
3.5 实时消息提醒及好友头像闪烁
(这个功能在本次练习中无法进行演示 : ( 可以学习一下实现代码)
3.5.1 判断用户是否在好友列表中
在Frm_Main代码编辑区添加如下代码。
private bool HasShowUser(int ID)
{
//是否在当前显示出的用户列表中找到了该用户
bool find = false;
//循环lvFriend中的2个组,寻找发消息的人是否在列表中
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < lvFriend.Groups[i].Items.Count; j++)
{
if (Convert.ToInt32(lvFriend.Groups[i].Items[j].Name) == ID)
{
find = true;
}
}
}
return find;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
3.5.2 显示陌生人列表
private void UpdateStranger(int ID)
{
lvFriend.Items.Clear(); //清空原来的列表
//获取指定用户的昵称及头像ID
string sql = "select NickName, HeadID from tb_User where ID=" + ID;
SqlDataReader dataReader = dataOper.GetDataReader(sql); //执行查询
int i = lvFriend.Items.Count; //记录添加到ListView中的项索引
while (dataReader.Read()) //循环添加陌生人列表
{
string strTemp = dataReader["NickName"].ToString(); //记录好友昵称
//对好友昵称进行处理
string strName = strTemp;
if (strTemp.Length < 9)
strName = strTemp.PadLeft(9, ' ');
else
strName = (strTemp.Substring(0, 2) + "...").PadLeft(9, ' ');
//向ListView中添加项,Name:用户ID,值:昵称,要显示的头像
lvFriend.Items.Add(fromUserID.ToString(), strName, (int)dataReader["HeadID"]);
lvFriend.Items[i].Group = lvFriend.Groups[1]; //设置项的分组为陌生人
i++; //临时变量加1
}
dataReader.Close(); //关闭读取器
DataOperator.connection.Close(); //关闭数据库连接
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
3.5.3 未读消息提示
触发tmMessage
的Tick
事件,编写如下代码,在显示未读消息的同时,进行消息提示。
private void tmMessage_Tick(object sender, EventArgs e)
{
if (lvFriend.SelectedItems.Count > 0) //判断好友列表中有选中项
{
if (lvFriend.SelectedItems[0].Group == lvFriend.Groups[0])//如果选中项属于第1组
{
tsMenuDel.Visible = true; //显示删除菜单
tsMenuAdd.Visible = false; //隐藏添加好友菜单
}
//如果选中项属于第2组
else if (lvFriend.SelectedItems[0].Group == lvFriend.Groups[1])
{
tsMenuDel.Visible = false; //隐藏删除菜单
tsMenuAdd.Visible = true; //显示添加好友菜单
}
}
int messageTypeID = 1; //消息类型
int messageState = 1; //消息状态
string sql = "select top 1 FromUserID, MessageTypeID, MessageState from tb_Message where
ToUserID=" + PublicClass.loginID + " and MessageState=0"; //查找未读消息对应的好友ID
SqlDataReader dataReader = dataOper.GetDataReader(sql); //执行查询
if (dataReader.Read()) //读取未读消息
{
fromUserID = (int)dataReader["FromUserID"]; //记录消息发送者
messageTypeID = (int)dataReader["MessageTypeID"]; //记录消息类型
messageState = (int)dataReader["MessageState"]; //记录消息状态
}
dataReader.Close(); //关闭读取器
DataOperator.connection.Close(); //关闭数据库连接
//消息有两种类型:聊天消息、添加好友消息
//判断消息类型,如果是添加好友消息,启动消息提醒定时器
if (messageTypeID == 2 && messageState == 0)
{
SoundPlayer player = new SoundPlayer("system.wav"); //系统消息提示
player.Play(); //播放指定声音文件
tmAddFriend.Start(); //启动消息提醒定时器
}
//如果是聊天消息,启动聊天定时器,使好友头像闪烁
else if (messageTypeID == 1 && messageState == 0)
{
sql = "select HeadID from tb_User where ID=" + fromUserID;//获取消息发送者的ID
friendHeadID = dataOper.ExecSQL(sql); //设置发消息好友的头像ID
//如果发消息的人不在好友列表中,将其添加到陌生人列表中
if (!HasShowUser(fromUserID))
{
UpdateStranger(fromUserID); //显示陌生人列表
}
SoundPlayer player = new SoundPlayer("msg.wav"); //聊天消息提示
player.Play(); //播放指定声音文件
tmChat.Start(); //启动聊天定时器
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
3.5.4 消息提醒
触发tmAddFriend
的Tick
事件,编写如下代码,获取系统消息图像
索引,并显示在工具栏中。
private void tmAddFriend_Tick(object sender, EventArgs e)
{
messageImageIndex = messageImageIndex == 0 ? 1:0; //实时获取系统消息图像索引
//工具栏中显示消息读取状态图像
tsbtnMessageReading.Image = imglistMessage.Images[messageImageIndex];
}
- 1
- 2
- 3
- 4
- 5
- 6
3.5.5 头像闪动
触发tmChat
的Tick
事件,编写如下代码,实现好友发消息时的头像闪动。
private void tmChat_Tick(object sender, EventArgs e)
{
//循环好友列表两个组中的每项,找到消息发送者,使其头像闪烁
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < lvFriend.Groups[i].Items.Count; j++)
{
//判断是否为消息发送者
if (Convert.ToInt32(lvFriend.Groups[i].Items[j].Name) == fromUserID)
{
if (frmChat != null && frmChat.friendID != 0)
{
//直接显示头像,避免闪烁效果
lvFriend.SelectedItems[0].ImageIndex = friendHeadID;
}
else
{
if (lvFriend.Groups[i].Items[j].ImageIndex < 100)
{
//索引为100的图片是一个空白图片,为了实现闪烁效果
lvFriend.Groups[i].Items[j].ImageIndex = 100;
}
else
{
//要显示的消息发送者头像索引
lvFriend.Groups[i].Items[j].ImageIndex = friendHeadID;
}
}
}
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
十二、聊天窗体
1、聊天布局设计
聊天窗体可以有纯控件来做,为了美观和方便,还是添加了一个背景。
2、聊天控件填充
- 1、
PictureBox
Name
:pboxHead
BackColor
:Transparent
SizeMode
:Zoom
- 2、
Label
Name
:lblFriend
- 3、
RichTextBox
Name
:rtxtMessage
ReadOnly
:True
ScrollBars
:Vertical
- 4、
RichTextBox
Name
:rtxtChat
ReadOnly
:True
ScrollBars
:Vertical
- 5、
Button
Name
:btnClose
text
:关闭
- 6、
Button
Name
:btnSend
text
:发送
- 7、
PictureBox
Name
:pboxMin
BackColor
:Transparent
- 8、
PictureBox
Name
:pboxClose
BackColor
:Transparent
3、聊天功能实现
需要操作数据库,所以在实现代码编写之前,引用相应命名空间。
using System.Data.SqlClient;
- 1
3.1 显示好友头像及好友信息
切换到Frm_Main
代码编辑区,触发窗体的Load加载时间,添加如下代码,实现显示好友头像和好友名称的功能。
private void Frm_Chat_Load(object sender, EventArgs e)
{
this.Text = "与\"" + nickName + "\"聊天中"; //设置窗体标题
pboxHead.Image = imglistHead.Images[headID]; //设置好友头像
lblFriend.Text = string.Format("{0}({1})", nickName, friendID); //设置好友名称
rtxtMessage.ScrollToCaret(); //滚动条总在最下方
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
3.2 显示未读消息
切换到Frm_Main
窗体公共变量或方法的编辑区,添加如下代码,查询未读聊天消息。
private void SetMessage(string messageID)
{
string[] messageIDs = messageID.Split('_'); //分割出每个消息ID
string sql = "update tb_Message set MessageState=1 where ID="; //定义更新SQL语句
foreach (string id in messageIDs) //遍历所有消息ID
{
if (id != "")
{
sql += id; //设置更新条件
int result = dataOper.ExecSQLResult(sql); //执行数据表更新操作
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
添加如下代码,编写显示信息方法。
private void ShowMessage()
{
string messageID = ""; //消息ID组成的字符串
string message; //消息内容
string messageTime; //消息发送时间
//读取消息的SQL语句
string sql = "select ID,Message,MessageTime from tb_Message where FromUserID=" + friendID
+ " and ToUserID=" + PublicClass.loginID + " and MessageTypeID=1 and MessageState=0";
SqlDataReader datareader = dataOper.GetDataReader(sql);
//循环将消息添加到窗体上
while (datareader.Read())
{
messageID += datareader["ID"] + "_"; //消息ID
message = datareader["Message"].ToString(); //消息
//消息的发送时间
messageTime = Convert.ToDateTime(datareader["MessageTime"]).ToString();
//设置消息显示格式
rtxtMessage.Text += "\n" + nickName + " " + messageTime + "\n " + message + "";
}
DataOperator.connection.Close(); //关闭数据库连接
if (messageID.Length > 1) //判断是否存在消息
{
messageID.Remove(messageID.Length - 1); //去掉最后的连接符
SetMessage(messageID); //将显示的消息设置为已读
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
3.3 显示所有未读消息
触发tmShowMessage
的Tick
事件,添加显示未读聊天消息的方法。
private void tmShowMessage_Tick(object sender, EventArgs e)
{
ShowMessage(); //读取所有的未读消息,显示在窗体中
}
- 1
- 2
- 3
- 4
3.4 消息发送
触发btnSend
控件的Click
事件,添加如下代码,实现发送消息的功能。
private void btnSend_Click(object sender, EventArgs e)
{
if (rtxtChat.Text.Trim() == "") //不能发送空消息
{
MessageBox.Show("不能发送空消息!", "提示", MessageBoxButtons.OK,
MessageBoxIcon.Information);
return;
}
else //发送消息
{
//此处的MessageTypeId为1,表示聊天消息;MessageState为0,表示消息未读
string sql = string.Format(
"INSERT INTO tb_Message (FromUserID, ToUserID, Message, MessageTypeID,
MessageState) VALUES ({0},{1},'{2}',{3},{4})",PublicClass.loginID, friendID, rtxtChat.Text,
1, 0);
int result = dataOper.ExecSQLResult(sql); //调用方法实现消息插入操作
rtxtMessage.Text += "\n" + Frm_Main.nickName + " " + DateTime.Now + "\n " +
rtxtChat.Text + "";
if (result != 1) //如果返回结果不是1,表示没有发送成功
{
MessageBox.Show("消息发送失败,请重新发送!", "提示", MessageBoxButtons.OK,
MessageBoxIcon.Information);
}
rtxtChat.Text = ""; //清空消息
rtxtChat.Focus(); //定位鼠标输入焦点
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
3.5 按下Enter发送消息
触发rtxtChat
控件的keyDown
事件,添加如下代码,实现消息发送。
private void rtxtChat_KeyDown(object sender, KeyEventArgs e)
{
if (e.Control && e.KeyValue == 13) //当同时按下Ctrl和Enter时,发送消息
{
e.Handled = true;
btnSend_Click(this, null); //发送消息
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
3.6 查看消息记录
触发消息记录图片的pboxInfo
的Click
事件,添加如下代码,查看与当前好友的聊天记录。
private void pboxInfo_Click(object sender, EventArgs e)
{
rtxtMessage.Clear(); //清空聊天信息显示窗口
string messageID = ""; //消息ID组成的字符串
string message; //消息内容
string messageTime; //消息发送时间
string sql = "select ID,NickName,Message,MessageTime from v_Message where (FromUserID="
+ friendID + " and ToUserID=" + PublicClass.loginID + ") or (FromUserID=" + PublicClass.loginID
+ " and ToUserID=" + friendID + ") order by MessageTime asc ";//读取消息的SQL语句
SqlDataReader datareader = dataOper.GetDataReader(sql);
while (datareader.Read()) //循环将消息添加到窗体上
{
messageID += datareader["ID"] + "_"; //消息ID
message = datareader["Message"].ToString(); //消息
//消息的发送时间
messageTime = Convert.ToDateTime(datareader["MessageTime"]).ToString();
rtxtMessage.Text += "\n" + datareader["NickName"] + " " + messageTime + "\n " + message
+ ""; //设置消息显示格式
}
DataOperator.connection.Close(); //关闭数据库连接
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
3.7 最小化及关闭
分别双击进入最小化和关闭的两个图片按钮,编写如下代码即可。
private void pboxMin_Click(object sender, EventArgs e)
{
this.WindowState = FormWindowState.Minimized;
}
- 1
- 2
- 3
- 4
private void pboxClose_Click(object sender, EventArgs e)
{
this.Close();
}
- 1
- 2
- 3
- 4
十三、完整程序
1、DataOperator
using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.SqlClient;
namespace SunTalk
{
class DataOperator
{
//数据库连接字符串
private static string connString = @"Data Source=LAPTOP-KQ506P5I;Database=db_SunTalk;User ID=sa;Pwd=sun@@15395459989;";
//数据库连接对象
public static SqlConnection connection = new SqlConnection(connString);
public int ExecSQL(string sql)
{
SqlCommand command = new SqlCommand(sql, connection); //指定要执行的SQL语句
if (connection.State == ConnectionState.Closed) //如果当前数据库连接处于关闭状态
connection.Open(); //打开数据库连接
int num = Convert.ToInt32(command.ExecuteScalar()); //执行查询
connection.Close(); //关闭数据库连接
return num; //返回结果中的第一行第一列
}
public int ExecSQLResult(string sql)
{
SqlCommand command = new SqlCommand(sql, connection); //指定要执行的SQL语句
if (connection.State == ConnectionState.Closed) //如果当前数据库连接处于关闭状态
connection.Open(); //打开数据库连接
int result = command.ExecuteNonQuery(); //执行SQL语句
connection.Close(); //关闭数据库连接
return result; //返回受影响的行数
}
public DataSet GetDataSet(string sql)
{
SqlDataAdapter sqlda = new SqlDataAdapter(sql, connection); //指定要执行的SQL语句
DataSet ds = new DataSet(); //创建数据集对象
sqlda.Fill(ds); //填充数据集
return ds; //返回数据集
}
public SqlDataReader GetDataReader(string sql)
{
SqlCommand command = new SqlCommand(sql, connection);//指定要执行的SQL语句
if (connection.State == ConnectionState.Open)//如果当前数据连接处于打开状态
connection.Close(); //关闭数据库连接
connection.Open();//打开数据库连接
SqlDataReader datareader = command.ExecuteReader();//生成SqlDataReader
return datareader;//返回SqlDataReader
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
2、PublicClass
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SunTalk
{
class PublicClass
{
public static int loginID;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
3、Frm_Login
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace SunTalk
{
public partial class Frm_Login : Form
{
public Frm_Login()
{
InitializeComponent();
}
DataOperator dataOper = new DataOperator();
private bool ValidateInput()
{
if (txtID.Text.Trim() == "") //登录账号
{
MessageBox.Show("请输入登录账号", "登录提示", MessageBoxButtons.OK,
MessageBoxIcon.Information);
txtID.Focus(); //使登录账号文本框获得鼠标焦点
return false;
}
else if (int.Parse(txtID.Text.Trim()) > 65535)
{
MessageBox.Show("请输入正确的登录账号", "登录提示", MessageBoxButtons.OK,
MessageBoxIcon.Information);
txtID.Focus(); //使登录账号文本框获得鼠标焦点
return false;
}
else if (txtID.Text.Length > 5 && txtPwd.Text.Trim() == "")//密码
{
MessageBox.Show("请输入密码", "登录提示", MessageBoxButtons.OK,
MessageBoxIcon.Information);
txtPwd.Focus(); //使密码文本框获得鼠标焦点
return false;
}
return true;
}
private void txtID_KeyPress(object sender, KeyPressEventArgs e)
{
//判断是否为数字
if (char.IsDigit(e.KeyChar) || (e.KeyChar == '\r') || (e.KeyChar == '\b'))
e.Handled = false; //在控件中显示该字符
else
e.Handled = true;
}
private void pboxLogin_Click(object sender, EventArgs e)
{
if (ValidateInput()) //调用自定义方法验证用户输入
{
string sql = "select count(*) from tb_User where ID=" + int.Parse(txtID.Text.Trim())
+ " and Pwd = '" + txtPwd.Text.Trim() + "'"; //定义查询SQL语句
int num = dataOper.ExecSQL(sql);
if (num == 1) //验证通过
{
PublicClass.loginID = int.Parse(txtID.Text.Trim()); //设置登录的用户号码
if (cboxRemember.Checked)
{
dataOper.ExecSQLResult("update tb_User set Remember=1 where ID=" +
int.Parse(txtID.Text.Trim())); //记住密码
if (cboxAutoLogin.Checked)
dataOper.ExecSQLResult("update tb_User set AutoLogin=1 where ID=" +
int.Parse(txtID.Text.Trim())); //自动登录
}
else
{
dataOper.ExecSQLResult("update tb_User set Remember=0 where ID=" +
int.Parse(txtID.Text.Trim()));
dataOper.ExecSQLResult("update tb_User set AutoLogin=0 where ID=" +
int.Parse(txtID.Text.Trim()));
}
dataOper.ExecSQLResult("update tb_User set Flag=1 where ID=" +
int.Parse(txtID.Text.Trim())); //设置在线状态
Frm_Main frmMain = new Frm_Main(); //创建主窗体对象
frmMain.Show(); //显示主窗体
this.Visible = false; //隐藏登录主窗体
}
else
{
MessageBox.Show("输入的用户名或密码有误!", "登录提示", MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
}
}
private void txtPwd_KeyPress(object sender, KeyPressEventArgs e)
{
if (e.KeyChar == '\r') //判断是否按下回车键
pboxLogin_Click(sender, e); //使登录按钮获得鼠标焦点
}
private void cboxRemember_CheckedChanged(object sender, EventArgs e)
{
if (!cboxRemember.Checked) //判断忘记密码文本框为未选中状态
cboxAutoLogin.Checked = false; //自动登录设置为未选中
}
private void txtID_TextChanged(object sender, EventArgs e)
{
ValidateInput();
//根据号码查询其密码、记住密码和自动登录字段的值
string sql = "select Pwd,Remember,AutoLogin from tb_User where ID=" +
int.Parse(txtID.Text.Trim()) + "";
DataSet ds = dataOper.GetDataSet(sql); //查询结果存储到数据集中
if (ds.Tables[0].Rows.Count > 0) //判断是否存在该用户
{
if (Convert.ToInt32(ds.Tables[0].Rows[0][1]) == 1) //判断是否记住密码
{
cboxRemember.Checked = true; //记录密码复选框选中
txtPwd.Text = ds.Tables[0].Rows[0][0].ToString(); //自动输入密码
if (Convert.ToInt32(ds.Tables[0].Rows[0][2]) == 1) //判断是否自动登录
{
cboxAutoLogin.Checked = true; //自动登录复选框选中
pboxLogin_Click(sender, e); //自动登录
}
}
}
}
private void llinkLinkColor_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
Frm_Register frmRegister = new Frm_Register(); //创建申请账号对象
frmRegister.Show();
}
private void pboxMin_Click(object sender, EventArgs e)
{
this.WindowState = FormWindowState.Minimized;
}
private void pboxClose_Click(object sender, EventArgs e)
{
Application.ExitThread();
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
4、Frm_Register
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Data.SqlClient;
namespace SunTalk
{
public partial class Frm_Register : Form
{
public Frm_Register()
{
InitializeComponent();
}
DataOperator dataOper = new DataOperator();
private void Frm_Register_Load(object sender, EventArgs e)
{
cboxStar.SelectedIndex = cboxBloodType.SelectedIndex = 0; //设置星座和血型的默认值
}
private void btnRegister_Click(object sender, EventArgs e)
{
if (txtNickName.Text.Trim() == "" || txtNickName.Text.Length > 20)//验证昵称
{
MessageBox.Show("昵称输入有误!", "提示", MessageBoxButtons.OK,
MessageBoxIcon.Information);
txtNickName.Focus();
return;
}
if (txtAge.Text.Trim() == "") //验证年龄
{
MessageBox.Show("请输入年龄!", "提示", MessageBoxButtons.OK,
MessageBoxIcon.Information);
txtAge.Focus();
return;
}
if (!rbtnMale.Checked && !rbtnFemale.Checked) //验证性别
{
MessageBox.Show("请选择性别!", "提示", MessageBoxButtons.OK,
MessageBoxIcon.Information);
lblSex.Focus();
return;
}
if (txtPwd.Text.Trim() == "") //验证密码
{
MessageBox.Show("请输入密码!", "提示", MessageBoxButtons.OK,
MessageBoxIcon.Information);
txtPwd.Focus();
return;
}
if (txtPwdAgain.Text.Trim() == "") //验证确认密码
{
MessageBox.Show("请输入确认密码!", "提示", MessageBoxButtons.OK,
MessageBoxIcon.Information);
txtPwdAgain.Focus();
return;
}
if (txtPwd.Text.Trim() != txtPwdAgain.Text.Trim()) //验证两次密码是否一致
{
MessageBox.Show("两次输入的密码不一样!", "提示", MessageBoxButtons.OK,
MessageBoxIcon.Information);
txtPwdAgain.Focus();
return;
}
int myQQNum = 0; //QQ号码
string message; //弹出的消息
string sex = rbtnMale.Checked ? rbtnMale.Text : rbtnFemale.Text; //获得选中的性别
string sql = string.Format("insert into tb_User (Pwd, NickName, Sex, Age, Name, Star, BloodType) values('{0}', '{1}', '{2}',{3},'{4}','{5}','{6}'); select @@Identity from tb_User",txtPwd.Text.Trim(), txtNickName.Text.Trim(), sex, int.Parse(txtAge.Text.Trim()),
txtName.Text.Trim(), cboxStar.Text, cboxBloodType.Text);
SqlCommand command = new SqlCommand(sql, DataOperator.connection);//指定要执行的SQL语句
DataOperator.connection.Open(); //打开数据库连接
int result = command.ExecuteNonQuery(); //执行SQL语句
if (result == 1) //判断是否成功
{
sql = "select SCOPE_IDENTITY() from tb_User"; //查询新增加的记录的标识号
command = new SqlCommand(sql, DataOperator.connection); //执行查询
myQQNum = Convert.ToInt32(command.ExecuteScalar()); //获取最新增加的账号
message = string.Format("注册成功!你的MyQQ号码是" + myQQNum);
}
else
{
message = "注册失败,请重试!";
}
DataOperator.connection.Close(); //关闭数据库连接
MessageBox.Show(message, "注册结果", MessageBoxButtons.OK, MessageBoxIcon.Information);
this.Close(); //关闭当前窗体
}
private void btnCancel_Click(object sender, EventArgs e)
{
this.Close();
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
5、Frm_Main
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Data.SqlClient;
using System.Media;
namespace SunTalk
{
public partial class Frm_Main : Form
{
public Frm_Main()
{
InitializeComponent();
}
int fromUserID; //消息发送者
int friendHeadID; //发消息好友的头像ID
int messageImageIndex = 0; //工具栏中的消息图标的索引
public static string nickName = ""; //自己的昵称
public static string strFlag = "[离线]";
DataOperator dataOper = new DataOperator(); //创建数据操作类的对象
public void ShowInfo()
{
int headID = 0; //头像索引
//获取当前用户的昵称、头像
string sql = "select NickName, HeadID,Sign from tb_User where ID=" + PublicClass.loginID
+ "";
SqlDataReader dataReader = dataOper.GetDataReader(sql); //执行查询操作
if (dataReader.Read()) //读取查询结果
{
if (!(dataReader["NickName"] is DBNull)) //判断NickName不为空
{
nickName = dataReader["NickName"].ToString(); //记录自己的昵称
}
headID = Convert.ToInt32(dataReader["HeadID"]); //记录自己的头像ID
txtSign.Text = dataReader["Sign"].ToString(); //显示个性签名
}
dataReader.Close(); //关闭读取器
DataOperator.connection.Close(); //关闭数据库连接
this.Text = PublicClass.loginID.ToString(); //设置窗体标题为当前用户账号
pboxHead.Image = imglistHead.Images[headID]; //显示用户头像
lblName.Text = nickName + "(" + PublicClass.loginID + ")"; //显示昵称及账号
}
private void ShowFriendList()
{
lvFriend.Items.Clear(); //清空原来的列表
//定义查找好友的SQL语句
string sql = "select FriendID,NickName,HeadID,Flag from tb_User,tb_Friend where tb_Friend.HostID = " + PublicClass.loginID + " and tb_User.ID = tb_Friend.FriendID";
SqlDataReader dataReader = dataOper.GetDataReader(sql); //执行查询
int i = lvFriend.Items.Count; //记录添加到ListView中的项索引
while (dataReader.Read()) //循环添加好友列表
{
if (dataReader["Flag"].ToString() == "0")
strFlag = "[离线]";
else
strFlag = "[在线]";
string strTemp = dataReader["NickName"].ToString(); //记录好友昵称
//对好友昵称进行处理
string strFriendName = strTemp;
if (strTemp.Length < 9)
strFriendName = strTemp.PadLeft(9, ' ');
else
strFriendName = (strTemp.Substring(0, 2) + "...").PadLeft(9, ' ');
//向ListView中添加项,Name:好友ID,值:昵称,要显示的头像
lvFriend.Items.Add(dataReader["FriendID"].ToString(), strFriendName + strFlag,
(int)dataReader["HeadID"]);
lvFriend.Items[i].Group = lvFriend.Groups[0]; //设置项的分组为我的好友
i++; //临时变量加1
}
dataReader.Close(); //关闭读取器
DataOperator.connection.Close(); //关闭数据库连接
}
private void Frm_Main_Load(object sender, EventArgs e)
{
tsbtnMessageReading.Image = imglistMessage.Images[0]; //工具栏的消息图标
ShowInfo(); //显示个人信息
ShowFriendList();
}
private void tsbtnInfo_Click(object sender, EventArgs e)
{
Frm_EditInfo frmInfo = new Frm_EditInfo(); //创建个人信息窗体对象
frmInfo.Show();
}
private void tsbtnSearchFriend_Click(object sender, EventArgs e)
{
Frm_AddFriend frmAddFriend = new Frm_AddFriend(); //创建查找好友窗体对象
frmAddFriend.Show();
}
private void tsbtnUpdateFriendList_Click(object sender, EventArgs e)
{
ShowFriendList();
}
private void tsbtnMessageReading_Click(object sender, EventArgs e)
{
tmAddFriend.Stop(); //停止消息提醒定时器
messageImageIndex = 0; //头像恢复正常
//显示正常的系统消息提醒图标
tsbtnMessageReading.Image = imglistMessage.Images[messageImageIndex];
Frm_Remind frmRemind = new Frm_Remind(); //创建系统消息窗体对象
frmRemind.Show();
}
private void tsbtnExit_Click(object sender, EventArgs e)
{
DialogResult result = MessageBox.Show("确实要退出吗?", "确认", MessageBoxButtons.YesNo,
MessageBoxIcon.Question); //弹出确定对话框
if (result == DialogResult.Yes) //如果单击是按钮
{
Application.ExitThread(); //退出当前程序
}
}
Frm_Chat frmChat;//聊天窗体对象
//双击打开聊天窗体
private void lvFriend_MouseDoubleClick(object sender, MouseEventArgs e)
{
if (lvFriend.SelectedItems.Count > 0)//判断是否有选中项
{
if (frmChat == null)//判断聊天窗体对象是否为空
{
frmChat = new Frm_Chat();//创建聊天窗体对象
//记录聊天的账号
frmChat.friendID = Convert.ToInt32(lvFriend.SelectedItems[0].Name);
frmChat.nickName = dataOper.GetDataSet("select NickName from tb_User where ID=" + frmChat.friendID).Tables[0].Rows[0][0].ToString();//记录昵称
frmChat.headID = Convert.ToInt32(dataOper.GetDataSet("select HeadID from tb_User where ID=" + frmChat.friendID).Tables[0].Rows[0][0]) + 1;//记录头像ID
frmChat.ShowDialog();//以对话框显示聊天窗体对象
frmChat = null;//将聊天窗体对象设置为空
}
if (tmChat.Enabled == true)//如果聊天定时器处于可用状态
{
tmChat.Stop();//停止聊天定时器
lvFriend.SelectedItems[0].ImageIndex = friendHeadID;//将选中项的头像显示为正常状态
}
}
}
private bool HasShowUser(int ID)
{
//是否在当前显示出的用户列表中找到了该用户
bool find = false;
//循环lvFriend中的2个组,寻找发消息的人是否在列表中
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < lvFriend.Groups[i].Items.Count; j++)
{
if (Convert.ToInt32(lvFriend.Groups[i].Items[j].Name) == ID)
{
find = true;
}
}
}
return find;
}
private void UpdateStranger(int ID)
{
lvFriend.Items.Clear(); //清空原来的列表
//获取指定用户的昵称及头像ID
string sql = "select NickName, HeadID from tb_User where ID=" + ID;
SqlDataReader dataReader = dataOper.GetDataReader(sql); //执行查询
int i = lvFriend.Items.Count; //记录添加到ListView中的项索引
while (dataReader.Read()) //循环添加陌生人列表
{
string strTemp = dataReader["NickName"].ToString(); //记录好友昵称
//对好友昵称进行处理
string strName = strTemp;
if (strTemp.Length < 9)
strName = strTemp.PadLeft(9, ' ');
else
strName = (strTemp.Substring(0, 2) + "...").PadLeft(9, ' ');
//向ListView中添加项,Name:用户ID,值:昵称,要显示的头像
lvFriend.Items.Add(fromUserID.ToString(), strName, (int)dataReader["HeadID"]);
lvFriend.Items[i].Group = lvFriend.Groups[1]; //设置项的分组为陌生人
i++; //临时变量加1
}
dataReader.Close(); //关闭读取器
DataOperator.connection.Close(); //关闭数据库连接
}
private void tmMessage_Tick(object sender, EventArgs e)
{
if (lvFriend.SelectedItems.Count > 0) //判断好友列表中有选中项
{
if (lvFriend.SelectedItems[0].Group == lvFriend.Groups[0])//如果选中项属于第1组
{
tsMenuDel.Visible = true; //显示删除菜单
tsMenuAdd.Visible = false; //隐藏添加好友菜单
}
//如果选中项属于第2组
else if (lvFriend.SelectedItems[0].Group == lvFriend.Groups[1])
{
tsMenuDel.Visible = false; //隐藏删除菜单
tsMenuAdd.Visible = true; //显示添加好友菜单
}
}
int messageTypeID = 1; //消息类型
int messageState = 1; //消息状态
string sql = "select top 1 FromUserID, MessageTypeID, MessageState from tb_Message where ToUserID = " + PublicClass.loginID + " and MessageState = 0"; //查找未读消息对应的好友ID
SqlDataReader dataReader = dataOper.GetDataReader(sql); //执行查询
if (dataReader.Read()) //读取未读消息
{
fromUserID = (int)dataReader["FromUserID"]; //记录消息发送者
messageTypeID = (int)dataReader["MessageTypeID"]; //记录消息类型
messageState = (int)dataReader["MessageState"]; //记录消息状态
}
dataReader.Close(); //关闭读取器
DataOperator.connection.Close(); //关闭数据库连接
//消息有两种类型:聊天消息、添加好友消息
//判断消息类型,如果是添加好友消息,启动消息提醒定时器
if (messageTypeID == 2 && messageState == 0)
{
SoundPlayer player = new SoundPlayer("system.wav"); //系统消息提示
player.Play(); //播放指定声音文件
tmAddFriend.Start(); //启动消息提醒定时器
}
//如果是聊天消息,启动聊天定时器,使好友头像闪烁
else if (messageTypeID == 1 && messageState == 0)
{
sql = "select HeadID from tb_User where ID=" + fromUserID;//获取消息发送者的ID
friendHeadID = dataOper.ExecSQL(sql); //设置发消息好友的头像ID
//如果发消息的人不在好友列表中,将其添加到陌生人列表中
if (!HasShowUser(fromUserID))
{
UpdateStranger(fromUserID); //显示陌生人列表
}
SoundPlayer player = new SoundPlayer("msg.wav"); //聊天消息提示
player.Play(); //播放指定声音文件
tmChat.Start(); //启动聊天定时器
}
}
private void tmAddFriend_Tick(object sender, EventArgs e)
{
messageImageIndex = messageImageIndex == 0 ? 1 : 0; //实时获取系统消息图像索引
//工具栏中显示消息读取状态图像
tsbtnMessageReading.Image = imglistMessage.Images[messageImageIndex];
}
private void tmChat_Tick(object sender, EventArgs e)
{
//循环好友列表两个组中的每项,找到消息发送者,使其头像闪烁
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < lvFriend.Groups[i].Items.Count; j++)
{
//判断是否为消息发送者
if (Convert.ToInt32(lvFriend.Groups[i].Items[j].Name) == fromUserID)
{
if (frmChat != null && frmChat.friendID != 0)
{
//直接显示头像,避免闪烁效果
lvFriend.SelectedItems[0].ImageIndex = friendHeadID;
}
else
{
if (lvFriend.Groups[i].Items[j].ImageIndex < 100)
{
//索引为100的图片是一个空白图片,为了实现闪烁效果
lvFriend.Groups[i].Items[j].ImageIndex = 100;
}
else
{
//要显示的消息发送者头像索引
lvFriend.Groups[i].Items[j].ImageIndex = friendHeadID;
}
}
}
}
}
}
private void pboxMin_Click(object sender, EventArgs e)
{
this.WindowState = FormWindowState.Minimized;
}
private void pboxClose_Click(object sender, EventArgs e)
{
Application.ExitThread();
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 251
- 252
- 253
- 254
- 255
- 256
- 257
- 258
- 259
- 260
- 261
- 262
- 263
- 264
- 265
- 266
- 267
- 268
- 269
- 270
- 271
- 272
- 273
- 274
- 275
- 276
- 277
- 278
- 279
- 280
- 281
- 282
- 283
- 284
- 285
- 286
- 287
- 288
- 289
- 290
- 291
- 292
- 293
6、Frm_Chat
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Data.SqlClient;
namespace SunTalk
{
public partial class Frm_Chat : Form
{
public Frm_Chat()
{
InitializeComponent();
}
public int friendID = 0; //当前聊天的好友号码
public string nickName; //当前聊天的好友昵称
public int headID; //当前聊天的好友头像ID
DataOperator dataOper = new DataOperator(); //创建数据操作类的对象
private void Frm_Chat_Load(object sender, EventArgs e)
{
this.Text = "与\"" + nickName + "\"聊天中"; //设置窗体标题
pboxHead.Image = imglistHead.Images[headID]; //设置好友头像
lblFriend.Text = string.Format("{0}({1})", nickName, friendID); //设置好友名称
rtxtMessage.ScrollToCaret(); //滚动条总在最下方
}
private void SetMessage(string messageID)
{
string[] messageIDs = messageID.Split('_'); //分割出每个消息ID
string sql = "update tb_Message set MessageState=1 where ID="; //定义更新SQL语句
foreach (string id in messageIDs) //遍历所有消息ID
{
if (id != "")
{
sql += id; //设置更新条件
int result = dataOper.ExecSQLResult(sql); //执行数据表更新操作
}
}
}
private void ShowMessage()
{
string messageID = ""; //消息ID组成的字符串
string message; //消息内容
string messageTime; //消息发送时间
//读取消息的SQL语句
string sql = "select ID,Message,MessageTime from tb_Message where FromUserID=" + friendID
+ " and ToUserID=" + PublicClass.loginID + " and MessageTypeID=1 and MessageState=0";
SqlDataReader datareader = dataOper.GetDataReader(sql);
//循环将消息添加到窗体上
while (datareader.Read())
{
messageID += datareader["ID"] + "_"; //消息ID
message = datareader["Message"].ToString(); //消息
//消息的发送时间
messageTime = Convert.ToDateTime(datareader["MessageTime"]).ToString();
//设置消息显示格式
rtxtMessage.Text += "\n" + nickName + " " + messageTime + "\n " + message + "";
}
DataOperator.connection.Close(); //关闭数据库连接
if (messageID.Length > 1) //判断是否存在消息
{
messageID.Remove(messageID.Length - 1); //去掉最后的连接符
SetMessage(messageID); //将显示的消息设置为已读
}
}
private void tmShowMessage_Tick(object sender, EventArgs e)
{
ShowMessage();
}
private void btnSend_Click(object sender, EventArgs e)
{
if (rtxtChat.Text.Trim() == "") //不能发送空消息
{
MessageBox.Show("不能发送空消息!", "提示", MessageBoxButtons.OK,
MessageBoxIcon.Information);
return;
}
else //发送消息
{
//此处的MessageTypeId为1,表示聊天消息;MessageState为0,表示消息未读
string sql = string.Format(
"INSERT INTO tb_Message (FromUserID, ToUserID, Message, MessageTypeID, MessageState) VALUES({0},{1},'{2}',{3},{4})",PublicClass.loginID, friendID, rtxtChat.Text,
1, 0);
int result = dataOper.ExecSQLResult(sql); //调用方法实现消息插入操作
rtxtMessage.Text += "\n" + Frm_Main.nickName + " " + DateTime.Now + "\n " +
rtxtChat.Text + "";
if (result != 1) //如果返回结果不是1,表示没有发送成功
{
MessageBox.Show("消息发送失败,请重新发送!", "提示", MessageBoxButtons.OK,
MessageBoxIcon.Information);
}
rtxtChat.Text = ""; //清空消息
rtxtChat.Focus(); //定位鼠标输入焦点
}
}
private void rtxtChat_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyValue == 13) //当同时按下Ctrl和Enter时,发送消息
{
e.Handled = true;
btnSend_Click(this, null); //发送消息
}
}
private void pboxInfo_Click(object sender, EventArgs e)
{
rtxtMessage.Clear(); //清空聊天信息显示窗口
string messageID = ""; //消息ID组成的字符串
string message; //消息内容
string messageTime; //消息发送时间
string sql = "select ID,NickName,Message,MessageTime from v_Message where (FromUserID="
+ friendID + " and ToUserID=" + PublicClass.loginID + ") or (FromUserID=" + PublicClass.loginID
+ " and ToUserID=" + friendID + ") order by MessageTime asc ";//读取消息的SQL语句
SqlDataReader datareader = dataOper.GetDataReader(sql);
while (datareader.Read()) //循环将消息添加到窗体上
{
messageID += datareader["ID"] + "_"; //消息ID
message = datareader["Message"].ToString(); //消息
//消息的发送时间
messageTime = Convert.ToDateTime(datareader["MessageTime"]).ToString();
rtxtMessage.Text += "\n" + datareader["NickName"] + " " + messageTime + "\n " + message
+ ""; //设置消息显示格式
}
DataOperator.connection.Close(); //关闭数据库连接
}
private void pboxMin_Click(object sender, EventArgs e)
{
this.WindowState = FormWindowState.Minimized;
}
private void pboxClose_Click(object sender, EventArgs e)
{
this.Close();
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
7、其他
还有一些空窗体,项目自带的程序等。
十四、后记
终于写好了,也算是跟着书本做了下来,算是有些收货,但是对于某些实现代码还需多多分析才能掌握。
本博客目的只是记录一下练习过程,没有书本上写的那么详细,本程序并不完美,可以说很不完美,但我们正不是因为不完美才不断学习的吗,这是我们的动力。
文章中可能会存在少许错误,还望各位批评指正!