当前位置:文档之家› C#技巧经典收藏版集

C#技巧经典收藏版集

如何在C#中使用Win32和其他库



这一次我们将深入探讨如何在C#中使用Win32 和其他现有库。C# 用户经常提出两个问
题:“我为什么要另外编写代码来使用内置于Windows 中的功能?在框架中为什么没有相应的
内容可以为我完成这一任务?”当框架小组构建他们的.NET部分时,他们评估了为使.NET 程
序员可以使用Win32 而需要完成的工作,结果发现Win32 API 集非常庞大。他们没有足够的资
源为所有Win32 API 编写托管接口、加以测试并编写文档,因此只能优先处理最重要的部分。
许多常用操作都有托管接口,但是还有许多完整的Win32 部分没有托管接口。



平台调用(P/Invoke) 是完成这一任务的最常用方法。要使用P/Invoke,您可以编写一个

如何调用函数的原型,然后运行时将使用此信息进行调用。另一种方法是使用Managed

Extensions to C++ 来包装函数,这部分内容将在以后的专栏中介绍。

要理解如何完成这一任务,最好的办法是通过示例。在某些示例中,我只给出了部分代码

完整的代码可以通过下载获得。

简单示例

在第一个示例中,我们将调用Beep() API 来发出声音。首先,我需要为Beep() 编写适当

定义。查看MSDN 中的定义,我发现它具有以下原型:

要用C# 来编写这一原型,需要将Win32 类型转换成相应的C# 类型。由于DWORD 是

字节的整数,因此我们可以使用int 或uint 作为C# 对应类型。由于int 是CLS 兼容类型(

用于所有.NET 语言),以此比uint 更常用,并且在多数情况下,它们之间的区别并不重要。

bool 类型与BOOL 对应。现在我们可以用C# 编写以下原型:

public static extern bool Beep(int frequency, int duration);

这是相当标准的定义,只不过我们使用了extern 来指明该函数的实际代码在别处。此原型

BOOL Beep(

DWORD dwFreq, // 声音频率

DWORD dwDuration // 声音持续时间

);

告诉运行时如何调用函数;现在我们需要告诉它在何处找到该函数。

我们需要回顾一下MSDN 中的代码。在参考信息中,我们发现Beep() 是在kernel32.lib

定义的。这意味着运行时代码包含在kernel32.dll 中。我们在原型中添加DllImport 属性将这

息告诉运行时:

[DllImport("kernel32.dll")]

这就是我们要做的全部工作。下面是一个完整的示例,它生成的随机声音在二十世纪六十

代的科幻电影中很常见。

它的声响足以刺激任何听者!由于DllImport 允许您调用Win32 中的任何代码,因此就有

能调用恶意代码。所以您必须是完全受信任的用户,运行时才能进行P/Invoke 调用。

枚举和常量

Beep() 可用于发出任意声音,但有时我们希望发出特定类型

的声音,因此我们改用

MessageBeep()。MSDN 给出了以下原型:

这看起来很简单,但是从注释中可以发现两个有趣的事实。

首先,uType 参数实际上接受一组预先定义的常量。

using System;

using System.Runtime.InteropServices;

namespace Beep

{

class Class1

{

[DllImport("kernel32.dll")]

public static extern bool Beep(int frequency, int duration);


static void Main(string[] args)

{

Random random = new Random();

for (int i = 0; i < 10000; i++)

{

Beep(random.Next(10000), 100);

}

}

}

}

BOOL MessageBeep(

UINT uType // 声音类型

);



其次,可能的参数值包括-1,这意味着尽管它被定义为uint 类型,但int 会更加适合。

对于uType 参数,使用enum 类型是合乎情理的。MSDN 列出了已命名的常量,但没有

具体值给出任何提示。由于这一点,我们需要查看实际的API。

如果您安装了Visual Studio? 和C++,则Platform SDK 位于\Program Files\Microsoft

Visual Studio .NET\Vc7\PlatformSDK\Include 下。

为查找这些常量,我在该目录中执行了一个findstr。

findstr "MB_ICONHAND" *.h

它确定了常量位于winuser.h 中,然后我使用这些常量来创建我的enum 和原型:

现在我可以用下面的语句来调用它: MessageBeep(BeepType.IconQuestion);

public enum BeepType

{

SimpleBeep = -1,

IconAsterisk = 0x00000040,

IconExclamation = 0x00000030,

IconHand = 0x00000010,

IconQuestion = 0x00000020,

Ok = 0x00000000,

}

[DllImport("user32.dll")]

public static extern bool MessageBeep(BeepType beepType);





处理结构

有时我需要确定我笔记本的电池状况。Win32 为此提供了电源管理函数。

搜索MSDN 可以找到GetSystemPowerStatus() 函数。

此函数包含指向某个结构的指针,我们尚未对此进行过处理。要处理结构,我们需要用C

定义结构。我们从非托管的定义开始:

然后,通过用C# 类型代替C 类型来得到C# 版本。

这样,就可以方便地编写出C# 原型:

在此原型中,我们用“ref”指明将传递结构指针而不是结构值。这是处理通过指针传递的

构的一般方法。

BOOL GetSystemPowerStatus(

LPSYSTEM_POWER_STATUS lpSystemPowerStatus

);

typedef struct _SYSTEM_POWER_STATUS {

BYTE ACLineStatus;


BYTE BatteryFlag;

BYTE BatteryLifePercent;

BYTE Reserved1;

DWORD BatteryLifeTime;

DWORD BatteryFullLifeTime;

} SYSTEM_POWER_STATUS, *LPSYSTEM_POWER_STATUS;

struct SystemPowerStatus

{

byte ACLineStatus;

byte batteryFlag;

byte batteryLifePercent;

byte reserved1;

int batteryLifeTime;

int batteryFullLifeTime;

}

[DllImport("kernel32.dll")]

public static extern bool GetSystemPowerStatus(

ref SystemPowerStatus systemPowerS

tatus);



此函数运行良好,但是最好将ACLineStatus 和batteryFlag 字段定义为enum:

请注意,由于结构的字段是一些字节,因此我们使用byte 作为该enum 的基本类型。

字符串

虽然只有一种.NET 字符串类型,但这种字符串类型在非托管应用中却有几项独特之处。

以使用具有内嵌字符数组的字符指针和结构,其中每个数组都需要正确的封送处理。

在Win32 中还有两种不同的字符串表示:

ANSI

Unicode

最初的Windows 使用单字节字符,这样可以节省存储空间,但在处理很多语言时都需要

的多字节编码。Windows NT? 出现后,它使用双字节的Unicode 编码。为解决这一差别,W

API 采用了非常聪明的做法。它定义了TCHAR 类型,该类型在Win9x 平台上是单字节字符

WinNT 平台上是双字节Unicode 字符。对于每个接受字符串或结构(其中包含字符数据)的

数,Win32 API 均定义了该结构的两种版本,用A 后缀指明Ansi 编码,用W 指明wide 编

(即Unicode)。如果您将C++ 程序编译为单字节,会获得A 变体,如果编译为Unicode,

获得W 变体。Win9x 平台包含Ansi 版本,而WinNT 平台则包含W 版本。

由于P/Invoke 的设计者不想让您为所在的平台操心,因此他们提供了内置的支持来自动

A 或W 版本。如果您调用的函数不存在,互操作层将为您查找并使用A 或W 版本。

通过示例能够很好地说明字符串支持的一些精妙之处。

enum ACLineStatus: byte

{

Offline = 0,

Online = 1,

Unknown = 255,

}

enum BatteryFlag: byte

{

High = 1,

Low = 2,

Critical = 4,

Charging = 8,

NoSystemBattery = 128,

Unknown = 255,


}





简单字符串

下面是一个接受字符串参数的函数的简单示例:

根路径定义为LPCTSTR。这是独立于平台的字符串指针。

由于不存在名为GetDiskFreeSpace() 的函数,封送拆收器将自动查找“A”或“W”变体

并调用相应的函数。我们使用一个属性来告诉封送拆收器,API 所要求的字符串类型。

以下是该函数的完整定义,就象我开始定义的那样:

不幸的是,当我试图运行时,该函数不能执行。问题在于,无论我们在哪个平台上,封送

收器在默认情况下都试图查找API 的Ansi 版本,由于LPTStr 意味着在Windows NT 平台上

使用Unicode 字符串,因此试图用Unicode 字符串来调用Ansi 函数就会失败。

有两种方法可以解决这个问题:一种简单的方法是删除MarshalAs 属性。如果这样做,将始终

用该函数的A 版本,如果在您所涉及的所有平台上都有这种版本,这是个很好的方法。但是,

会降低代码的执行速度,因为封送拆收器要将.NET 字符串从Unicod

e 转换为多字节,然后调

函数的A 版本(将字符串转换回Unicode),最后调用函数的W 版本。

要避免出现这种情况,您需要告诉封送拆收器,要它在Win9x 平台上时查找A 版本,而

NT 平台上时查找W 版本。要实现这一目的,可以将CharSet 设置为DllImport 属性的一部分

[DllImport("kernel32.dll", CharSet = CharSet.Auto)]

BOOL GetDiskFreeSpace(

LPCTSTR lpRootPathName, // 根路径

LPDWORD lpSectorsPerCluster, // 每个簇的扇区数

LPDWORD lpBytesPerSector, // 每个扇区的字节数

LPDWORD lpNumberOfFreeClusters, // 可用的扇区数

LPDWORD lpTotalNumberOfClusters // 扇区总数

);

[DllImport("kernel32.dll")]

static extern bool GetDiskFreeSpace(

[MarshalAs(UnmanagedType.LPTStr)]

string rootPathName,

ref int sectorsPerCluster,

ref int bytesPerSector,

ref int numberOfFreeClusters,

ref int totalNumberOfClusters);



在我的非正式计时测试中,我发现这一做法比前一种方法快了大约百分之五。

对于大多数Win32 API,都可以对字符串类型设置CharSet 属性并使用LPTStr。但是,

有一些不采用A/W 机制的函数,对于这些函数必须采取不同的方法。



字符串缓冲区

.NET 中的字符串类型是不可改变的类型,这意味着它的值将永远保持不变。对于要将字符

值复制到字符串缓冲区的函数,字符串将无效。这样做至少会破坏由封送拆收器在转换字符串

创建的临时缓冲区;严重时会破坏托管堆,而这通常会导致错误的发生。无论哪种情况都不可

获得正确的返回值。

要解决此问题,我们需要使用其他类型。StringBuilder 类型就是被设计为用作缓冲区的,

们将使用它来代替字符串。下面是一个示例:

使用此函数很简单:

请注意,StringBuilder 的Capacity 传递的是缓冲区大小。


具有内嵌字符数组的结构

某些函数接受具有内嵌字符数组的结构。例如,GetTimeZoneInformation() 函数接受指向

下结构的指针:

在C# 中使用它需要有两种结构。一种是SYSTEMTIME,它的设置很简单:

[DllImport("kernel32.dll", CharSet = CharSet.Auto)]

public static extern int GetShortPathName(

[MarshalAs(UnmanagedType.LPTStr)]

string path,

[MarshalAs(UnmanagedType.LPTStr)]

StringBuilder shortPath,

int shortPathLength);

StringBuilder shortPath = new StringBuilder(80);

int result = GetShortPathName(

@"d:\test.jpg", shortPath, shortPath.Capacity);

string s = shortPath.ToString();

typedef struct _TIME_ZONE_INFORMATION {

LONG Bias;

WCHAR StandardName[ 32 ];

SYSTEMTIME StandardDate;

LONG StandardBias;

WCHAR DaylightName[ 32 ];

SYSTEMTIME DaylightDate;

LONG DaylightBias;

} TIME_ZONE_INFORMATION, *PTIME_ZONE_INFORMATION;




里没有什么特别之处;另一种是TimeZoneInformation,它的定义要复杂一些:

此定义有两个重要的细节。第一个是MarshalAs 属性:

[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]

查看ByValTStr 的文档,我们发现该属性用于内嵌的字符数组;另一个是SizeConst,它

于设置数组的大小。

我在第一次编写这段代码时,遇到了执行引擎错误。通常这意味着部分互操作覆盖了某些

存,表明结构的大小存在错误。我使用Marshal.SizeOf() 来获取所使用的封送拆收器的大小,

果是108 字节。我进一步进行了调查,很快回忆起用于互操作的默认字符类型是Ansi 或单字

节。而函数定义中的字符类型为WCHAR,是双字节,因此导致了这一问题。

我通过添加StructLayout 属性进行了更正。结构在默认情况下按顺序布局,这意味着所有

段都将以它们列出的顺序排列。CharSet 的值被设置为Unicode,以便始终使用正确的字符类

型。

经过这样处理后,该函数一切正常。您可能想知道我为什么不在此函数中使用

CharSet.Auto。这是因为,它也没有A 和W 变体,而始终使用Unicode 字符串,因此我采

上述方法编码。

struct SystemTime

{

public short wYear;

public short wMonth;

public short wDayOfWeek;

public short wDay;

public short wHour;

public short wMinute;

public short wSecond;

public short wMilliseconds;

}


[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]

struct TimeZoneInformation

{

public int bias;

[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]

public string standardName;

SystemTime standardDate;

public int standardBias;

[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]

public string daylightName;

SystemTime daylightDate;

public int daylightBias;

}

具有回调的函数

当Win32 函数需要返回多项数据时,通常都是通过回调机制来实现的。开发人员将函数

传递给函数,然后针对每一项调用开发人员的函数。

在C# 中没有函数指针,而是使用“委托”,在调用Win32 函数时使用委托来代替函数

针。

EnumDesktops() 函数就是这类函数的一个示例:

HWINSTA 类型由IntPtr 代替,而LPARAM 由int 代替。DESKTOPENUMPROC 所需

作要多一些。下面是MSDN 中的定义:

我们可以将它转换为以下委托:

完成该定义后,我们可以为EnumDesktops() 编写以下定义:

这样该函数就可以正常运行了。

在互操作中使用委托时有个很重要的技巧:封送拆收器创建了指向委托的函数指针,该函

指针被传递给非托管函数。但是,封送拆收器无法确定非托管函数要使用函数指针做些什么,

此它假定函数指针只需在调用

该函数时有效即可。

结果是如果您调用诸如SetConsoleCtrlHandler() 这样的函数,其中的函数指针将被保存

将来使用,您就需要确保在您的代码中引用委托。如果不这样做,函数可能表面上能执行,但

将来的内存回收处理中会删除委托,并且会出现错误。

BOOL EnumDesktops(

HWINSTA hwinsta, // 窗口实例的句柄

DESKTOPENUMPROC lpEnumFunc, // 回调函数

LPARAM lParam // 用于回调函数的值

);

BOOL CALLBACK EnumDesktopProc(

LPTSTR lpszDesktop, // 桌面名称

LPARAM lParam // 用户定义的值

);

delegate bool EnumDesktopProc(

[MarshalAs(UnmanagedType.LPTStr)]

string desktopName,

int lParam);

[DllImport("user32.dll", CharSet = CharSet.Auto)]

static extern bool EnumDesktops(

IntPtr windowStation,

EnumDesktopProc callback,

int lParam);



其他高级函数


迄今为止我列出的示例都比较简单,但是还有很多更复杂的Win32 函数。下面是一个示例

前两个参数的处理比较简单:ulong 很简单,并且可以使用UnmanagedType.LPArray 来

送缓冲区。

但第三和第四个参数有一些问题。问题在于定义ACL 的方式。ACL 结构仅定义了ACL

头,而缓冲区的其余部分由ACE 组成。ACE 可以具有多种不同类型,并且这些不同类型的

的长度也不同。

如果您愿意为所有缓冲区分配空间,并且愿意使用不太安全的代码,则可以用C# 进行处

理。但工作量很大,并且程序非常难调试。而使用C++ 处理此API 就容易得多。

属性的其他选项

DLLImport 和StructLayout 属性具有一些非常有用的选项,有助于P/Invoke 的使用。下

列出了所有这些选项:

DLLImport

CallingConvention

您可以用它来告诉封送拆收器,函数使用了哪些调用约定。您可以将它设置为您的函数的

用约定。通常,如果此设置错误,代码将不能执行。但是,如果您的函数是Cdecl 函数,并且

用StdCall(默认)来调用该函数,那么函数能够执行,但函数参数不会从堆栈中删除,这会

堆栈被填满。

CharSet

控制调用A 变体还是调用W 变体。

EntryPoint

此属性用于设置封送拆收器在DLL 中查找的名称。设置此属性后,您可以将C# 函数重

名为任何名称。

ExactSpelling

DWORD SetEntriesInAcl(

ULONG cCountOfExplicitEntries, // 项数

PEXPLICIT_ACCESS pListOfExplicitEntries, // 缓冲区

PACL OldAcl, // 原始ACL

PACL *NewAcl // 新ACL

);



将此属性设置为true,封送拆收器将关闭A 和W 的查找特性。

PreserveSig

COM 互操作使得具有最终输出参数的函数看起来是由它返回的该值。此属性用于关闭这

性。

SetLastError

确保调用Win32 API SetLastError(),以便您找出发生

的错误。

StructLayout

LayoutKind

结构在默认情况下按顺序布局,并且在多数情况下都适用。如果需要完全控制结构成员所

置的位置,可以使用LayoutKind.Explicit,然后为每个结构成员添加FieldOffset 属性。当您需

创建union 时,通常需要这样做。

CharSet

控制ByValTStr 成员的默认字符类型。

Pack

设置结构的压缩大小。它控制结构的排列方式。如果C 结构采用了其他压缩方式,您可能

要设置此属性。

Size

设置结构大小。不常用;但是如果需要在结构末尾分配额外的空间,则可能会用到此属性

从不同位置加载


您无法指定希望DLLImport 在运行时从何处查找文件,但是可以利用一个技巧来达到这一

的。

DllImport 调用LoadLibrary() 来完成它的工作。如果进程中已经加载了特定的DLL,那么

使指定的加载路径不同,LoadLibrary() 也会成功。

这意味着如果直接调用LoadLibrary(),您就可以从任何位置加载DLL,然后DllImport

LoadLibrary() 将使用该DLL。

由于这种行为,我们可以提前调用LoadLibrary(),从而将您的调用指向其他DLL。如果

编写库,可以通过调用GetModuleHandle() 来防止出现这种情况,以确保在首次调用P/Invo

之前没有加载该库。

P/Invoke 疑难解答



如果您的P/Invoke 调用失败,通常是因为某些类型的定义不正确。以下是几个常见问题

1.long != long。在C++ 中,long 是4 字节的整数,但在C# 中,它是8 字节的整数。

2.字符串类型设置不正确。






用 C#设计 Windows 应用程序模板






通常 windows 应用程序都有相似的特征:控件、菜单、工具条、状态栏等等。每次我们开
始作一个新的 windows 应用程序时都是以相同的事情开始:建立项目,添加控件和事件处理器。
如果我们有一个模板,那么我们就可以节约大量的时间了。
在介绍如何建立模板的过程中,将涉及大量的微软.net framework 类库的基本知识。如果
你没有使用集成开发环境那么本文介绍的模板对你将非常有用,如果你使用了 visual https://www.doczj.com/doc/8c7723023.html,
这样的集成开发环境你也可以从中了解控件的工作方式,这对你也是很有用的。
写一个 windows 应用程序总是从下面的几个步骤开始:
1、创建一个窗体
2、给窗体添加控件
3、添加菜单和菜单项,并绑定到窗体上
4、创建工具条
5、添加状态栏
6、添加事件处理器
在 windows 应用程序开发中,你不可能完全跳过这些步骤,你可以对他作些修改,但不可
能完全跳过。下面是完全的模板图:

.ü..?..?..?..?..?..
--------------图 1 -----------







创建窗体


在.Net FrameWork 程序设计中,

窗体总是 System.Windows.Forms.Form 类的子
类,他继承聊着各类的全部特征,你甚至不用添加任何一行代码就可以创建 window 窗
体了。现在我们创建一个 form 类的子类 myapp,需要注意的是在说明继承时使用冒号:。


using System.Windows.Forms;
public class MyWinApp: Form {
}




与其他程序相似,我们需要一个 main()的主方法作为应用程序的入口。在 main()
中使用 System.Windows.Forms 的 run 方法来开始应用程序和显示窗体。run 方法有三
种重载方式,对于 windows 应用程序,我们采取重载一个窗体对象的方式:


public static void Main() {
MyWinApp form = new MyWinApp();
Application.Run(form);
}
作为选择,可以在 main()方法中将两行代码写成一行:
public static void Main() {
Application.Run(new MyWinApp());
}




设置属性
一旦有了窗体,就可以设置它的属性(象高度、宽度、标题等)并指定一个象图 1
那样的图标,可以通过代码或从构造器重调用一个方法来完成这个设置。在 list1 中有
一个方法 InitializeComponent,可以将初始化代码写入其中。
使用 width 和 height 属性设置宽度和高度

this.Width = 400;
this.Height = 300;




使用窗体的 text 属性设置标题

this.Text = "My Windows Application";




设置图标


如果未作说明,windows 应用程序将显示一个缺省图标,可以修改这个图标,方法
是设置窗体的 icon 属性,这个属性接受一个 System.Drawing.Icon 对象。Icon 类有几
个可以用于初始化 Icon 对象的构造函数,需要说明的是最好提供.ico 文件的完全路径。

this.Icon = new Icon(imageFolder + "applicationLogo.ico");




这里 imageFolder 是一个静态域,用于指示 icon 文件的目录,imageFolder 的定
义如下:

static String imageFolder = "Images" +
Path.DirectorySeparatorChar.ToString();




这告诉应用程序 icon 文件在应用程序的 image 目录下,这里使用了 System.IO.Path
类的 DirectorySeparatorChar 属性获得操作系统用于分隔目录和父目录的分隔符,在这
里没有使用"/",这是因为对于 windows 操作系统这是正确的,但对于 linux 和 unix 则不
然。这也是为了证明模板对于其他操作系统也是方便的。
窗体的位置
使窗体居中时非常有用的,要达到这个目的,需要使用 StartPosition 属性,并将
FormStartPosition 的一个成员赋给他。

this.StartPosition = FormStartPosition.CenterScreen;




当然也可以用 form 类的 CenterToScreen 方法是窗体居中,但这个方法不能直接使
用。

this.CenterToScreen();




form 类还有其他一些让人感兴趣的属性和方法,这里列出了其中的部分:
1、设置 Opacity 属性创建一个透明或半透明的窗


2、设置 modal 属性使窗体为模式的或非模式的
3、通过 BackColor 属性改变窗体的背景颜色
4、将 TopMost 属性设置为 true,以确定窗体在其他所有非最顶部窗体之上

给窗体添加控件
windows 控件均继承自 System.Windows.Forms.Control 类,control 类处理用户输
入、安全等,他给窗体的控件提供了一个 windows 句柄,以及一些重要的属性,如 Name,


Enabled, Text, BackColor, Left, Top, Size, Location, Visible, Width, 和 Height。
System.Windows.Forms 名称空间提供了 12 个控件,每一个控件都有它自己的属
性和特征,所以在篇文章中我们不可能全部讨论。给窗体添加控减非常容易,下面的代
码给窗体添加了三个控件,分别是:Label, Button, 和 TreeView。

Label label;
Button button;
TreeView tree;




为了简便,可以在声明的同时实例化这些对象。

Label label = new Label();
Button button = new Button();
TreeView tree = new TreeView();




然后在 InitializeComponent 方法中设置这些控件的属性,尤其是设置控件的大小
和在窗体中的位置,对于大小可以使用 width 和 height 属性,比如 treeview 控件的大
小可以使用下面的属性:

tree.Width = 100;
tree.Height = 100;




确定控件的位置可以使用控件的 left 和 top 属性,这两个属性决定了控件的左上角
的位置,就像下面的语句决定了 treeview 的位置:

tree.Top = 40;
tree.Left = 20;




当然你也可以使用更简单的 Location 属性,将 System.Drawing.Point 结构的实例
赋给他。我们用这种方法确定 Label 和 Button 的位置。

label.Location = new Point(220, 40);
button.Location = new Point(220, 80);




下一步就是要使控件在窗体上可见。使用 Form.ControlCollection 类的 add 方法将
每个控件添加到窗体的 ControlCollection 中,ControlCollection 可以使用窗体的控件属
性访问。

this.Controls.Add(label);
this.Controls.Add(button);
this.Controls.Add(tree);



添加菜单和菜单项


要找到没有菜单的 windows 应用程序非常困难,菜单使访问应用程序的功能变得
很简单,在大多数环境下可以最小的使用控件。菜单比控件占用更少的空间,同时使应
用程序显得更有组织。
在 System.Windows.Forms 名称空间中,所有与菜单相关的控件都是 menu 类的
子类。menu 是一个抽象类,你不能直接实例化,menu 类有三个子类:

ContextMenu
MainMenu
MenuItem




ContextMenu 类表示快捷菜单,在控件或窗体区域点击鼠标右键时显示。快捷菜单
常用于组合窗体 mainmenu 类的菜单项给出应用程序的上下文,这对于用户时非常有用
的。
MainMenu 表示传统的位于窗体顶部的菜单,你可以把它看成窗体菜单结构

的容
器。一个菜单是由 MenuItem 表示的菜单项组成的,对于应用程序而言每一个菜单项是
一个命令或其它子菜单项的父菜单。form 类都有一个 menu 属性,采用将 mainmenu
对象赋给 menu 属性的方式将 mainmenu 对象绑定到窗体。
在这个模板中,我们没有使用 ContextMenu 类,但我们示范了如何使用 MainMenu
和 MenuItem 类。我们首先需要在窗体中添加一个菜单,给窗体添加一个 MainMenu 对
象。

MainMenu mainMenu = new MainMenu();




现在 MainMenu 对象中什么都没有,下面我们给他添加一个 MenuItem 对象。在
List1 中主菜单称为 fileMenuItem,它的 text 属性是&File,&表示他后面的字母带下划线,
是该菜单的快捷键。通过使用 Menu 对象的 MenuItemCollection 的 add 方法为
MainMenu 添加一个或几个 MenuItem,这个集合可以通过 menu 类的 MenuItems 属性
访问。

MenuItem fileMenuItem = new MenuItem();
mainMenu.MenuItems.Add(fileMenuItem);




我们在 fileMenuItem 菜单项中还添加了其它 MenuItem,这些 MenuItem 是
fileMenuItem 的子菜单。你也可以给子菜单添加子菜单。图 2 显示了菜单的等级结构。


.ü..?..?..?..?..?..
---------图 2---------




菜单项的声明如下:

MenuItem fileNewMenuItem;
MenuItem fileOpenMenuItem;
MenuItem fileSaveMenuItem;
MenuItem fileSaveAsMenuItem;
MenuItem fileMenuWithSubmenu;
MenuItem submenuMenuItem;
MenuItem fileExitMenuItem;




MenuItem 类有几个构造函数,使用这些构造函数实例化你的 MenuItem 对象,并
添加一个事件处理器。

// the following constructor is the same as:
// menuItem fileNewMenuItem = new MenuItem();
// fileNewMenuItem.Text = "&New";
// fileNewMenuItem.Shortcut = Shortcut.CtrlN;
// fileNewMenuItem.Click += new
// System.EventHandler(this.fileNewMenuItem_Click);
fileNewMenuItem = new MenuItem("&New",
new System.EventHandler(this.fileNewMenuItem_Click), Shortcut.CtrlN);
fileOpenMenuItem = new MenuItem("&Open",
new System.EventHandler(this.fileOpenMenuItem_Click), Shortcut.CtrlO);
fileSaveMenuItem = new MenuItem("&Save",
new System.EventHandler(this.fileSaveMenuItem Click), Shortcut.CtrlS);




Event handling is discussed further in the section "Adding Event Handlers."
As mentioned, the menu items are added to the fileMenuItem control.
fileMenuItem.MenuItems.Add(fileNewMenuItem);
fileMenuItem.MenuItems.Add(fileOpenMenuItem);
fileMenuItem.MenuItems.Add(fileSaveMenuItem);
fileMenuItem.MenuItems.Add(fileSaveAsMenuItem);
fileMenuItem.MenuItems.Add(fileMenuWithSubmenu);
fileMenuWithSubmenu.MenuItems.Add(submenuMenuItem);
fileMenuItem.MenuItems.Add("-"); // add a separator
fileMenuItem.MenuItems.Add(fileExitMenuItem);
fileSaveAsMenuItem = new MenuItem("Save &As",
new System.EventHandler(this.fileSaveAsMenuItem_Click));
fileMenuWithSu

bmenu = new MenuItem("&With Submenu");
submenuMenuItem = new MenuItem("Su&bmenu",
new System.EventHandler(this.submenuMenuItem_Click));
fileExitMenuItem = new MenuItem("E&xit",
new System.EventHandler(this.fileExitMenuItem_Click));




注意在菜单项之间可以创建一个分隔符,方法是使用 menu 类的 MenuItems 集合
的 add 方法。上面的例子中使用的分隔符是"-"。



创建工具条
为了使应用程序的界面更友好,可以在窗体中添加一个工具条。工具条由
System.Windows.Forms.ToolBar 类描述。窗体中可有多个工具条,工具条中包含了一
个或多个 ToolBarButton 类描述的按钮,可以在每个按钮中插入图像或图标,要达到这
个目的你需要一个 ImageList 控件作为图像容器。

ImageList imageList = new ImageList();




对于每个图像文件首先要实例化为 image 对象,然后将这些图像添加到 ImageList
控件中,Image 和 Bitmap 类可以在 System.Drawing 名称空间中找到。

Image newFileImage = new Bitmap(imageFolder + "newFile.bmp");
Image openFileImage = new Bitmap(imageFolder + "openFile.gif");




Image saveFileImage = new Bitmap(imageFolder + "saveFile.bmp");
Image printImage = new Bitmap(imageFolder + "print.gif");
.
.
.
imageList.Images.Add(newFileImage);
imageList.Images.Add(openFileImage);
imageList.Images.Add(saveFileImage);
imageList.Images.Add(printImage);




注意你可以使用 Images 集合的 add 方法将 image 对象加入到 imagelist 控件中。
现在为将这些图加入到控件中,必须将 ImageList 控件赋给 ToolBar 的 ImageList 属性。

toolBar.ImageList = imageList;




然后将 ImageList 控件中的图像赋给工具按钮的 ImageIndex 属性。

newToolBarButton.ImageIndex = 0;
openToolBarButton.ImageIndex = 1;
saveToolBarButton.ImageIndex = 2;
printToolBarButton.ImageIndex = 3;




象菜单项一样,现在必须把工具按钮加入到工具条中。

toolBar.Buttons.Add(separatorToolBarButton);
toolBar.Buttons.Add(newToolBarButton);
toolBar.Buttons.Add(openToolBarButton);
toolBar.Buttons.Add(saveToolBarButton);
toolBar.Buttons.Add(separatorToolBarButton);
toolBar.Buttons.Add(printToolBarButton);




最后将工具条加入到窗体中。

this.Controls.Add(toolBar);




添加状态条
状态条由 System.Windows.Forms.StatusBar 描述,它提供了定制控件的外观的属
性,状态条由 StatusBarPanel 对象组成,在我们的模板中状态条有两个嵌套板:

StatusBar statusBar = new StatusBar();
StatusBarPanel statusBarPanel1 = new StatusBarPanel();
StatusBarPanel statusBarPanel2 = new StatusBarPanel();




状态条和状态跳上的嵌套板由下面的代码设置:

statusBarPanel1.BorderStyle = StatusBarPanelBorderStyle.Sunken;
statusBarPanel1.Text = "Press F1 for Help";
statusBarPanel1.A

utoSize = StatusBarPanelAutoSize.Spring;
statusBarPanel2.BorderStyle = StatusBarPanelBorderStyle.Raised;
statusBarPanel2.ToolTipText = System.DateTime.Now.ToShortTimeString();
statusBarPanel2.Text = System.DateTime.Today.ToLongDateString();
statusBarPanel2.AutoSize = StatusBarPanelAutoSize.Contents;
statusBar.ShowPanels = true;
statusBar.Panels.Add(statusBarPanel1);
statusBar.Panels.Add(statusBarPanel2);




同样我们需要将状态条添加到窗体中:

this.Controls.Add(statusBar);




事件处理器
在 windows 程序设计中添加事件处理器是最重要的任务。事件处理器保证了程序
与用户交互,同时完成其他重要的功能。在 c#中你可以给控件和菜单事件添加事件处
理器以俘获你想处理的事件,下面的代码给 Button 控件的 click 事件设计了一个事件处
理器:

button.Click += new System.EventHandler(this.button_Click);
button_Click 事件处理器必须被处理:
private void button_Click(Object sender, System.EventArgs e) {
MessageBox.Show("Thank you.", "The Event Information");
}




MenuItem 对象在实例化的同时可以给赋以一个事件处理器:

fileNewMenuItem = new MenuItem("&New",
new System.EventHandler(this.fileNewMenuItem_Click), Shortcut.CtrlN);
fileOpenMenuItem = new MenuItem("&Open",
new System.EventHandler(this.fileOpenMenuItem_Click), Shortcut.CtrlO);
fileSaveMenuItem = new MenuItem("&Save",
new System.EventHandler(this.fileSaveMenuItem_Click), Shortcut.CtrlS);





fileSaveAsMenuItem = new MenuItem("Save &As",
new System.EventHandler(this.fileSaveAsMenuItem_Click));
fileMenuWithSubmenu = new MenuItem("&With Submenu");
submenuMenuItem = new MenuItem("Su&bmenu",
new System.EventHandler(this.submenuMenuItem_Click));
fileExitMenuItem = new MenuItem("E&xit",
new System.EventHandler(this.fileExitMenuItem_Click));




你不能给工具按钮指派一个事件处理器,但可以给工具条指派一个事件处理器:

toolBar.ButtonClick += new
ToolBarButtonClickEventHandler(this.toolBar_ButtonClick);
protected void toolBar_ButtonClick(Object sender, ToolBarButtonClickEventArgs
e) {
// Evaluate the Button property to determine which button was clicked.
switch (toolBar.Buttons.IndexOf(e.Button)) {
case 1:
MessageBox.Show("Second button.", "The Event Information");
break;
case 2:
MessageBox.Show("third button", "The Event Information");
break;
case 3:
MessageBox.Show("fourth button.", "The Event Information");
break;
}
}




例子中也给窗体的 close 事件设计了一个事件处理器,通过重载 OnClosing 方法你
可以接收用户点击窗体的 X 按钮,这样你可以取消关闭事件:

protected override void OnClosing(CancelEventArgs e) {
MessageBox.Show("Exit now.", "The Event Information");
}




现在我们的模板就完成了,你可以使用他开始你的 W

INDOWS 应用程序设计。

附录 Listing 1. C# Windows 应用程序模板


/*
to compile this source file, type
csc MyWinApp.cs
*/
using System;
using System.Windows.Forms;
using System.Drawing;
using System.IO;
using https://www.doczj.com/doc/8c7723023.html,ponentModel;
public class MyWinApp: Form {
Label label = new Label();
Button button = new Button();
TreeView tree = new TreeView();
ImageList imageList = new ImageList();
static String imageFolder = "Images" +
Path.DirectorySeparatorChar.ToString();
// -------------- Images declarations ------------------------------------
Image newFileImage = new Bitmap(imageFolder + "newFile.bmp");
Image openFileImage = new Bitmap(imageFolder + "openFile.gif");
Image saveFileImage = new Bitmap(imageFolder + "saveFile.bmp");
Image printImage = new Bitmap(imageFolder + "print.gif");
// -------------- End of Images declaration
------------------------------------
// -------------- menu ------------------------------------
MainMenu mainMenu = new MainMenu();
MenuItem fileMenuItem = new MenuItem();
MenuItem fileNewMenuItem;
MenuItem fileOpenMenuItem;
MenuItem fileSaveMenuItem;
MenuItem fileSaveAsMenuItem;
MenuItem fileMenuWithSubmenu;
MenuItem submenuMenuItem;
MenuItem fileExitMenuItem;
// -------------- End of menu ------------------------------------




// -------------- Toolbar ------------------------------------
ToolBar toolBar = new ToolBar();
ToolBarButton separatorToolBarButton = new ToolBarButton();
ToolBarButton newToolBarButton = new ToolBarButton();
ToolBarButton openToolBarButton = new ToolBarButton();
ToolBarButton saveToolBarButton = new ToolBarButton();
ToolBarButton printToolBarButton = new ToolBarButton();
// -------------- End of Toolbar ------------------------------------
// -------------- StatusBar ------------------------------------
StatusBar statusBar = new StatusBar();
StatusBarPanel statusBarPanel1 = new StatusBarPanel();
StatusBarPanel statusBarPanel2 = new StatusBarPanel();
// -------------- End of StatusBar ------------------------------------
public MyWinApp() {
InitializeComponent();
}
private void InitializeComponent() {
this.Text = "My Windows Application";
this.Icon = new Icon(imageFolder + "applicationLogo.ico");
this.Width = 400;
this.Height = 300;
this.StartPosition = FormStartPosition.CenterScreen;
imageList.Images.Add(newFileImage);
imageList.Images.Add(openFileImage);
imageList.Images.Add(saveFileImage);
imageList.Images.Add(printImage);
// menu
fileMenuItem.Text = "&File";
// the following constructor is the same as:
// menuItem fileNewMenuItem = new MenuItem();
// fileNewMenuItem.Text = "&New";
// fileNewMenuItem.Shortcut = Shortcut.CtrlN;




// fileNewMenuItem.Click += new
System.EventHandler(this.fileNewMenuItem_Click);
fileNewMenuItem = new MenuItem("&New",
new System.EventHandler(this.fileNewMenuItem_Click), Shortcut.CtrlN);
fileOpen

MenuItem = new MenuItem("&Open",
new System.EventHandler(this.fileOpenMenuItem_Click), Shortcut.CtrlO);
fileSaveMenuItem = new MenuItem("&Save",
new System.EventHandler(this.fileSaveMenuItem_Click), Shortcut.CtrlS);
fileSaveAsMenuItem = new MenuItem("Save &As",
new System.EventHandler(this.fileSaveAsMenuItem_Click));
fileMenuWithSubmenu = new MenuItem("&With Submenu");
submenuMenuItem = new MenuItem("Su&bmenu",
new System.EventHandler(this.submenuMenuItem_Click));
fileExitMenuItem = new MenuItem("E&xit",
new System.EventHandler(this.fileExitMenuItem_Click));
mainMenu.MenuItems.Add(fileMenuItem);
fileOpenMenuItem.Checked = true;
fileMenuItem.MenuItems.Add(fileNewMenuItem);
fileMenuItem.MenuItems.Add(fileOpenMenuItem);
fileMenuItem.MenuItems.Add(fileSaveMenuItem);
fileMenuItem.MenuItems.Add(fileSaveAsMenuItem);
fileMenuItem.MenuItems.Add(fileMenuWithSubmenu);
fileMenuWithSubmenu.MenuItems.Add(submenuMenuItem);
fileMenuItem.MenuItems.Add("-"); // add a separator
fileMenuItem.MenuItems.Add(fileExitMenuItem);
toolBar.Appearance = ToolBarAppearance.Normal;
//toolBar.Appearance = ToolBarAppearance.Flat;
toolBar.ImageList = imageList;
toolBar.ButtonSize = new Size(14, 6);
separatorToolBarButton.Style = ToolBarButtonStyle.Separator;
newToolBarButton.ToolTipText = "New Document";
newToolBarButton.ImageIndex = 0;




openToolBarButton.ToolTipText = "Open Document";
openToolBarButton.ImageIndex = 1;
saveToolBarButton.ToolTipText = "Save";
saveToolBarButton.ImageIndex = 2;
printToolBarButton.ToolTipText = "Print";
printToolBarButton.ImageIndex = 3;
toolBar.ButtonClick += new
ToolBarButtonClickEventHandler(this.toolBar_ButtonClick);
toolBar.Buttons.Add(separatorToolBarButton);
toolBar.Buttons.Add(newToolBarButton);
toolBar.Buttons.Add(openToolBarButton);
toolBar.Buttons.Add(saveToolBarButton);
toolBar.Buttons.Add(separatorToolBarButton);
toolBar.Buttons.Add(printToolBarButton);
tree.Top = 40;
tree.Left = 20;
tree.Width = 100;
tree.Height = 100;
label.Location = new Point(220, 40);
label.Size = new Size(160, 30);
label.Text = "Yes, click the button";
button.Location = new Point(220, 80);
button.Size = new Size(100, 30);
button.Text = "Click this";
button.Click += new System.EventHandler(this.button_Click);
statusBarPanel1.BorderStyle = StatusBarPanelBorderStyle.Sunken;
statusBarPanel1.Text = "Press F1 for Help";
statusBarPanel1.AutoSize = StatusBarPanelAutoSize.Spring;
statusBarPanel2.BorderStyle = StatusBarPanelBorderStyle.Raised;
statusBarPanel2.ToolTipText = System.DateTime.Now.ToShortTimeString();
statusBarPanel2.Text = System.DateTime.Today.ToLongDateString();
statusBarPanel2.AutoSize = StatusBarPanelAutoSize.Contents;
statusBar.ShowPanels = true;
statusBar.Panels.Add(statusBarPanel1);
statusBar.Panels.Add(statusBarPanel2);





this.Menu = mainMenu;
this.Controls.Add(toolBar);
this.Controls.Add(tree);
this.Controls

.Add(label);
this.Controls.Add(button);
this.Controls.Add(statusBar);
}
// -------------- Event Handlers --------------------------
private void fileNewMenuItem_Click(Object sender, EventArgs e) {
MessageBox.Show("You clicked the File -- New menu.", "The Event
Information");
}
private void fileOpenMenuItem_Click(Object sender, EventArgs e) {
MessageBox.Show("You clicked the File -- Open menu.", "The Event
Information");
}
private void fileSaveMenuItem_Click(Object sender, EventArgs e) {
MessageBox.Show("You clicked the File -- Save menu.", "The Event
Information");
}
private void fileSaveAsMenuItem_Click(Object sender, EventArgs e) {
MessageBox.Show("You clicked the File -- Save As menu.", "The Event
Information");
}
private void fileExitMenuItem_Click(Object sender, EventArgs e) {
MessageBox.Show("You clicked the File -- Exit As menu.", "The Event
Information");
}
private void submenuMenuItem_Click(Object sender, EventArgs e) {
MessageBox.Show("You clicked the submenu.", "The Event Information");
}




protected void toolBar_ButtonClick(Object sender,
ToolBarButtonClickEventArgs e) {
// Evaluate the Button property to determine which button was clicked.
switch (toolBar.Buttons.IndexOf(e.Button)) {
case 1:
MessageBox.Show("Second button.", "The Event Information");
break;
case 2:
MessageBox.Show("third button", "The Event Information");
break;
case 3:
MessageBox.Show("fourth button.", "The Event Information");
break;
}
}
protected override void OnClosing(CancelEventArgs e) {
MessageBox.Show("Exit now.", "The Event Information");
}
private void button_Click(Object sender, System.EventArgs e) {
MessageBox.Show("Thank you.", "The Event Information");
}
// -------------- End of Event Handlers -------------------
public static void Main() {
MyWinApp form = new MyWinApp();
Application.Run(form);
}
}






用 Visual C#实现文件下载功能

一.概述:
本文通过一个实例向大家介绍用 Visual C#进行 Internet 通讯编程的一些基本知识。
我们知道.Net 类包含了请求/响应层、应用协议层、传输层等层次。在本程序中,我们
运用了位于请求/响应层的 WebRequest 类以及 WebClient 类等来实现高抽象程度的
Internet 通讯服务。本程序的功能是完成网络文件的下载。
二.实现原理:
程序实现的原理比较简单,主要用到了 WebClient 类和 FileStream 类。其中
WebClient 类处于 https://www.doczj.com/doc/8c7723023.html, 名字空间中,该类的主要功能是提供向 URI 标识的资源
发送数据和从 URI 标识的资源接收数据的公共方法。我们利用其中的 DownloadFile()
方法将网络文件下载到本地。然后用 FileStream 类的实例对象以数据流的方式将文件
数据写入本地文件。这样就完成了网络文件的下载。
三.实现步骤:
首先,打开 Visual https://www.doczj.com/doc/8c7723023.html,,新建

一个 Visual C# Windows 应用程序的工程,不
妨命名为"MyGetCar"。
接着,布置主界面。我们先往主窗体上添加如下控件:两个标签控件、两个文本框控件、
一个按钮控件以及一个状态栏控件。最终的主窗体如下图所示:

.ü..?..?..?..?..?..





完成主窗体的设计,我们接着完成代码的编写。
在理解了基本原理的基础上去完成代码的编写是相当容易。程序中我们主要用到的
是WebClient 类,不过在我们调用WebClient 类的实例对象前,我们需要用WebRequest
类的对象发出对统一资源标识符(URI)的请求。

try
{
WebRequest myre=WebRequest.Create(URLAddress);




}
catch(WebException exp)
{
MessageBox.Show(exp.Message,"Error");
}




这是一个 try-catch 语句,try 块完成向 URI 的请求,catch 块则捕捉可能的异常并
显示异常信息。其中的 URLAddress 为被请求的网络主机名。
在请求成功后,我们就可以运用 WebClient 类的实例对象中的 DownloadFile()
方法实现文件的下载了。其函数原型如下:
public void DownloadFile( string address, string fileName);
其中,参数 address 为从中下载数据的 URI,fileName 为要接收数据的本地文件
的名称。
之后我们用 OpenRead()方法来打开一个可读的流,该流完成从具有指定 URI 的资
源下载数据的功能。其函数原型如下:

ublic Stream OpenRead(string address);




其中,参数 address 同上。
最后就是新建一个 StreamReader 对象从中读取文件的数据,并运用一个 while 循
环体不断读取数据,只到读完所有的数据。
还有在使用以上方法时,你将可能需要处理以下几种异常:
WebException:下载数据时发生错误。
UriFormatException:通过组合 BaseAddress、address 和 QueryString 所构
成的 URI 无效。
这部分的代码如下:(client 为 WebClient 对象,在本类的开头处声明即可)

statusBar.Text = "开始下载文件...";
client.DownloadFile(URLAddress,fileName);
Stream str = client.OpenRead(URLAddress);
StreamReader reader = new StreamReader(str);
byte[] mbyte = new byte[100000];
int allmybyte = (int)mbyte.Length;
int startmbyte = 0;
statusBar.Text = "正在接收数据...";
while(allmybyte>0)




{
int m = str.Read(mbyte,startmbyte,allmybyte);
if(m==0)
break;
startmbyte+=m;
allmybyte-=m;
}




完成了文件数据的读取工作后,我们运用 FileStream 类的实例对象将这些数据写
入本地文件中:

FileStream fstr = new
FileStream(Path,FileMode.OpenOrCreate,FileAccess.Write);
fstr.Write(mbyte,0,startmbyte);




这样,程序主体部分的代码已经完成了,不过要完成全部程序还需要一些工作。由
于在程序接收网络文件数据的时候运用到了 while 循环体,这样

会很占程序资源,表现
的形式就是主窗体不能自由移动。为了解决这个问题,我们在程序中用到了多线程机制。
我们在响应按钮的事件中新建一个线程,该线程就是用来实现网络文件下载功能的。如
此,文件下载的线程和程序主线程并存,共享进程资源,使得程序顺畅运行。这样,我
们在按钮控件的消息响应函数里添加如下代码:

Thread th = new Thread(new ThreadStart(StartDownload));
th.Start();




该线程的实现函数就是 StartDownload(),而上面介绍的那些代码就是这个函数的
主体部分。
最后,因为程序中运用到了 WebRequest、WebClient、FileStream、Thread 等类,
所以最重要的就是在程序的开始处添加如下名字空间:

using https://www.doczj.com/doc/8c7723023.html,;
using System.IO;
using System.Threading;



下面就是程序的源代码:

using System;
using System.Drawing;
using System.Collections;
using https://www.doczj.com/doc/8c7723023.html,ponentModel;
using System.Windows.Forms;
using System.Data;




///
/// 必需的设计器变量。
///
private https://www.doczj.com/doc/8c7723023.html,ponentModel.Container components = null;
public Form1()
{
//
// Windows 窗体设计器支持所必需的
//
InitializeComponent();
using https://www.doczj.com/doc/8c7723023.html,;
using System.IO;
using System.Threading;
namespace MyGetCar
{
///
/// Form1 的摘要说明。
///
public class Form1 : System.Windows.Forms.Form
{
private https://www.doczj.com/doc/8c7723023.html,bel label1;
private https://www.doczj.com/doc/8c7723023.html,bel label2;
private System.Windows.Forms.TextBox srcAddress;
private System.Windows.Forms.TextBox tarAddress;
private System.Windows.Forms.StatusBar statusBar;
private System.Windows.Forms.Button Start;
private WebClient client = new WebClient();
//
// TODO: 在 InitializeComponent 调用后添加任何构造函数代码
//
}
///
/// 清理所有正在使用的资源。
///
protected override void Dispose( bool disposing )
{
if( disposing )
{




this.Start = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// label1
//
https://www.doczj.com/doc/8c7723023.html,bel1.Location = new System.Drawing.Point(8, 32);
https://www.doczj.com/doc/8c7723023.html, = "label1";
https://www.doczj.com/doc/8c7723023.html,bel1.Size = new System.Drawing.Size(72, 23);
https://www.doczj.com/doc/8c7723023.html,bel1.TabIndex = 0;
https://www.doczj.com/doc/8c7723023.html,bel1.Text = "文件地址:";
https://www.doczj.com/doc/8c7723023.html,bel1.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
//
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#region Windows Form Designer generated code
///
/// 设计器支持所需的方法 - 不要使用代码编辑器修改
/// 此方法的内容。
///
private void InitializeComponent()
{
https://www.doczj.com/doc/8c7723023.html,bel1 = new https://www.doczj.com/doc/8c7723023.html,bel();
https://www.doczj.com/doc/8c7723023.html,bel2 = new https://www.doczj.com/doc/8c7723023.html,bel();
this.srcAddress = new System.Windows.Forms.TextBox();
this.tarAddress = new System.Windows.Forms.TextBox();
this.statusBar = new System.Windows.Forms.Status

Bar();
// label2
//
https://www.doczj.com/doc/8c7723023.html,bel2.Location = new System.Drawing.Point(8, 72);
https://www.doczj.com/doc/8c7723023.html, = "label2";
https://www.doczj.com/doc/8c7723023.html,bel2.Size = new System.Drawing.Size(72, 23);
https://www.doczj.com/doc/8c7723023.html,bel2.TabIndex = 1;
https://www.doczj.com/doc/8c7723023.html,bel2.Text = "另存到:";
https://www.doczj.com/doc/8c7723023.html,bel2.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
//
// srcAddress
//
this.srcAddress.Location = new System.Drawing.Point(80, 32);




// Start
//
this.Start.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.Start.Location = new System.Drawing.Point(216, 112);
https://www.doczj.com/doc/8c7723023.html, = "Start";
this.Start.Size = new System.Drawing.Size(75, 24);
this.Start.TabIndex = 5;
this.Start.Text = "开始下载";
this.Start.Click += new System.EventHandler(this.Start_Click);
//
// Form1
//
https://www.doczj.com/doc/8c7723023.html, = "srcAddress";
this.srcAddress.Size = new System.Drawing.Size(216, 21);
this.srcAddress.TabIndex = 2;
this.srcAddress.Text = "";
//
// tarAddress
//
this.tarAddress.Location = new System.Drawing.Point(80, 72);
https://www.doczj.com/doc/8c7723023.html, = "tarAddress";
this.tarAddress.Size = new System.Drawing.Size(216, 21);
this.tarAddress.TabIndex = 3;
this.tarAddress.Text = "";
//
// statusBar
//
this.statusBar.Location = new System.Drawing.Point(0, 151);
https://www.doczj.com/doc/8c7723023.html, = "statusBar";
this.statusBar.Size = new System.Drawing.Size(312, 22);
this.statusBar.TabIndex = 4;
//
this.AutoScaleBaseSize = new System.Drawing.Size(6, 14);
this.ClientSize = new System.Drawing.Size(312, 173);
this.Controls.AddRange(new System.Windows.Forms.Control[] {
this.Start,
this.statusBar,
this.tarAddress,
this.srcAddress,
https://www.doczj.com/doc/8c7723023.html,bel2,
https://www.doczj.com/doc/8c7723023.html,bel1});
this.MaximizeBox = false;
https://www.doczj.com/doc/8c7723023.html, = "Form1";
this.Text = "文件下载器";




string fileName = URL.Substring(n+1,URL.Length-n-1);
string Dir = tarAddress.Text;
string Path = Dir+'\\'+fileName;
try
{
WebRequest myre=WebRequest.Create(URLAddress);
}
catch(WebException exp)
{
MessageBox.Show(exp.Message,"Error");
}
this.ResumeLayout(false);
}
#endregion
///
/// 应用程序的主入口点。
///
[STAThread]
static void Main()
{
Application.Run(new Form1());
}
private void StartDownload()
{
Start.Enabled = false;
string URL = srcAddress.Text;
int n = https://www.doczj.com/doc/8c7723023.html,stIndexOf('/');
string URLAddress = URL.Substring(0,n);
try
{
statusBar.Text = "开始下载文件...";
client.DownloadFile(URLAddress,fileName);
Stream str = client.OpenRead(URLAddress);
StreamReader reader = new StreamReader(str);
byte[] mbyte = new byte[100000];
int allmybyte = (int)mbyte.Length;
int startmbyte = 0;
statusBar.Text = "正在接收数据...";
while(allmybyte>0)




statusBar.Text = "";
}
Start.Enabled = true;
}
private void Start_Click(object sender, System.EventArgs e)
{
Thread th = new Thread(new ThreadStart(StartDownload));
th.Start();
}
{
int m = str.Read(mbyte,startmbyte,allmybyte);
if(m==0)
break;
startmbyte+=

m;
allmybyte-=m;
}
FileStream fstr = new
FileStream(Path,FileMode.OpenOrCreate,FileAccess.Write);
fstr.Write(mbyte,0,startmbyte);
str.Close();
fstr.Close();
statusBar.Text = "下载完毕!";
}
catch(WebException exp)
{
MessageBox.Show(exp.Message,"Error");
}
}




程序完毕,运行程序图示如下:


.ü..?..?..?..?..?..
(开始下载文件时)





.ü..?..?..?..?..?..
(文件下载完毕时)




四.总结:
以上我通过一个实例向大家展示了如何用 Visual C#实现网络文件的下载,我们不
难发现用 Visual C#进行 Internet 通讯编程是非常方便的。在上面的程序中,我们仅仅
用到了 WebClient 类的一些方法,而 WebClient 类不光提供了网络文件下载的方法,还
提供了文件上传的方法,有兴趣的读者不妨一试――用之实现一个文件上传器。同时这
个程序只是一个非常简单的例子,程序下载完一个网页后,它所获得的仅仅是主页面的
内容,并不能获得其中的图片、CSS 等文件,所以要做出一个比较好的文件下载器还
需读者进一步改进之。



相关主题
文本预览
相关文档 最新文档