【转】WinForm开发心得之上网小管家(含源码下载)

一、目标与目的:

1 .开发一个小软件,从而可以对上网时间进行监控。当满足预先设置的条件时就将网络连接强行挂断。从而减小自制力差的人过度迷信网络,哈。

2 .很想试试开发 Windows 程序,通过这个小程序的开发来熟悉一下 .NET 下的 WinForm 的开发。


二、软件的功能简介:

1 .检测是否连接到互联网,本软件采用的是每隔 5 秒钟检测一次的设计方式,所以,如果有连接的话,就加上 5 秒钟,否则不加。(暂时还不能检测用校园网网关的连接)

2 .计算当月、当天的上网时间,及总共的上网时间。如果当月或当天上网时间超过了您设置的最大值时,软件将强行中断网络,防止您的网络费用超支。

3 .设置每天、每月上网时间的最大值。 您每月或者每天的上网时间将不能超过此值。

4 .可以设置登陆密码。 只有拥有登陆密码的人才能修改软件的设置信息,这样,没有密码的人的上网时间将在本软件的严格限制下。恩,很适合给小孩子使用啊。

5 .热键功能。 这个功能可以使您快速的显示或者隐藏主窗口,或者您设置了不显示托盘图标的话,只有通过热键来呼叫出程序主窗口了。


三、设计;

<1> 其中 MainForm , SettingForm , LoginForm ,是三个窗体类,他们主要是显示界面,调用一些其他类的方法,在 MainForm 中有很多的序列化和反序列化的地方。

<2> RAS 类主要是包装对 RASAPI 的调用方法的类,这个类中的方法于测试网络连接,断开网络连接有关系。将他们放在一起,可以使层次比较清晰。
<3> NativeWIN32 这个类也是关于 API 的调用,他们大部分都与设置热键有关系。
<4> TimeManager 类里面存储了软件的大部分设置信息,包括,是否启用限制上网,每月限制小时数,以及每个月和每天的上网计时的起始时间等信息。另外他还有两个重要的方法, UpDateStartMonth() 和 UpDateStartDay() 。这两个方法用来更新上网的计时的起始时间。
<5> TotalTimeUsed 类包含了每天、每月以及总的上网时间。这个类并没有什么重要的方法。


<6> Authentic 类和登录软件相关,它里面存贮了登陆的用户名和密码。另外还有一个 IsRightUser() 的方法,这个方法用来验证用户名和密码是否正确。

四、开发过程中的问题及解决方法:

对于 Windows 的程序开发,我以前根本没有参与和实践过,这个程序也很早就很想做。因为没有经验,也只好步步为营,一个功能一个功能的做,所以前期的考虑也不是很多。因为很多东西根本不知道怎么实现才好。下面我就说说我的开发历程和所遇到过的困难。

1 .检测网络连接
这个问题是我最先想解决的,一开始,我想用 Ping 来检测,不过后来证明它的性能太差劲了。后来这个问题是在我解决第二个问题的时候给捎带着解决了。这是 Ping 的代码:

string PingCmd(string Ip)
{
Process p = new Process();
p.StartInfo.FileName ="cmd.exe";
// 调用的是 CMD.EXE
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.CreateNoWindow = true;
string pingrst;
p.Start();
p.StandardInput.WriteLine("ping -n 1 "+ Ip);
// ping –n 1 IP
p.StandardInput.WriteLine("exit");
string strRst = p.StandardOutput.ReadToEnd();
//取得输出结果
if(strRst.IndexOf("(0% loss)")!=-1)
pingrst = " 连接 ";
else
pingrst =” 无法连接 ”;
p.Close();
return pingrst;
}

2 .用编程方式强行中断网络连接
一开始,我以为这个问题不会很难,顶多调用 Windows API 不就完事了。后来我查了一天的资料才搞定。中间还有一次误入歧途,甚是郁闷啊。最后我是在 VCHome 的网站上找到了一点线索。知道了是用 RASAPI32 中的函数。他们分别是 RasEnumConnections (枚举网络连接)、 RasGetConnectStatus (得到连接状态)、 RasHangUp (断开网络连接)。在写平台调用的时候出现了更大的麻烦。因为必须用 .NET 中的类型来对应 API 中使用的类型,尤其是结构,要在代码中从新定义。可能只有参考 MSDN 才能说清楚这件事情。如果你实在定义不出来,也可以参考一下网上别人的定义。 http://www.webtropy.com/ ,这个网站上又很多 API 在 .NET 下的引用方法。
不过,基本的有几点要注意:
<1>. HWND 对应 IntPtr
<2>. 如果想用与 API 不同的函数名称应该在 Dllimport 属性中注明程序入口点
<3>. 所要用的结构要从新定义在工程里,其中结构中的定长数组要用 MarshalAs 属性封送为 UnmanagedType.ByValTStr 类型,还要注明 SizeCount 的大小。另外如果是缓冲区的话,就用 StringBuilder 封送。但也要注明大小。不要忘了大小要加 1 。
这样的话,上面提到的三个函数在 C# 中的原型也就是如下面所示

[DllImport("RASAPI32", SetLastError= true ,
CharSet=CharSet.Auto)]
public static extern int RasEnumConnections(
[In, Out] RASCONN[] lprasconn,
ref int lpcb,
ref int lpcConnections);
[DllImport("RASAPI32", SetLastError= true,
CharSet=CharSet.Auto)]
public static extern int RasGetConnectStatus(
IntPtr hrasconn,
ref RASCONNSTATUS lprasconnstatus);
[DllImport("RASAPI32",SetLastError= true ,
CharSet=CharSet.Auto)]
public static extern int RasHangUp
(IntPtr hrasconn);

下面是一个结构的定义:注意它里面的定长数组的定义。

[StructLayout(LayoutKind.Sequential,CharSet=CharSet.Auto)]
public struct RASCONN
{
public int dwSize;
public IntPtr hrasconn;
[MarshalAs(UnmanagedType.ByValTStr,
SizeConst=RAS_MaxEntryName+1)]
public string szEntryName;
[MarshalAs(UnmanagedType.ByValTStr,
SizeConst=RAS_MaxDeviceType+1)]
public string szDeviceType;
[MarshalAs(UnmanagedType.ByValTStr,
SizeConst=RAS_MaxDeviceName+1)]
public string szDeviceName;
[MarshalAs(UnmanagedType.ByValTStr,
SizeConst=MAX_PATH)]
public string szPhonebook;
public int dwSubEntry;
}

话说回来上次的第一个没解决的问题只要调用以下 RasEnumConnections ,然后处理一下结果就行了。需要什么连接信息都可以从结构中获得。定义了原型以后,我们就可以在想需要他们的地方随意调用了。反正他们也是静态的函数。后面就是 .NET 平台自己去查找 API 函数并将他们载入到内存中。并将参数压入堆栈。这些都是自动的,我们不用去管。不过,平台调用成功与否与原型定义和数据封送有很大的关系。

3. 保存运行时状态(使用 .NET 的序列化功能)
对于保存运行时的状态参数的问题,我的一个直接的想法是保存到文本文件中,事实证明,这种方法对于保存数量较少的状态参量还可以,否则,要进行复杂的字符串操作。后来,我又想到了用比较先进的 XML 技术。因为自己对 XML 并不很熟悉,并且还要写一些 XML 文件。觉得不是很爽。最后突然想到了某次技术交流的时候,师哥们猛吹 .NET 的序列化是如何如何的方便。于是也就决定用序列化了。
.NET 提供的序列化真的很方便,它的步骤很简单。
最简单的办法就是用 [Serialization] 来标注你要序列化的对象的类。使用 NonSerialized 属性来标注你不想序列化的变量。

[Serializable]
public class TotalTimeUsed
{
private int useNetTime;
private int useNetDayTime;
private int useNetTotalTime;
public TotalTimeUsed()
{
}
}

然后用如下操作来将其序列化为二进制文件。

IFormatter formatter = new BinaryFormatter();
Stream stream = new FileStream("use.sav",
FileMode.OpenOrCreate,FileAccess.ReadWrite,
FileShare.None);
formatter.Serialize(stream, this .timeUsedObject);
stream.Close() ;

对于反序列化也非常简单,只要调用,相应的 Deserialize 方法就行了。 但是要注意的是,反序列化, 出来的对象是 Object 类型的,要把它强制类型转化成实际的类型。

4. 在 .NET 平台下使用资源
使用资源的好处很多,包括实现国际化,资源不用编译等等。
在 .NET 下使用资源也非常的方便,我觉得最简的方法莫过于使用 ResEditor 工具,不用怕,它是一个可视化的工具,而且是 IDE 自带的,它在 SDK\v1.1\Samples\Tutorials\resourcesandlocalization\r
Eseditor 这个目录下面,需要先运行 bat 文件,然后就会出现 .exe 的可执行文件。不过,我运行完了以后只是出现了两个 .cs 文件,给他们编译完了就会出现想要的 ResEditor.ext ,好了,将它加入到 VS.NET 中的 Tools 中吧。由于每个 Form 窗体都对应一个 .resx 文件,这就是我们要找的资源文件,我只要用刚才生成的工具打开这个文件就可以对资源文件进行添加,删除,重命名等。可以说是非常的智能化,下面就是 ResEditor 的截图。

这样,通过这个工具,把你想要的资源都加入好了以后,就可以保存了。
可是怎么使用呢?
是这样的,如果要使用资源,就要用到 ResourceManager 类,在要用资源的地方新建一个 ResourceManager 类,例如: ResourceManager resources = new System.Resources.ResourceManager( typeof (MainForm));
MainForm 就是所在的窗体类的类名,这样新建出的 ResourceManager 类就可以管理 MainForm 的默认资源文件了,即: MainForm.resx 。这样第一步就完成了,下面,就是调用 GetObject 方法来取得资源了。例如: this .NotifyIcon1.Icon = ((System.Drawing.Icon)(resources.GetObject("NotifyIcon1.Icon")));
GetObject 的参数就是要取得的资源的名字。


5. 对于 MDI 编程


只有一个问题困扰了我一会,主要怎么设置才能让弹出对话框的时候,主窗口处于不可用状态,其实只要调用不同的方法就行了。对于这个问题只要调用 ShowDialog 就行了,而不是调用 Show 。当然也不用设置父窗口。

6. 程序自动运行


这个问题并不难,最简单的方法就是写入注册表到 HKEY_LOCALMACHINE\SOFTWARE\MICROSOFT\WINDOWS\CurrentVersion\Run 就可以了。
注册表的读取和写入相对于文件要简单的多。先给出我的代码。
public void ReadWriteReg()
{
Assembly aa = Assembly.GetExecutingAssembly() ;
// 获得当前运行的程序集
string location = aa.Location ;
// 获得当前程序集的物理地址
RegistryKey Hklm = Registry.LocalMachine ;
// 读取 LocalMachine 键
RegistryKey HkSoftware = Hklm.OpenSubKey
("SoftWare") ;
// 读取 Software 子键
RegistryKey HkMicrosoft = HkSoftware.OpenSubKey
("Microsoft") ;
RegistryKey HkWindows = HkMicrosoft.OpenSubKey
("Windows") ;
RegistryKey HkCurrent = HkWindows.OpenSubKey
("CurrentVersion");
RegistryKey HkRun = HkCurrent.OpenSubKey
("Run", true ) ;
// 打开 Run 子键,并且它是可写入的
HkRun.SetValue("LimitNetUseTime",location);
// 写入键值
HkRun.Close() ;
HkCurrent.Close() ;
HkWindows.Close() ;
HkMicrosoft.Close() ;
HkSoftware.Close() ;
Hklm.Close() ;

}

基本上就需要两个类, RegistryKey 和 Registry ,其中 Registry 主要用来获取注册表的根,通过它的属性就可以获得你想要的根,然后就是要层层深入的读取。在你想要写入的键值的地方调用 SetValue 就行了。要说明的是 OpenSubKey 有两个参数的时候,第二个参数代表是否可以写入。另外操作完后,要调用 Close() 。

7. 编写托盘程序


鉴于要编写的程序的性质,所以要把它编写为托盘程序,也就是,它只在托盘区有个小图标的那种程序。其实要使用到一个控件,它就是 NotifyIcon ,还可以添加 ContextMenu 控件来增加右键菜单,然后给菜单加上事件处理。
另外再重写 OnClosing 函数。
protected override void OnClosing
( System.ComponentModel.CancelEventArgs e )
{
SaveUse();
// 保存使用时间
e.Cancel = true ;
this .SetSettingEnable( false ) ;
this .SetMainTitle(" 限制--您未登陆,
请您输入密码 ") ;
Hide();
if OpenWindow = false ;
}

这样的话,还可以实现 FlashGet 的那种效果,即:点击窗口上的 X 时,并不关闭程序的效果。


五、总结

这个程序现在也只是停留在基本上能用的阶段,因为时间并不多,也只能暂时告一段落了,等有时间了再把界面改改,再加上流量统计,按月份或者按天生成报表,如果要是能把进程插入到其他系统进程中,防止用户中止进程就好了。要是给家长用,再给加上键盘记录,屏幕界图。啊 … .. 怎么越说越像木马了,太可怕了。我可是好孩子呀,呵呵 :)
虽然只有几天的时间,几个简单的类,但是做这个小程序却让我收获很大。让我真真切切的感觉到 .NET 平台对开发程序的种种优良的特性。如果你也觉得哪个软件用的不爽,也写一个啦。在最后,我也发现有很多我写的代码都是相同的,于是也进行了一次小小的重构,比起一开始写的代码要好很多。还有,在我的开发过程中,有两个工具对我来说是不可或缺的,那就是 Google 和 MSDN ,用他们几乎可以找到任何你想要的东西,关键看你能否找的出来。就连黑客们都用 Google 来寻找他们要进攻的对象,实在太厉害啊,所以啊,有什么问题―――先放“狗”去搜吧。

源代码下载
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值