前言 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
控件和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
软件业务流程:用户——>软件登录——>是否注册(未注册进入注册页面)——>登录验证——>主窗体——>双击头像——>聊天窗体。
数据库设计 后续功能的实现都需要操作数据库,所以设计数据库是第一要义,设计数据库之前,我们应该知道我们需要怎样的数据,进而需要设计怎样的表格,数据库的设计和程序窗体的设计应该是并行的,但为了后面调用的方便,还是先将数据库的设计好。
创建数据库 右键对象资源管理器
下的数据库
,选择新建,为数据库起个名字,点击确定。
右键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
:(根据背景拖动窗体就可以了)
登录窗体控件填充 下面我们分析下该登录窗体需要哪些控件,这里头像显示部分的功能没有实现,共包含了以下八个控件。
(各控件最重要的部分其实是名字,后续功能实现需要通过名字对其进行调用,控件的其他属性这里只列出部分,可自定义设置,比如字体颜色,字体大小,控件位置,控件背景色等等)
Name
:txtID
BorderStyle
:None
Name
:txtPwd
BorderStyle
:None
Name
:cboxRemember
Text
:记住密码
Name
:cboxAutoLogin
Text
:自动登录
Name
:linklblReg
Text
:注册账号
Name
:pboxLogin
BackColor
:Transparent
Name
:pboxMin
BackColor
:Transparent
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); 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); if (connection.State == ConnectionState.Closed) connection.Open(); int result = command.ExecuteNonQuery(); 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() + "'" ; 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); DataSet ds = new DataSet(); sqlda.Fill(ds); return ds; }
实时检测账号,自动填充密码 当用户设置了记住密码,则在用户输入账号时,对账号进行实时检测,如果在数据库中检测到有匹配记录,则对登录密码自动填充。
此方法需要引用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(); }
账号注册 通过单击注册账号超链接即可打开申请账号窗体,其中包含了必填资料和选填资料。
注册窗体布局设计 注册窗体的设计没有添加背景采用的是纯控件组合。
注册窗体控件填充 字体大小颜色等其他属性没有列出,可自己尝试。
Name
:picLogo
BackColor
:Transparent
Image
:(自定义个图片)
SizeMode
:StretchImage
Name
:grpBaseInfo
BackColor
:Transparent
text
:注册基本资料
Name
:grpDetails
BackColor
:Transparent
text
:详细资料(选填)
Name
:lblNickName
Text
:昵称
Name
:lblAge
Text
:年龄
Name
:lblSex
Text
:性别
Name
:lblPwd
Text
:密码
Name
:lblPwdAgain
Text
:重复密码
Name
:txtNickName
Name
:txtAge
Name
:rbtnMale
Text
:男
Name
:rbtnFemale
Text
:女
Name
:txtPwd
Name
:txtPwdAgain
BackColor
:Transparent
Name
:lblName
Text
:真实姓名
Name
:lblStar
Text
:星座
Name
:lblBloodType
Text
:血型
Name
:txtName
Name
:cboxStar
DropDownStyle
:DropDownlist
Items
:白羊座…
Name
:cboxBloodType
DropDownStyle
:DropDownlist
Items
:A型…
Name
:btnRegister
Text
:注册
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 ; 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); DataOperator.connection.Open(); int result = command.ExecuteNonQuery(); 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属性分别如下。
Name
:tmMessage
Enabled
:True
Interval
:2000
Name
:tmAddFriend
Enabled
:False
Interval
:1000
Name
:tmChat
Enabled
:False
Interval
:500
添加控件
Name
:pboxHead
BackColor
:Transparent
SizeMode
:Zoom
Name
:lblName
Text
:
AutoSize
:True
Name
:txtSign
BorderStyle
:None
Name
:lvFriend
LargeImageList
:imglistHead
Groups
:添加两个分组,Header属性分别为“我的好友”“陌生人”
MultiSelect
:False
SmallImageList
:imglistSmallHead
StateImageList
:imglistSmallHead
Name
:pboxMin
BackColor
:Transparent
Name
:pboxClose
BackColor
:Transparent
设计工具栏 如上图中的5所示,我们需要在主窗体处添加一个工具栏。首先我们向主窗体 中添加toolStrip
控件,并修改其Name
属性为tsOperation
先将控件的toolStrip
的Dock
属性设置为Bottom
。
添加5个Button
按钮,分别设置其属性。
Image
:(自定义)
Text
:个人信息
Image
:(自定义)
Text
:查找
(为了突出查找按钮,可以设置成既显示图片又显示文字,方法是:右键该控件设置DisplayStyle
属性为ImageAndText
)
3、Name
:tsbUpdateFriendList
Image
:(自定义)
Text
:更新好友列表
4、Name
:tsbtnMessageReading
Image
:(自定义)
Text
:系统消息
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; 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); if (connection.State == ConnectionState.Open) connection.Close(); connection.Open(); SqlDataReader datareader = command.ExecuteReader(); return datareader; }
显示用户信息 切换到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 = dataReader["NickName" ].ToString(); } headID = Convert.ToInt32(dataReader["HeadID" ]); 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(); 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; 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 , ' ' ); lvFriend.Items.Add(dataReader["FriendID" ].ToString(), strFriendName + strFlag, (int )dataReader["HeadID" ]); lvFriend.Items[i].Group = lvFriend.Groups[0 ]; i++; } 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; 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 ; 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 ; 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(); string sql = "select NickName, HeadID from tb_User where ID=" + ID; SqlDataReader dataReader = dataOper.GetDataReader(sql); int i = lvFriend.Items.Count; 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 , ' ' ); lvFriend.Items.Add(fromUserID.ToString(), strName, (int )dataReader["HeadID" ]); lvFriend.Items[i].Group = lvFriend.Groups[1 ]; i++; } dataReader.Close(); DataOperator.connection.Close(); }
未读消息提示 触发tmMessage
的Tick
事件,编写如下代码,在显示未读消息的同时,进行消息提示。
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 ]) { tsMenuDel.Visible = true ; tsMenuAdd.Visible = false ; } 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" ; 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; friendHeadID = dataOper.ExecSQL(sql); if (!HasShowUser(fromUserID)) { UpdateStranger(fromUserID); } SoundPlayer player = new SoundPlayer("msg.wav" ); player.Play(); tmChat.Start(); } }
消息提醒 触发tmAddFriend
的Tick
事件,编写如下代码,获取系统消息图像
索引,并显示在工具栏中。
1 2 3 4 5 6 private void tmAddFriend_Tick (object sender, EventArgs e ){ messageImageIndex = messageImageIndex == 0 ? 1 :0 ; tsbtnMessageReading.Image = imglistMessage.Images[messageImageIndex]; }
头像闪动 触发tmChat
的Tick
事件,编写如下代码,实现好友发消息时的头像闪动。
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 ) { lvFriend.Groups[i].Items[j].ImageIndex = 100 ; } else { lvFriend.Groups[i].Items[j].ImageIndex = friendHeadID; } } } } } }
聊天窗体 聊天布局设计 聊天窗体可以有纯控件来做,为了美观和方便,还是添加了一个背景。
聊天控件填充
Name
:pboxHead
BackColor
:Transparent
SizeMode
:Zoom
Name
:lblFriend
Name
:rtxtMessage
ReadOnly
:True
ScrollBars
:Vertical
Name
:rtxtChat
ReadOnly
:True
ScrollBars
:Vertical
Name
:btnClose
text
:关闭
Name
:btnSend
text
:发送
Name
:pboxMin
BackColor
:Transparent
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('_' ); string sql = "update tb_Message set MessageState=1 where ID=" ; foreach (string id in messageIDs) { 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 = "" ; string message; string messageTime; 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" ] + "_" ; 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); } }
显示所有未读消息 触发tmShowMessage
的Tick
事件,添加显示未读聊天消息的方法。
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 { 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 ) { 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 ) { e.Handled = true ; btnSend_Click(this , null ); } }
查看消息记录 触发消息记录图片的pboxInfo
的Click
事件,添加如下代码,查看与当前好友的聊天记录。
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 = "" ; 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 " ; SqlDataReader datareader = dataOper.GetDataReader(sql); while (datareader.Read()) { messageID += datareader["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); 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); if (connection.State == ConnectionState.Closed) connection.Open(); int result = command.ExecuteNonQuery(); connection.Close(); return result; } public DataSet GetDataSet (string sql ) { SqlDataAdapter sqlda = new SqlDataAdapter(sql, connection); DataSet ds = new DataSet(); sqlda.Fill(ds); return ds; } public SqlDataReader GetDataReader (string sql ) { SqlCommand command = new SqlCommand(sql, connection); if (connection.State == ConnectionState.Open) connection.Close(); connection.Open(); SqlDataReader datareader = command.ExecuteReader(); return datareader; } } }
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() + "'" ; 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 ; 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); DataOperator.connection.Open(); int result = command.ExecuteNonQuery(); 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; 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 = dataReader["NickName" ].ToString(); } headID = Convert.ToInt32(dataReader["HeadID" ]); 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(); 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; 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 , ' ' ); lvFriend.Items.Add(dataReader["FriendID" ].ToString(), strFriendName + strFlag, (int )dataReader["HeadID" ]); lvFriend.Items[i].Group = lvFriend.Groups[0 ]; i++; } 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 ; frmChat.ShowDialog(); frmChat = null ; } if (tmChat.Enabled == true ) { tmChat.Stop(); lvFriend.SelectedItems[0 ].ImageIndex = friendHeadID; } } } private bool HasShowUser (int ID ) { bool find = false ; 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(); string sql = "select NickName, HeadID from tb_User where ID=" + ID; SqlDataReader dataReader = dataOper.GetDataReader(sql); int i = lvFriend.Items.Count; 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 , ' ' ); lvFriend.Items.Add(fromUserID.ToString(), strName, (int )dataReader["HeadID" ]); lvFriend.Items[i].Group = lvFriend.Groups[1 ]; i++; } 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 ]) { tsMenuDel.Visible = true ; tsMenuAdd.Visible = false ; } 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" ; 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; friendHeadID = dataOper.ExecSQL(sql); 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 ) { 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; 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('_' ); string sql = "update tb_Message set MessageState=1 where ID=" ; foreach (string id in messageIDs) { if (id != "" ) { sql += id; int result = dataOper.ExecSQLResult(sql); } } } private void ShowMessage ( ) { string messageID = "" ; string message; string messageTime; 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" ] + "_" ; 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 { 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 ) { MessageBox.Show("消息发送失败,请重新发送!" , "提示" , MessageBoxButtons.OK, MessageBoxIcon.Information); } rtxtChat.Text = "" ; rtxtChat.Focus(); } } private void rtxtChat_KeyDown (object sender, KeyEventArgs e ) { if (e.KeyValue == 13 ) { e.Handled = true ; btnSend_Click(this , null ); } } private void pboxInfo_Click (object sender, EventArgs e ) { rtxtMessage.Clear(); string messageID = "" ; 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 " ; SqlDataReader datareader = dataOper.GetDataReader(sql); while (datareader.Read()) { messageID += datareader["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(); } } }
其他 还有一些空窗体,项目自带的程序等。
后记 终于写好了,也算是跟着书本做了下来,算是有些收货,但是对于某些实现代码还需多多分析才能掌握。
本博客目的只是记录一下练习过程,没有书本上写的那么详细,本程序并不完美,可以说很不完美,但我们正不是因为不完美才不断学习的吗,这是我们的动力。
文章中可能会存在少许错误,还望各位批评指正!