前言

  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/

项目介绍

  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控件和ImageList组件的结合使用;
  • 数据库及数据表的建立与管理;
  • 使用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软件业务流程:用户——>软件登录——>是否注册(未注册进入注册页面)——>登录验证——>主窗体——>双击头像——>聊天窗体。

数据库设计

  后续功能的实现都需要操作数据库,所以设计数据库是第一要义,设计数据库之前,我们应该知道我们需要怎样的数据,进而需要设计怎样的表格,数据库的设计和程序窗体的设计应该是并行的,但为了后面调用的方便,还是先将数据库的设计好。

创建数据库

  右键对象资源管理器下的数据库,选择新建,为数据库起个名字,点击确定。

  右键db_SunTalk下的表,选择新建表,表的设计分别如下。

  值得注意的是,以下各表中的ID字段列属性的标识规范(是标识)需要设计成(是),表示增量和标识种子分别为1。

  记得在初次更改标识规范时,出现过无法更改的现象,这时需要在工具下拉菜单的选项中取消阻止保存要求重新创建表的更改复选框的对勾。

数据表设计

tb_User(用户信息表)

tb_Friend(好友信息表)

tb_Message(消息表)

tb_MessageType(消息类型表)

tb_FriendLimit(添加好友条件表)

视图设计

新建查询然后输入以下代码,执行创建消息视图。

1
2
3
4
5
6
7
8
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

创建项目

选择 .NET Framework 框架

  打开visual studio 2019,创建新项目。这里记得使用.NET Framework框架。

以100%缩放比例重启VS

  创建后的界面如下,可能窗口布局会不一样,这没有关系,不过较为显著的是本显示窗体缩放比例已设置为125%,使用100%缩放比例重新启动visual Studio 帮我决定提示,这个要选择使用100%缩放比例重新启动Visual Studio,点击蓝色的超链接即可,否则的话在缩放比例为125%或者150%下设计的窗体,运行调试后控件会错位。

用户登录窗体

登录窗体布局设计

  本次练习参考腾讯QQ的登录页面,窗体设计本身很复杂,需要设计各种图标,不过可以偷个懒,腾讯QQ的界面改改,作为SunTalk窗体的背景,在背景的基础上添加相应控件。

  实现上图效果,首先我们将form1窗体NAME更改成Frm_Login,以便在实现代码中调用。(其他窗体也要相应的更改哦,尤其是名字

  • Backgroundimage:添加登录背景图片
  • FormBorderStyle:None
  • StartPosition:CenterScreen
  • Text:SunTalk登录
  • Size:(根据背景拖动窗体就可以了)

登录窗体控件填充

  下面我们分析下该登录窗体需要哪些控件,这里头像显示部分的功能没有实现,共包含了以下八个控件。

  (各控件最重要的部分其实是名字,后续功能实现需要通过名字对其进行调用,控件的其他属性这里只列出部分,可自定义设置,比如字体颜色,字体大小,控件位置,控件背景色等等)

  • 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

登录窗体功能实现

  选中窗体,在空白区域(无其他控件的地方)双击,或者右键查看代码进入代码编辑区。

便于测试

  为了方便测试登录窗体,实现点击安全登陆按钮可以代开主窗体的操作,我们应该事先在数据库中添加一条用户数据。

  这样我们输入账号即可测试记住密码,打开主窗体等功能。

判断账号密码格式

  首先我们需要编写一个函数ValidateInput来判断用户输入账号和密码的格式问题。

  规则是:账号和密码不应为空,并且账号ID据库里设计的是int类型,最大数据范围是65535。

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
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;
}

账号类型必须是数字类型

  账号类型必须是数字类型,为此,我们需要限定此文本框的输入,选中txtID账号登录文本框,在事件中找到KeyPress事件,双击该事件,进入改事件的代码编辑区。输入以下代码。

  值得注意的是,第一行代码和大括号在我们双击该事件的时候自动生成,我们只需添加大括号内的实现代码。(其余的代码块同理

1
2
3
4
5
6
7
8
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; //取消在控件中显示该字符
}

实现用户登录

  实现用户登录,我们首先需要在数据库中查询是否存在此账户,然后判断账号和密码是否正确,若正确则进入SunTalk主界面。

  为此我们需要建立此程序与数据库之间的连接。

创建DataOperator类

  在项目资源管理器下右键项目文件,在右键菜单中选择添加,为程序添加一个DataOperator类,此类实现了本程序与本地数据库的连接。实现代码如下。

  在DataOperator类的代码编辑区,我们首先引用两个命名空间,这是操作数据库必要的。

1
2
using System.Data;
using System.Data.SqlClient;

连接数据库

  在公共代码编辑区添加如下代码连接到本地数据库,这里的需要更改成自己的数据库账号和密码。

1
2
3
4
//数据库连接字符串
private static string connString = @"Data Source=LAPTOP-KQ506P5I;Database=db_SunTalk;User ID=sa;Pwd=!!!!!!!;";
//数据库连接对象
public static SqlConnection connection = new SqlConnection(connString);

执行查询,返回列

  定义一个ExecSQL方法来查询数据库,并返回查询结果结果中的第一行第一列。

1
2
3
4
5
6
7
8
9
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; //返回结果中的第一行第一列
}

返回结果,返回行数

  定义一个ExecSQLResult方法来查询数据库,并返回受影响的行数。

1
2
3
4
5
6
7
8
9
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; //返回受影响的行数
}

创建Publicclass类

  为程序添加一个Publicclass类,在该类中定义一个静态变量来记录loginID用来记录用户登录账号。

1
public static int loginID;

创建Publicclass类的实例对象

  在Frm_Login的公共变量或方法的代码编辑区创建用户账号的的实例对象。

1
DataOperator dataOper = new DataOperator();

触发安全登录窗体

  选中安全登录控件,双击进入该控件的click点击事件,该事件通过查询tb_User数据表中是否存在相匹配的账户来实现用户登录功能,实现代码如下。

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
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);
}
}
}

添加Frm_Main窗体

  编写完上面的代码,不出意外的话,会报错,原因在这行代码。

1
Frm_Main frmMain = new Frm_Main();				//创建主窗体对象

  是的,在创建主窗体实例对象时,我们应该保证此窗体时真实存在的,于是,我们必须事先添加此窗体。

  在项目资源管理器下右键项目文件,在右键菜单中选择添加,为程序添加一个form窗体,并将改窗体的Name设置成Frm_Main

按下回车键自动登录

  如何实现当账号和密码都输入完成后,不点击安全登录空间,而是按下Enter键便实现登录功能呢?

  我们将txtPwd控件的KeyPress事件和Enter键相关联即可。实现代码如下。

1
2
3
4
5
private void txtPwd_KeyPress(object sender, KeyPressEventArgs e)
{
if (e.KeyChar == '\r') //判断是否按下回车键
pboxLogin_Click(sender, e); //使登录按钮获得鼠标焦点
}

实现记住密码和自动登录功能

自动登录的逻辑

  要实现自动登录,必须先记住密码。为此,需要在记住密码下添加如下逻辑。

  选中cboxRemember控件,双击即可进入CheckedChanged事件代码编辑区编写如下代码。

1
2
3
4
5
private void cboxRemember_CheckedChanged(object sender, EventArgs e)
{
if (!cboxRemember.Checked) //判断记住密码文本框为未选中状态
cboxAutoLogin.Checked = false; //自动登录设置为未选中
}

判断数据表中自动登录字段

  当我们第一次登录软件的时候,如果勾选了记住密码,当我们成功登录后,自动登录的字段值就会从默认的0变成1,提交修改到数据库中。

  当我们第二次登录时,我们需要判断数据表中的自动登录字段,于是我们需要在DataOperator类中添加一个GetDataSet方法

1
2
3
4
5
6
7
public DataSet GetDataSet(string sql)
{
SqlDataAdapter sqlda = new SqlDataAdapter(sql, connection); //指定要执行的SQL语句
DataSet ds = new DataSet(); //创建数据集对象
sqlda.Fill(ds); //填充数据集
return ds; //返回数据集
}

实时检测账号,自动填充密码

  当用户设置了记住密码,则在用户输入账号时,对账号进行实时检测,如果在数据库中检测到有匹配记录,则对登录密码自动填充。

  此方法需要引用System.Data命名空间,于是在程序在上方,需要添加如下代码。

1
using System.Data;

  事件实现代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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); //自动登录
}
}
}
}

打开注册账号窗体

  同打开主界面窗体一样,在我们想要打开账号注册窗体时,我们应保证其事先存在,同添加主窗体一样,添加账号注册窗体,并将该窗体的Name属性更改成Frm_Register

  选中注册账号控件双击,进入此控件的点击事件代码编辑区,添加如下代码,实现注册窗体的打开。

1
2
3
4
5
private void linklblReg_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
Frm_Register frmRegister = new Frm_Register(); //创建申请账号对象
frmRegister.Show(); //显示申请账号窗体
}

窗体最小化

  双击pboxMin控件,编写其点击事件代码,如下。

1
2
3
4
private void pboxMin_Click(object sender, EventArgs e)
{
this.WindowState = FormWindowState.Minimized; //设置窗体最小化
}

关闭应用

  双击pboxClose控件,编写其点击事件代码,如下。

1
2
3
4
private void pboxClose_Click(object sender, EventArgs e)
{
Application.ExitThread(); //退出当前应用程序
}

账号注册

  通过单击注册账号超链接即可打开申请账号窗体,其中包含了必填资料和选填资料。

注册窗体布局设计

  注册窗体的设计没有添加背景采用的是纯控件组合。

注册窗体控件填充

  字体大小颜色等其他属性没有列出,可自己尝试。

  • 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:取消

注册窗体功能实现

星座和血型的默认设置

  将“星座”和“血型”下拉选择框默认选项设置为第一项(索引为0),触发Frm_Register窗体的Load事件,双击窗体空白处进入代码编辑区,编写如下代码即可实现此功设置。

1
2
3
4
private void Frm_Register_Load(object sender, EventArgs e)
{
cboxStar.SelectedIndex = cboxBloodType.SelectedIndex = 0; //设置星座和血型的默认值
}

创建数据库操作类的对象

  实现申请账号功能时,需要像数据库里添加数据,所以需要创建DataOperator对象。

  在Frm_Register的公共变量和方法编辑区编写如下代码。

1
DataOperator dataOper = new DataOperator();

实现账号注册

  触发注册按钮的点击事件,双击注册按钮即可,在事件编辑区编写如下代码该事件首先验证用户输入,如果条件都满足,则将用户输入的信息添加到tb_User表中。并获得新注册的账号。

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
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
private void btnCancel_Click(object sender, EventArgs e)
{
this.Close();
}

主窗体

  主窗体主要显示当前用户的个人信息(头像,昵称,账号、个性签名)好友列表(头像,昵称、是否在线)和快捷工具栏,用户可以通过双击某个好友,与其进行聊天。

主窗体布局设计

  本窗体设计用了背景做了填充,也可以不用。

主窗体控件填充

添加组件

控件是指在窗体上可以看到的对象,而组件则在窗体中看不到。

  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

添加控件

  • 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

设计工具栏

  如上图中的5所示,我们需要在主窗体处添加一个工具栏。首先我们向主窗体 中添加toolStrip控件,并修改其Name属性为tsOperation先将控件的toolStripDock属性设置为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:退出

设计快捷菜单

  首先我们像主窗体中添加ContextImageList控件,将其Name属性修改成cmsFriendKList

  在请在此处输入处添加三个按钮,右键即可设置其属性,后续可在代码中调用。

  • 小头像

    Name:tsmenuView

    Visible:False

  • 添加好友

    Name:tsmenuAdd

    Visible:False

  • 删除

    Name:tsmenuDel

    Visible:False

主窗体功能实现

添加应用及一些必要变量

  在Frm_Main的命名空间引用区域添加如下代码

1
2
3
using System.Data;
using System.Data.SqlClient;
using System.Media;

  在Frm_Main的公共代码编辑区添加如下代码

1
2
3
4
5
6
int fromUserID;									//消息发送者
int friendHeadID; //发消息好友的头像ID
int messageImageIndex = 0; //工具栏中的消息图标的索引
public static string nickName = ""; //自己的昵称
public static string strFlag = "[离线]";
DataOperator dataOper = new DataOperator();

加载用户相关信息

数据库查询

  窗体加载时,从数据库中获取用户的好友信息,因此需要在DataOperator类中添加GetDataReader方法来执行Sql查询。

1
2
3
4
5
6
7
8
9
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
}
显示用户信息

  切换到Frm_Main代码页,在公共变量编辑区添加如下代码来显示用户的头像,昵称,账号等信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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
22
23
24
25
26
27
28
29
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
private void Frm_Main_Load(object sender, EventArgs e)
{
tsbtnMessageReading.Image = imglistMessage.Images[0]; //工具栏的消息图标
ShowInfo(); //显示个人信息
ShowFriendList(); //显示好友列表
}

工具栏按钮功能实现

个人信息
1
2
3
4
5
private void tsbtnInfo_Click(object sender, EventArgs e)
{
Frm_EditInfo frmInfo = new Frm_EditInfo(); //创建个人信息窗体对象
frmInfo.Show(); //显示个人信息窗体
}
查找好友
1
2
3
4
5
private void tsbtnSearchFriend_Click(object sender, EventArgs e)
{
Frm_AddFriend frmAddFriend = new Frm_AddFriend(); //创建查找好友窗体对象
frmAddFriend.Show(); //显示查找好友窗体
}
显示好友列表
1
2
3
4
private void tsbtnUpdateFriendList_Click(object sender, EventArgs e)
{
ShowFriendList(); //显示好友列表
}
显示系统消息
1
2
3
4
5
6
7
8
9
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
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
public int friendID=0;						//当前聊天的好友号码
public string nickName; //当前聊天的好友昵称
public int headID; //当前聊天的好友头像ID
DataOperator dataOper = new DataOperator(); //创建数据操作类的对象

  触发lvFriend控件的MouseDoubleClick事件,编写如下代码,实现双击头像打开聊天窗体功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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;//将选中项的头像显示为正常状态
}
}
}

实时消息提醒及好友头像闪烁

  (这个功能在本次练习中无法进行演示 : ( 可以学习一下实现代码)

判断用户是否在好友列表中

  在Frm_Main代码编辑区添加如下代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
18
19
20
21
22
23
24
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(); //关闭数据库连接
}
未读消息提示

  触发tmMessageTick事件,编写如下代码,在显示未读消息的同时,进行消息提示。

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
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(); //启动聊天定时器
}
}
消息提醒

  触发tmAddFriendTick事件,编写如下代码,获取系统消息图像索引,并显示在工具栏中。

1
2
3
4
5
6
private void tmAddFriend_Tick(object sender, EventArgs e)
{
messageImageIndex = messageImageIndex == 0 ? 1:0; //实时获取系统消息图像索引
//工具栏中显示消息读取状态图像
tsbtnMessageReading.Image = imglistMessage.Images[messageImageIndex];
}
头像闪动

  触发tmChatTick事件,编写如下代码,实现好友发消息时的头像闪动。

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
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、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

聊天功能实现

  需要操作数据库,所以在实现代码编写之前,引用相应命名空间。

1
using System.Data.SqlClient;

显示好友头像及好友信息

  切换到Frm_Main代码编辑区,触发窗体的Load加载时间,添加如下代码,实现显示好友头像和好友名称的功能。

1
2
3
4
5
6
7
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(); //滚动条总在最下方
}

显示未读消息

  切换到Frm_Main窗体公共变量或方法的编辑区,添加如下代码,查询未读聊天消息。

1
2
3
4
5
6
7
8
9
10
11
12
13
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
14
15
16
17
18
19
20
21
22
23
24
25
26
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); //将显示的消息设置为已读
}
}

显示所有未读消息

  触发tmShowMessageTick事件,添加显示未读聊天消息的方法。

1
2
3
4
private void tmShowMessage_Tick(object sender, EventArgs e)
{
ShowMessage(); //读取所有的未读消息,显示在窗体中
}

消息发送

  触发btnSend控件的Click事件,添加如下代码,实现发送消息的功能。

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
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(); //定位鼠标输入焦点
}
}

按下Enter发送消息

  触发rtxtChat控件的keyDown事件,添加如下代码,实现消息发送。

1
2
3
4
5
6
7
8
private void rtxtChat_KeyDown(object sender, KeyEventArgs e)
{
if (e.Control && e.KeyValue == 13) //当同时按下Ctrl和Enter时,发送消息
{
e.Handled = true;
btnSend_Click(this, null); //发送消息
}
}

查看消息记录

  触发消息记录图片的pboxInfoClick事件,添加如下代码,查看与当前好友的聊天记录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;
}
1
2
3
4
private void pboxClose_Click(object sender, EventArgs e)
{
this.Close();
}

完整程序

DataOperator

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
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
}

}
}

PublicClass

1
2
3
4
5
6
7
8
9
10
11
12
13
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SunTalk
{
class PublicClass
{
public static int loginID;
}
}

Frm_Login

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
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();
}
}
}

Frm_Register

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
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();
}
}
}

Frm_Main

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
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();
}
}
}

Frm_Chat

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
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();
}
}
}

其他

  还有一些空窗体,项目自带的程序等。

后记

  终于写好了,也算是跟着书本做了下来,算是有些收货,但是对于某些实现代码还需多多分析才能掌握。

  本博客目的只是记录一下练习过程,没有书本上写的那么详细,本程序并不完美,可以说很不完美,但我们正不是因为不完美才不断学习的吗,这是我们的动力。

  文章中可能会存在少许错误,还望各位批评指正!