如何在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 等文件,所以要做出一个比较好的文件下载器还
需读者进一步改进之。