VS2005 C#学习实践笔记

《VS2005 C#学习实践笔记》

这是一篇几个月前我帮别人写的“作业”,现在“作业”没人收了,所以我就大胆的把它贴出来了,给自己留一个备份。虽然是以别人的口吻写的,但是故事确实是我自己遇到的。

 

 

序言:

    儿子要照百日照了。拍照的摄影公司是上门服务的,但所有摄影公司都是要根据客户算选片的数量收费的。也就是说无论他拍了N张,只有你掏钱买的才会给你。于是我产生了一个想法:既然是在家里拍摄,那么拍摄完我要求在家里选片应该是一件很正常的事。我可以不要求摄影公司将照片拷贝到我的电脑上,而只是插上他的读卡器,一张一张地看而已,而且整个我过程由摄影公司的人来操作,我远远站着看,根本不动电脑,他就该放心了吧。

 

    而我的小算盘是,我在电脑上放上一个后台进程,一旦发现有移动存储设备接入,立即从里面查找指定文件(使用文件通配符)并复制到我硬盘的指定目录中。这一切过程都是自动的,也就是我远远站着看的时候,这一切都发生了。当摄影公司的人离开的时候,我会发现所有的照片,甚至他之前给别人家的宝宝拍的照片都会躺在我的硬盘里。这样的做法是否有悖于我国的法律,我并没有过多的研究,但我保证之后写出的程序不会发到网上供他人使用,就当是我自己自娱自乐好了。(事情发展到最后,我是没有做非法的事情的)

 

第一章 开始一个解决方案


(.Net里叫解决方案了,不再是VB6的工程了,一个解决方案可以包含N个工程)
    任务有了,接下来就是付之行动了。


    首先是选择开发平台。因为是Windows桌面应用,加之势必要调用一些Win32 API,所以微软的开发平台应该是肯定的。Visual Basic 6.0 是我再熟悉不过的了,但是为了让自己的路子更宽一些、跟上一些时代的步伐,我还是放弃了VB6,而是选择了 Visual Studio 2005,学习和尝试一下 .Net 开发。接下来是最让我犹豫不定的VB .Net 和 C# 之间的选择。前者虽然与VB6有着本质的区别,但至少语法相通,似乎更适合我这样的VB6程序员;但后者的应用者似乎比前者多上几倍,也是未来微软推崇的方向。最终我还是选择了C# ,因为我想既然是抱着学习和实践为目的的,那么就该放开手脚。加之我之前也有Java的底子,又有微软系传统的友好性,所以我不怕,呵呵。


    打开VS2005,按照向导,首先创建一个C# 的Windows应用程序。第一步就是要给我的解决方案起个名字,鉴于它的用途,我在Baidu上搜了一通,最终起了个名字叫Hustle。是一部非常有名的英国电视剧,中文译为《飞天大盗》,哈哈。
 


    解决方案创建了,接下来该如何做呢?由于时间等关系,我没有找 .Net 开发的书籍,更不可能系统的学习C# 开发,只能借助于Internet这个最好的老师。有人说内事不明问Baidu、外事不明问Google。我觉得掌握一种开发工具固然是能力的提高,但能从互联网中学习、提高自己是更应具备的能力。


    经过几篇入门文章的拜读,很快我可以按照自己的设想去画画面了。其实,本来微软的IDE就非常友好,和VB6也没有什么差别,如果说有差别,也是更加强大、更为友好了。


 

    反倒是花费时间最长的是我要去找一些自己喜欢的Icon图标资源。
接下来,我将“挑战”一系列技术问题。首当其冲的就是如何知道有移动存储设备插入,其次是遍历磁盘(移动设备),选择性复制文件,最好支持多种格式的通配符,如:*.jpg;T*.raw,第三是拷贝过程中插入时间延迟,避免过多的CPU及IO占有,要不然就被发觉啦!之后,还有将配置信息保存入Windows注册表、调用浏览对话框选择存储目录等。下面将逐一展开介绍。

 

 

第二章 从工具类开始

 

 

 

    因为有过Java开发经验,所以我觉得应该充分尊重和利用C# 的面向对象编程。将我要到的一些共通功能以Util类的形式编写。

 

 


    我一共做了3个工具类,分别是:读写注册表工具、浏览文件夹对话框、可递归所有子目录的文件夹拷贝。其实前两个Util的绝大部分代码都是可以从网上找来的,这时候我也庆幸当初选择了C# ,讲C# 的文章在网上还是蛮多的。


    通过这3个类的编写,我只是学到了using 和 namespace 的用法,可以类比为Java中的import 和 package。只是,似乎第三方的工具包与Java相比还是少的可怜。我分析有这么几个原因:


1. Java程序无论是可供他人使用的功能性类,还是自用的业务类都编译成.class按包名存放到目录中或是打包成.jar,这样便于将共通部分提炼出来。


而.Net 下只有开发者有意识的将共通部分编译为DLL才有可能提供给他人调用,大部分开发者都是把所有的代码都一起做成EXE的


2. 我们能看到的Java的第三方包,基本上都是伴随着源码的。而微软系就很少有开发源码的理念了。所以即便有人提供一个DLL而没有源码,大多数人还是不敢用的。


3. 还是和开放源码有关,Sun和Microsoft在这方面的政策和做事方式的差别,应该也是造成两套系统的第三方API多寡的原因

 

    读写注册表的核心API是微软的RegistryKey类,在其HotKey下进行OpenSubKey后GetValue或SetValue即可读写注册表的键值。另外还有CreateSubKey、DeleteSubKeyTree等操作用于创建和删除键值。


    浏览文件夹对话框则主要是利用FolderNameEditor.FolderBrowser这个类来完成的,整个代码都可以从网上找到,所以也就没有什么可特别介绍的了。


    文件夹拷贝这个类是比较重要的,我在这个类里,使用了私有/公有函数,实践了类的封装性,还实践了重载和静态方法,当然还有递归算法,这是我学生时代打下根基的有数个算法之一。


    其中私有方法RecursiveCopy是完成文件夹拷贝的主要函数,这个函数有4个参数,分别是源路径、目标路径、要拷贝的文件种类(通配符)、每个文件拷贝后的延时量(单位100毫秒)。


    RecursiveCopy函数中最为主要的方法就是DirectoryInfo.GetFiles(fType),参数为文件通配符。我通过两层foreach循环来编译当前目录中的所有文件和文件种类参数中的多个用分号或逗号分割的文件通配符。这样就可以将当前目录中所有符合条件的文件拷贝,别忘了在拷贝之后让程序延时片刻,这里用到了进程控制方法Thread.Sleep(毫秒数)。之后再DirectoryInfo.GetDirectories()取得所有子目录,然后foreach递归就可以了。


    作为对外的服务,我提供了重载的3个CopyFolder方法,当然是Public的。它们分别支持传入2个、3个和4个参数,也就是说后两个参数对调用者来说是可选的。C#不支持像VB6和C++中的默认参数(也就是声明时有默认值,如果调用时不给参数就用默认值),而是和Java一样要求写成这样纯粹的函数重载

public static void CopyFolder(string sourceDirectory, string targetDirectory)
{
    Copy(sourceDirectory, targetDirectory, "", 0);
}

public static void CopyFolder(string sourceDirectory, string targetDirectory, string fileTypes)
{
    Copy(sourceDirectory, targetDirectory, fileTypes, 0);
}

public static void CopyFolder(string sourceDirectory, string targetDirectory, string fileTypes, int timeDelay)
{
    RecursiveCopy(sourceDirectory, targetDirectory, fileTypes, timeDelay);
}

 

   由于是工具类,所以其提供的方法自然是静态方法,不需要实例化功能类,而是直接用类名点取静态方法。

 

 

第三章 主程序来了


3.1 监测移动存储设备插入
    比起这之前的代码来说,主程序就不那么容易上手了。毕竟之前的代码与Java较为类似,即便是用到了一些微软的Win32 API,也是一查手册就立即上手,甚至是有很多example程序可Copy的。


    主程序中涉及到的监测移动存储器插入、应用程序最小化到系统托盘、最小化时的动画效果等都是我以前从未接触过的Win32 API(上学时没有学MFC是我的一大遗憾,而且至今也还没有这个机会)


    以下部分是必须要参考微软的手册才能写出的,当然我又是借助Baidu和Google而走了“捷径”。
首先是Message这个类,这是Windows的系统消息,可以理解为系统事件,Windows中的所有事件都要派发出一个Windows消息。为了监测可移动设备的插入,就要监测const int WM_DEVICECHANGE = 0x219这个消息。当发现系统派发了一个WM_DEVICECHANGE消息,就是说明有驱动器的变化,可能是增加也可能是减少。那么如何知道新增的驱动器是可移动设备呢?有两种办法:第一种是通过设备种类常量来判断。DriveType.Removable;DriveType.Fixed;DriveType.CDRom。第二种是,在应用程序启动时,扫描所有设备并将所有驱动器符号保存起来,当有设备变化(WM_DEVICECHANGE)时,再扫描所有驱动器,看看是否有之前保存的列表中不包含的驱动器,如果有,那么就是新接入的。


    对于我这个应用,我选择了第二种方法。其实第一种方法是来自网络搜索的,而第二种方法是我自己想到的。至于新接入的设备是可移动设备(如U盘、读卡器)还是移动硬盘还是光盘,我在应用程序的界面上做了一组CheckBox,以选择都对哪些类型的媒体做拷贝。这样程序就可对新接入的,且是用户选择了的媒体类型的驱动器做拷贝了。

 

3.2 辅助功能
    至此,主要功能似乎已经实现。但还有大量的辅助功能。


3.2.1 通过注册表保存程序设置
    用户界面上的选择保存路径、上述三种媒体类型的选择、是否随Windows开机启动、设置文件拷贝间隙延时的长短等UI处理我就不过多介绍了,只说说要将它们写入注册表并在应用程序启动时从注册表读出。


    当用户按“确定”或“退出”按钮时的第一个工作就是将当前UI的配置保存入Windows注册表。这里主要介绍一下媒体类型和“开机自动运行”。


    不同于路径和通配符这样的文本保存,媒体类型我用的是十六进制类型保存。程序中为三种媒体类型分别定义了十六进制的常量

        /* 监测设备种类:可移动磁盘*/
        private const int DISK_REMOVEABLE = 0x01;

        /* 监测设备种类:硬盘(本地磁盘、移动硬盘)*/
        private const int DISK_FIXED = 0x02;

        /* 监测设备种类:CD/DVD */
        private const int DISK_CDROM = 0x04;

 

    对应的二进制就是 001 010 100 ,这样用户选择了的类型都累加起来,存入注册表。然后在应用程序启动时,读出这个值后,分别与三个常量做位与,即可得知相应的类型是否被选择了。这样的好处是不必为每一个选项都设置一个变量和存储位置,如果是用数据库存储的话,日后要增加新项目时要增加新字段,就是一个比较麻烦的事情了。而应用位与,只要项目数不超过所用的数据类型的有效位数(如int型的32位),就可以简单的增加管理项目。


     第二个是“开机自动运行”,实现这一功能的原理是将应用程序自身的路径及文件名写入注册表的自动运行目录SOFTWARE\Microsoft\Windows\CurrentVersion\Run。即可实现下次Windows启动时自动启动应用程序。

 

3.2.2 程序初始化操作
    应用程序启动后的初始化操作,包括:
◆ 将应用程序最小化到到系统托盘
◆ 从注册表中读取用户设置,并初始化各项目控件
◆ 将应用程序图标置为空闲状态的图标(绿色)
◆ 初始化系统磁盘列表
◆ 开始监听Windows消息


    其中,前两项不必再说了。第三项也非常简单,只要将窗体的icon和托盘的icon都赋值为资源中的某一图标资源即可。
至于系统磁盘列表我是采用List<string>来保存的。初始化时将DriveInfo.GetDrives()函数返回的所有驱动器的Name都写入List即可。之后,当有设备变动时,再做一次DriveInfo.GetDrives(),如果此次取出的驱动器的Name未在List之中,就说明是新插入的。


    最后调用3.1中所述的Windows消息监听函数,开始工作。

 

3.2.3 最小化到系统托盘
    很多软件都要做出最小化到系统托盘的效果,.Net也提供了很方便的方法,就是使用一个NotifyIcon控件,在窗口大小变化时,当最小化窗体时,将窗体隐藏并在系统托盘中显示图标即可。

    private void MainForm_SizeChanged(object sender, EventArgs e)
    {
        if (this.WindowState == FormWindowState.Minimized)
        {
            this.Hide();
            this.notifyIcon1.Visible = true;
        }
    }

 

  

    但是,我发现大多数软件最小化后,都是在任务栏存留的,如

  但显然我这个应用程序是不希望在最小化后还在任务栏留存的,那不就现形了吗。我希望它最小化后仅在系统托盘中出现(比较不容易被不知情的人发现)。但是,我发现当不让任务栏存留(this.ShowInTaskbar = false;)时,奇怪的现象发生了,窗体是“飞”到Windows的“开始”按钮下方,再水平向右“飞”到系统托盘中的。


    按说,作为一个练习程序,只有我一个人使用,这样的怪怪动画效果也不至于影响使用,但是为了实现类似MSN和QQ点“X”后“飞入”系统托盘的动画效果(金山词霸虽然点“X”后也是任务栏没有留存,但它是无动画效果的),我又搜索了一番,花在这上面的时间,足有整个的三分之一之多。


    经过一些列的Win32 API的调用是生用“动画”去实现的这一功能。包括找到用户当前的任务栏、系统托盘的左上、右下坐标(根据用户的屏幕分辨率不同而异)。总之,网上找来的许多代码总是需要经过自己的调试和改造后才能真正工作的,这里就不再敖述了。


效果是这样的:

 

3.2.4 使用多线程
    在调试程序的时候,我发现,如果插入的移动硬盘有多个分区的话,要么只能拷贝第一个分区,要么靠前的分区会被多次拷贝。经过程序跟踪和分析,我发现原因是:


    当移动硬盘插入时,Windows系统先识别新的USB设备,然后是按顺序Mount磁盘分区。当第一个分区(假设为 G:)被Mount上时(Windows系统托盘飘出一个“磁盘可使用”的吹出框)Hustle监测到磁盘变化,这时将启动Copy函数进行拷贝。大约间隔1-2秒钟后,Windows会Mount上下一个分区H:,如果此时Copy函数还未执行完,则这次Windows消息就没有被Hustle监测到,所以就只拷贝了G:上文件。


    如果G:上文件很少,在Windows准备好H:之前就完成了拷贝,那么,当H:准备好时,Hustle会又监测到磁盘变化,并且可以得知G:和H:都是应用程序启动时没有的设备,于是G:和H:都要被拷贝一遍,就会造成G:被复制了2遍。如果有3个磁盘分区G:/H:/I:,假设拷贝动作都足够快(比Windows识别/准备磁盘分区还快)的话,结果将是G:被拷贝3次、H:被拷贝2次、I:被拷贝1次。但大部分时候,是拷贝的速度没有Windows准备磁盘分区快的,所以大部分时候是只能拷贝下第一磁盘分区。


    面对这个问题,必须让Hustle在执行CopyFolder的同时,还能继续保持对Windows消息的监听,很明显现在需要多线程了。


    多线程在解决这个问题的同时,还能带来一个好处就是由于并行操作而提高拷贝速度。好在C#中多线程并不比Java复杂,而且可以很容易地使一个函数被子线程启动,唯一要注意的就是这个函数是不能有参数的。为此只能用类变量来传递参数了,我觉得没有必要为此再写一个类,就用Struct了。

        /* 保存用于向文件复制线程传递的四个参数的类*/
        private struct CopyInfo
        {
            public string sourceDirectory;
            public string targetDirectory;
            public string fileTypes;
            public int timeDelay;
        }

 

 

    为了得知什么时候,所有线程均已执行完毕,我用了一个Hashtable threadTable来保存所有当前执行中的线程ID。当线程启动时,写入哈希表,结束时从哈希表中清除。当哈希表的count值为0时,就表示所有线程均已结束,并将表示繁忙的图标(红色)变为空闲(绿色)。


    这次使用Hashtable,而不再是List,原因是Hashtable是线程安全的。这还用配合look语句使用,即在写Hashtable时lock (threadTable.SyncRoot)。


    这种使用哈希表来存储进程ID以便得知是否进程是否执行完毕的方法是我想出的笨办法,而且不敢保证这里面没有理解不到位或存在隐患的地方,也请各位给些帮助。似乎更应该使用Join()方法,但既然我有了自己动脑子的机会,就不妨先试试验一把,也许以后我会将其改为Join()方法的。

 

 

第四章 总结


    很高兴能顺利完成这个自我实践的小软件。在开始之初真的不知道一个周末的时间是否能完成此次尝试。这里要感谢Baidu,感谢Google,感谢Internet,感谢发帖、写博客的各位网友……哈哈。


    总结,这个实践中,一共涉及到了如下知识:
◆ Visual Studio编程方式入门
◆ C#语法初步(确实和Java比较像)
◆ 面向对象的理念虽然不多,但至少使用了类
◆ 重载
◆ 静态函数
◆ List和哈希表
◆ 多线程
◆ NotifyIcon控件
◆ 以下Win32 API:注册表读写
◆ 文件夹信息读取
◆ 文件拷贝
◆ 线程休眠
◆ 文件夹选择
◆ 应用程序单实例运行
◆ 监测Windows消息
◆ 取得磁盘服务器信息
◆ 取得当前屏幕分辨率
◆ 取得系统托盘句柄
◆ 窗体飞行动画

 

 


尾声:

    当Hustle程序写好了之后,等待的就是摄影公司的上面拍照了。终于第二个周末到了我们预约的日子。摄影公司的人是很小心的,进门后就明确叮嘱,他们拍摄时是不允许客户在旁边用自己的相机拍照的。


    拍摄工作完成后,摄影公司的人要求我们过一周去他们公司选照片。这当然是不能接受的,我的Hustle还在那里等着呢。我已路程遥远拒绝了对方的方案,并给出了自己的方案,就如序言中所写的一样,什么我不动电脑,你来操作之类的,为了打消对方的顾虑。可是没有想到的是,对方是“你有千言万语,我有一定之规”,无论如何也不肯在我的电脑上看照片。最后他们不惜去取来自己的笔记本电脑来让我们选照片。天哪,我的Hustle最终还是没能派上用场,只能为自己喜欢的照片买单了。


    其实能够锻炼自己已经足够了。我是一名程序员,而且现在还保持着是一名守法公民,哈哈。

 

 

注:由于此程序可能涉及到法律问题,所以我没有贴出源码。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值