短信设备软件实现篇短信收发
分享短信设备软件终于完成,虽然不很完善,但已可以完成所需的大多功能。在软件完成期间有很多的支持,在这里感谢大家的支持,谢谢大家。
运行主界面:
界面实现:Form1,主界面 上方pictureBox控件,下方用splitContainer控件分为两个部分,左边嵌套菜单窗体,右边嵌套对应每个菜单项要显示的窗体;Form2,菜单窗体;其他Form,对应菜单项窗体。
Form1:设置MaxmizeBox属性为False,使最大化按钮无效;设置AutoSizeMode属性为GrowAndShrink,不能手动调整窗体的大小;设置Start Position属性为CenterScreen。
构造函数完成窗体的嵌套
public Form1() { InitializeComponent(); //菜单窗体 f2 = new Form2(); f2.TopLevel = false; f2.FormBorderStyle = FormBorderStyle.None; f2.Parent = this.splitContainer1.Panel1; f2.Show(); f2.F2Event += new Form2.F2EventHandler(f2_F2Event); //绑定Form2事件 //首页窗体 f3 = new Form3(); f3.TopLevel = false; f3.FormBorderStyle = FormBorderStyle.None; f3.Parent = this.splitContainer1.Panel2; f3.Show(); f3.BringToFront(); //发送短信窗体 f4 = new Form4(); f4.TopLevel = false; f4.FormBorderStyle = FormBorderStyle.None; f4.Parent = this.splitContainer1.Panel2; f4.Show(); //读取短信窗体 f5 = new Form5(); f5.TopLevel = false; f5.FormBorderStyle = FormBorderStyle.None; f5.Parent = this.splitContainer1.Panel2; f5.Show(); //设备管理窗体 f6 = new Form6(); f6.TopLevel = false; f6.FormBorderStyle = FormBorderStyle.None; f6.Parent = this.splitContainer1.Panel2; f6.Show(); //帮助窗体 f7 = new Form7(); f7.TopLevel = false; f7.FormBorderStyle = FormBorderStyle.None; f7.Parent = this.splitContainer1.Panel2; f7.Show(); }
Form2:菜单窗体,仅有6个菜单按钮,单击时引发相应事件(退出按钮除外,退出按钮单击后只运行Application.Exit();即退出程序)
private void button_Click(object sender, EventArgs e) { GSMEventArgs ge = new GSMEventArgs(); ge.Message = ((Button)sender).Text; if (F2Event != null) { F2Event(this, ge); } }
ge传递按钮信息到主窗体,由主窗体处理相关信息
void f2_F2Event(object sender, GSMEventArgs e) { switch (e.Message) { case "首页": f3.BringToFront(); //首页窗体移至最前 break; case "发送短信": f4.BringToFront(); //发送短信窗体移至最前 break; case "读取短信": f5.BringToFront(); //读取短信窗体移至最前 break; case "设备管理": f6.BringToFront(); //设备管理窗体移至最前 break; case "帮助": f7.BringToFront(); //帮助窗体移至最前 break; default: break; } }
事件F2Event对应代码
//委托 public delegate void F2EventHandler(object sender, GSMEventArgs e); //事件 public event F2EventHandler F2Event;
类GSMEventArgs 代码(位于Form1代码的下面)
public class GSMEventArgs : EventArgs { public string Message; }
类 GSMEventArgs用于在不同窗体的事件传递有关信息
然后每个窗体上放上对应控件,界面设计完成!
首页和退出功能上面已经完成,下面不再讲解
设备类实例化:使用设备类前要添加对GSMMODEM.Dll的引用并:
using GSMMODEM;
设备类完成短信的发送、接收、和读取;要允许多个窗体访问;C#中没有全局变量的概念,所有变量都必须在类中定义;所以本程序采用了一个替代方案
在类Program(位于文件Program.cs中)中定义并实例化静态设备对象,代码如下
static class Program { public static GSMModem gm = new GSMModem(); //设备类,全局变量,方便不同窗体使用 添加代码,定义并实例化设备类 /// <summary> /// 应用程序的主入口点。 /// </summary> [STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new Form1()); } }
这样,在所有窗体中均可访问这个设备对象。访问方式:Program.gm
设备管理:设备管理目前只支持单个猫的控制,需要可以自己添加。
设备管理完成设备端口、波特率设置及设备的打开与关闭。Form6为设备管理串口,可以完成设备的设置与管理,设置波特率和串口号,及是否自动打开设备
构造函数添加控件初始化代码:
//初始化控件comboBox1 foreach (string s in SerialPort.GetPortNames()) { comboBox1.Items.Add(s); } if (comboBox1.Items.Count > 0) { //comboBox1.SelectedIndex = 0; } else { label3.Text = "未检测到串口"; } //初始化控件comboBox2 //comboBox2.SelectedIndex = 0;
串口号用静态方法SerialPort.GetPortNames()扫描得到,是当前电脑注册表里的串口号。
设置文件位于Properties文件夹下,双击Settings.settings文件进入设置的管理窗口,添加ComPort、BaudRate、AutoOpen属性,类型分别为:string、string、bool;用于程序设置;
private void Form6_Load(object sender, EventArgs e) { comboBox1.SelectedText = Properties.Settings.Default.ComPort; //读取设置 comboBox2.SelectedText = Properties.Settings.Default.BaudRate; checkBox1.Checked = Properties.Settings.Default.AutoOpen; //设备属性初始化 Program.gm.ComPort = Properties.Settings.Default.ComPort; Program.gm.BaudRate = Convert.ToInt32(Properties.Settings.Default.BaudRate); Program.gm.AutoDelMsg = true; if (Properties.Settings.Default.AutoOpen) //根据设置决定是否打开设备 { button1_Click(sender, e); //调用button1的事件,完成设备的打开 } }
窗体加载时写入上次程序关闭时的(设置文件中的)端口号、波特率、是否自动打开设备;若自动打开为true则打开设备
private void button1_Click(object sender, EventArgs e) { if (Program.gm.IsOpen == false) { try { Program.gm.Open(); label3.Text = "打开成功"; label3.ForeColor = Color.Green; button1.Text = "关闭设备"; } catch { label3.Text = "打开失败"; label3.ForeColor = Color.Red; } } else { try { Program.gm.Close(); label3.Text = "关闭成功"; label3.ForeColor = Color.Red; button1.Text = "打开设备"; } catch { label3.Text = "关闭失败"; label3.ForeColor = Color.Yellow; } } }
button1_Click事件完成设备的打开、关闭。
当comboBox1、comboBox2、checkBox1选项改变时,写入设置并保存设置
private void checkBox1_CheckedChanged(object sender, EventArgs e) { Properties.Settings.Default.AutoOpen = checkBox1.Checked; //更改设置文件 Properties.Settings.Default.Save(); //保存设置文件 }
三个事件的代码一致,其他两个仅多出了设备类属性的改变,这里就不贴出来了,详见工程项目文件的源代码
发送短信:窗体Form4完成短信的发送。
发送短信 目标号码部分:用listview控件实现,添加按钮完成单个号码的添加
private void button6_Click(object sender, EventArgs e) { Form9 adNum = new Form9(); adNum.Owner = this; //事件 传值 adNum.AddNumEvent += new Form9.AddNumEventHandler(adNum_AddNumEvent); adNum.ShowDialog(); }
Form9是添加号码窗体 通过事件AddNumEvent完成号码等信息的传递
void adNum_AddNumEvent(object sender, GSMEventArgs e) { string[] s = e.Message.Split(','); listView2.Items.Add(s[1]); listView2.Items [listView2.Items.Count - 1].SubItems.Add(s[0]); listView2.Items[listView2.Items.Count - 1].SubItems.Add(s[2]); }
Form9中对应代码:
private void button1_Click(object sender, EventArgs e) { if (Regex.IsMatch(textBox2.Text, @"^(1[3|5|8][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9])[ DISCUZ_CODE_13 ]quot;)) { //引发事件 if (AddNumEvent != null) { GSMEventArgs ge = new GSMEventArgs(); ge.Message = textBox1.Text + "," + textBox2.Text + "," + textBox3.Text; AddNumEvent(this, ge); } this.Close(); } else { MessageBox.Show("手机号码不正确"); } }
事件的定义:
//委托 public delegate void AddNumEventHandler(object sender, GSMEventArgs e); //事件 public event AddNumEventHandler AddNumEvent;
这样即完成了listview的号码添加。
删除号码:
private void button7_Click(object sender, EventArgs e) { ListView.SelectedIndexCollection numToRemove = listView2.SelectedIndices; try { int j = 0; foreach (int i in numToRemove) { listView2.Items.RemoveAt(i - j); j = j + 1; } } catch (Exception ex) { MessageBox.Show(ex.Message); } }
这是从listview控件中删除对应条目,完成手机号码删除。
另外还有号码导入功能,完成多个号码的添加,目前只能从excel中导入;而且需要满足格式要求。代码如下:
private void button8_Click(object sender, EventArgs e) { //xPath指示excel文件路径 string xPath = null; OpenFileDialog openFileDialog1 = new OpenFileDialog(); openFileDialog1.Filter = "Excel Files(*.xls)|*.xls"; openFileDialog1.Title = "导入手机号码"; listView2.SelectedItems.Clear(); // Show the Dialog. // If the user clicked OK in the dialog and // a .CUR file was selected, open it. if (openFileDialog1.ShowDialog() == DialogResult.OK) { xPath = openFileDialog1.FileName; string strCon; strCon = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + xPath + ";Extended Properties='Excel 8.0;HDR=False;IMEX=1'"; OleDbConnection OleConn = new OleDbConnection(strCon); string sql = "SELECT * FROM [Sheet1$]"; DataSet ds = new DataSet(); try { OleConn.Open(); OleDbDataAdapter da = new OleDbDataAdapter(sql, strCon); da.Fill(ds, "sheet1"); OleConn.Close(); } catch { MessageBox.Show("无法打开!请关闭其他打开此文件的程序后再试"); } DataTable dt = ds.Tables[0]; try { foreach (DataRow row in dt.Rows) { string str = row["手机号码"].ToString(); if (Regex.IsMatch(str, @"^(1[3|5|8][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9])[ DISCUZ_CODE_16 ]quot;)) { ListViewItem lvi = new ListViewItem(str); lvi.SubItems.Add(row["姓名"].ToString()); lvi.SubItems.Add(row["备注"].ToString()); listView2.Items.Add(lvi); } else { MessageBox.Show("表格第二列不是手机号码或是有错误号码!\n请检查后再试"); break; } } } catch { MessageBox.Show("程序支持文件为前三列都有内容的xls表格\n第一列“姓名”,第二列“手机号码”,第三列“备注”"); } } }
excel文件第一行前三列内容:第一列“姓名”,第二列“手机号码”,第三列“备注”,这样才能正常导入号码。
发送部分,类 MsgList负责存储要发送的短信及目的号码,对多存储255个号码(同一条短信)
//类 MsgList 每次发送创建对象,开启另一线程 根据此类的数据发送短信 class MsgList { public MsgList(string[] phonelist, string msg) { PhoneList = phonelist; Msg = msg; } //号码列表 public string[] PhoneList; //短信内容 public string Msg; }
下面是发送按钮的函数,首先判断字数及号码是否符合规范,上一条是否发送完成(群发 多个号码一条短信)如果完成,则准备发送:更改进度条和前面标签控件的相应属性,以给用户指示发送的状态。最后创建另外线程发送短信。
private void button9_Click(object sender, EventArgs e) { if (textBox1.Text.Length > 70) { MessageBox.Show("短信字数太长"); return; } else if (textBox1.Text.Length == 0) { MessageBox.Show("不允许发送空短信"); } else if (listView2.Items.Count == 0||listView2.Items.Count>255) { MessageBox.Show("没有号码或是号码超过255个"); } else if (msgList != null) { MessageBox.Show("上一条尚未发送完成!无法发送"); } else { //发送短信时 对应有控件显示 progressBar1.Visible = true; progressBar1.Minimum = 0; progressBar1.Maximum = listView2.Items.Count; progressBar1.Value = 0; label5.Visible = true; label5.Text = "正在发送"; label5.ForeColor = Color.Black; string[] s = new string[255]; //最多255条号码 for (int i = 0; i < listView2.Items.Count; i++) { s[i] = listView2.Items[i].Text; } msgList = new MsgList(s, textBox1.Text); //赋值 SendOneMsg = new SendEventHandler(OnSendOneMsg); //实例化SendOneMsg,以每次发送完一条短信回调OnSendOneMsg函数 Thread threadSendMsg = new Thread(SendMsg); threadSendMsg.Start(); //创建并运行新线程 } }
新建线程对应的函数:
private void SendMsg() { foreach (string s in msgList.PhoneList) { if (s != null && s.Length != 0) { try { Program.gm.SendMsg(s, msgList.Msg); GSMEventArgs ge = new GSMEventArgs(); ge.Message = s; //SendOneMsg(this, ge); Invoke(SendOneMsg,this,ge); } catch { GSMEventArgs ge = new GSMEventArgs(); ge.Message = "发送失败" + s; Invoke(SendOneMsg, this, ge); return; } } else { GSMEventArgs ge = new GSMEventArgs(); ge.Message = "发送完成"; Invoke(SendOneMsg, this, ge); } }
发送一条或是失败、完成均调用SendOneMsg指向的函数OnSendOneMsg
OnSendOneMsg函数:
void OnSendOneMsg(object sender, GSMEventArgs ge) { if (ge.Message.Substring(0, 4) == "发送失败") { label5.Text = "发送失败"; label5.ForeColor = Color.Red; } else if (ge.Message.Substring(0, 4) == "发送完成") { //发送完成,控件隐藏 label5.Visible = false; progressBar1.Visible = false; label5.Text = "发送完成"; label5.ForeColor = Color.Green; msgList = null; } else { progressBar1.Value += 1; } }
判断发送一条,或者失败,或者完成而改变控件属性,通知用户短信发送的当前状态。
//委托 完成窗体的变化 事件 delegate void SendEventHandler(object sender, GSMEventArgs ge); //异步调用 每发送一条调用一次 SendEventHandler SendOneMsg = null;
这是对应委托和回调的声明,两个参数object sender, GSMEventArgs ge传递相关信息。
至此发送窗体部分完成。
读取短信: 读取信息窗体Form5
读取信收到短信: 收到短信,在设备OnRecieved事件中调用ReadNewMsg方法读取信息,回调主线程函数完成相应控件的更改
回调声明和实例化:
//委托 收到短信时回调 delegate void RecievedMsgHandler(string s); RecievedMsgHandler RecievedMsg = null;
在Form5_Load中完成实例化和事件OnRecieved的绑定。
private void Form5_Load(object sender, EventArgs e) { //添加事件,收到短信 Program.gm.OnRecieved += new GSMMODEM.GSMModem.OnRecievedHandler(gm_OnRecieved); //实例化回调函数 RecievedMsg = new RecievedMsgHandler(UpdateCtrols); }
在事件OnRecieved中完成已读短信的保存和回调相应函数:
void gm_OnRecieved(object sender, EventArgs e) { string s = Program.gm.ReadNewMsg(); //把收到短信内容写入文件 using (StreamWriter sw = File.AppendText("已收短信.txt")) { sw.WriteLine(s); sw.Close(); } //回调 Invoke(RecievedMsg, s); }
RecievedMsg指向的方法是UpdateCtrols,其完成listview控件属性更改,在界面显示短信内容。
private void UpdateCtrols(string s) { listView3.Items.Add(s.Split(',')[1]); listView3.Items [listView3.Items.Count - 1].SubItems.Add(s.Split(',')[3]); listView3.Items[listView3.Items.Count - 1].SubItems.Add(s.Split(',')[0]); listView3.Items[listView3.Items.Count - 1].SubItems.Add(s.Split(',')[2]); }
控件listview3添加入手机号码列,短信内容列 和不显示的短信中心及时间字符串(用于详细信息显示)。这样新短息就能正常读取,保存并显示在listview中。
读取设备中已收到,但未读短信:设备打开时可能已有未读信息,这里解决这一问题,设备打开时从设备管理窗体引发一个事件,通知本窗体设备被打开,相关代码如下:
//委托 事件 通知其他窗体,设备打开 public delegate void GSMOpenHandler(object sender, GSMEventArgs ge); public event GSMOpenHandler GSMOpen = null;
委托和事件 用来通知其他窗体设备打开。事件引发代码:
//新线程 事件引发 Thread thread = new Thread(GSMOpened); thread.Start();
这两句代码添加在设备打开的下方,设备打开后即开启另一个线程(事件要读取未读信息,可能要较长时间),引发事件。私有方法GSMOpened代码
//新线程函数 事件引发执行 private void GSMOpened() { if (GSMOpen != null) { GSMOpen(this, new GSMEventArgs("设备成功打开")); } }
引发事件,通知其他窗体。
本窗体相关内容:响应事件,在其中读取并保存未读信息,回调相关函数显示未读信息
void f6_GSMOpen(object sender, GSMEventArgs ge) { string[] strs = null; try { strs = Program.gm.GetUnreadMsg(); if (strs == null) return; } catch(Exception ex) { MessageBox.Show(ex.Message); } foreach (string s in strs) { if (s == null || s.Length == 0) //未读信息已读完 { return; } //把收到短信内容写入文件 using (StreamWriter sw = File.AppendText("已收短信.txt")) { sw.WriteLine(s); sw.Close(); } Invoke(HaveUnreadMsg,s); } }
事件代码:HaveUnreadMsg指向的是UpdateCtrols方法,让listview做出相关显示。
详细信息、回复:详细信息和回复功能相同,显示详细信息窗体(其中包含回复所需控件)
//详细信息 private void button19_Click(object sender, EventArgs e) { if (listView3.SelectedItems.Count == 0) { MessageBox.Show("没有选择项"); return; } Form8 f8 = new Form8(listView3.SelectedItems[0].SubItems[2].Text, listView3.SelectedItems[0].SubItems[3].Text, listView3.SelectedItems[0].SubItems[0].Text, listView3.SelectedItems[0].SubItems[1].Text); f8.TopLevel = false; f8.FormBorderStyle = FormBorderStyle.None; f8.Parent = this.Parent; f8.Show(); f8.BringToFront(); }
Form8即使是详细信息窗体 其通过构造函数完成参数的传递
Form8构造函数
/// <summary> /// 构造函数 传递参数 /// </summary> /// <param name="msgCenter">短信中心</param> /// <param name="time">时间字符串</param> /// <param name="phone">手机号码</param> /// <param name="msg">短信内容</param> public Form8(string msgCenter,string time,string phone,string msg) { InitializeComponent(); // textBox1.Text = msgCenter; textBox2.Text = time.Substring(0, 4) + "-" + time.Substring(4, 2) + "-" + time.Substring(6, 2) + " " + time.Substring(8, 2) + ":" + time.Substring(10, 2) + ":" + time.Substring(12); textBox3.Text = phone; textBox4.Text = msg; }
Form8中的回复按钮
private void button1_Click(object sender, EventArgs e) { if (textBox5.Text == null || textBox5.Text.Length == 0) { MessageBox.Show("不允许发送空短信"); } else if (textBox5.Text.Length > 70) { MessageBox.Show("短信字数过长"); } else { label6.Visible = true; try { Program.gm.SendMsg(textBox3.Text, textBox5.Text); } catch { MessageBox.Show("发送失败"); } label6.Visible = false; this.Close(); } }
仅完成单条信息发送,而且还会阻塞主线程,这个可以做进一步改进。
读取信息部分完成,这一部分完成的质量最不好,希望大家不要介意,这里只为大家提供了一个使用示例。
帮助:帮助部分仅有一个窗体,未加入其他内容,在这里就不做讲解了。
短信猫软件的实现(C#)系列文章到此全部结束了,类库如有错误或是严重Bug还会更新,更新时会更新相应文件并在文章最后予以说明,或是添加其他文章对其说明,关于程序的bug或是不足之处,欢迎拍砖。
程序中肯定有不少错误或是Bug,欢迎大家提出宝贵意见,谢谢大家支持了啊。
附件:
回帖 ( 0 )