Visual C#编写屏幕保护程

 Visual C#是微软公司推出的新一代程序开发语言,是微软.Net框架中的一个重要组成部分。屏幕保护程序是以scr为扩展名的标准Windows可执行程序。屏幕保护程序不仅可以延长显示器的使用寿命,还可以保护私人信息。本文向大家介绍一个.Net平台上用C#编写的一个动态文本及图形的屏幕保护程序。

  一、具体实现步骤:

  (1)在Visual Studio.Net下新建一个C#的Windows应用程序工程,不妨命名为screen_saver

  (2)现在我们来设计程序的主界面:

  先将窗体的Name属性设置为screenText属性设置为空,BackColor属性设置为BlackSize属性设置为(800, 600) ControlBoxMaximizeBoxMinimizeBoxShowInTaskbar属性设置均为falseFormBorderStyle属性设置为None。再往窗体上添加Label控件、PictureBox控件、Timer控件各一个。将Label控件的Name设置为wordText属性设置为空;将PictureBox控件的Name设置为picture1Image设置为一个预知图片;将Timer控件的Name设置为timerSaverEnabled 属性设为trueInterval属性设为5

  (3)现在我们开始编写完整程序代码部分:

//导入使用到的名称空间

using System;

using System.Drawing;

using System.Collections;

using System.ComponentModel;

using System.Windows.Forms;

using System.Data;

file://

   namespace screen_saver

   {

///

/// Form1
的摘要说明。

///

public class screen : System.Windows.Forms.Form

{

 file://加入私有成员变量

 private System.ComponentModel.IContainer components;

 private int iSpeed = 2;

 private string str="福建南纺股份公司计算机中心";

 file://定义文本字体及大小

 private System.Drawing.Font TextStringFont = new System.Drawing.Font ("宋体”, 10,System.Drawing.FontStyle.Bold);

 private Color TextStringcolor =System.Drawing.Color.Yellow; file://文本字体颜色

 private int iDistance;

 private int ixStart= 0;

 private int iyStart= 0;

 private int speed;

 private int x1,y1;

 int width1,height1;

 private System.Windows.Forms.Timer timerSaver;  file://计时器控件

 private System.Windows.Forms.PictureBox picture1; file://图形控件

 private System.Windows.Forms.Label word; file://文本显示控件

///

///
必需的设计器变量。

///



 public screen()

 {

 file://

 // Windows 窗体设计器支持所必需的

 file://

  InitializeComponent();

  word.Font=TextStringFont;

  word.ForeColor=TextStringcolor;

  System.Windows.Forms.Cursor.Hide(); file://隐藏光标

  file://

  // TODO: InitializeComponent 调用后添加任何构造函数代码

  file://

 }

  ///

 /// 清理所有正在使用的资源。

 ///

 protected override void Dispose( bool disposing )

 {

  if( disposing )

  {

   if (components != null)

   {

    components.Dispose();

   }

  }

  base.Dispose( disposing );

 }

 #region Windows Form Designer generated code

 ///

 /// 设计器支持所需的方法 - 不要使用代码编辑器修改

 /// 此方法的内容。

 ///

 private void InitializeComponent() file://初始化程序中使用到的组件

 {

  this.components = new System.ComponentModel.Container();

  System.Resources.ResourceManager resources = new   system.Resources.ResourceManger(typeof(screen));      

  this.word = new System.Windows.Forms.Label();

  this.timerSaver = new System.Windows.Forms.Timer(this.components);

  this.picture1 = new System.Windows.Forms.PictureBox();

  this.SuspendLayout();

  //

  // 设置文本显示控件(word)属性

  this.word.ForeColor = System.Drawing.Color.Yellow;

  this.word.Location = new System.Drawing.Point(624, 8);

  this.word.Name = "word";

  this.word.Size = new System.Drawing.Size(168, 16);

  this.word.TabIndex = 0;

  this.word.Visible = false;

  //

  // 设置计时器控件(timerSaver)属性

  this.timerSaver.Enabled = true;

  this.timerSaver.Interval = 5;

  this.timerSaver.Tick += new System.EventHandler(this.timerSaver_Tick);

  //

  // 设置图片控件(picture1)属性

  this.picture1.Image = ((System.Drawing.Bitmap)(resources.GetObject("picture1.Image")));

  this.picture1.Location = new System.Drawing.Point(800, 600);

  this.picture1.Name = "picture1";

  this.picture1.Size = new System.Drawing.Size(304, 224);

  this.picture1.SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage;

  this.picture1.TabIndex = 1;

  this.picture1.TabStop = false;

  //

  // 设置窗体(screen)属性

  this.AutoScaleBaseSize = new System.Drawing.Size(6, 14);

  this.BackColor = System.Drawing.Color.Black;

  this.ClientSize = new System.Drawing.Size(800, 600);

  this.ControlBox = false;

  this.Controls.AddRange(new System.Windows.Forms.Control[] {this.picture1,this.word});

  this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;

  this.KeyPreview = true;

  this.MaximizeBox = false;

  this.MinimizeBox = false;

  this.Name = "screen";

  this.ShowInTaskbar = false;

  this.StartPosition = System.Windows.Forms.FormStartPosition.Manual;

  this.WindowState = System.Windows.Forms.FormWindowState.Maximized;

  file://键盘按下响应事件

  this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.screen_KeyDown);

            file://鼠标按下响应事件
 
  this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.screen_MouseDown);

  file://窗体启动调用事件

  this.Load += new System.EventHandler(this.Form1_Load);

            file://鼠标移动响应事件

  this.MouseMove += new System.Windows.Forms.MouseEventHandler(this.screen_MouseMove);

  this.ResumeLayout(false);

 }

 #endregion

 ///

 /// 应用程序的主入口点。

 ///

 [STAThread]

 static void Main(string[] args)

 {

  if(args.Length==1)

   if(args[0].Substring(0,2).Equals("/c"))

   {

    MessageBox.Show("没有设置项功能","C# Screen Saver");

    Application.Exit();

   }

   else if(args[0]=="/s")

   Application.Run(new screen());

  else if(args[0]=="/a")

  {

   MessageBox.Show("没有口令功能","C# Screen saver");

   Application.Exit();

  }

  else

  Application.Run(new screen());

 }



 private void Form1_Load(object sender, System.EventArgs e)

 {

  speed=0;

  System.Drawing.Rectangle ssWorkArea=System.Windows.Forms.Screen.GetWorkingArea(this);
  file://屏幕显示区域

  width1=ssWorkArea.Width; file://屏幕宽度

  height1=ssWorkArea.Height; file://屏幕高度

 }


 private void timerSaver_Tick(object sender, System.EventArgs e) file://计时器响应事件

 {

  word.Visible=true;

  word.Text=str;

  word.Height=word.Font.Height; file://设置文本的高度

  word.Width=word.Text.Length*(int)word.Font.Size*2; file://设置文本的宽度

  PlayScreenSaver();

 }

 private void PlayScreenSaver() file://自定义函数

 {

  file://下面设置文本显示框的位置坐标

  word.Location =new System.Drawing.Point(width1-iDistance,word.Location.Y);

  word.Visible=true; file://设置为可见

  iDistance+=iSpeed;

  if(word.Location.X<=-(word.Width))

  {

   iDistance=0;

   if(word.Location.Y==0)

    word.Location=new System.Drawing.Point(word.Location.X,height1/2);

   else if(word.Location.Y==height1/2)

    word.Location=new System.Drawing.Point(word.Location.X,height1-word.Height);

   else

    word.Location=new System.Drawing.Point(word.Location.X,0);

  }

  file://下面是计算图片框移动坐标

  speed++;

  if(speed<=2*height1)

  {

   x1=System.Math.Abs(width1-speed);

   y1=System.Math.Abs(height1-speed);

  }

  else if(speed>2*height1 && speed<=2*width1)

  {

   x1=System.Math.Abs(width1-speed);

   y1=System.Math.Abs(height1-(speed-speed/height1*height1));

  }

  else if(speed>2*width1 &&speed<=3*height1)

  {

   x1=System.Math.Abs(width1-(speed-speed/width1*width1));

   y1=System.Math.Abs(height1-(speed-speed/height1*height1));

  }

  else if(speed>3*height1 && speed<4*height1)

  {

   x1=System.Math.Abs(width1-(speed-speed/width1*width1));

   y1=System.Math.Abs(speed-speed/height1*height1);

  }

  else if(speed>=4*height1 && speed<5*height1)

  {

   x1=System.Math.Abs(speed-speed/width1*width1);

   y1=System.Math.Abs(height1-(speed-speed/height1*height1));

  }

  else if(speed>=5*height1 && speed<4*width1)

  {

   x1=System.Math.Abs(speed-speed/width1*width1);

   y1=System.Math.Abs(speed-speed/height1*height1);

  }

  else if(speed>=4*width1 && speed<6*height1)

  {

   x1=System.Math.Abs(width1-(speed-speed/width1*width1));

   y1=System.Math.Abs(speed-speed/height1*height1);

  }

  else if(speed>=6*height1 && speed<5*width1)

  {

   x1=System.Math.Abs(width1-(speed-speed/width1*width1));

   y1=System.Math.Abs(height1-(speed-speed/height1*height1));

  }

  else if(speed>=5*width1 && speed<7*height1)

  {

   x1=System.Math.Abs(speed-speed/width1*width1);

   y1=System.Math.Abs(height1-(speed-speed/height1*height1));

  }

  else if(speed>=7*height1 && speed<6*width1)

  {

   x1=System.Math.Abs(speed-speed/width1*width1);

   y1=System.Math.Abs(speed-speed/height1*height1);

  }

  if(speed==6*width1)

  speed=0;

  picture1.Location=new System.Drawing.Point(x1,y1);

 }

 private void StopScreenSaver() file://停止屏幕保护程序运行

 {

  System.Windows.Forms.Cursor.Show();

  timerSaver.Enabled=false;

  Application.Exit();

 }


 private void screen_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e) 
 file://鼠标移动事件

 {

  if(ixStart==0 && iyStart==0)

  {

   ixStart=e.X;

   iyStart=e.Y;

   return;

  }

  else if(e.X!=ixStart||e.Y!=iyStart)

  StopScreenSaver();

 }

 private void screen_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
 file://鼠标按下事件

 {

  StopScreenSaver(); file://停止运行屏幕保护程序

 }

 private void screen_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e) 
 file://键盘按下事件

 {

  StopScreenSaver(); file://停止运行屏幕保护程序

 }

}

}


  最后运行该程序,把screen_saver.exe改为screen_saver.scr,拷入Windows系统目录中,这样就可以运行该屏幕保护程序。

C#创建COM对象

在本篇文章中,我们将讨论下面的问题:

  ·使用C#创建一个简单的COM对象(使用COMInterop特性)。
  ·从VC++客户端软件中访问COM。客户端软件使用了TypeLibrary.TLB文件)。

  为了简单和方便开发人员使用、测试起见,我们使用了SQLSERVER数据库软件的缺省安装中的Northwind数据库。

  ·修改COM对象中SQLServer的名字,与SQLServer连接。
  ·我们已经创建了连接数据库用的分别为scotttiger的用户名和口令,我们可以使用它或者其他现有的用户名和口令。

  第一部分:用C#创建简单的COM对象

  COM对象是ClassLibrary类,它生成DLL文件。要在VS开发环境中创建一个简单的COM对象,我们可以依次选择“文件”->;“新创建”->;“工程”->;“VisualC#工程”->;“类库”,然后创建一个名字为Database_COMObject的工程。

  需要注意的是:在COM中调用VC#对象需要下面的条件:

  ·类必须是public性质。
  ·特性、方法和事件必须是public性质的。
  ·特性和方法必须在类接口中定义。
  ·事件必须在事件接口中定义。

  不是在这些接口中定义的public性质的类成员不能被COM访问,但它们可以被其他的.NET Framework对象访问。要让COM能够访问特性和方法,我们必须在类接口中定义它们,使它们具有DispId属性,并在类中实现这些特性和方法。这些成员定义时的顺序也就是它们在COM中顺序。要让COM访问类中的事件,必须在事件接口中定义这些事件,并赋予它们DispId属性。事件接口不应当由类完成,类只实现类接口(它可以实现不止一个接口,但第一个接口是缺省接口),应当在缺省接口中实现需要让COM访问的方法和特性,方法和特性必须被标识为public性质,并符合在类接口中的定义。需要让COM访问的事件也在缺省的类接口中完成,它们也必须被标识为public性质,并符合事件接口中的定义。

  在接口名字之前,每个接口需要一个GUID特性。要生成变个唯一的Guid,需要运行guidgen.exe工具软件,并选择“注册表格式” 下面是一个类界面:

[Guid"694C1820-04B6-4988-928F-FD858B95C880";)]
public interface DBCOM_Interface
{
[DispId
1]
void Init
string userid , string password);
[DispId
2]
bool ExecuteSelectCommand
string selCommand);
[DispId
3]
bool NextRow
();
[DispId
4]
void ExecuteNonSelectCommand
string insCommand);
[DispId
5]
string GetColumnData
int pos);
}

  COM事件接口:

// 事件接口Database_COMObjectEvents
[Guid
"47C976E0-C208-4740-AC42-41212D3C34F0";),
InterfaceType
ComInterfaceType.InterfaceIsIDispatch]
public interface DBCOM_Events
{
}

  下面是实际的类定义:

[Guid"9E5E5FB2-219D-4ee7-AB27-E4DBED8E123E";),
ClassInterface
ClassInterfaceType.None,
ComSourceInterfaces
typeofDBCOM_Events))]
public class DBCOM_Class : DBCOM_Interface
{

  需要注意的是,在类的前面,需要设置下面的特性:

ClassInterfaceClassInterfaceType.None,
ComSourceInterfaces
typeofDBCOM_Events))]

  ClassInterfaceType.None表示没有为该类生成类接口,如果没有明确地实现接口,类只能通过IDispatch提供后期绑定访问。用户希望通过明确地由类实现的接口使外部对象能够访问类的功能,这也是推荐的ClassInterfaceAttribute的设置。

  ComSourceInterfacestypeofDBCOM_Events))]确定许多作为COM事件向外部对象提供的接口。在本文的例子中,我们不对外部对象开放任何事件。

  下面是COM对象完整的源代码:

using System
using System.Runtime.InteropServices

using System.IO

using System.Text

using System.Data.SqlClient

using System.Windows.Forms


namespace Database_COMObject
{
[Guid
"694C1820-04B6-4988-928F-FD858B95C880";)]
public interface DBCOM_Interface
{
[DispId
1]
void Init
string userid , string password);
[DispId
2]
bool ExecuteSelectCommand
string selCommand);
[DispId
3]
bool NextRow
();
[DispId
4]
void ExecuteNonSelectCommand
string insCommand);
[DispId
5]
string GetColumnData
int pos);
}

//
事件接口Database_COMObjectEvents
[Guid
"47C976E0-C208-4740-AC42-41212D3C34F0";),
InterfaceType
ComInterfaceType.InterfaceIsIDispatch]
public interface DBCOM_Events
{
}


[Guid
"9E5E5FB2-219D-4ee7-AB27-E4DBED8E123E";),
ClassInterface
ClassInterfaceType.None,
ComSourceInterfaces
typeofDBCOM_Events))]
public class DBCOM_Class : DBCOM_Interface
{
private SqlConnection myConnection = null

SqlDataReader myReader = null


public DBCOM_Class
()
{
}

public void Init
string userid , string password
{
try
{
string myConnectString = "
user id="+userid+";;password="+password+
"
;;Database=NorthWindServer=SKYWALKERConnect Timeout=30";;
myConnection = new SqlConnection
myConnectString);
myConnection.Open
();
MessageBox.Show
"CONNECTED";);
}
catch
Exception e
{
MessageBox.Show
e.Message);
}
}

public bool ExecuteSelectCommand
string selCommand
{
if
myReader != null
myReader.Close
()

SqlCommand myCommand = new SqlCommand
selCommand);
myCommand.Connection = myConnection

myCommand.ExecuteNonQuery
();
myReader = myCommand.ExecuteReader
();
return true

}

public bool NextRow
()
{
if
! myReader.Read()
{
myReader.Close
();
return false

}
return true

}

public string GetColumnData
int pos
{
Object obj = myReader.GetValue
pos);
if
obj == null return ""
return obj.ToString
()
}

public void ExecuteNonSelectCommand
string insCommand
{
SqlCommand myCommand = new SqlCommand
insCommand , myConnection);
int retRows = myCommand.ExecuteNonQuery
();
}

}
}

  在创建COM对象前,我们必须向COM Interop注册该对象。右击方案管理器中的工程名字,点击快捷菜单上的“属性”选项,然后再点击“配置”->;“创建”,扩展output小节,将Register for COM Interop选项的值设置为true。这样,一个COM对象就能够与可管理性应用程序进行交互。

  为了使COM对象能够被外部对象调用,类库组合必须有一个强名字。创建强名字需要用到SN.EXE名字:

sn -k Database_COM_Key.snk
打开AssemblyInfo.cs,并修改下面一行的内容:
[assembly: AssemblyKeyFile
"Database_COM_Key.snk";)]

  创建对象。创建对象会生成一个可以被导入到可管理性或非可管理性代码中的类库。

  第二部分:使用Visual C++创建访问COM对象的客户端软件

  ·使用VC++开发环境创建一个简单的工程。
  ·使用#import directive导入类型库。
  ·在界面中创建一个Smart Pointer,从接口中执行COM类提供的功能。确保在应用程序加载时添加CoInitialize()调用:

CoInitializeNULL);

Database_COMObject::DBCOM_InterfacePtr p
__uuidofDatabase_COMObject::DBCOM_Class));
db_com_ptr = p

db_com_ptr-
>;Init"scott" , "tiger";);

  下面的代码对Customers数据库表执行一个SQL命令,返回给定ID的客户的信息:

char cmd[1024];
sprintf(cmd , ";SELECT COMPANYNAME , CONTACTNAME ,
CONTACTTITLE , ADDRESS FROM CUSTOMERS WHERE CUSTOMERID = '%s'"; , m_id );
const char *p ;

bool ret = db_com_ptr->;ExecuteSelectCommand(cmd);

if ( ! db_com_ptr->;NextRow() ) return ;

_bstr_t mData = db_com_ptr->;GetColumnData(3);
p = mData ;
m_address = (CString)p ;

C#设计Windows应用程序模板

通常windows应用程序都有相似的特征:控件、菜单、工具条、状态栏等等。每次我们开始作一个新的windows应用程序时都是以相同的事情开始:建立项目,添加控件和事件处理器。如果我们有一个模板,那么我们就可以节约大量的时间了。

  在介绍如何建立模板的过程中,将涉及大量的微软.net framework类库的基本知识。如果你没有使用集成开发环境那么本文介绍的模板对你将非常有用,如果你使用了visual studio.net这样的集成开发环境你也可以从中了解控件的工作方式,这对你也是很有用的。

  写一个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.Formsrun方法来开始应用程序和显示窗体。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,可以将初始化代码写入其中。

  使用widthheight属性设置宽度和高度

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操作系统这是正确的,但对于linuxunix则不然。这也是为了证明模板对于其他操作系统也是方便的。

  窗体的位置

  使窗体居中时非常有用的,要达到这个目的,需要使用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方法中设置这些控件的属性,尤其是设置控件的大小和在窗体中的位置,对于大小可以使用widthheight属性,比如treeview控件的大小可以使用下面的属性:

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


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

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


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

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类,但我们示范了如何使用MainMenuMenuItem类。我们首先需要在窗体中添加一个菜单,给窗体添加一个MainMenu对象。

MainMenu mainMenu = new MainMenu();


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

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


  我们在fileMenuItem 菜单项中还添加了其它MenuItem,这些MenuItemfileMenuItem的子菜单。你也可以给子菜单添加子菜单。图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);

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));


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);


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


  创建工具条

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

ImageList imageList = new ImageList();


  对于每个图像文件首先要实例化为image对象,然后将这些图像添加到ImageList控件中,ImageBitmap类可以在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控件赋给ToolBarImageList属性。

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.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.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");
}


  现在我们的模板就完成了,你可以使用他开始你的WINDOWS应用程序设计。


  附录 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 System.ComponentModel;

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);

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));


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);
}

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

C#处理鼠标和键盘事件

在程序运行中,产生事件的主体有很多,其中尤其以键盘和鼠标为最多。本文就来探讨一下在C#中和这二个主体相关的事件的处理过程。

一.本文介绍的程序设计和运行的软件环境:

   1.微软公司视窗2000服务器版

   2..Net FrameWork SDK Beta 2

二.C#中处理鼠标相关的事件:

   鼠标相关的事件大致有六种,分别是

    "MouseHover""MouseLeave""MouseEnter""MouseMove""MouseDown""MouseUp"

   1.如何在C#程序中定义这些事件:

   C#中是通过不同的Delegate来描述上述事件,其中描述"MouseHover""MouseLeave""MouseEnter"事件的Delegate"EventHandler",而描述后面的三个事件的Delegate"MouseEventHandler"来描述。这二个Delegate分别被封装在不同的命名空间,其中"EventHandler"被封装在"System"命名空间;"MouseEventHandler"被封装在"Syetem.Windows.Froms"命名空间中的。在为"MouseHover""MouseLeave""MouseEnter"事件通过数据的类是"EventArgs",他也被封装在"System"命名空间中;而为后面的三个事件提供数据的类是"MouseEventArgs",他却被封装在"Syetem.Windows.Froms"命名空间。以上这些就决定了在C#中定义这些事件和响应这些事件有着不同的处理办法。下面就来介绍这些不同点。


   对于上述的前三个事件,是用以下语法来定义的:

    "组件名称"."事件名称"+= new System.EventHandler"事件名称");

   下面是程序中具体实现代码:

    button1.MouseLeave += new Syetem.EvenHandlerbutton1_MLeave);

   在完成了事件的定义以后,就要在程序中加入响应此事件的代码,否则程序编译的时候会报错。下面是响应上面事件的基本结构。

private void button1_MLeave ( object sender , System.EventArgs e )
{
此处加入响应此事件的代码
}
   定义"MouseMove""MouseDown""MouseUp"事件的语法和前面介绍的三个事件大致相同,具体如下:

    "组件名称"."事件名称"+= new System.Windows.Forms. MouseEventHandler"事件名称");

   下面是程序中具体实现代码:

button1.MouseMove += new System.Windows.Forms.MouseEventHandler
button1_MMove);
   下面是响应上面事件的基本结构:

private void button1_MMove ( object sender , System.Windows.Forms. MouseEventArgs e )
{
此处加入响应此事件的代码
}
   注释:在上述程序中的"button1"是定义的一个按钮组件。
   2.鼠标相关事件中的典型问题处理办法:

   在掌握了C#中定义和鼠标相关的事件,我们就来探讨一下和鼠标相关事件的典型问题。其一是读取鼠标的当前位置;其二是判定到底是那个鼠标按键按动。

   判定鼠标的位置可以通过事件"MouseMove"来处理,在"MouseEventArgs"类中提供了二个属性"X""Y",来判定当前鼠标纵坐标和横坐标。而判定鼠标按键的按动情况,可以通过事件"MouseDown"来处理,并且在"MouseEventArgs"类中也提供了一个属性"Button"来判定鼠标按键情况。根据这些知识,可以得到用C#编写的读取鼠标当前位置和判定鼠标按键情况的程序代码。下面就是此代码(mouse.cs)和此代码编译后运行界面:


01:用C#读取鼠标位置和鼠标按键的程序运行界面
mouse.cs
的源程序代码如下:

using System ;
using System.Drawing ;
using System.Collections ;
using System.ComponentModel ;
using System.Windows.Forms ;
using System.Data ;
public class Form1 : Form
{
private System.ComponentModel.Container components = null ;

public Form1 ( )
{
file://
初始化窗体中的各个组件
InitializeComponent ( ) ;
}
file://
清除程序中使用过的资源
protected override void Dispose ( bool disposing )
{
if ( disposing )
{
if (components != null)
{
components.Dispose ( ) ;
}
}
base.Dispose ( disposing ) ;
}
private void InitializeComponent ( )
{
this.AutoScaleBaseSize = new System.Drawing.Size ( 6 , 14) ;
this.ClientSize = new System.Drawing.Size ( 292 , 273 ) ;
this.Name = "Form1" ;
this.Text = "C
#处理鼠标按动事件!" ;
file://
为鼠标按动定义一个事件处理过程"Form1_MouseDown"
this.MouseDown += new MouseEventHandler ( Form1_MouseDown ) ;
file://
为鼠标移动定义一个事件处理过程"Form1_MouseMove"
this.MouseMove += new MouseEventHandler ( Form1_OnMouseMove ) ;

}
static void Main ( )
{
Application.Run ( new Form1 ( ) ) ;
}
private void Form1_OnMouseMove ( object sender , MouseEventArgs e )
{
this.Text = "
当前鼠标的位置为:( " + e.X + " , " + e.Y + ")" ;
}

private void Form1_MouseDown ( object sender , MouseEventArgs e )
{
file://
响应鼠标的不同按键
if ( e.Button == MouseButtons.Left )
{
MessageBox.Show ( "
按动鼠标左键!" ) ;
}
if ( e.Button == MouseButtons.Middle )
{
MessageBox.Show ( "
按动鼠标中键!") ;
}
if ( e.Button == MouseButtons.Right )
{
MessageBox.Show ( "
按动鼠标右键!") ;
}
}
}

 

三.C#中处理和键盘相关的事件:

   C#中和键盘相关的事件相对比较少,大致就三种:"KeyDown""KeyUp""KeyPress"

   1.如何在C#程序中定义这些事件:

   C#中描述"KeyDown""KeyUp"的事件的Delegate"KeyEventHandler"。而描述"KeyPress"所用的Delegate"KeyPressEventHandler"。这二个Delegate都被封装在命名空间"Syetem.Windows.Froms"中。为"KeyDown""KeyUp"的事件提供数据的类是"KeyEventArgs"。而为"KeyPress"事件提供数据的类是"KeyPressEventArgs"。同样这二者也被封装在命名空间"Syetem.Windows.Froms"中。

   C#程序定义"KeyDown""KeyUp"事件的语法如下:

    "组件名称"."事件名称"+= new Syetem.Windows.Froms. KeyEventHandler"事件名称");

   下面是程序中具体实现代码:

button1. KeyUp += new Syetem.Windows.Froms. KeyEventHandlerbutton1_KUp);

  下面是响应上面事件的基本结构。

private void button1_KUp ( object sender , Syetem.Windows.Froms. KeyEventArgs e )
{
此处加入响应此事件的代码
}

  在C#程序定义"KeyPress"事件的语法如下:

    "组件名称"."事件名称"+= new Syetem.Windows.Froms. KeyPressEventHandler"事件名称");

   下面是程序中具体实现代码:

button1. KeyPress += new Syetem.Windows.Froms. KeyPressEventArgsbutton1_KPress);

  在完成了事件的定义以后,就要在程序中加入响应此事件的代码,否则程序编译的时候会报错。下面是响应上面事件的基本结构。

private void button1_KPress ( object sender , Syetem.Windows.Froms. KeyPressEventArgs e )
{
此处加入响应此事件的代码
}

  注释:程序中出现的"button1"是定义的一个按钮组件。
   2.和键盘相关事件中的典型问题处理办法:

   和键盘相关的典型问题无非就是判定到底是哪个按键被按动。通过上面的三个事件都可以完成。并且在"KeyEventArgs"类中通过了一个属性"KeyCode",可以用他来读取当前按键。所以就在"KeyUp"或者"KeyDown"事件中处理这个问题。根据上面这些知识,可以得到用C#编写读取读取按键的程序代码,下面就是此代码(key.cs)和此代码运行后的界面:


02:用C#读取键盘按键的程序运行界面

key.cs的代码如下:

using System ;
using System.Drawing ;
using System.Collections ;
using System.ComponentModel ;
using System.Windows.Forms ;
using System.Data ;
public class Form1 : Form
{
private System.ComponentModel.Container components = null ;

public Form1 ( )
{
file://
初始化窗体中的各个组件
InitializeComponent ( ) ;
}
protected override void Dispose ( bool disposing )
{
file://
清除程序中使用过的资源
if ( disposing )
{
if ( components != null )
{
components.Dispose ( ) ;
}
}
base.Dispose ( disposing ) ;
}
private void InitializeComponent ( )
{
this.AutoScaleBaseSize = new System.Drawing.Size ( 6 , 14 ) ;
this.ClientSize = new System.Drawing.Size ( 292 , 273 ) ;
this.Name = "Form1" ;
this.Text = "C
#处理键盘事件!" ;
file://
为按键的按动定义一个事件处理过程"Form1_KeyUp"
this.KeyUp += new KeyEventHandler ( this.Form1_KeyUp ) ;

}
static void Main ( )
{
Application.Run ( new Form1 ( ) ) ;
}
file://
显示你所按动的按键名称
private void Form1_KeyUp ( object sender , KeyEventArgs e )
{
MessageBox.Show ( e.KeyCode.ToString ( ) , "
您所按动的健为:" ) ;

}
}

四.总结:

   本文介绍了在C#中如何定义和鼠标和键盘相关的事件和在这些事件中一些典型问题的处理办法。虽然这些知识最为基本,但也最为重要,因为在程序设计中,这些问题和我们打交道的机会最多。当然和鼠标和键盘相关的事件和问题还有许多,可以参照根据上面的解决办法加以解决。

实战Visual C#数据库编程

[编者的话:]关于数据库编程,微软提供了一个统一的数据对象访问模型,在Visual Studio6.0中称为ADO,在.NET中则统一为ADO.NET,掌握ADO.NET就等于掌握了数据库编程的核心,因此有必要首先复习一下以前发表的《ADO.NET完全攻略》。

  针对数据库编程始终是程序设计语言的一个重要方面的内容,也是一个难点。数据库编程的内容十分丰富,但最为基本编程的也就是那么几点,譬如:连接数据库、得到需要的数据和针对数据记录的浏览、删除、修改、插入等操作。其中又以后面针对数据记录的数据操作为重点。本文就来着重探讨一下Visual C#数据库基本编程,即:如何浏览记录、修改记录、删除记录和插入记录。

  一.程序设计和运行的环境设置:

  (1.视窗2000服务器版

  (2.Microsoft Data Acess Component 2.6 以上版本 ( MDAC 2.6 )

  (3..Net FrameWork SDK Beta 2

  为了更清楚的说明问题,在数据库的选用上,采用了当前比较典型的数据库,一个是本地数据库Access 2000,另外一个是远程数据库Sql Server 2000。其中本地数据库名称为"db.mdb",在其中定义了一张数据表"person""person"表的数据结构如下表:

字段名称

字段类型

字段意思

id

数字

序号

xm

文本

姓名

xb

文本

性别

nl

文本

年龄

zip

文本

邮政编码


  远程数据库Sql Server 2000的数据库服务器名称为"Server1",数据库名称为"Data1",登陆的ID"sa",口令为空,在数据库也定义了一张"person"表,数据结构如上表。


  二.如何浏览数据:

  在《Visual C#的数据绑定》中,已经了解了如何把数据集中的某些字段绑定到WinForm组件的某个属性上,这样程序员就可以根据以WinForm组件的来定制数据显示的形式,并且此时的WinForm组件显示内容就可以随着记录指针的变化而改变。至此可见,浏览数据记录的关键就是如何改变记录指针。要实现这种操作,就要使用到BindingManagerBase类,此类的主要作用是管理对于那些实现了对同一个数据源进行绑定的对象。说的具体些,就是能够使得Windows窗体上的已经对同一数据源进行数据绑定的组件保持同步。在BindingManagerBase类中定义了一个属性"Position",通过这个属性就可以改变BindingManagerBase对象中的数据指针。创建BindingManagerBase对象必须要使用到BindingContext类,其实每一个由Control类中继承而得到的对象,都有单一的BindingContext对象,在大多数创建窗体中实现数据绑定组件的BindingManagerBase对象是使用Form类的BindingContext来得到。下列代码是以Access 2000数据库为模型,创建的一个名称为"myBind"BindingManagerBase对象。

//创建一个 OleDbConnection
string strCon = " Provider = Microsoft.Jet.OLEDB.4.0 ; Data Source = db.mdb" ;
OleDbConnection myConn = new OleDbConnection ( strCon ) ;
string strCom = " SELECT * FROM person " ;
file://
创建一个 DataSet
myDataSet = new DataSet ( ) ;

myConn.Open ( ) ;
file://
OleDbDataAdapter 得到一个数据集
OleDbDataAdapter myCommand = new OleDbDataAdapter ( strCom , myConn ) ;
file://
Dataset绑定books数据表
myCommand.Fill ( myDataSet , "person" ) ;
file://
关闭此OleDbConnection
myConn.Close ( ) ;
myBind = this.BindingContext [ myDataSet , "person" ] ;


  下列代码是以Sql Server 2000数据库为模型,创建一个名称为"myBind"BindingManagerBase对象。

// 设定数据连接字符串,此字符串的意思是打开Sql server数据库,服务器名称为server1,数据库为data1
string strCon = "Provider = SQLOLEDB.1 ; Persist Security Info = False ; User ID = sa ; Initial Catalog = data1 ; Data Source = server1 " ;
OleDbConnection myConn = new OleDbConnection ( strCon ) ;
myConn.Open ( ) ;
string strCom = " SELECT * FROM person " ;
file://
创建一个 DataSet
myDataSet = new DataSet ( ) ;
file://
OleDbDataAdapter 得到一个数据集
OleDbDataAdapter myCommand = new OleDbDataAdapter ( strCom , myConn ) ;
file://
Dataset绑定person数据表
myCommand.Fill ( myDataSet , " person " ) ;
file://
关闭此OleDbConnection
myConn.Close ( ) ;
myBind = this.BindingContext [ myDataSet , "person" ] ;


  得到了是同一数据源的BindingManagerBase对象,通过改变此对象的"Position"属性值,这样绑定数据的组件显示的数据就随之变化,从而实现导航数据记录。

  < I > .导航按钮"上一条"实现方法:

protected void GoPrevious ( object sender , System.EventArgs e )
{
if ( myBind.Position == 0 )
MessageBox.Show ( "
已经到了第一条记录!" , "信息提示!" , MessageBoxButtons.OK , MessageBoxIcon.Information ) ;
else
myBind.Position -= 1 ;
}


  < II > . 导航按钮"下一条"实现方法:

protected void GoNext ( object sender , System.EventArgs e )
{
if ( myBind.Position == myBind.Count -1 )
MessageBox.Show ( "
已经到了最后一条记录!", "信息提示!" , MessageBoxButtons.OK , MessageBoxIcon.Information ) ;
else
myBind.Position += 1 ;
}


  < III > . 导航按钮"至尾"实现方法:

protected void GoLast ( object sender , System.EventArgs e )
{
myBind.Position = myBind.Count - 1 ;
}
< IV > .
导航按钮"至首"实现方法:
protected void GoFirst ( object sender , System.EventArgs e )
{
myBind.Position = 0 ;
}


  注释:"Count"BindingManagerBase对象的另外一个重要的属性,是数据集记录的总数。


  三.实现删除记录:

  在对数据记录进行操作的时候,有二点必须十分清晰:

  其一:在对数据记录进行操作的时候,我想有一些程序员一定有这样一个疑惑,当对数据库服务器请求数据集的时候,就会产生"DataSet"对象,用以管理数据集,这样如果这些对数据库服务器的请求非常多,同样也就会产生很多的"DataSet"对象,达到一定时候必然会使得数据库服务器崩溃。这种想法是自然的,但和实际并不相符,因为"DataSet"对象并不是在服务器端产生的,而是在客户端产生的。所以面对众多的数据请求的时候对数据库服务器的影响并不十分太大。

  其二:记得在用Delphi编写三层数据模型的时候的,每一次对数据库的修改其实只是对第二层产生的数据集的修改,要真正修改数据库,还必须调用一个另外的方法。在用ADO.NET处理数据库的时候,虽然处理的直接对象是数据库,但此时"DataSet"对象中的内容并没有随之改变,而绑定的数据组件显示的数据又来源于"DataSet"对象,这样就会产生一个错觉,就是修改了的记录并没有修改掉,删除的记录并没有删除掉。所以对数据记录进行操作的时候,在修改数据库后,还要对"DataSet"对象进行必要的修改,这样才能保证"DataSet"对象和数据库内容一致、同步。下面代码是删除当前绑定组件显示的记录的程序代码,此代码是以Access 2000数据库为模板的:

protected void Delete_record ( object sender , System.EventArgs e )
{
DialogResult r = MessageBox.Show ( "
是否删除当前记录!" , "删除当前记录!" , MessageBoxButtons.YesNo , MessageBoxIcon.Question ) ;
int ss = ( int ) r ;
  if ( ss == 6 ) // 按动"确定"按钮
{
try{
file://
连接到一个数据库
string strCon = " Provider = Microsoft.Jet.OLEDB.4.0 ; Data Source = db.mdb " ;
OleDbConnection myConn = new OleDbConnection ( strCon ) ;
myConn.Open ( ) ;
string strDele = "DELETE FROM person WHERE id= " + t_id.Text ;
OleDbCommand myCommand = new OleDbCommand ( strDele , myConn ) ;
file://
从数据库中删除指定记录
myCommand.ExecuteNonQuery ( ) ;
file://
DataSet中删除指定记录
myDataSet.Tables [ "person" ] . Rows [ myBind.Position ] . Delete ( ) ;
myDataSet.Tables [ "person" ] . AcceptChanges ( ) ;
myConn.Close ( ) ;
}
catch ( Exception ed )
{
MessageBox.Show ( "
删除记录错误信息: " + ed.ToString ( ) , "错误!" ) ;
}
}
}


  下面代码是删除当前绑定组件显示的记录的程序代码,此代码是以Sql Server 2000数据库为模板的,二者的区别仅仅在于数据链接:

protected void Delete_record ( object sender , System.EventArgs e )
{
DialogResult r = MessageBox.Show ( "
是否删除当前记录!" , "删除当前记录!" , MessageBoxButtons.YesNo , MessageBoxIcon.Question ) ;
int ss = ( int ) r ;
  if ( ss == 6 ) // 按动"确定"按钮
{
try{
//
设定数据连接字符串,意思是打开Sql server数据库,服务器名称为server1,数据库为data1
string strCon = "Provider = SQLOLEDB.1 ; Persist Security Info = False ; User ID = sa ; Initial Catalog = data1 ; Data Source = server1 " ;
OleDbConnection myConn = new OleDbConnection ( strCon ) ;
myConn.Open ( ) ;
string strDele = "DELETE FROM person WHERE id= " + t_id.Text ;
OleDbCommand myCommand = new OleDbCommand ( strDele , myConn ) ;
file://
从数据库中删除指定记录
myCommand.ExecuteNonQuery ( ) ;
file://
DataSet中删除指定记录
myDataSet.Tables [ "person" ] . Rows [ myBind.Position ] . Delete ( ) ;
myDataSet.Tables [ "person" ] . AcceptChanges ( ) ;
myConn.Close ( ) ;
}
catch ( Exception ed )
{
MessageBox.Show ( "
删除记录错误信息: " + ed.ToString ( ) , "错误!" ) ;
}
}
}


  在这二段代码中,在更改数据库的同时也对"DatsSet"对象进行了必要的修改。
下图是程序中对数据记录进行删除操作的运行界面:


    

 


  四.插入数据记录:

  对数据库进行插入记录操作和删除记录操作基本的思路是一致的,就是通过ADO.NET首先插入数据记录到数据库,然后对"DataSet"对象进行必要的修改。下列代码就是以Access 2000数据库为模型修改当前记录的代码:

protected void Update_record ( object sender , System.EventArgs e )
{
int i = myBind.Position ;
try{
file://
连接到一个数据库
string strCon = " Provider = Microsoft.Jet.OLEDB.4.0 ; Data Source = db.mdb " ;
OleDbConnection myConn = new OleDbConnection ( strCon ) ;
myConn.Open ( ) ;
myDataSet.Tables [ "person" ] . Rows [ myBind.Position ] . BeginEdit ( ) ;
file://
从数据库中修改指定记录
string strUpdt = " UPDATE person SET xm = '"
+ t_xm.Text + "' , xb = '"
+ t_xb.Text + "' , nl = "
+ t_nl.Text + " , zip = "
+ t_books.Text + " WHERE id = " + t_id.Text ;
OleDbCommand myCommand = new OleDbCommand ( strUpdt , myConn ) ;
myCommand.ExecuteNonQuery ( ) ;
myDataSet.Tables [ "person" ] . Rows [ myBind.Position ] . EndEdit ( ) ;
myDataSet.Tables [ "person" ] . AcceptChanges ( ) ;
myConn.Close ( ) ;
}
catch ( Exception ed )
{
MessageBox.Show ( "
修改指定记录错误: " + ed.ToString ( ) , "错误!" ) ;
}
myBind.Position = i ;
}


  由于对Sql Server 2000数据记录修改操作和Access 2000数据记录修改操作的差异只在于不同的数据链接,具体的代码可以参考"删除数据记录"中的代码,在这里就不提供了。


  五.插入数据记录:

  和前面二种操作在思路是一致的,就是通过ADO.NET首先插入数据记录到数据库,然后对"DataSet"对象进行必要的修改。下列代码就是以Access 2000数据库为模型插入一条数据记录的代码

protected void Insert_record ( object sender , System.EventArgs e )
{
try
{
file://
判断所有字段是否添完,添完则执行,反之弹出提示
if ( t_id.Text != "" && t_xm.Text != "" && t_xb.Text != "" && t_nl.Text != "" && t_books.Text != "" )
{
string myConn1 = " Provider = Microsoft.Jet.OLEDB.4.0 ; Data Source = db.mdb" ;
OleDbConnection myConn = new OleDbConnection ( myConn1 ) ;
myConn.Open ( ) ;
string strInsert = " INSERT INTO person ( id , xm , xb , nl , zip ) VALUES ( " ;
strInsert += t_id.Text + ", '" ;
strInsert += t_xm.Text + "', '" ;
strInsert += t_xb.Text + "', " ;
strInsert += t_nl.Text + ", " ;
strInsert += t_books.Text + ")" ;
OleDbCommand inst = new OleDbCommand ( strInsert , myConn ) ;
inst.ExecuteNonQuery ( ) ;
myConn.Close ( ) ;
myDataSet.Tables [ "person" ] . Rows [ myBind.Position ] . BeginEdit ( ) ;
myDataSet.Tables [ "person" ] . Rows [ myBind.Position ] . EndEdit ( ) ;
myDataSet.Tables [ "person" ] . AcceptChanges ( ) ;
}
else
{
MessageBox.Show ( "
必须填满所有字段值!" , "错误!" ) ;
}
}
catch ( Exception ed )
{
MessageBox.Show ( "
保存数据记录发生 " + ed.ToString ( ) , "错误!" ) ;
}
}


  同样对Sql Server 2000数据库进行插入记录操作和Access 2000数据库插入记录操作的差异也只在于不同的数据链接,具体的代码可以参考"删除数据记录"中的代码,在这里就不提供了。


  六.Visual C#数据库编程的完成源代码和程序运行的主界面:

  掌握了上面要点,编写一个完整的数据库编程的程序就显得非常容易了,下面是Visual C#进行数据库编程的完整代码(Data01.cs),此代码是以Access 2000数据库为模型设计的,具体如下:

using System ;
using System.Drawing ;
using System.ComponentModel ;
using System.Windows.Forms ;
using System.Data.OleDb ;
using System.Data ;

public class Data : Form
{
private System.ComponentModel.Container components = null ;
private Button lastrec ;
private Button nextrec ;
private Button previousrec ;
private Button firstrec ;
private TextBox t_books ;
private TextBox t_nl ;
private ComboBox t_xb ;
private TextBox t_xm ;
private TextBox t_id ;
private Label l_books ;
private Label l_nl ;
private Label l_xb ;
private Label l_xm ;
private Label l_id ;
private Label label1 ;
private DataSet myDataSet ;
private Button button1 ;
private Button button2 ;
private Button button3 ;
private Button button4 ;
private BindingManagerBase myBind ;

public Data ( )
{
file://
连接到一个数据库
GetConnected ( ) ;
//
对窗体中所需要的内容进行初始化
InitializeComponent ( ) ;
}
file://
清除在程序中使用过的资源
protected override void Dispose( bool disposing )
{
if( disposing )
{
if ( components != null )
{
components.Dispose ( ) ;
}
}
base.Dispose( disposing ) ;
}
public static void Main ( )
{
Application.Run ( new Data ( ) ) ;
}
public void GetConnected ( )
{
try
{
file://
创建一个 OleDbConnection
string strCon = " Provider = Microsoft.Jet.OLEDB.4.0 ; Data Source = db.mdb" ;
OleDbConnection myConn = new OleDbConnection ( strCon ) ;
string strCom = " SELECT * FROM person " ;
file://
创建一个 DataSet
myDataSet = new DataSet ( ) ;

myConn.Open ( ) ;
file://
OleDbDataAdapter 得到一个数据集
OleDbDataAdapter myCommand = new OleDbDataAdapter ( strCom , myConn ) ;
file://
Dataset绑定books数据表
myCommand.Fill ( myDataSet , "person" ) ;
file://
关闭此OleDbConnection
myConn.Close ( ) ;
}
catch ( Exception e )
{
MessageBox.Show ( "
连接错误! " + e.ToString ( ) , "错误" ) ;
}
}

private void InitializeComponent ( )
{

file://添加控件,略

this.Name = "Data" ;
this.Text = "Visual C#的数据库编程!" ;
this.ResumeLayout(false) ;
myBind = this.BindingContext [ myDataSet , "person" ] ;
}
protected void New_record ( object sender , System.EventArgs e )
{

t_id.Text = ( myBind.Count + 1 ).ToString ( ) ;
t_xm.Text = "" ;
t_xb.Text = "" ;
t_nl.Text = "" ;
t_books.Text = "" ;

}
protected void Insert_record ( object sender , System.EventArgs e )
{
try
{
file://判断所有字段是否添完,添完则执行,反之弹出提示
if ( t_id.Text != "" && t_xm.Text != "" && t_xb.Text != "" && t_nl.Text != "" && t_books.Text != "" )
{
string myConn1 = " Provider = Microsoft.Jet.OLEDB.4.0 ; Data Source = db.mdb" ;
OleDbConnection myConn = new OleDbConnection ( myConn1 ) ;
myConn.Open ( ) ;
string strInsert = " INSERT INTO person ( id , xm , xb , nl , zip ) VALUES ( " ;
strInsert += t_id.Text + ", '" ;
strInsert += t_xm.Text + "', '" ;
strInsert += t_xb.Text + "', " ;
strInsert += t_nl.Text + ", " ;
strInsert += t_books.Text + ")" ;
OleDbCommand inst = new OleDbCommand ( strInsert , myConn ) ;
inst.ExecuteNonQuery ( ) ;
myConn.Close ( ) ;
myDataSet.Tables [ "person" ] . Rows [ myBind.Position ] . BeginEdit ( ) ;
myDataSet.Tables [ "person" ] . Rows [ myBind.Position ] . EndEdit ( ) ;
myDataSet.Tables [ "person" ] . AcceptChanges ( ) ;
}
else
{
MessageBox.Show ( "必须填满所有字段值!" , "错误!" ) ;
}
}
catch ( Exception ed )
{
MessageBox.Show ( "保存数据记录发生 " + ed.ToString ( ) , "错误!" ) ;
}
}

protected void Update_record ( object sender , System.EventArgs e )
{
int i = myBind.Position ;
try{
file://连接到一个数据库
string strCon = " Provider = Microsoft.Jet.OLEDB.4.0 ; Data Source = db.mdb " ;
OleDbConnection myConn = new OleDbConnection ( strCon ) ;
myConn.Open ( ) ;
myDataSet.Tables [ "person" ] . Rows [ myBind.Position ] . BeginEdit ( ) ;
file://从数据库中修改指定记录
string strUpdt = " UPDATE person SET xm = '"
+ t_xm.Text + "' , xb = '"
+ t_xb.Text + "' , nl = "
+ t_nl.Text + " , zip = "
+ t_books.Text + " WHERE id = " + t_id.Text ;
OleDbCommand myCommand = new OleDbCommand ( strUpdt , myConn ) ;
myCommand.ExecuteNonQuery ( ) ;
myDataSet.Tables [ "person" ] . Rows [ myBind.Position ] . EndEdit ( ) ;
myDataSet.Tables [ "person" ] . AcceptChanges ( ) ;
myConn.Close ( ) ;
}
catch ( Exception ed )
{
MessageBox.Show ( "修改指定记录错误: " + ed.ToString ( ) , "错误!" ) ;
}
myBind.Position = i ;
}


protected void Delete_record ( object sender , System.EventArgs e )
{
DialogResult r = MessageBox.Show ( "是否删除当前记录!" , "删除当前记录!" , MessageBoxButtons.YesNo , MessageBoxIcon.Question ) ;
int ss = ( int ) r ;
  if ( ss == 6 ) // 按动"确定"按钮
{
try{
file://连接到一个数据库
string strCon = " Provider = Microsoft.Jet.OLEDB.4.0 ; Data Source = db.mdb " ;
OleDbConnection myConn = new OleDbConnection ( strCon ) ;
myConn.Open ( ) ;
string strDele = "DELETE FROM person WHERE id= " + t_id.Text ;
OleDbCommand myCommand = new OleDbCommand ( strDele , myConn ) ;
file://从数据库中删除指定记录
myCommand.ExecuteNonQuery ( ) ;
file://从DataSet中删除指定记录
myDataSet.Tables [ "person" ] . Rows [ myBind.Position ] . Delete ( ) ;
myDataSet.Tables [ "person" ] . AcceptChanges ( ) ;
myConn.Close ( ) ;
}
catch ( Exception ed )
{
MessageBox.Show ( "删除记录错误信息: " + ed.ToString ( ) , "错误!" ) ;
}
}
}

file://按钮"尾记录"对象事件程序
protected void GoLast ( object sender , System.EventArgs e )
{
myBind.Position = myBind.Count - 1 ;
}

file://按钮"下一条"对象事件程序
protected void GoNext ( object sender , System.EventArgs e )
{
if ( myBind.Position == myBind.Count -1 )
MessageBox.Show ( "已经到了最后一条记录!", "信息提示!" , MessageBoxButtons.OK , MessageBoxIcon.Information ) ;
else
myBind.Position += 1 ;
}
file://按钮"上一条"对象事件程序
protected void GoPrevious ( object sender , System.EventArgs e )
{
if ( myBind.Position == 0 )
MessageBox.Show ( "已经到了第一条记录!" , "信息提示!" , MessageBoxButtons.OK , MessageBoxIcon.Information ) ;
else
myBind.Position -= 1 ;
}
file://按钮"首记录"对象事件程序
protected void GoFirst ( object sender , System.EventArgs e )
{
myBind.Position = 0 ;
}
}

 


 

  对于以Sql Server 2000数据库为模型的程序代码,只要把Data01.cs中的数据链接,即:

string myConn1 = " Provider = Microsoft.Jet.OLEDB.4.0 ; Data Source = db.mdb" ;

  改换成:

string strCon = "Provider = SQLOLEDB.1 ; Persist Security Info = False ; User ID = sa ; Initial Catalog = data1 ; Data Source = server1 " ;

  注释:此数据链接代表的意思是:打开Sql server数据库,服务器名称为server1,数据库为data1
就可以得到Visual C#针对Sql Server 2000数据库为模板编程的完成源程序代码了。所以本文就不再提供了。

  七.总结:

  数据库编程始终是程序编程内容中的一个重点和难点。而以上介绍的这些操作又是数据库编程中最为基本,也是最为重要的内容。那些复杂的编程无非是以上这些处理的若干个叠加。

C#数据库事务原理及实践()

什么是数据库事务

  数据库事务是指作为单个逻辑工作单元执行的一系列操作。

设想网上购物的一次交易,其付款过程至少包括以下几步数据库操作:

  · 更新客户所购商品的库存信息

  · 保存客户付款信息--可能包括与银行系统的交互

  · 生成订单并且保存到数据库中

  · 更新用户相关信息,例如购物数量等等

正常的情况下,这些操作将顺利进行,最终交易成功,与交易相关的所有数据库信息也成功地更新。但是,如果在这一系列过程中任何一个环节出了差错,例如在更新商品库存信息时发生异常、该顾客银行帐户存款不足等,都将导致交易失败。一旦交易失败,数据库中所有信息都必须保持交易前的状态不变,比如最后一步更新用户信息时失败而导致交易失败,那么必须保证这笔失败的交易不影响数据库的状态--库存信息没有被更新、用户也没有付款,订单也没有生成。否则,数据库的信息将会一片混乱而不可预测。

数据库事务正是用来保证这种情况下交易的平稳性和可预测性的技术。

  数据库事务的ACID属性

事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠。一个逻辑工作单元要成为事务,必须满足所谓的ACID(原子性、一致性、隔离性和持久性)属性:

  · 原子性

事务必须是原子工作单元;对于其数据修改,要么全都执行,要么全都不执行。通常,与某个事务关联的操作具有共同的目标,并且是相互依赖的。如果系统只执行这些操作的一个子集,则可能会破坏事务的总体目标。原子性消除了系统处理操作子集的可能性。

  · 一致性

事务在完成时,必须使所有的数据都保持一致状态。在相关数据库中,所有规则都必须应用于事务的修改,以保持所有数据的完整性。事务结束时,所有的内部数据结构(如 B 树索引或双向链表)都必须是正确的。某些维护一致性的责任由应用程序开发人员承担,他们必须确保应用程序已强制所有已知的完整性约束。例如,当开发用于转帐的应用程序时,应避免在转帐过程中任意移动小数点。

  · 隔离性

由并发事务所作的修改必须与任何其它并发事务所作的修改隔离。事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看中间状态的数据。这称为可串行性,因为它能够重新装载起始数据,并且重播一系列事务,以使数据结束时的状态与原始事务执行的状态相同。当事务可序列化时将获得最高的隔离级别。在此级别上,从一组可并行执行的事务获得的结果与通过连续运行每个事务所获得的结果相同。由于高度隔离会限制可并行执行的事务数,所以一些应用程序降低隔离级别以换取更大的吞吐量。

  · 持久性

 事务完成之后,它对于系统的影响是永久性的。该修改即使出现致命的系统故障也将一直保持。
DBMS
的责任和我们的任务

企业级的数据库管理系统(DBMS)都有责任提供一种保证事务的物理完整性的机制。就常用的SQL Server2000系统而言,它具备锁定设备隔离事务、记录设备保证事务持久性等机制。因此,我们不必关心数据库事务的物理完整性,而应该关注在什么情况下使用数据库事务、事务对性能的影响,如何使用事务等等。

本文将涉及到在.net框架下使用C#语言操纵数据库事务的各个方面。

  体验SQL语言的事务机制

作为大型的企业级数据库,SQL Server2000对事务提供了很好的支持。我们可以使用SQL语句来定义、提交以及回滚一个事务。

如下所示的SQL代码定义了一个事务,并且命名为"MyTransaction"(限于篇幅,本文并不讨论如何编写SQL语言程序,请读者自行参考相关书籍):

DECLARE @TranName VARCHAR(20)

SELECT @TranName = 'MyTransaction'
BEGIN TRANSACTION @TranNameGOUSE pubs
GO

UPDATE roysched
SET royalty = royalty * 1.10
WHERE title_id LIKE 'Pc%'
GO

COMMIT TRANSACTION MyTransaction
GO


这里用到了SQL Server2000自带的示例数据库pubs,提交事务后,将为所有畅销计算机书籍支付的版税增加 10%

打开SQL Server2000的查询分析器,选择pubs数据库,然后运行这段程序,结果显而易见。

可是如何在C#程序中运行呢?我们记得在普通的SQL查询中,一般需要把查询语句赋值给SalCommand.CommandText属性,这里也就像普通的SQL查询语句一样,将这些语句赋给SqlCommand.CommandText属性即可。要注意的一点是,其中的"GO"语句标志着SQL批处理的结束,编写SQL脚本是需要的,但是在这里是不必要的。我们可以编写如下的程序来验证这个想法:

//TranSql.csusing System;
using System.Data;
using System.Data.SqlClient;
namespace Aspcn
{
 public class DbTranSql
 {
  file://将事务放到SQL Server中执行
  public void DoTran()
  {
   file://建立连接并打开
   SqlConnection myConn=GetConn();myConn.Open();
   SqlCommand myComm=new SqlCommand();
   try
   {
    myComm.Connection=myConn;
    myComm.CommandText="DECLARE @TranName VARCHAR(20) ";
    myComm.CommandText+="SELECT @TranName = 'MyTransaction' ";
    myComm.CommandText+="BEGIN TRANSACTION @TranName ";
    myComm.CommandText+="USE pubs ";
    myComm.CommandText+="UPDATE roysched SET royalty = royalty * 1.10 WHERE title_id LIKE 'Pc%' ";
    myComm.CommandText+="COMMIT TRANSACTION MyTransaction ";
    myComm.ExecuteNonQuery();
   }
   catch(Exception err)
   {
    throw new ApplicationException("事务操作出错,系统信息:"+err.Message);
   }
   finally
   {
    myConn.Close();
   }
  }
  file://获取数据连接
  private SqlConnection GetConn()
  {
   string strSql="Data Source=localhost;Integrated Security=SSPI;user id=sa;password=";
   SqlConnection myConn=new SqlConnection(strSql);
   return myConn;
  }
 }

 public class Test
 {
  public static void Main()
  {
   DbTranSql tranTest=new DbTranSql();
   tranTest.DoTran();
   Console.WriteLine("事务处理已经成功完成。");
   Console.ReadLine();
  }
 }
}



  注意到其中的SqlCommand对象myComm,它的CommandText属性仅仅是前面SQL代码字符串连接起来即可,当然,其中的"GO"语句已经全部去掉了。这个语句就像普通的查询一样,程序将SQL文本事实上提交给DBMS去处理了,然后接收返回的结果(如果有结果返回的话)。

很自然,我们最后看到了输出"事务处理已经成功完成",再用企业管理器查看pubs数据库的roysched表,所有title_id字段以"PC"开头的书籍的royalty字段的值都增加了0.1倍。

这里,我们并没有使用ADO.net的事务处理机制,而是简单地将执行事务的SQL语句当作普通的查询来执行,因此,事实上该事务完全没有用到.net的相关特性。
了解.net中的事务机制

如你所知,在.net框架中主要有两个命名空间(namespace)用于应用程序同数据库系统的交互:System.Data.SqlClientSystem.Data.OleDb。前者专门用于连接Microsoft公司自己的SQL Server数据库,而后者可以适应多种不同的数据库。这两个命名空间中都包含有专门用于管理数据库事务的类,分别是System.Data.SqlClient.SqlTranscation类和System.Data.OleDb.OleDbTranscation类。

就像它们的名字一样,这两个类大部分功能是一样的,二者之间的主要差别在于它们的连接机制,前者提供一组直接调用 SQL Server 的对象,而后者使用本机 OLE DB 启用数据访问。 事实上,ADO.net 事务完全在数据库的内部处理,且不受 Microsoft 分布式事务处理协调器 (DTC) 或任何其他事务性机制的支持。本文将主要介绍System.Data.SqlClient.SqlTranscation类,下面的段落中,除了特别注明,都将使用System.Data.SqlClient.SqlTranscation类。

  事务的开启和提交

现在我们对事务的概念和原理都了然于心了,并且作为已经有一些基础的C#开发者,我们已经熟知编写数据库交互程序的一些要点,即使用SqlConnection类的对象的Open()方法建立与数据库服务器的连接,然后将该连接赋给SqlCommand对象的Connection属性,将欲执行的SQL语句赋给它的CommandText属性,于是就可以通过SqlCommand对象进行数据库操作了。对于我们将要编写的事务处理程序,当然还需要定义一个SqlTransaction类型的对象。并且看到SqlCommand对象的Transcation属性,我们很容易想到新建的SqlTransaction对象应该与它关联起来。

基于以上认识,下面我们就开始动手写我们的第一个事务处理程序。我们可以很熟练地写出下面这一段程序:

//DoTran.csusing System;
using System.Data;
using System.Data.SqlClient;
namespace Aspcn
{
 public class DbTran
 {
  file://执行事务处理
  public void DoTran()
  {
   file://建立连接并打开
   SqlConnection myConn=GetConn();
   myConn.Open();
   SqlCommand myComm=new SqlCommand();
   SqlTransaction myTran=new SqlTransaction();
   try
   {
    myComm.Connection=myConn;
    myComm.Transaction=myTran;
   
    file://定位到pubs数据库 
    myComm.CommandText="USE pubs";
    myComm.ExecuteNonQuery();

    file://更新数据
    file://将所有的计算机类图书
    myComm.CommandText="UPDATE roysched SET royalty = royalty * 1.10 WHERE title_id LIKE 'Pc%'";
    myComm.ExecuteNonQuery();//提交事务
    myTran.Commit();
   }
   catch(Exception err)
   {
    throw new ApplicationException("事务操作出错,系统信息:"+err.Message);
   }
   finally
   {
    myConn.Close();
   }
  }
  file://获取数据连接
  private SqlConnection GetConn()
  {
   string strSql="Data Source=localhost;Integrated Security=SSPI;user id=sa;password=";
   SqlConnection myConn=new SqlConnection(strSql);
   return myConn;
  }
 }

 public class Test{public static void Main()
 {
  DbTran tranTest=new DbTran();
  tranTest.DoTran();
  Console.WriteLine("事务处理已经成功完成。");
  Console.ReadLine();
 }
}
}


显然,这个程序非常简单,我们非常自信地编译它,但是,出乎意料的结果使我们的成就感顿时烟消云散:

error CS1501: 重载"SqlTransaction"方法未获取"0"参数

是什么原因呢?注意到我们初始化的代码:

SqlTransaction myTran=new SqlTransaction();


显然,问题出在这里,事实上,SqlTransaction类并没有公共的构造函数,我们不能这样新建一个SqlTrancaction类型的变量。在事务处理之前确实需要有一个SqlTransaction类型的变量,将该变量关联到SqlCommand类的Transcation属性也是必要的,但是初始化方法却比较特别一点。在初始化SqlTransaction类时,你需要使用SqlConnection类的BeginTranscation()方法:

SqlTransaction myTran; myTran=myConn.BeginTransaction();

  
该方法返回一个SqlTransaction类型的变量。在调用BeginTransaction()方法以后,所有基于该数据连接对象的SQL语句执行动作都将被认为是事务MyTran的一部分。同时,你也可以在该方法的参数中指定事务隔离级别和事务名称,如:

SqlTransaction myTran;
myTran=myConn.BeginTransaction(IsolationLevel.ReadCommitted,"SampleTransaction");

  
关于隔离级别的概念我们将在随后的内容中探讨,在这里我们只需牢记一个事务是如何被启动,并且关联到特定的数据链接的。

先不要急着去搞懂我们的事务都干了些什么,看到这一行:

myTran.Commit();


是的,这就是事务的提交方式。该语句执行后,事务的所有数据库操作将生效,并且为数据库事务的持久性机制所保持--即使系统在这以后发生致命错误,该事务对数据库的影响也不会消失。

对上面的程序做了修改之后我们可以得到如下代码(为了节约篇幅,重复之处已省略,请参照前文):

//DoTran.cs……

file://
执行事务处理
public void DoTran()
{
 file://建立连接并打开
 SqlConnection myConn=GetConn();
 myConn.Open();
 SqlCommand myComm=new SqlCommand();

 file://SqlTransaction myTran=new SqlTransaction();
 file://注意,SqlTransaction类无公开的构造函数

 SqlTransaction myTran;

 file://创建一个事务
 myTran=myConn.BeginTransaction();
 try
 {
  file://从此开始,基于该连接的数据操作都被认为是事务的一部分
  file://下面绑定连接和事务对象
  myComm.Connection=myConn;
  myComm.Transaction=myTran; file://定位到pubs数据库
  myComm.CommandText="USE pubs";
  myComm.ExecuteNonQuery();//更新数据
  file://将所有的计算机类图书
  myComm.CommandText="UPDATE roysched SET royalty = royalty * 1.10 WHERE title_id LIKE 'Pc%'";
  myComm.ExecuteNonQuery();
 
  file://提交事务
  myTran.Commit();
 }
 catch(Exception err)
 {
  throw new ApplicationException("事务操作出错,系统信息:"+err.Message);
  }
 finally
 {
  myConn.Close();
  }
}
……


到此为止,我们仅仅掌握了如何开始和提交事务。下一步我们必须考虑的是在事务中可以干什么和不可以干什么。

C#数据库事务原理及实践(下)

 另一个走向极端的错误

满怀信心的新手们可能为自己所掌握的部分知识陶醉不已,刚接触数据库库事务处理的准开发者们也一样,踌躇满志地准备将事务机制应用到他的数据处理程序的每一个模块每一条语句中去。的确,事务机制看起来是如此的诱人——简洁、美妙而又实用,我当然想用它来避免一切可能出现的错误——我甚至想用事务把我的数据操作从头到尾包裹起来。

看着吧,下面我要从创建一个数据库开始:

using System;
using System.Data;
using System.Data.SqlClient;

namespace Aspcn
{
 public class DbTran
 {
  file://执行事务处理
  public void DoTran()
  {
   file://建立连接并打开
   SqlConnection myConn=GetConn();
   myConn.Open();

   SqlCommand myComm=new SqlCommand();
   SqlTransaction myTran;

   myTran=myConn.BeginTransaction();

   file://下面绑定连接和事务对象
   myComm.Connection=myConn;
   myComm.Transaction=myTran;

   file://试图创建数据库TestDB
   myComm.CommandText="CREATE database TestDB";
   myComm.ExecuteNonQuery();

   file://提交事务
   myTran.Commit();
  }

  file://获取数据连接
  private SqlConnection GetConn()
  {
   string strSql="Data Source=localhost;Integrated Security=SSPI;user id=sa;password=";
   SqlConnection myConn=new SqlConnection(strSql);
   return myConn;
  }
 }

 public class Test
 {
  public static void Main()
  {
   DbTran tranTest=new DbTran();
   tranTest.DoTran();
   Console.WriteLine("事务处理已经成功完成。");
   Console.ReadLine();
  }
 }
}

//---------------

  未处理的异常: System.Data.SqlClient.SqlException: 在多语句事务内不允许使用 CREATE DATABASE 语句。

at System.Data.SqlClient.SqlCommand.ExecuteNonQuery()
at Aspcn.DbTran.DoTran()
at Aspcn.Test.Main()

注意,如下的SQL语句不允许出现在事务中:

ALTER DATABASE

修改数据库

BACKUP LOG

备份日志

CREATE DATABASE

创建数据库

DISK INIT

创建数据库或事务日志设备

DROP DATABASE

删除数据库

DUMP TRANSACTION

转储事务日志

LOAD DATABASE

装载数据库备份复本

LOAD TRANSACTION

装载事务日志备份复本

RECONFIGURE

更新使用 sp_configure 系统存储过程更改的配置选项的当前配置(sp_configure 结果集中的 config_value 列)值。

RESTORE DATABASE

还原使用BACKUP命令所作的数据库备份

RESTORE LOG

还原使用BACKUP命令所作的日志备份

UPDATE STATISTICS

在指定的表或索引视图中,对一个或多个统计组(集合)有关键值分发的信息进行更新

除了这些语句以外,你可以在你的数据库事务中使用任何合法的SQL语句。

  事务回滚

事务的四个特性之一是原子性,其含义是指对于特定操作序列组成的事务,要么全部完成,要么就一件也不做。如果在事务处理的过程中,发生未知的不可预料的错误,如何保证事务的原子性呢?当事务中止时,必须执行回滚操作,以便消除已经执行的操作对数据库的影响。

一般的情况下,在异常处理中使用回滚动作是比较好的想法。前面,我们已经得到了一个更新数据库的程序,并且验证了它的正确性,稍微修改一下,可以得到:

//RollBack.cs
using System;
using System.Data;
using System.Data.SqlClient;

namespace Aspcn
{
 public class DbTran
 {
  file://执行事务处理
  public void DoTran()
  {
   file://建立连接并打开
   SqlConnection myConn=GetConn();
   myConn.Open();

   SqlCommand myComm=new SqlCommand();
   SqlTransaction myTran;

   file://创建一个事务
   myTran=myConn.BeginTransaction();
   file://从此开始,基于该连接的数据操作都被认为是事务的一部分
   file://下面绑定连接和事务对象
   myComm.Connection=myConn;
   myComm.Transaction=myTran;

   try
   {
    file://定位到pubs数据库
    myComm.CommandText="USE pubs";
    myComm.ExecuteNonQuery();
   
    myComm.CommandText="UPDATE roysched SET royalty = royalty * 1.10 WHERE title_id LIKE 'Pc%'";
    myComm.ExecuteNonQuery();

    file://下面使用创建数据库的语句制造一个错误
    myComm.CommandText="Create database testdb";
    myComm.ExecuteNonQuery();

    myComm.CommandText="UPDATE roysched SET royalty = royalty * 1.20 WHERE title_id LIKE 'Ps%'";
    myComm.ExecuteNonQuery();

    file://提交事务
    myTran.Commit();
   }
   catch(Exception err)
   {
    myTran.Rollback();
    Console.Write("事务操作出错,已回滚。系统信息:"+err.Message);
   }
  }

  file://获取数据连接
  private SqlConnection GetConn()
  {
   string strSql="Data Source=localhost;Integrated Security=SSPI;user id=sa;password=";
   SqlConnection myConn=new SqlConnection(strSql);
   return myConn;
  }
 }
 public class Test
 {
  public static void Main()
  {
   DbTran tranTest=new DbTran();
   tranTest.DoTran();
   Console.WriteLine("事务处理已经成功完成。");
   Console.ReadLine();
  }
 }
}

首先,我们在中间人为地制造了一个错误——使用前面讲过的Create database语句。然后,在异常处理的catch块中有如下语句:

myTran.Rollback();

当异常发生时,程序执行流跳转到catch块中,首先执行的就是这条语句,它将当前事务回滚。在这段程序可以看出,在Create database之前,已经有了一个更新数据库的操作——将pubs数据库的roysched表中的所有title_id字段以“PC”开头的书籍的royalty字段的值都增加0.1倍。但是,由于异常发生而导致的回滚使得对于数据库来说什么都没有发生。由此可见,Rollback()方法维护了数据库的一致性及事务的原子性。

  使用存储点

事务只是一种最坏情况下的保障措施,事实上,平时系统的运行可靠性都是相当高的,错误很少发生,因此,在每次事务执行之前都检查其有效性显得代价太高——绝大多数的情况下这种耗时的检查是不必要的。我们不得不想另外一种办法来提高效率。

事务存储点提供了一种机制,用于回滚部分事务。因此,我们可以不必在更新之前检查更新的有效性,而是预设一个存储点,在更新之后,如果没有出现错误,就继续执行,否则回滚到更新之前的存储点。存储点的作用就在于此。要注意的是,更新和回滚代价很大,只有在遇到错误的可能性很小,而且预先检查更新的有效性的代价相对很高的情况下,使用存储点才会非常有效。

使用.net框架编程时,你可以非常简单地定义事务存储点和回滚到特定的存储点。下面的语句定义了一个存储点“NoUpdate”:

myTran.Save("NoUpdate");

当你在程序中创建同名的存储点时,新创建的存储点将替代原有的存储点。

在回滚事务时,只需使用Rollback()方法的一个重载函数即可:

  myTran.Rollback("NoUpdate");

下面这段程序说明了回滚到存储点的方法和时机:

using System;
using System.Data;
using System.Data.SqlClient;

namespace Aspcn
{
 public class DbTran
 { 
  file://执行事务处理
  public void DoTran()
  {
   file://建立连接并打开
   SqlConnection myConn=GetConn();
   myConn.Open();

   SqlCommand myComm=new SqlCommand();
   SqlTransaction myTran;

   file://创建一个事务
   myTran=myConn.BeginTransaction();
   file://从此开始,基于该连接的数据操作都被认为是事务的一部分
   file://下面绑定连接和事务对象
   myComm.Connection=myConn;
   myComm.Transaction=myTran;

   try
   {
    myComm.CommandText="use pubs";
    myComm.ExecuteNonQuery();

    myTran.Save("NoUpdate");

    myComm.CommandText="UPDATE roysched SET royalty = royalty * 1.10 WHERE title_id LIKE 'Pc%'";
    myComm.ExecuteNonQuery();

    file://提交事务
    myTran.Commit();
   }
   catch(Exception err)
   {
    file://更新错误,回滚到指定存储点
    myTran.Rollback("NoUpdate");
    throw new ApplicationException("事务操作出错,系统信息:"+err.Message);
   }
  }

  file://获取数据连接
  private SqlConnection GetConn()
  {
   string strSql="Data Source=localhost;Integrated Security=SSPI;user id=sa;password=";
   SqlConnection myConn=new SqlConnection(strSql);
   return myConn;
  }
 }
 public class Test
 {
  public static void Main()
  {
   DbTran tranTest=new DbTran();
   tranTest.DoTran();
   Console.WriteLine("事务处理已经成功完成。");
   Console.ReadLine();
  }
 }
}

很明显,在这个程序中,更新无效的几率是非常小的,而且在更新前验证其有效性的代价相当高,因此我们无须在更新之前验证其有效性,而是结合事务的存储点机制,提供了数据完整性的保证。

  隔离级别的概念

企业级的数据库每一秒钟都可能应付成千上万的并发访问,因而带来了并发控制的问题。由数据库理论可知,由于并发访问,在不可预料的时刻可能引发如下几个可以预料的问题:

  脏读:包含未提交数据的读取。例如,事务1 更改了某行。事务2 在事务1 提交更改之前读取已更改的行。如果事务1 回滚更改,则事务2 便读取了逻辑上从未存在过的行。

  不可重复读取:当某个事务不止一次读取同一行,并且一个单独的事务在两次(或多次)读取之间修改该行时,因为在同一个事务内的多次读取之间修改了该行,所以每次读取都生成不同值,从而引发不一致问题。

  幻象:通过一个任务,在以前由另一个尚未提交其事务的任务读取的行的范围中插入新行或删除现有行。带有未提交事务的任务由于该范围中行数的更改而无法重复其原始读取。

如你所想,这些情况发生的根本原因都是因为在并发访问的时候,没有一个机制避免交叉存取所造成的。而隔离级别的设置,正是为了避免这些情况的发生。事务准备接受不一致数据的级别称为隔离级别。隔离级别是一个事务必须与其它事务进行隔离的程度。较低的隔离级别可以增加并发,但代价是降低数据的正确性。相反,较高的隔离级别可以确保数据的正确性,但可能对并发产生负面影响。

根据隔离级别的不同,DBMS为并行访问提供不同的互斥保证。在SQL Server数据库中,提供四种隔离级别:未提交读、提交读、可重复读、可串行读。这四种隔离级别可以不同程度地保证并发的数据完整性:

隔离级别

不可重复读取

未提交读

提交读

可重复读

可串行读

可以看出,“可串行读”提供了最高级别的隔离,这时并发事务的执行结果将与串行执行的完全一致。如前所述,最高级别的隔离也就意味着最低程度的并发,因此,在此隔离级别下,数据库的服务效率事实上是比较低的。尽管可串行性对于事务确保数据库中的数据在所有时间内的正确性相当重要,然而许多事务并不总是要求完全的隔离。例如,多个作者工作于同一本书的不同章节。新章节可以在任意时候提交到项目中。但是,对于已经编辑过的章节,没有编辑人员的批准,作者不能对此章节进行任何更改。这样,尽管有未编辑的新章节,但编辑人员仍可以确保在任意时间该书籍项目的正确性。编辑人员可以查看以前编辑的章节以及最近提交的章节。这样,其它的几种隔离级别也有其存在的意义。

在.net框架中,事务的隔离级别是由枚举System.Data.IsolationLevel所定义的:

[Flags]
[Serializable]
public enum IsolationLevel

其成员及相应的含义如下:

Chaos

无法改写隔离级别更高的事务中的挂起的更改。

ReadCommitted

在正在读取数据时保持共享锁,以避免脏读,但是在事务结束之前可以更改数据,从而导致不可重复的读取或幻像数据。

ReadUncommitted

可以进行脏读,意思是说,不发布共享锁,也不接受独占锁。

RepeatableRead

在查询中使用的所有数据上放置锁,以防止其他用户更新这些数据。防止不可重复的读取,但是仍可以有幻像行。

Serializable

DataSet上放置范围锁,以防止在事务完成之前由其他用户更新行或向数据集中插入行。

Unspecified

正在使用与指定隔离级别不同的隔离级别,但是无法确定该级别。

显而意见,数据库的四个隔离级别在这里都有映射。

默认的情况下,SQL Server使用ReadCommitted(提交读)隔离级别。

关于隔离级别的最后一点就是如果你在事务执行的过程中改变了隔离级别,那么后面的命名都在最新的隔离级别下执行——隔离级别的改变是立即生效的。有了这一点,你可以在你的事务中更灵活地使用隔离级别从而达到更高的效率和并发安全性。

  最后的忠告

无疑,引入事务处理是应对可能出现的数据错误的好方法,但是也应该看到事务处理需要付出的巨大代价——用于存储点、回滚和并发控制所需要的CPU时间和存储空间。

本文的内容只是针对Microsoft SQL Server数据库的,对应于.net框架中的System.Data.SqlClient命名空间,对于使用OleDb的情形,具体的实现稍有不同,但这不是本文的内容,有兴趣的读者可以到.net中华网(www.aspcn.com)的论坛里找到答案。

Oracle数据库系统性能优化策略

一个数据库系统的生命周期可以分成设计、开发和成品三个阶段。在设计阶段进行数据库性能优化的成本最低,收益最大。在成品阶段进行数据库性能优化的成本最高,收益最小。数据库的优化可以通过对网络、硬件、操作系统、数据库参数和应用程序的优化来进行。最常见的优化手段就是对硬件的升级。据统计,对网络、硬件、操作系统、数据库参数进行优化所获得的性能提升,全部加起来只占数据库系统性能提升的40%左右,其余的60%系统性能提升来自对应用程序的优化。许多优化专家认为,对应用程序的优化可以得到80%的系统性能的提升。

一、数据库性能的优化

数据库设计是应用程序设计的基础,其性能直接影响应用程序的性能。数据库性能包括存储空间需求量的大小和查询响应时间的长短两个方面。为了优化数据库性能,需要对数据库中的表进行规范化。规范化的范式可分为第一范式、第二范式、第三范式、BCNF范式、第四范式和第五范式。一般来说,逻辑数据库设计会满足规范化的前3级标准,但由于满足第三范式的表结构容易维护且基本满足实际应用的要求。因此,实际应用中一般都按照第三范式的标准进行规范化。但是,规范化也有缺点:由于将一个表拆分成为多个表,在查询时需要多表连接,降低了查询速度。

由于规范化有可能导致查询速度慢的缺点,考虑到一些应用需要较快的响应速度,在设计表时应同时考虑对某些表进行反规范化。反规范化可以采用以下几种方法:

1. 分割表

分割表包括水平分割和垂直分割。

水平分割是按照行将一个表分割为多个表,这可以提高每个表的查询速度,但查询、更新时要选择不同的表,统计时要汇总多个表,因此应用程序会更复杂。

垂直分割是对于一个列很多的表,若某些列的访问频率远远高于其它列,就可以将主键和这些列作为一个表,将主键和其它列作为另外一个表。通过减少列的宽度,增加了每个数据页的行数,一次I/O就可以扫描更多的行,从而提高了访问每一个表的速度。但是由于造成了多表连接,所以应该在同时查询或更新不同分割表中的列的情况比较少的情况下使用。

2. 保留冗余列

当两个或多个表在查询中经常需要连接时,可以在其中一个表上增加若干冗余的列,以避免表之间的连接过于频繁。由于对冗余列的更新操作必须对多个表同步进行,所以一般在冗余列的数据不经常变动的情况下使用。

3. 增加派生列

派生列是由表中的其它多个列计算所得,增加派生列可以减少统计运算,在数据汇总时可以大大缩短运算时间。

二、应用程序性能的优化

应用程序的优化通常可分为两个方面:源代码和SQL语句。由于涉及到对程序逻辑的改变,源代码的优化在时间成本和风险上代价很高,而对数据库系统性能的提升收效有限,因此应用程序的优化应着重在SQL语句的优化。对于海量数据,劣质SQL语句和优质SQL语句之间的速度差别可以达到上百倍,可见对于一个系统不是简单地能实现其功能就行,而是要写出高质量的SQL语句,提高系统的可用性。

下面就某些SQL语句的where子句编写中需要注意的问题作详细介绍。在这些where子句中,即使某些列存在索引,但是由于编写了劣质的SQL,系统在运行该SQL语句时也不能使用该索引,而同样使用全表扫描,这就造成了响应速度的极大降低。

1. IS NULL IS NOT NULL

不能用null作索引,任何包含null值的列都将不会被包含在索引中。即使索引有多列的情况下,只要这些列中有一列含有null,该列就会从索引中排除。也就是说如果某列存在空值,即使对该列建索引也不会提高性能。

任何在where子句中使用is nullis not null的语句优化器是不允许使用索引的。

2. 联接列

对于有联接的列,即使最后的联接值为一个静态值,优化器不会使用索引的。例如,假定有一个职工表(employee),对于一个职工的姓和名分成两列存放(FIRST_NAMELAST_NAME),现在要查询一个叫乔治•布什(George Bush)的职工。 下面是一个采用联接查询的SQL语句:

select * from employee where first_name||''||last_name ='George Bush';

上面这条语句完全可以查询出是否有George Bush这个员工,但是这里需要注意,系统优化器对基于last_name创建的索引没有使用。

当采用下面这种SQL语句的编写,Oracle系统就可以采用基于last_name创建的索引:

Select * From employee where first_name ='George' and last_name ='Bush';

遇到下面这种情况又如何处理呢?如果一个变量(name)中存放着George Bush这个员工的姓名,对于这种情况我们又如何避免全程遍历使用索引呢?可以使用一个函数,将变量name中的姓和名分开就可以了,但是有一点需要注意,这个函数是不能作用在索引列上。下面是SQL查询脚本:

select * from employee where first_name = SUBSTR('&&name',1,INSTR('&&name',' ')-1)

and last_name = SUBSTR('&&name',INSTR('&&name,' ')+1) ;

3. 带通配符(%)的like语句

同样以上面的例子来看这种情况。目前的需求是这样的,要求在职工表中查询名字中包含Bush的人。可以采用如下的查询SQL语句:

select * from employee where last_name like '%Bush%';

这里由于通配符(%)在搜寻词首出现,所以Oracle系统不使用last_name的索引。在很多情况下可能无法避免这种情况,但是一定要心中有底,通配符如此使用会降低查询速度。然而当通配符出现在字符串其他位置时,优化器就能利用索引。例如,在下面的查询中索引得到了使用:

select * from employee where last_name like 'c%';

4. Order by语句

Order by语句决定了Oracle如何将返回的查询结果排序。Order by语句对要排序的列没有什么特别的限制,也可以将函数加入列中(象联接或者附加等)。任何在Order by语句的非索引项或者有计算表达式都将降低查询速度。

仔细检查order by语句以找出非索引项或者表达式,它们会降低性能。解决这个问题的办法就是重写order by语句以使用索引,也可以为所使用的列建立另外一个索引,同时应绝对避免在order by子句中使用表达式。

5. NOT

我们在查询时经常在where子句使用一些逻辑表达式,如大于、小于、等于以及不等于等等,也可以使用and(与)、or(或)以及not(非)。NOT可用来对任何逻辑运算符号取反。下面是一个NOT子句的例子:

... where not (status ='VALID')

如果要使用NOT,则应在取反的短语前面加上括号,并在短语前面加上NOT运算符。NOT运算符包含在另外一个逻辑运算符中,这就是不等于(<>)运算符。换句话说,即使不在查询where子句中显式地加入NOT词,NOT仍在运算符中,见下例:

... where status <>'INVALID';

再看下面这个例子:

select * from employee where salary<>3000;

对这个查询,可以改写为不使用NOT的语句:

select * from employee where salary<3000 or salary>3000;

虽然这两种查询的结果一样,但是第二种查询方案会比第一种查询方案更快些。第二种查询允许Oraclesalary列使用索引,而第一种查询则不能使用索引。

6. INEXISTS

有时候会将一列和一系列值相比较。最简单的办法就是在where子句中使用子查询。在where子句中可以使用两种格式的子查询。

第一种格式是使用IN操作符: ... where column in(select * from ... where ...);

第二种格式是使用EXIST操作符: ... where exists (select 'X' from ...where ...);

绝大多数人会使用第一种格式,因为它比较容易编写,而实际上第二种格式要远比第一种格式的效率高。在Oracle中可以将几乎所有的IN操作符子查询改写为使用EXISTS的子查询。

第二种格式中,子查询以‘select 'X'’开始。运用EXISTS子句不管子查询从表中抽取什么数据它只查看where子句。这样优化器就不必遍历整个表而仅根据索引就可完成工作(这里假定在where语句中使用的列存在索引)。相对于IN子句来说,EXISTS使用相连子查询,构造起来要比IN子查询困难一些。

通过使用EXISTSOracle系统会首先检查主查询,然后运行子查询直到找到第一个匹配项,这就节省了时间。Oracle系统在执行IN子查询时,首先执行子查询,并将获得的结果列表存放在一个加了索引的临时表中。在执行子查询之前,系统先将主查询挂起,待子查询执行完毕,存放在临时表中以后再执行主查询。这也就是使用EXISTS比使用IN通常查询速度快的原因。

同时应尽可能使用NOT EXISTS来代替NOT IN,尽管二者都使用了NOT(不能使用索引而降低速度),但NOT EXISTS要比NOT IN查询效率更高。

(作者单位:中国银行福建省分行信息科技部)

Visual C#的Excel编程

Excel是微软公司办公自动化套件中的一个软件,他主要是用来处理电子表格。Excel以其功能强大,界面友好等受到了许多用户的欢迎。在办公的时候,正是由于Excel的这么多的优点,许多重要的数据,往往以Excel电子表格的形式存储起来。这样就给程序员带来了一个问题,虽然Excel功能比较强大,但毕竟不是数据库,在程序中处理数据库中的数据比其处理Excel表格中的数据容易许多。那么如何用Visual C#读取Excel表格中的数据?在以前用Delphi编程的时候,对于不同的用户,他们对于打印的需求是不一样的,如果要使得程序中的打印功能适用于每一个用户,可以想象程序设计是十分复杂的。这时想到Excel,由于Excel表格的功能强大,又由于几乎每一台机器都安装了它,如果把程序处理的结果放到Excel表格中,这样每一个用户就可以根据自己的需要在Excel中定制自己的打印。这样不仅使得程序设计简单,而且又满足了诸多用户的要求,更加实用了。那么用Visual C#如何调用Excel,如何又把数据存放到Excel表格中?本文就来探讨一下上述问题的解决办法。

 一.程序设计及运行环境

    1.微软视窗2000 服务器版

    2..Net Framework SDK Beta 2

    3.Microsoft Data Access Component 2.6以上版本(MDAC2.6

    4.Office 2000套件

二.Visual C#读取Excel表格中的数据:

   本节将通过一个程序来介绍Visual C#读取Excel表格中的数据,并把数据以DataGrid的形式显示出来。

   1.如何读取数据:

   其实读取Excel表格中的数据和读取数据库中的数据是非常类似的,因为在某种程度上Excel表格可以看成是一张一张的数据表。其二者的主要区别在于所使用的数据引擎不一样。在本文的程序中,通过下列代码实现读取Excel表格数据,具体如下:

//创建一个数据链接
string strCon = " Provider = Microsoft.Jet.OLEDB.4.0 ; Data Source = c:\\sample.xls;Extended Properties=Excel 8.0" ;
OleDbConnection myConn = new OleDbConnection ( strCon ) ;
string strCom = " SELECT * FROM [Sheet1$] " ;
myConn.Open ( ) ;
file://
打开数据链接,得到一个数据集
OleDbDataAdapter myCommand = new OleDbDataAdapter ( strCom , myConn ) ;
file://
创建一个 DataSet对象
myDataSet = new DataSet ( ) ;
file://
得到自己的DataSet对象
myCommand.Fill ( myDataSet , "[Sheet1$]" ) ;
file://
关闭此数据链接
myConn.Close ( ) ;


   怎么样读取Excel表格中的数据其实和读取数据库中的数据没有什么实质上的区别。

   注释:这里读取的是C盘根目录下的"Sample.xls"文件。

   2.DataGrid来显示得到的数据集:

   在得到DataSet对象后,只需要通过下列二行代码,就可以把数据集用DataGrid显示出来了:

DataGrid1.DataMember= "[Sheet1$]" ;
DataGrid1.DataSource = myDataSet ;

  (3.Visual C#读取Excel表格,并用DataGrid显示出来的程序代码(Read.cs)和程序运行的界面:

   掌握了上面二点,水到渠成就可以得到以下代码:

using System ;
using System.Drawing ;
using System.Collections ;
using System.ComponentModel ;
using System.Windows.Forms ;
using System.Data ;
using System.Data.OleDb ;
public class Form1 : Form
{
private Button button1 ;
private System.Data.DataSet myDataSet ;
private DataGrid DataGrid1 ;
private System.ComponentModel.Container components = null ;

public Form1 ( )
{
file://
初始化窗体中的各个组件
InitializeComponent ( ) ;
file://
打开数据链接,得到数据集
GetConnect ( ) ;
}
file://
清除程序中使用过的资源
protected override void Dispose ( bool disposing )
{
if ( disposing )
{
if ( components != null )
{
components.Dispose ( ) ;
}
}
base.Dispose ( disposing ) ;
}

private void GetConnect ( )
{
file://
创建一个数据链接
string strCon = " Provider = Microsoft.Jet.OLEDB.4.0 ; Data Source = c:\\sample.xls;Extended Properties=Excel 8.0" ;
OleDbConnection myConn = new OleDbConnection ( strCon ) ;
string strCom = " SELECT * FROM [Sheet1$] " ;
myConn.Open ( ) ;
file://
打开数据链接,得到一个数据集
OleDbDataAdapter myCommand = new OleDbDataAdapter ( strCom , myConn ) ;
file://
创建一个 DataSet对象
myDataSet = new DataSet ( ) ;
file://
得到自己的DataSet对象
myCommand.Fill ( myDataSet , "[Sheet1$]" ) ;
file://
关闭此数据链接
myConn.Close ( ) ;
}
private void InitializeComponent ( )
{
DataGrid1 = new DataGrid ( ) ;
button1 = new Button ( ) ;
SuspendLayout ( ) ;
DataGrid1.Name = "DataGrid1";
DataGrid1.Size = new System.Drawing.Size ( 400 , 200 ) ;

button1.Location = new System.Drawing.Point ( 124 , 240 ) ;
button1.Name = "button1" ;
button1.TabIndex = 1 ;
button1.Text = "
读取数据" ;
button1.Size = new System.Drawing.Size (84 , 24 ) ;
button1.Click += new System.EventHandler ( this.button1_Click ) ;

this.AutoScaleBaseSize = new System.Drawing.Size ( 6 , 14 ) ;
this.ClientSize = new System.Drawing.Size ( 400 , 280 ) ;
this.Controls.Add ( button1 ) ;
this.Controls.Add ( DataGrid1 ) ;
this.Name = "Form1" ;
this.Text = "
读取Excle表格中的数据,并用DataGrid显示出来!" ;
this.ResumeLayout ( false ) ;

}
private void button1_Click ( object sender , System.EventArgs e )
{
DataGrid1.DataMember= "[Sheet1$]" ;
DataGrid1.DataSource = myDataSet ;

}
static void Main ( )
{
Application.Run ( new Form1 ( ) ) ;
}
}


   下图是程序编译后,运行结果:


01:用Visual C#读取"c:\sample.xls"的运行界面

  (4.总结:

   以上只是读取了Excel表格中"Sheet1"中的数据,对于其他"Sheet"中的内容,可以参照读取"Sheet1"中的程序,只作一点修改就可以了,譬如要读取"Sheet2"中的内容,只需要把"Read.cs"程序中的"Sheet1$"改成"Sheet2$"就可以了。
三.Visual C#调用Excel表格,并在Excel表格中存储数据:

   Visual C#中调用Excel表格,并不像读取Excel表格中的数据那么容易了,因为在Visual C#中调用Excel表格要使用到ExcelCOM组件。如果你安装Office套件在"C"盘,那么在"C:\Program Files\Microsoft Office\Office"可以找到这个COM组件"EXCEL9.OLB",在《Visual C#如何使用Active X组件》一文中,这些COM组件都是非受管代码的,要在Visual C#中使用这些非受管代码的COM组件,就必须把他们转换成受管代码的类库。所以在用Visual C#调用Excel表格之前,必须完成从COM组件的非受管代码到受管代码的类库的转换。

   1.非受管代码COM组件转换成受管代码的类库:

   首先把COM组件"EXCEL9.OLB"拷贝到C盘的根目录下,然后输入下列命令:

tlbimp excel9.olb

  这样在C盘的根目录下面就产生了三个DLL文件:"Excel.dll""Office.dll""VBIDE.dll"。在产生了上面的三个文件后,这种转换就成功完成了。在下面的程序中,就可以利用这转换好的三个类库编写和Excel表格相关的各种操作了。

   2.Visual C#打开Excel表格:

   "Excel.dll"中定义了一个命名空间"Excel",在差命名空间中封装了一个类"Application",这个类和启动Excel表格有非常重要的关系,在Visual C#中,只需要下列三行代码就可以完成打开Excel表格的工作,具体如下:

Excel.Application excel = new Excel.Application ( ) ;
excel.Application.Workbooks.Add ( true ) ;

excel.Visible = true ;

  但此时的Excel表格是一个空的表格,没有任何内容,下面就来介绍如何往Excel表格中输入数据。

   3.Excel表格中输入数据:

   在命名空间"Excel"中,还定义了一个类"Cell",这个类所代表的就是Excel表格中的一个下单元。通过给差"Cell"赋值,从而实现往Excel表格中输入相应的数据,下列代码功能是打开Excel表格,并且往表格输入一些数据。

Excel.Application excel = new Excel.Application ( ) ;
excel.Application.Workbooks.Add ( true ) ;
excel.Cells[ 1 , 1 ] = "
第一行第一列" ;
excel.Cells[ 1 , 2 ] = "
第一行第二列" ;
excel.Cells[ 2 , 1 ] = "
第二行第一列" ;
excel.Cells[ 2 , 2 ] = "
第二行第二列" ;
excel.Cells[ 3 , 1 ] = "
第三行第一列" ;
excel.Cells[ 3 , 2 ] = "
第三行第二列" ;
excel.Visible = true ;

4. Visual C#调用Excel表格,并在Excel表格中存储数据的程序代码(Excel.cs):

   了解了上面的这些知识,得到完成上述功能的程序代码就显得比较容易了,具体如下:

using System ;
using System.Drawing ;
using System.Collections ;
using System.ComponentModel ;
using System.Windows.Forms ;
using System.Data ;
using System.Data.SqlClient ;
public class Form1 : Form
{
private Button button1 ;
private System.ComponentModel.Container components = null ;
public Form1 ( )
{
file://
初始化窗体中的各个组件
InitializeComponent ( ) ;
}
file://
清除程序中使用的各个资源
protected override void Dispose ( bool disposing )
{
if ( disposing )
{
if ( components != null )
{
components.Dispose ( ) ;
}
}
base.Dispose( disposing ) ;
}
private void InitializeComponent ( )
{
button1 = new Button ( ) ;
SuspendLayout ( ) ;
button1.Location = new System.Drawing.Point ( 32 , 72 ) ;
button1.Name = "button1" ;
button1.Size = new System.Drawing.Size ( 100 , 30 ) ;
button1.TabIndex = 0 ;
button1.Text = "
调用Excel文件!" ;
button1.Click += new System.EventHandler ( button1_Click ) ;

AutoScaleBaseSize = new System.Drawing.Size ( 5 , 13 ) ;

this.ClientSize = new System.Drawing.Size ( 292 , 273 ) ;
this.Controls.Add ( button1 ) ;
this.Name = "Form1" ;
this.Text = "
如何用Visual C#调用Excel表格!" ;
this.ResumeLayout ( false ) ;

}
static void Main ( )
{
Application.Run ( new Form1 ( ) ) ;
}
private void button1_Click ( object sender , System.EventArgs e )
{
Excel.Application excel = new Excel.Application ( ) ;
excel.Application.Workbooks.Add ( true ) ;
excel.Cells[ 1 , 1 ] = "
第一行第一列" ;
excel.Cells[ 1 , 2 ] = "
第一行第二列" ;
excel.Cells[ 2 , 1 ] = "
第二行第一列" ;
excel.Cells[ 2 , 2 ] = "
第二行第二列" ;
excel.Cells[ 3 , 1 ] = "
第三行第一列" ;
excel.Cells[ 3 , 2 ] = "
第三行第二列" ;
excel.Visible = true ;
}
}

  (5.编译源程序和程序运行界面:

   在经过了下列命令编译后:

Csc.exe /r:system.dll /r:system.windows.forms.dll /r:system.drawing.dll /r:excel.dll /r:office.dll /r:vbide.dll excel.cs

  就可以得到"Excel.exe",运行后界面如下:


02Visual C#调用Excel表格,并存储数据的程序运行界面

四.Visual C#处理Office套件中的其他成员程序:

   本文虽然只介绍了Visual C#在处理Excel表格中经常遇到的一些问题的解决方法,但其实对Office套件的其他成员也有很强的借鉴意义,譬如Visual C#来处理Word文档,在调用Word文档的时候,必须先完成COM组件从非受管代码到受管代码的转换,WordCOM组件位"MSWORD9.OLB",经过转换后也会产生三个DLL文件,但分别是"Word.dll""Office.dll""VBIDE.dll"。其实在Visual C#中调用Word,也非常容易。只需要把调用Excel表格中的代码换成调用Word的代码就可以了,具体如下:

Word.Application word = new Word.Application ( ) ;
word.Application.Visible = true ;

  不信你试一下,看看是否达到你的要求。对于针对Word的其他的操作,总体来说和对Excel表格的操作相类似。由于针对Word只是一个文档,程序对Word进行的操作是比较少的,所以就不一一介绍了。

五.总结:

   本文介绍Visual C#来处理Excel表格的几种最常遇到的情况,虽然针对的只是Excel表格,但对其他Office套件中的成员也具有十分的借鉴意义。

Visual C#中用ListView显示数据记录

 

如果要你在程序中显示数据库中的数据记录,你首先想用的显示工具肯定是DataGrid。当然用DataGrid显示数据记录是一种既常用又简单的方法。但是在程序控制方面,它却无法那么随心所欲。本文就是介绍另外一种显示数据记录的方法--ListView来显示数据记录,由于他是手动加入记录,虽然在程序设计中稍微烦琐了些,但对于那些在特殊的显示要求,却往往能够满足要求。

   .Net FrameWork SDK中定义了许多组件,Visual C#就是通过获得这些组件的实例来丰富自己的界面的。列表(ListView)是程序设计中一个常用的组件,由于其自身的特点,往往被使用显示比较庞大的数据信息。本文就是利用他的这个特点来看看它如何来显示数据记录。

一. 程序设计和运行的环境

   1.微软视窗2000专业版本

   2..Net FrameWork SDK Beta 2

   3.Microsoft Data Acess Component 2.6 (MDAC2.6)

二. 程序设计的具体思路

   1.首先要建立数据连接,打开数据集

   2.对列表进行初始化,并使得列表的显示条件符合数据记录的条件

   3.对数据集中的数据记录进行遍历,在遍历中添加记录到列表中

   4.关闭数据集,关闭数据连接

三. 具体的实现步骤

   1.首先要建立数据连接,打开数据集

   对于如何建立数据连接和获得数据集的内容可以参考本站的一篇文章--《在Visual C#中访问不同的数据库》,在此文中对此类问题有比较详细的介绍,本文就不多叙述,具体实现语句如下:

//
定义数据连接的字符串,程序中使用的是Acess 2000数据库

private static string strConnect = "Provider = Microsoft.Jet.OLEDB.4.0 ; Data Source = " +
Application.StartupPath + "\\MY.MDB" ;
private OleDbConnection conConnection = new OleDbConnection ( strConnect ) ;
OleDbDataReader reader ;
//
获得Person里面的所以数据记录
string strCommand = "SELECT * FROM Persons" ;
this.conConnection.Open ( ) ; //
打开数据连接
OleDbCommand cmd = new OleDbCommand ( strCommand , conConnection ) ;
reader = cmd.ExecuteReader ( ) ; file://
获得数据集
   2.对列表进行初始化,并使得列表的显示条件符合数据记录的条件。需要说明的是在下面源代码中,lv是在Class中定义的一个ListView的一个实例

//
初始化ListView
lv = new ListView ( ) ;
lv.Left = 0 ;
lv.Top = 0 ;
lv.Width = 700 ;
lv.Height = this.ClientRectangle.Height ;
lv.GridLines = true ; file://
显示各个记录的分隔线
lv.FullRowSelect = true ; file://
要选择就是一行
lv.View = View.Details ; file://
定义列表显示的方式
lv.Scrollable = true ; file://
需要时候显示滚动条
lv.MultiSelect = false ; //
不可以多行选择
lv.HeaderStyle = ColumnHeaderStyle.Nonclickable ;
//
针对数据库的字段名称,建立与之适应显示表头
lv.Columns.Add ( "
姓名" , 60 , HorizontalAlignment.Right ) ;
lv.Columns.Add ( "
住宅电话" , 100 , HorizontalAlignment.Left ) ;
lv.Columns.Add ( "
办公电话" , 100 , HorizontalAlignment.Left ) ;
lv.Columns.Add ( "
移动电话" , 100 , HorizontalAlignment.Left ) ;
lv.Columns.Add ( "
居住地点" , 100 , HorizontalAlignment.Left ) ;
lv.Columns.Add ( "
工作单位" , 100 , HorizontalAlignment.Left ) ;
lv.Columns.Add ( "
电子邮件" , 100 , HorizontalAlignment.Left ) ;
lv.Visible = true ;
   3.对数据集中的数据记录进行遍历,在遍历中添加记录到列表中。

   可以利用数据集中的Read ( )方法,来实现对数据记录的遍历,Read ( )方法是首先指向首数据记录,并判断从此记录是否为尾记录,如果不是则返回false,如果是则返回true。并且如果不是尾记录则自动把数据指针移到下一条记录上,然后在判断此记录是否是尾记录,如此循环,直至到尾记录为止。根据此可设计以下代码:

while ( reader.Read ( ) )
{
ListViewItem li = new ListViewItem ( ) ;
li.SubItems.Clear ( ) ;
li.SubItems[0].Text = reader["name"].ToString ( ) ;
li.SubItems.Add ( reader["HomePhone"].ToString ( ) ) ;
li.SubItems.Add ( reader["WorkPhone"].ToString ( ) ) ;
li.SubItems.Add ( reader["MobilePhone"].ToString ( ) ) ;
li.SubItems.Add ( reader["City"].ToString ( ) ) ;
li.SubItems.Add ( reader["Address"].ToString ( ) ) ;
li.SubItems.Add ( reader["Email"].ToString ( ) ) ;
lv.Items.Add ( li ) ;
}
   4. 关闭数据集,关闭数据连接。
关闭数据集和关闭数据连接是很容易的,只要调用这二个对象的Close()方法即可,也只要调用在程序中具体如下:

reader.Close ( ) ; file://
关闭数据集
this.conConnection.Close ( ) ; //
关闭数据连接
四. 程序运行结果界面和程序源代码(list.cs :

   程序源代码:

using System ;
using System.Windows.Forms ;
using System.Drawing ;
using System.Data ;
using System.Data.OleDb ;
class MainForm : Form
{ //
定义数据连接的字符串
private static string strConnect = "Provider = Microsoft.Jet.OLEDB.4.0 ; Data Source = " +
Application.StartupPath + "\\MY.MDB" ;
private OleDbConnection conConnection = new OleDbConnection ( strConnect ) ;
private ListView lv ;
public MainForm ( )
{
//
初始化Form
this.Left = 0 ;
this.Top = 0 ;
this.Text = "
ListView中显示数据库内容!" ;

//
初始化ListView
lv = new ListView ( ) ;
lv.Left = 0 ;
lv.Top = 0 ;
lv.Width = 700 ;
lv.Height = this.ClientRectangle.Height ;
lv.GridLines = true ; file://
显示各个记录的分隔线
lv.FullRowSelect = true ; file://
要选择就是一行
lv.View = View.Details ; file://
定义列表显示的方式
lv.Scrollable = true ; file://
需要时候显示滚动条
lv.MultiSelect = false ; //
不可以多行选择
lv.HeaderStyle = ColumnHeaderStyle.Nonclickable ;
//
针对数据库的字段名称,建立与之适应显示表头
lv.Columns.Add ( "
姓名" , 60 , HorizontalAlignment.Right ) ;
lv.Columns.Add ( "
住宅电话" , 100 , HorizontalAlignment.Left ) ;
lv.Columns.Add ( "
办公电话" , 100 , HorizontalAlignment.Left ) ;
lv.Columns.Add ( "
移动电话" , 100 , HorizontalAlignment.Left ) ;
lv.Columns.Add ( "
居住地点" , 100 , HorizontalAlignment.Left ) ;
lv.Columns.Add ( "
工作单位" , 100 , HorizontalAlignment.Left ) ;
lv.Columns.Add ( "
电子邮件" , 100 , HorizontalAlignment.Left ) ;
lv.Visible = true ;

OleDbDataReader reader ;
string strCommand = "SELECT * FROM Persons" ;
this.conConnection.Open ( ) ;//
打开数据连接
OleDbCommand cmd = new OleDbCommand ( strCommand , conConnection ) ;
reader = cmd.ExecuteReader ( ) ;//
获得数据集
//
不断往列表中添加数据记录
while ( reader.Read ( ) )
{
ListViewItem li = new ListViewItem ( ) ;
li.SubItems.Clear ( ) ;
li.SubItems[0].Text = reader["name"].ToString ( ) ;
li.SubItems.Add ( reader["HomePhone"].ToString ( ) ) ;
li.SubItems.Add ( reader["WorkPhone"].ToString ( ) ) ;
li.SubItems.Add ( reader["MobilePhone"].ToString ( ) ) ;
li.SubItems.Add ( reader["City"].ToString ( ) ) ;
li.SubItems.Add ( reader["Address"].ToString ( ) ) ;
li.SubItems.Add ( reader["Email"].ToString ( ) ) ;
lv.Items.Add ( li ) ;
}
reader.Close ( ) ; //
关闭数据集
//
Form中添加此列表
this.Controls.Add ( lv ) ;
//
关闭Form的时候,同时也关闭数据连接
this.Closed+=new EventHandler ( this_Closed ) ;
}
protected void this_Closed ( object sender , EventArgs eArgs )
{
this.conConnection.Close ( ) ; file://
关闭数据连接
}
public static void Main ( )
{
Application.Run ( new MainForm ( ) ) ;
}
}

   在成功编译了上面源程序代码以后,在同一目录下建立一个Acess 2000的数据库,命名为MY.MDB,然后在其中建立一张数据表,字段如下:nameHomePhoneWorkPhoneMobilePhoneCityAddressEmail。此时运行编译后的程序就可以得到如下运行界面:

Visual C#建立简单消息传递系统

 

 摘要:本文讨论基于套接字(socket)的体系结构以及怎样建立一个高效的、易于使用的、可以同时在PCPocket PC上运行的消息传递(message-passing)系统。

  套接字和消息

  目前,大多数Web服务和所有的远程应用程序都使用了远程过程调用(remote-procedure-callRPC)方法。你所做工作好像是在调用一个函数,但是在其后台执行了大量的操作以确保它在服务器上发生。在较低的层次,系统是在两台计算机之间传递消息,但这是不可视的。

  然而,当你转换到套接字操作的时候,你就在纯粹的基于消息的系统中编程了。这会改变你编写的代码的类型,因为读取返回的数据的唯一途径是通过消息。它与使用无返回值或输出参数的.NET类有点类似,在这些情况下所有的返回信息都需要通过事件传递。

  由于我希望服务器程序告诉客户端什么时候应该改变曲目,使用消息就很有利,因为信息可以从服务器到达客户端,不需要客户端明确地请求该信息。但是,它要求你使用不同的方式达到目标。

  在解释所有操作之前,我想先谈论一点点安全性方面的问题。如果你在自己的计算机上打开了某个端口,其它人可能利用这个端口做不利的事情。他们可能希望写入没有意义的信号,以确定自己是否能够控制你的计算机或者使它崩溃。当你编写这类应用程序的时候考虑一下这种可能性是必要的。我的例子将运行在防火墙后面的网络上,所以我感觉到相对安全。

  简单的套接字

  我从建立一个服务器程序开始,它能给一个整数加上1,下面是服务器端代码:

public static void Main()
{
IPAddress localAddr = IPAddress.Parse("127.0.0.1");
TcpListener listener = new TcpListener(localAddr, 9999);

Console.WriteLine("Waiting for initial connection");
listener.Start();
Socket socket = listener.AcceptSocket();
Console.WriteLine("Connected");
NetworkStream stream = new NetworkStream(socket);
BinaryReader reader = new BinaryReader(stream);
BinaryWriter writer = new BinaryWriter(stream);

int i = reader.ReadInt32();
i++;
writer.Write(i);
}


  它首先在本机的9999端口上建立了一个TCP监听器,接着启动监听器并等待连接。一旦得到了连接,它就接收一个整数,给它加1,并把它发送回去。

  需要指出的是我在此处使用的本地地址是127.0.0.1。在测试的时候这种情形可以很好地运行,这个时候客户端和服务器程序在相同的计算机上运行,但是当它们在不同的计算机上运行时,程序就不能运行了。在后面的部分中我将给出更复杂的代码。



  传递消息

  通过套接字传递未经处理的数据毫无乐趣,而通过套接字传递对象可能更有趣一些。为了达到这个目标,我们需要一个得到对象并把它转换为字节流的途径。最明显的解决方案是使用运行时(runtime)提供的串行化(serialization)支持。不幸的是,使用这种方法会有少量的问题。

  第一个问题是串行化需要很大的开销,这意味着它使用的字节比传递数据必要的字节多一些。如果使用SOAP格式化,这个问题就更严重。这是否成为一个问题依赖于应用程序的性能需求。第二个问题是串行化在简洁框架组件中不能使用。由于没有简单的实现方法,我们需要自己做这个工作。在这个过程中,我们做的事情比串行化要少多了。

  我们从建立一个枚举开始,它定义了可以传递什么类型的消息:

public enum MessageType
{
RequestEmployee = 1,
Employee,
}


  对于每种消息类型,我们需要一个对象定义该对象:

public class RequestEmployee: ISocketObject
{
int id;
public RequestEmployee(int id)
{
this.id = id;
}

}

public RequestEmployee(BinaryReader reader)
{
id = reader.ReadInt32();
}

public int ID
{
get
{
return id;
}
}

public void Send(BinaryWriter writer)
{
writer.Write((int) MessageType.RequestEmployee);
writer.Write(id);
}
}


  我们使用的这种途径与ISerializable接口很相似。ISocketObject接口定义了一个Send()函数,它串行化通过通道的数据,接着还有一个并行化该数据的构造函数。

  在这些对象中的某个串行化自身的时候,它发送的第一个信息是消息标识符。它让接收者知道将到达哪种类型的对象并建立该对象。下面是客户端的代码:

RequestEmployee requestEmployee = new RequestEmployee(15);
requestEmployee.Send(writer);

MessageType messageType = (MessageType) reader.ReadInt32();

switch (messageType)
{
case MessageType.Employee:
Employee employee = new Employee(reader);
Console.WriteLine("{0} = {1}", employee.Name, employee.Address);
break;
}


  RequestEmployee这段代码建立了一个对象并把它发送给服务器程序,接着它找出返回的是哪种对象,并且并行化它。

  尽管这个示例项目被标记为clientserver,但是两者之间唯一真正的差别是连接建立的方式。当这个过程完成后,它们都使用相似的代码发送和接收消息,即使它们需要处理自己的消息集合。

  面向对象设计vs.实用主义

  这种方法的缺点之一是你必须使用一个大的switch语句结束,但是前辈一直教导我们大的switch语句是较差的设计的表现。通常的面向对象(Object OrientedOO)的途径是使用多态性(polymorphism)的。为了达到这个目的,我们先建立一个抽象的基类(base class),接着从该类衍生出所有的消息对象。每个类需要执行串行化、并行化和处理消息等多个方法,主要的代码是:

  · 读取消息类型

  · 建立实例(使用反射)

  · 调用虚HandleMessage()函数

  这样做是可以实现的,但是效果很差,我并不喜欢。首先,编写建立实例的代码很难,并且由于它使用了反射,它的速度更慢。更重要的是,消息的处理过程并不在HandleMessage()函数之内,这意味着它必须是共享库的一部分。这是不宜使用的,因为消息的处理过程与消息如何传递没有什么关系。由于这些问题的存在,我依然决定使用较少面向对象但是更加容易编写的途径。

  前面的示例只处理了单个消息。在现实世界中,我们需要同时处理多个消息。

  服务器端的多线程

  我的最终的目标是把该服务器程序的功能添加到一个已有的应用程序中。因为不希望修改已有的应用程序的代码,我就必须在某个线程上运行服务器程序。同样,我希望可以同时接受多个连接。

  上面的例子在端口9999上监听,但是由于一个客户端只能与一个端口对话,我需要为每个连接使用不同端口的途径。SocketListener类将在9999端口上监视,当新的连接请求到达的时候,它将查找一个未使用的端口并把它发送回给客户端。下面是这个类的大致情形:

public class SocketListener
{
int port;
Thread thread;

public SocketListener(int port)
{
this.port = port;
ThreadStart ts = new ThreadStart(WaitForConnection);
thread = new Thread(ts);
thread.IsBackground = true;
thread.Start();
}

public void WaitForConnection()
{
//
主要的代码
}
}


  WaitForConnection()是执行所有这些操作的方法。这个类的构造函数执行建立新线程的任务,这个线程将运行WaitForConnection()。打开套接字并接受连接与前面的例子相似。下面是该线程的主循环:

while (true)
{
Console.WriteLine("Waiting for initial connection");
listener.Start();
Socket socket = listener.AcceptSocket();
NetworkStream stream = new NetworkStream(socket);
BinaryReader reader = new BinaryReader(stream);
BinaryWriter writer = new BinaryWriter(stream);

Console.WriteLine("Connection Requested");

int userPort = port + 1;
TcpListener specificListener;
while (true)
{
try
{
specificListener =
new TcpListener(localAddr, userPort);
specificListener.Start();
break;
}
catch (SocketException)
{
userPort++;
}
}
//
远程用户应该使用specificListener
//
把该端口发送回给远程用户,并为我们在该端口上建立服务器应用程序。
SocketServer socketServer = new SocketServer(specificListener);

writer.Write(userPort);
writer.Close();
reader.Close();
stream.Close();
socket.Close();
}


  我希望能够支持多个连接,因此使用一个端口以便于客户端表明它们希望建立一个连接,接着服务器程序找到一个空的端口并把该端口发送回给客户端,该端口用于特定客户端的连接。

  我没有找到查找未使用端口的方法,因此该While循环用于找出未使用的端口。接着它把该端口号发送回客户端并清除对象。

  此处还有需要指出的一点点微妙之处。SocketServer的原始版本把端口号作为一个参数。不幸的是,这意味着在该端口上建立监听器之前客户端不能作出请求,这是很不好的。为了防止出现这种情况,我在给客户端发送端口号之前建立了TcpListener,它确保不会出现这种紧急情况。

  SocketServer类建立了额外的线程,并使用了下面的主循环:

try
{
while (true)
{
MessageType messageType = (MessageType) reader.ReadInt32();

switch (messageType)
{
case MessageType.RequestEmployee:
Employee employee =
new Employee("Eric Gunnerson", "One Microsoft Way");
employee.Send(writer);
break;
}
}
}
catch (IOException)
{

}
finally
{
socket.Close();
}


  这个主循环是一个简单的获取请求/处理请求的循环。try-catch-finally在此处用于当客户端断开连接的时候从异常中恢复过来。

  客户端的事件

  在客户端,我编写了一个Windows传统客户端程序,可以供PC使用也可以供Pocket PC使用。该Windows窗体环境是基于事件的,而且使用事件处理套接字消息也是理想的。这是通过SocketClient类实现的。第一步是为每个消息定义一个委托和事件:

public delegate void EmployeeHandler(Employee employee);
public event EmployeeHandler EmployeeReceived;


  第二步是编写发送事件的代码:

case MessageType.Employee:
Employee employee = new Employee(reader);
if (EmployeeReceived != null)
form.Invoke(EmployeeReceived, new object[] {employee});
break;


  当事件发生的时候就应该更新窗体了。为了更可靠,这个操作需要在主UI线程上发生。这是通过调用窗体的Invoke()实现的,它将安排在主UI线程上调用的委托。

  因为这种基于消息的体系结构,服务器程序要有对于异步事件的内建的支持。示例有一个CurrentCount消息,它是由服务器程序每秒钟发送的。

  总结

  我对这个基于套接字的体系结构很满意,它是轻量级的、易于使用的,并且它可以同时在PCPocket PC上运行。

使用C#控制远程计算机的服务

 .net中提供了一些类来显示和控制Windows系统上的服务,并可以实现对远程计算机服务服务的访问,如System.ServiceProcess命名空间下面的ServiceController 类,System.Management下面的一些WMI操作的类。虽然用ServiceController可以很方便的实现对服务的控制,而且很直观、简洁和容易理解。但是我认为他的功能同通过WMI来操作服务相比,那可能就有些单一了,并且对多个服务的操作可能就比较麻烦,也无法列出系统中的所有服务的具体数据。这里要讲的就是如何使用System.Management组件来操作远程和本地计算机上的服务。
        WMI
作为Windows 2000操作系统的一部分提供了可伸缩的,可扩展的管理架构.公共信息模型(CIM)是由分布式管理任务标准协会(DMTF)设计的一种可扩展的、面向对象的架构,用于管理系统、网络、应用程序、数据库和设备。Windows管理规范也称作CIM for Windows,提供了统一的访问管理信息的方式。如果需要获取详细的WMI信息请读者查阅MSDNSystem.Management组件提供对大量管理信息和管理事件集合的访问,这些信息和事件是与根据 Windows 管理规范 (WMI) 结构对系统、设备和应用程序设置检测点有关的。
        
但是上面并不是我们最关心的,下面才是我们需要谈的话题。
毫无疑问,我们要引用System.Management.Dll程序集,并要使用System.Management命名空间下的类,如ManagementClass,ManagementObject等。下面用一个名为Win32ServiceManager的类把服务的一些相关操作包装了一下,代码如下:
using System;
using System.Management;
namespace ZZ.Wmi
{
     public class Win32ServiceManager
     {
         private string strPath;
         private ManagementClass managementClass;
         public Win32ServiceManager():this(".",null,null)
         {
         }
         public Win32ServiceManager(string host,string userName,string password)
         {
              this.strPath = "\\\\"+host+"\\root\\cimv2:Win32_Service";
              this.managementClass = new ManagementClass(strPath);
               if(userName!=null&&userName.Length>0)
              {
                   ConnectionOptions connectionOptions = new ConnectionOptions();
                   connectionOptions.Username = userName;
                   connectionOptions.Password = password;
                   ManagementScope managementScope = new ManagementScope( "\\\\" +host+ "\\root\\cimv2",connectionOptions) ;
                   this.managementClass.Scope = managementScope;
              }
         }
         //
验证是否能连接到远程计算机
         public static bool RemoteConnectValidate(string host,string userName,string password)
         {
              ConnectionOptions connectionOptions = new ConnectionOptions();
              connectionOptions.Username = userName;
              connectionOptions.Password = password;
              ManagementScope managementScope = new ManagementScope( "\\\\" +host+ "\\root\\cimv2",connectionOptions) ;
              try
              {
                   managementScope.Connect();
              }
              catch
              {
              }
              return managementScope.IsConnected;
         }
         //
获取指定服务属性的值
         public object GetServiceValue(string serviceName,string propertyName)
         {
              ManagementObject mo = this.managementClass.CreateInstance();
              mo.Path = new ManagementPath(this.strPath+".Name=\""+serviceName+"\"");
              return mo[propertyName];
         }
         //
获取所连接的计算机的所有服务数据
         public string [,] GetServiceList()
         {
              string [,] services = new string [this.managementClass.GetInstances().Count,4];
              int i = 0;
              foreach(ManagementObject mo in this.managementClass.GetInstances())
              {
                   services[i,0] = (string)mo["Name"];
                   services[i,1] = (string)mo["DisplayName"];
                   services[i,2] = (string)mo["State"];
                   services[i,3] = (string)mo["StartMode"];
                   i++;
              }
              return services;
         }
         //
获取所连接的计算机的指定服务数据
         public string [,] GetServiceList(string serverName)
         {
              return GetServiceList(new string []{serverName});
         }
         //
获取所连接的计算机的的指定服务数据
         public string [,] GetServiceList(string [] serverNames)
         {
              string [,] services = new string [serverNames.Length,4];
              ManagementObject mo = this.managementClass.CreateInstance();
              for(int i = 0;i<serverNames.Length;i++)
              {
                   mo.Path = new ManagementPath(this.strPath+".Name=\""+serverNames[i]+"\"");
                   services[i,0] = (string)mo["Name"];
                   services[i,1] = (string)mo["DisplayName"];
                   services[i,2] = (string)mo["State"];
                   services[i,3] = (string)mo["StartMode"];
              }
              return service

   }
         //
停止指定的服务
         public string StartService(string serviceName)
         {
              string strRst = null;
              ManagementObject mo = this.managementClass.CreateInstance();
              mo.Path = new ManagementPath(this.strPath+".Name=\""+serviceName+"\"");
              try
              {
                   if((string)mo["State"]=="Stopped")//!(bool)mo["AcceptStop"]
                       mo.InvokeMethod("StartService",null);
              }
              catch(ManagementException e)
              {
                   strRst =e.Message;
              }
              return strRst;
         }
         //
暂停指定的服务
         public string PauseService(string serviceName)
         {
              string strRst = null;
              ManagementObject mo = this.managementClass.CreateInstance();
              mo.Path = new ManagementPath(this.strPath+".Name=\""+serviceName+"\"");
              try
              {
                   //
判断是否可以暂停
                   if((bool)mo["acceptPause"]&&(string)mo["State"]=="Running")
                       mo.InvokeMethod("PauseService",null);
              }
              catch(ManagementException e)
              {
                   strRst =e.Message;
              }
              return strRst;
         }
         //
恢复指定的服务
         public string ResumeService(string serviceName)
         {
              string strRst = null;
              ManagementObject mo = this.managementClass.CreateInstance();
              mo.Path = new ManagementPath(this.strPath+".Name=\""+serviceName+"\"");
              try
              {
                   //
判断是否可以恢复
                   if((bool)mo["acceptPause"]&&(string)mo["State"]=="Paused")
                       mo.InvokeMethod("ResumeService",null);
              }
              catch(ManagementException e)
              {
                   strRst =e.Message;
              }
              return strRst;
         }
         //
停止指定的服务
         public string StopService(string serviceName)
         {
              string strRst = null;
              ManagementObject mo = this.managementClass.CreateInstance();
              mo.Path = new ManagementPath(this.strPath+".Name=\""+serviceName+"\"");
              try
              {
                   //
判断是否可以停止
                   if((bool)mo["AcceptStop"])//(string)mo["State"]=="Running"
                       mo.InvokeMethod("StopService",null);
              }
              catch(ManagementException e)
              {
                   strRst =e.Message;
              }
              return strRst;
         }
    }
}
Win32ServiceManager中通过RemoteConnectValidate静态方法来测试连接成功与否;另外提供了GetServiceValue方法和GetServiceList方法以及它的重载来获取服务信息;后面的四个方法就是对服务的状态控制了。
     
下面建立一个简单的窗口来使用它。
大致的界面如下:

通过vs.net 2003可以很快做出上面的窗体,下面列出了一些增加的代码:


using ZZ.Wmi;
namespace ZZForm
{
     public class Form1 : System.Windows.Forms.Form
     {
         //
……
         private Win32ServiceManager serviceManager;
         public Form1()
         {
              InitializeComponent();
              this.serviceManager = null;
         }
         //
……
         [STAThread]
         static void Main()
         {
              Application.Run(new Form1());
         }
         //
修改服务状态
         private void buttonChangeState_Click(object sender, System.EventArgs

 {
              switch(((Button)sender).Text)
              {
                   case "
启动":
                       string startRst = this.serviceManager.StartService(this.listViewService.SelectedItems[0].SubItems[0].Text);
                       if(startRst==null)
                            MessageBox.Show("
操作成功,请点击获取刷新按钮刷新结果!");
                       else
                            MessageBox.Show(startRst);
                       break;
                   case "
暂停":
                       string startPause = this.serviceManager.PauseService(this.listViewService.SelectedItems[0].SubItems[0].Text);
                       if(startPause==null)
                            MessageBox.Show("
操作成功,请点击获取刷新按钮刷新结果!");
                       else
                            MessageBox.Show(startPause);
                       break;
                   case "
继续":
                       string startResume = this.serviceManager.ResumeService(this.listViewService.SelectedItems[0].SubItems[0].Text);
                       if(startResume==null)
                            MessageBox.Show("
操作成功,请点击获取刷新按钮刷新结果!");
                       else
                            MessageBox.Show(startResume);
                       break;
                   case "
停止":
                       string startStop = this.serviceManager.StopService(this.listViewService.SelectedItems[0].SubItems[0].Text);
                       if(startStop==null)
                            MessageBox.Show("
操作成功,请点击获取刷新按钮刷新结果!");
                       else
                            MessageBox.Show(startStop);
                       break;
              }
         }
         //
获取和刷新数据
         private void buttonLoadRefresh_Click(object sender, System.EventArgs e)
         {
              if(this.textBoxHost.Text.Trim().Length>0)
              {
                   if(this.textBoxHost.Text.Trim()==".")
                   {
                       this.serviceManager = new Win32ServiceManager();
                   }
                   else
                   {
                        if(Win32ServiceManager.RemoteConnectValidate(this.textBoxHost.Text.Trim(),this.textBoxName.Text.Trim(),this.textBoxPassword.Text.Trim()))
                       {
                            this.serviceManager = new Win32ServiceManager(this.textBoxHost.Text.Trim(),this.textBoxName.Text.Trim(),this.textBoxPassword.Text.Trim());
                       }
                       else
                       {
                            MessageBox.Show("
连接到远程计算机验证错误.");
                            return;
                       }
                   }
                   string [,] services = serviceManager.GetServiceList();
                   this.listViewService.BeginUpdate();
                   this.listViewService.Items.Clear();
                   for(int i=0;i<services.GetLength(0);i++)
                   {
                       ListViewItem item = new ListViewItem(new string[]{services[i,0],services[i,1],services[i,2],services[i,3]});
                       this.listViewService.Items.Add(item);

 this.listViewService.EndUpdate();
              }
              else
                   MessageBox.Show("
请输入计算机名或IP地址")

  }
    }
}
     
说明,其实一个服务的属性和方法除了上面这几个还有很多,我们可以通过实例化ManagementClass类,使用它的Properties属性和Methods属性列出所有的属性和方法。上面的Win32ServiceManager中生成的每个服务实例都是ManagementObejct类型的,其实还有一种强类型的类,可以通过编程和工具来生成。
     
总结,通过引用System.Management命名空间,上面简单的实现了通过访问\root\cimv2:Win32_Service名称空间对服务进行显示和操作。此外,我们还可以通过访问其他名称空间来访问计算机的一些硬件信息,软件信息以及网络等,有兴趣的读者可以研究一下。 

 

C#打造自己的文件浏览器

C#的功能十分强大,用它可以轻松地做出属于自己的文件浏览器。下面简单地介绍一下文件浏览器的大致实现过程。其中涉及的有关这些控件的具体用法可参见C#的联机帮助。

  你需要用到几个控件:

   TreeView(用于显示显示目录树)

   ListView(用于显示文件和目录列表)

   Splitter(用于允许用户调整TreeViewListView的大小)

   其它的如:MainMenuToolBarStatusBarImageList等等就看你的实际需要了。

  首先,新建一个C#项目(Windows应用程序),命名为MyFileView,将窗口命名为mainForm,调整主窗口大小(Size)。添加MainMenuToolBarStatusBarImageList等控件。

  然后,添加TreeView控件,命名为treeViewDock属性设为Left,再添加Splitter控件,同样将Dock属性设为Left。最后添加ListView控件,命名为listViewDock属性设为Fill。如下图所示。



   界面做好了,那么怎样才能在这个界面里显示文件夹和文件呢?这需要我们添加代码来实现。

  首先引用以下名字空间:

using System;

using System.Drawing;

using System.Collections;

using System.ComponentModel;

using System.Windows.Forms;

using System.Data;

using System.IO ;

using System .Runtime .InteropServices ;

mainForm_Load事件中添加以下代码,用于在treeView控件中显示目录树:

private void mainForm_Load(object sender, System.EventArgs e)

//获取逻辑驱动器

string[] LogicDrives=System.IO .Directory .GetLogicalDrives();

TreeNode[] cRoot =new TreeNode[LogicDrives.Length];

for (int i=0;i< LogicDrives.Length ;i++)

{

 TreeNode drivesNode=new TreeNode(LogicDrives[i]);

 treeView.Nodes .Add (drivesNode);

 if (LogicDrives[i]!="A:\\" && LogicDrives[i]!="B:\\" )

  getSubNode(drivesNode,true);

}

}

  其中,getSubNode为一方法,用于获取子目录,以创建目录树节点,参数:PathName为获取的子目录在此节点下创建子节点,参数isEnd:结束标志,true则结束。

private void getSubNode(TreeNode PathName,bool isEnd)

{

 if(!isEnd)

  return; //exit this

  TreeNode curNode;

  DirectoryInfo[] subDir;

  DirectoryInfo curDir=new DirectoryInfo (PathName.FullPath);

  try

  {

   subDir=curDir.GetDirectories();

  }

  catch{}

   foreach(DirectoryInfo d in subDir)

   {

    curNode=new TreeNode(d.Name);

    PathName.Nodes .Add (curNode);

    getSubNode(curNode,false);

   }

}

  当鼠标单击目录节点左边的+号时,节点将展开,此时,应在AfterExpand事件中加入以下代码,以获取此目录下的子目录节点:

private void treeView_AfterExpand(object sender, System.Windows.Forms.TreeViewEventArgs e)

{

 try

 {

  foreach(TreeNode tn in e.Node .Nodes )

  {

   if (!tn.IsExpanded)

    getSubNode(tn,true);

   }

  }

 catch{;}

}

  当鼠标单击选中目录节点时,右边的listView控件应显示此目录下的文件和目录,代码如下:

private void treeView_AfterSelect(object sender,System.Windows.Forms.TreeViewEventArgs e)

{

 listView.Items.Clear();

 DirectoryInfo selDir=new DirectoryInfo(e.Node.FullPath );

 DirectoryInfo[] listDir;

 FileInfo[] listFile;

 try

 {

  listDir=selDir.GetDirectories();

  listFile=selDir.GetFiles();

 }

 catch{}

foreach (DirectoryInfo d in listDir)

  listView.Items .Add (d.Name,6);

 foreach (FileInfo d in listFile)

  listView.Items .Add (d.Name);

}

  至此,一个简单的文件浏览器就做成了,当然,它还很初级,甚至不能用它打开一个文件,加另外,它也不能显示文件和目录的图标,没有进行错误处理,没有进行安全控制……它能给你的只是一个思路。

 

c#写的五子棋程序

前几天没事,写了一个小程序,可以用于学习C#
  
程序使用了VS.NET环境编译,你的机器只要安装了.NET Framework SDK就可以运行。
  
源码和执行文件可以下载
  
你不想下载也可读一下源码(图片资源等需要下载)。
  namespace Leimom.FiveChess
  {
   using System;
   using System.Drawing;
   using System.Collections;
   using System.ComponentModel;
   using System.WinForms;
   using System.Data;
   ///
   /// Summary description for Form1.
   ///
   public class FiveForm : System.WinForms.Form
   {
   ///
   /// Required designer variable.
   ///
   private System.ComponentModel.Container components;
   private System.WinForms.ImageList imageListbw;
   //define the hot Rectangle
   private Rectangle[] pointSquares;
   //chess information
   private int[] chessTable;
   private int nextTurn;
   private const int bTurn = 1;
   private const int wTurn = 2;
   private Stack chessIndex;
   public FiveForm()
   {
   //
   // Required for Windows Form Designer support
   //
   InitializeComponent();
   //
   // TODO: Add any constructor code after InitializeComponent call
   //
   chessIndex = new Stack();
   nextTurn = bTurn;
   chessTable = new int[225];
   pointSquares = new Rectangle[225];
   Size size = new Size(18,18);
   int x = 0;
   int y = 0;
   for(int i = 0;i < 225;i++)
   {
   x = i%15;
   y = i/15;
   pointSquares[i].Size = size;
   pointSquares[i].Offset(9+x*20,6+y*20);
   chessTable[i] = 0;
   }
   }
  
   protected override void OnPaint(PaintEventArgs e)
   {
   //you may paint
   Graphics g = e.Graphics;
   }
   protected override void OnMouseDown(System.WinForms.MouseEventArgs e)
   {
   switch( e.Button )
   {
   //take left button down
   case MouseButtons.Left:
   OnLButtonDown(new Point(e.X,e.Y));
   break;
   //take right button down
   case MouseButtons.Right:
   OnRButtonDown(new Point(e.X,e.Y));
   break;
   }
   base.OnMouseDown(e);
   }
   private void OnLButtonDown(Point p)
   {
   int nPos = GetRectID(p);
   //click hot Rectangle witch have no chess
   if(nPos != -1&&chessTable[nPos] == 0)
   {
   Graphics g = this.CreateGraphics();
   if(nextTurn==bTurn)
   {
   //draw white chess
   DrawBlack(g,nPos);
   chessTable[nPos] = bTurn;
   nextTurn = wTurn;
   chessIndex.Push(bTurn);
   chessIndex.Push(nPos);
   }
   else
   {
   //draw Black chess
   DrawWhite(g,nPos);
   chessTable[nPos] = wTurn;
   nextTurn = bTurn;
   chessIndex.Push(wTurn);
   chessIndex.Push(nPos);
   }
   g.Dispose();
   //witch win
   CheckGameResult(nPos,nextTurn);
   }
   }
   private void CheckGameResult(int nPos,int nextTurn)
   {
   //witch win
   Stack isFive = new Stack();
   int thisTurn = (nextTurn == bTurn)?wTurn:bTurn;
   int x = nPos%15;
   int y = nPos/15;
   //scan x have five
   for(int i=0;i<15;i++)
   {
   if(chessTable[y*15+i] == thisTurn)
   {
   isFive.Push(y*15+i);
   if(isFive.Count == 5)
   {
   MessageBox.Show("Game Over","Notes",MessageBox.OK);
   ReSetGame();
   return;
   }
   }
   else
   {
   isFive.Clear();
   }
   }
   isFive.Clear();
   //scan y have five
   for(int i=0;i<15;i++)
   {
   if(chessTable[i*15+x] == thisTurn)
   {
   isFive.Push(i*15+x);
   if(isFive.Count == 5)
   {
   MessageBox.Show("Game Over","Notes",MessageBox.OK);
   ReSetGame();
   return;
   }
   }
   else
   {
   isFive.Clear();
   }
   }
   isFive.Clear();
   //scan x=y have five
   for(int i=-14;i<15;i++)
   {
   if(x+i<0||x+i>14||y-i<0||y-i>14)
   {
   continue;
   }
   else
   {
   if(chessTable[(y-i)*15+x+i] == thisTurn)
   {
   isFive.Push((y-i)*15+x+i);
   if(isFive.Count == 5)
   {
   MessageBox.Show("Game Over","Notes",MessageBox.OK);
   ReSetGame();
   return;
   }
   }
   else
   {
   isFive.Clear();
   }
   }
   }
   isFive.Clear();
   //scan x=-y have five
   for(int i=-14;i<15;i++)
   {
   if(x+i<0||x+i>14||y+i<0||y+i>14)
   {
   continue;
   }
   else
   {
   if(chessTable[(y+i)*15+x+i] == thisTurn)
   {
   isFive.Push((y+i)*15+x+i);
   if(isFive.Count == 5)
   {
   MessageBox.Show("Game Over","Notes",MessageBox.OK);
   ReSetGame();
   return;
   }
   }
   else
   {
   isFive.Clear();
   }
   }
   }
   isFive.Clear();
   }
   private void ReSetGame()
   {
   //reset game
   nextTurn = bTurn;
   for(int i=0;i<225;i++)
   {
   chessTable[i] = 0;
   }
   this.Invalidate();
   }
   private int GetRectID(Point p)
   {
   //get witch rectangle click
   for(int i = 0;i < 225;i++)
   {
   if(pointSquares[i].Contains( p ))
   {
   return i;
   }
   }
   return -1;
   }
   private void OnRButtonDown(Point p)
   {
   //regret chess
   int nPos,x,y;
   if(chessIndex.Count != 0)
   {
   nPos = (int)chessIndex.Pop();
   x = nPos%15;
   y = nPos/15;
   chessTable[nPos] = 0;
   nextTurn = (int)chessIndex.Pop();
   this.Invalidate(new Rectangle(new Point(8+x*20,5+y*20),new Size(20,20)));
   }
   }
   private void DrawBlack(Graphics g,int nPos)
   {
   //draw Black chess
   int x,y;
   x = nPos%15;
   y = nPos/15;
   imageListbw.DrawImage(g,8+20*x,5+20*y,20,20,0,0);
   }
   private void DrawWhite(Graphics g,int nPos)
   {
   //draw White chess
   int x,y;
   x = nPos%15;
   y = nPos/15;
   imageListbw.DrawImage(g,8+20*x,5+20*y,20,20,0,1);
   }
   ///
   /// Clean up any resources being used.
   ///
   public override void Dispose()

   {
   base.Dispose();
   components.Dispose();
   }
   ///
   /// Required method for Designer support - do not modify
   /// the contents of this method with the code editor.
   ///
   private void InitializeComponent()
   {
   System.Resources.ResourceManager resources = new System.Resources.ResourceManager (typeof(FiveForm));
   this.components = new System.ComponentModel.Container ();
   this.imageListbw = new System.WinForms.ImageList ();
   //@this.TrayHeight = 90;
   //@this.TrayLargeIcon = false;
   //@this.TrayAutoArrange = true;
   //@imageListbw.SetLocation (new System.Drawing.Point (7, 7));
   imageListbw.ImageSize = new System.Drawing.Size (20, 20);
   imageListbw.ImageStream = (System.WinForms.ImageListStreamer) resources.GetObject ("imageListbw.ImageStream");
   imageListbw.ColorDepth = System.WinForms.ColorDepth.Depth8Bit;
   imageListbw.TransparentColor = System.Drawing.Color.Yellow;
   this.Text = "FiveForm";
   this.MaximizeBox = false;
   this.AutoScaleBaseSize = new System.Drawing.Size (6, 14);
   this.BorderStyle = System.WinForms.FormBorderStyle.FixedSingle;
   this.BackgroundImage = (System.Drawing.Image) resources.GetObject ("$this.BackgroundImage");
   this.TransparencyKey = System.Drawing.Color.White;
   this.ClientSize = new System.Drawing.Size (314, 311);
   }
  
   ///
   /// The main entry point for the application.
   ///
   public static int Main(string[] args)
   {
   Application.Run(new FiveForm());
   return 0;
   }
   }
  }  

 

Visual C#编写3D游戏框架示例

你可能对实际地编写游戏代码期待已久了。由于DirectX SDK 2004年夏季更新包含了一个牢固的示例框架组件,并且它被设计成能在你自己的代码中直接使用,同时还为你处理了很多事务,所以你只要简单的使用它,就可以节省大量的时间和精力。
  
  
本文中的例子使用的就是这个示例框架组件,在本文中,你将学习到的内容有:
  
  
· 如何建立自己的项目
  
  
· 如何使用示例框架组件来列举设备
  
  
建立项目
  
  
在本文中,我假定你的所有开发工作都将使用Visual Studio .NET 2003来完成。如果你不希望使用这个环境,可以使用命令行编译代码,它允许你使用任意的文本编辑器或集成开发环境(IDE)。
  
  
启动Visual Studio .NET 2003并点击起始页面中的"新建项目"按钮。如果你没有使用起始页面,可以点击"文件"菜单下的"新建"子菜单中的"项目"菜单项,或者使用Ctrl+Shift+N。选择"Visual C#项目"区域中的"Windows项目"数据项。把这个项目命名为Blockers,这是游戏的名称。
  
  
在你查看自动生成的代码之前,首先把示例框架组件添加到你的项目中。一般情况下,我会在"解决方案浏览器"中建立一个新文件夹,并把这些文件放入一个这个独立的文件夹中(把这个文件夹的名字取为Framework)。右键点击这个新建的文件夹,从"添加"菜单中选择"添加已有的项"。导航到DirectX SDK文件夹,你会发现该示例框架文件位于Samples\Managed\Common文件夹中,选择每个文件并添加到你的项目中。
  
  
在示例框架组件被添加到项目中以后,你就可以去掉自动生成的代码了。这些代码中的大部分都是用于建立别致的Windows窗体应用程序的,因此,它与我们编写游戏的代码是无关的。用列表1中的代码替换已有的代码和类(Form1)。
  
  
列表1:空的框架组件
  
  using System;
  
  using System.Configuration;
  
  using Microsoft.DirectX;
  
  using Microsoft.DirectX.Direct3D;
  
  using Microsoft.Samples.DirectX.UtilityToolkit;
  
  public class GameEngine : IDeviceCreation
  
  {
  
  ///
程序入口。初始化所有部分并进入一个消息处理循环。用空闲时间显示场景
  
  static int Main()
  
  {
  
  using(Framework sampleFramework = new Framework())
  
  {
  
  return sampleFramework.ExitCode;
  
  }
  
  }
  
  }
  
  
这段新代码中有三个地方比较突出。首先,你可能注意到了除了静态的main方法之外,删除了所有东西,而且main方法也被修改过。剩余的代码是Windows窗体设计器的支撑代码。由于这个应用程序不需要使用该设计器,因此这些代码就没有用了,可以被删除。其次,这段代码不能编译,因为游戏引擎希望实现的两个接口还未实现。再次,这段代码实际上没有做任何事务。
  
  
在你开始解决后面两个问题之前,你必须添加一些引用。由于你准备在这个项目中显示奇特的3D图像,你就必须给项目添加能执行这样的显示操作的组件的引用。本文采用受控DirectX来执行这种操作,因此你需要在"项目"菜单中选择"添加引用"。图1显示了弹出的对话框。
  
  
  
1:添加引用对话框
  
  
如果你安装了DirectX 9 SDK 2004年夏季更新,你会发现有多个版本的受控DirectX组件可供使用。请选择最新的版本(1.0.2902.0版本)。对于这个项目来说,你需要添加三个不同的组件引用:
  
  
· Microsoft.DirectX
  
  
· Microsoft.DirectX.Direct3D
  
  
· Microsoft.DirectX.Direct3DX
  
  DirectX
根(root)组件包含了辅助显示计算的数学结构。其它两个组件相应地包含了Direct3DD3DX的功能。添加这些引用之后,你可以简单地查看一下列表1中添加的using语句,以确保名字空间被正确地引用了。这个步骤可以确保你不需要完整地限定类型。例如,如果不添加using语句,那么声明一个Direct3D设备变量,就必须使用下面的语句:
  
  Microsoft.DirectX.Direct3D.Device device = null;
  
  Using
语句可以减少很多输入内容(没有人希望在声明一个变量时输入全部的内容)。由于你已经增加了using语句,就可以使用如下所示的声明语句了:
  
  private Device device = null;
  
  
你可以看到,用这种方式声明变量简单多了,节省了大量的输入。在了解这些信息之后,你可以开始修补应用程序编译过程中的错误了,并准备好编写第一个3D游戏了。你现在必须实现的唯一一个接口是IDeviceCreation,它控制着设备的列举和建立。
  
  
你可能会想"列举设备做什么?我只有一个监视器"!尽管一般情况下是这样的,但是现在的显卡实际上是支持多个监视器的,即使你只有一个设备,你仍然拥有多个可选择的模式。显示器的格式可能不同(你可以在Windows桌面设置中看到这些种类,例如16位色和32位色)。全屏幕模式下的高度和宽度也可能有不同的值,你甚至于还可以控制屏幕的刷新率。总而言之,还是有些事情需要解决。
  
  
列表2中的代码修补的应用程序中的编译错误。
  
  
列表2:实现接口
  
  ///
设备初始化的时候调用。这段代码检查设备最小的性能,
  
  ///
如果没有通过检查,就返回false
  
  public bool IsDeviceAcceptable(Caps caps, Format adapterFormat,
  
  Format backBufferFormat, bool windowed)
  
  {
  
  if (!Manager.CheckDeviceFormat(caps.AdapterOrdinal, caps.DeviceType,
  
  adapterFormat, Usage.QueryPostPixelShaderBlending,
  
  ResourceType.Textures, backBufferFormat))
  
  return false;
  
  if (caps.MaxActiveLights == 0)
  
  return false;
  
  return true;
  
  }
  
  ///
在建立某个设备之前,这个回调函数会被立即调用,以允许应用程序修改设备的
  
  ///
设置信息。它提供的设置参数包含了框架组件为新设备挑选的设置, 并且应用程序
  
  ///
可以直接对这个结构进行任何需要的修改。请注意,示例框架没有纠正无效的
  
  ///
设备设置信息,因此必须小心地返回有效的设备设置,否则建立设备就会失败。
  
  public void ModifyDeviceSettings(DeviceSettings settings, Caps caps)
  
  {
  
  //
这个应用程序没有使用任何get方法,它被设计成在一个纯设备上工作。
  
  //
因此如果受到支持并且使用HWVP,就建立一个纯设备。
  
  if ( (caps.DeviceCaps.SupportsPureDevice) && ((settings.BehaviorFlags & CreateFlags.HardwareVertexProcessing) != 0 ) )
  
  settings.BehaviorFlags |= CreateFlags.PureDevice;
  
  }
  
  
请你查看一下声明的IsDeviceAcceptable方法。在示例框架忙着枚举系统中的设备的时候,它会在每个找到的组合上调用该方法。注意到该方法返回一个布尔型值吗?这使得你有权利告诉示例框架你认为某个设备是否符合需求。但是,在你仔细查看第一个方法中的代码的时候,请先看一下声明的第二个方法(ModifyDeviceSettings)。某个设备被建立之前,示例框架会立即调用这个方法,允许你加入任何希望的选项。但是你要小心地使用参数选项,因为它可能导致设备建立失败。
  
  
现在我们回到第一个方法:我们先看一下它的参数。首先,它带有一个Caps类型的参数,它是设备性能的简单结构体。该结构体包含了具体设备的巨量信息,可以帮助你决定某个类型是不是你正在使用的设备类型。其后的两个参数都是特定设备的格式参数:一个是后台的缓冲区格式,另一个是设备的格式。
  
  
请注意
  
  
后台缓冲区是实际要显示的数据(象素)在发送给显卡处理并输出到屏幕之前所存储的地方。后台缓冲区的格式决定了可以显示多少种色彩。大多数格式遵循特定的命名习惯--每个字符跟着一个数字,例如A8R8G8B8。字符所指定的构成部分拥有与其后面的数字相同数量的位(bit)。在A8R8G8B8中,该格式可以包含32位色彩信息,alpharedgreenblue各用8位。最常见的构成是:
  
  A Alpha
  
  R Red
  
  G Green
  
  B Blue
  
  X Unused
  
  
你可以查看DirectX SDK文档得到更多关于格式的信息。由于我们还需要知道该设备时候可以显示在窗体中,所以这个方法还有一个参数(最后一个)。尽管大多数游戏都运行在全屏模式下,但是编写和调试在全屏模式下运行的游戏却很困难。在调试过程中,这个应用程序在窗体模式而不是全屏模式下显示。
  
  
请注意
  
  
窗体模式是我们运行的大多数应用程序的显示方式。其中大多数应用程序带有边框和控制菜单,右上角带有最小化、最大化和关闭按钮。在全屏模式下,应用程序覆盖了整个屏幕,并且在大多数情况下没有边框。如果全屏模式使用了另外的屏幕大小(你当前使用的桌面),你可以改变桌面的分辨率。
  
  
你可能注意到了默认行为是接受该设备,但是在接受之前进行了两项检查。第一项检查确保了传递进来的设备可以进行alpha混合(游戏的用户界面需要这个),如果它不能够实现,就返回false表明这个设备不能被接受。接着,它检查是否支持活动光源(active light)的能力。没有光源的屏幕看起来是平面的、是假的,因此一般至少需要一个光源。
  
  
还有一些代码在设备建立之前的确修改了该设备,你可能想知道这段代码的作用。能够执行处理过程的设备需要用多种方法来显示顶点,要么在硬件中计算或软件中计算,或者两者都使用。如果处理过程完全在硬件中进行,这就是另外一种模式,就叫做"纯硬件设备",它潜在地提供了更高的性能。这段代码检查你当前是否要建立一个硬件处理设备,如果你正准备这样做,并且该纯设备是可以使用的,那么它就切换到这种模式中。你不能建立纯设备(如果该设备是可用的)的唯一情形是你计划调用该设备上的某个get方法或属性。由于在这个例子中你不需要这样操作,所以你可以自由地使用能力更加强大的设备。
  
  
示例框架中有一些不安全(unsafe)的代码,因此你需要更新自己的项目并处理这些问题。见图2
  
  
  
2:允许不安全的代码

 列举所有设备选项
  
  
现在你可以让框架组件开始列举系统中的设备了。首先,为游戏引擎类声明一个构造函数,并把main中建立的示例框架实例作为参数传递进去。如列表3所示。
  
  
列表3:添加构造函数
  
  private Framework sampleFramework = null; //
示例的框架组件
  
  ///
建立该类的一个新的实例
  
  public GameEngine(Framework f)
  
  {
  
  //
存储框架组件
  
  sampleFramework = f;
  
  }
  
  
该构造函数除了存储示例框架实例之外没有做其它的任何操作,这是因为这个实例是游戏中其它的一切东西几乎都需要使用的。在你调用示例框架之后,它所做的第一件事情是试图列举系统中的所有设备。在你的项目文件中,你在Framework文件夹中可以看到dxmutenum.cs文件。这个文件包含了列举系统中所有设备所需要的全部代码。由于理解如何列举和为什么列举设备是非常重要的,所以请你打开这个文件。
  
  
你首先应该注意到Enumeration类自身是不能被创建的,并且每个可用的成员和方法都是用static(静态的)关键字声明的。由于在正常情况下(最少是现在)应用程序在运行的时候,你的图形硬件是不会改变的,因此这些列举代码只需要在应用程序开头运行一次。
  
  
列举工作是从Enumerate方法开始的,该方法在设备建立之前被示例框架调用。请注意,这个方法的唯一参数是你自己在游戏引擎类中所实现的接口。这个接口被保存下来,因为随后,随着设备组合的列举,会调用IsDeviceAcceptable方法来决定某个设备是否应该添加到有效设备列表中。
  
  
那么设备到底是怎样列举出来的呢?这些功能都位于受控DirectXManager类中。如果你非常熟悉非受控的DirectX应用程序编程接口(API),那么我告诉你,这个类映射了IDirect3D9组件对象模型(COM)接口。请留意列表4中的Enumerate方法中的第一个循环。
  
  
列表4:列举设备
  
  //
查找系统中的每个适配器
  
  for each(AdapterInformation ai in Manager.Adapters)
  
  {
  
  EnumAdapterInformation adapterInfo = new EnumAdapterInformation();
  
  //
存储一些信息
  
  adapterInfo.AdapterOrdinal = (uint)ai.Adapter; //
序号
  
  adapterInfo.AdapterInformation = ai.Information; //
信息
  
  //
获取这个适配器上的所有显示模式
  
  //
建立一个所有显示适配器格式的临时列表
  
  adapterFormatList.Clear();
  
  //
现在检测支持哪种格式
  
  for(int i = 0; i < allowedFormats.Length; i++)
  
  {
  
  //
检查这种格式的每一种可支持的显示模式
  
  for each(DisplayMode dm in ai.SupportedDisplayModes[allowedFormats[i]])
  
  {
  
  if ( (dm.Width < minimumWidth) ||

  
  (dm.Height < minimumHeight) ||
  
  (dm.Width > maximumWidth) ||
  
  (dm.Height > maximumHeight) ||
  
  (dm.RefreshRate < minimumRefresh) ||
  
  (dm.RefreshRate > maximumRefresh) )
  
  {
  
  continue; //
这种格式是无效的
  
  }
  
  //
添加到列表中
  
  adapterInfo.displayModeList.Add(dm);
  
  //
如果先前并不存在就把它添加到格式列表中
  
  if (!adapterFormatList.Contains(dm.Format))
  
  {
  
  adapterFormatList.Add(dm.Format);
  
  }
  
  }
  
  }
  
  //
获取适配器显示模式
  
  DisplayMode currentAdapterMode = ai.CurrentDisplayMode;
  
  //
检查这种格式是否在列表中
  
  if (!adapterFormatList.Contains(currentAdapterMode.Format))
  
  {
  
  adapterFormatList.Add(currentAdapterMode.Format);
  
  }
  
  //
对显示模式列表进行排序
  
  adapterInfo.displayModeList.Sort(sorter);
  
  //
获取这个适配器上每个设备的信息
  
  EnumerateDevices(adapterInfo, adapterFormatList);
  
  //
如果适配器上至少有一个设备,并且它是兼容的,就把它添加到列表中
  
  if (adapterInfo.deviceInfoList.Count > 0)
  
  {
  
  adapterInformationList.Add(adapterInfo);
  
  }
  
  }
  
  Manager
类的Adapters(适配器)属性是一个包含了系统中所有"适配器"信息的集合。"适配器"这个术语可能有点不恰当,但是它的基本定义是指任何监视器可以连接到的东西。例如,假设你有一块ATI Radeon 9800 XT显卡。虽然只有一块显卡,但是可能把两个不同的监视器连接到它上面(通过视频图形适配器[VGA]端口和后面的数字视觉接口[DVI]端口)。当用这两种监视器的时候,这块显卡就有两个适配器,因此是两种设备。
  
  
请注意
  
  
这是一种通过把设备创建为适配器组的方式在"不同的"设备之间共享资源的方法。这种方法受到了少许限制。你可以查阅DirectX文档了解更多的信息。
  
  
这个循环至少会迭代一次,这依赖于你的系统。在把当前活动的适配器的基本信息存储起来以后,代码必须找到在全屏模式下这个适配器可以支持的所有显示模式。你可能发现了受到支持的模式都可以直接从当前正在列举的适配器信息中直接列举出来,代码也是这样做的。
  
  
列举某个适配器模式的时候,第一步是检查最小和最大的范围集合。大多数设备支持很多模式,但是其中很多我们现在不会使用了。很多年前,你可能见过在320x200全屏窗口中运行游戏,但是现在不会发生这种情况(除非你正好在玩手持式游戏,例如Gameboy Advance)。示例框架选择的最小的大小为640x480窗体,没有设置最大的尺寸。

 

C#创建一个万年历

在这篇文章里,我们将用Microsoft最新推出的.Net Framework-C#来创建一个万年历(文中所用的程序是一个带万年历功能的日记本)。

 首先打开MicrosoftVS.NETVisual Studio.NET)创建一个新项目。选择Visual C#项目,在模板中选择Windows应用程序,按“确定”后,一个Windows窗体就建好了。这就是我们平常说的Win Form啦。

  下面先在Visual C#界面下把日记本的样式设计出来。需要两个万年历控件,一个用来显示、添加、编辑日记内容的“丰富的文本框”(richtextbox控件),还有一个用来添加和修改日记的按钮(button控件)richtextbox控件和button控件都能很容易地在工具箱中找到,不用多说添加上去就可以了,关键在于万年历,我们不必自己去编写一个万年历,微软已经为我们写好了。要做的就是把它拿出来按照正确的方法使用。首先我来说说如何调用这个万年历。它是Microsoft公司VS.NET产品附送的一个控件。我们在VS.NET窗口下点击“项目”下拉菜单,然后选择“添加/引用”就会弹出一个用于引用控件的窗体,此窗体包含三个标签,即“.Net”、“COM”、“项目”。我们要用的日历控件就包含在其中的“COM”里面。点击它,然后在硬盘一阵狂响之后,会发现多出了好多控件。找到控件Microsoft Calendar Control 9.0

  选中这个控件,点击右边的选择按钮,控件的名称会出现在下边“选定的组件”窗口中,这时候选择下边的“确定”按钮,选定的控件便添加到你所创建的项目当中了。你可以在你的“工具箱”当中找到一个名字叫做MonthCalendar的控件和一个叫做DateTimePicker的控件,这两个控件便是我们刚才引用来的。在这里我们要用到的是第一个MonthCalendar,选择MonthCalendar控件并在窗体上划出它的轮廓。看,一个万年历便展示在我们面前了。

  但是只我们能看还不行,为了能察看或者修改以前的日记还需要让程序知道我们在这个控件选择的日期是哪一天,这样才能告诉数据库,我们要调用哪一天的日记。为了方便暂时用richtextbox做显示当前日期的容器。

现在来研究一下如何让richtextbox显示MounthCalendar所选择的日期。双击MounthCalendar控件,会跳转到代码界面,并且得到一个系统为我们自动创建的事件。这个事件就是日期选择事件,是MounthCalendar控件的默认事件。

 
private void monthCalendar_DateChanged(object sender,
System.Windows.Forms.DateRangeEventArgs e)
{
//
事件被触发后所执行的代码
}


  利用这个事件我们便可以让程序知道,用户何时重新选择了一个日期。但是如果想要让程序知道用户所选择的日期是哪一天并且让程序将这一天的日期显示在richtextbox上,我们还有一个问题,那就是如何读取用户所选择的日期,这就要用到下面这个属性

monthCalendar.SelectionRange.Start.ToShortDateString()

  这个属性的值便是用户所选择的日期。现在事件定义了,属性也找到了,下边让我们来看一下如何把日期显示在richtextbox容器内。

  添加这段代码到事件中
richTextBox.Text=monthCalendar.SelectionRange.Start.ToShortDateString();

  运行一下。当选择了一个日期后,这个日期便会显示在richtextbox内了。

  这样一个万年历就完成了。从这个例子可以看到,VS.NET提供了非常丰富的控件库。朋友们在使用VS.NET进行编程的时候,不要浪费这些资源,它们会帮助我们节省好多宝贵的时间和精力。

 

一步一步用Visual C#创建Web服务

引言:

  微软在其.Net战略中,对其主推的Web服务做了大肆的宣扬。现在,Web服务正如火如荼地发展着,相关的各项新技术层出不穷。Web服务的发展正构筑着互联网时代美好的明天。在本文中,我将向大家介绍Web服务的一些基本知识以及如何用Visual C#一步一步地创建一个简单的Web服务。

一.Web服务概述:

  Web服务是一种新的Web应用程序分支,它们是自包含、自描述、模块化的应用,可以发布、定位、通过Web调用。Web服务可以执行从简单的请求到复杂商务处理的任何功能。一旦部署以后,其他Web服务应用程序可以发现并调用它部署的服务。Web服务可以把业务逻辑划分一个一个的组件,然后在整个因特网的范围上执行其功能。所以,它是构造分布式、模块化应用程序的最新技术发展趋势。

二.为什么需要Web服务?

  以前,分布式的应用程序逻辑需要使用分布式的对象模型,通过使用DCOMCORBARMI之类的基本结构,开发人员仍可拥有使用本地模型所提供的丰富资源和精确性,并可将服务置于远程系统中。

  当已经有中意的中间件平台(RMIJiniCORBADCOM 等等)时,我们为什么还要为Web而烦恼呢?中间件确实提供了强大的服务实现手段,但是,这些系统有一个共同的缺陷,那就是它们无法扩展到互联网上:它们要求服务客户端与系统提供的服务本身之间必须进行紧密耦合,即要求一个同类基本结构。然而这样的系统往往十分脆弱:如果一端的执行机制发生变化,那么另一端便会崩溃。例如,如果服务器应用程序的接口发生更改,那么客户端便会崩溃。为了能扩展到互联网运用,我们需要一种松散偶合的基本结构来解决这个问题。如此的情况下就迎来了Web服务的诞生。

三.开发环境:

1
Windows 2000 Server操作系统或Windows XP操作系统;
2
.Net Framework以及Visual Studio.net开发工具。

四.创建Web服务工程:

我在这里向大家介绍一个美元到人民币转换的Web服务实例,该实例完成的功能相当简单,从名字我们就能知道其中的功能。但是这也是一个非常不错的例子,特别对于初学者,能起到很好的指导作用。在创建Web服务过程中,我们用到的是C#语言。下面就是具体的项目步骤了。

  首先,打开VS.net,新建一个项目,在左边的面板中选择“Visual C#项目”,右边的面板中选择“ASP.NET Web服务”,并命名为“WebService1”,图示如下:



1

  按下“确定”按钮后,VS.net就开始帮你新建该项目,期间还可能会出现一个用来显示Internet连接的对话框。在新建完项目后,在开发工具中会出现如下所示的界面:



2

  因为我们要实现的是一个非常简单的Web服务,所以我们需要的功能和我们的代码量都很小,于是就不必去考虑上图中的“服务器资源管理器”和“工具箱”两个超链接,而可以直接点击上图中的“此处”链接进行代码编辑,点击后会打开代码编辑框,图示如下:



3

  在上面的代码编辑框中,我们先把原有的“Hello WorldWeb服务的示例代码去掉,替换成我们的代码,最终如下:
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Web;
using System.Web.Services;

namespace WebService1
{
///
/// Service1
的摘要说明。
///
public class Service1 : System.Web.Services.WebService
{
public Service1()
{
//CODEGEN
:该调用是 ASP.NET Web 服务设计器所必需的
InitializeComponent();
}

#region Component Designer generated code

//Web
服务设计器所必需的
private IContainer components = null;

///
///
设计器支持所需的方法 - 不要使用代码编辑器修改
///
此方法的内容。
///
private void InitializeComponent()
{
}

///
///
清理所有正在使用的资源。
///
protected override void Dispose( bool disposing )
{
if(disposing && components != null)
{
components.Dispose();
}
base.Dispose(disposing);
}

#endregion

// WEB
服务
// DollarConvertToRMB()
服务完成美元到人民币的转换
//
若要测试此 Web 服务,请按 F5

[WebMethod]
public double DollarConvertToRMB(double Dollar)
{
return ( Dollar * 8.15);
}
}
}

  在上面的方法DollarConvertToRMB()中,我们返回的是一个double类型的值――Dollar*8.15,其中的8.15我想是不言而喻的(就是美元到人民币的汇率)。不过现实的汇率是不固定的,而且每天都要变动,所以要根据当天实际的汇率来计算,那么我们就要连接到数据库获得最新的信息了。不过,这里作为一个简单实例,我们当然不需要搞得那么复杂,所以在这里我就姑且假定汇率为18.15

  同时,我们还需要注意,在该Web服务的代码中我们用到了using System.Webusing System.Web.Services等名字空间,作为Web服务开发,这些名字空间是显然不能缺的,没有了这些,我们就不能调用.Net框架为我们提供的开发Web服务所必须的方法和函数等,所以一定不能忘掉。

  到此为止,代码编写已经完毕,然后把代码文件存放在某个虚拟目录下(通常是C:\Inetpub\wwwroot\WebService1)即可。把文件保存为Service1.asmxAsmx文件扩展名是.NET Web服务的标记。保存文件之后,你的Web服务就准备露脸了。

.测试Web服务:

现在准备好测试Web服务。期间你不必通过显式的编译全过程,只需要地在目录下保存文件然后调用它即可。为了调用最新创建的服务,请打开你的浏览器并输入服务路径,包括Asmx文件的名字。如果你把服务放了在C:\Inetpub\wwwroot\WebService1目录下,那么你要在浏览器地址栏上键入http://localhost/WebService1/Service1.asmx。(当然,在你使用VS.net开发时,你也可以通过Ctrl+F5直接进行Web服务的测试。)

  在调用服务时会显示一幅包含大量信息的网页。一开始这可能会令你觉得有点糊涂:不管怎么说,你还没有为这种服务创造过HTML网页。其实,你根本不必创建测试网页,因为.NET框架已经帮你这个忙了。当你直接通过浏览器调用Web服务时,框架就会为你产生一个网页并通过它向你显示Web服务的信息,同时列出所有可用的方法。下图即是Web服务的网页。



4

在这个例子中列出的方法没什么特别之处,因为仅有一个方法(DollarConvertToRMB)。鼠标点击这个方法会显示另一个网页,如图5所示。这一页就是该特定方法的测试页,其中包括对应方法接受的每个参数的文本框。现在请在文本框中输入“10”并按下“调用”按钮。



5

单击“调用”按钮会打开一个新的浏览器窗口,其中显示了一些XML代码。这些XML代码是由该Web服务返回的,其中包括了服务的结果。返回的XML代码如下图所示:



6

  返回的结果是一些XML代码,可能用户界面显得不那么友好,但是这些结果确实不一定非要采取对用户友好的格式,因为你通常不会从浏览器直接调用Web服务。相反,你往往从应用程序调用Web服务同时适当地处理返回的XML代码。不过,也很容易从上面的代码看出Web服务已经把10美元转换成了81.5人民币了。

六.总结:

  上面举的这个例子很简单,它完成的任务是:创建一个组件,如果组件放在Web服务器上就可以被世界上任何地方的任何人访问。客户不必装载COM DCOM;甚至也不必拥有Windows客户程序。任何能创建HTTP连接的客户程序都能调用Web服务并且收到结果。这种功能开辟了创建分布式应用程序、实现平台之间互操作的全新领域。同时,我们也不难发现用VS.net开发Web服务是一件相当容易的事。有兴趣的读者可以试着开发出功能更强大的Web服务并将它赋予实际应用之中。

 

 

 

 

// 下面是一个关于C#的农历算法

// 日期数据定义方法如下

// 前12个字节代表1-12月为大月或是小月,1为大月30天,0为小月29天,

// 第13位为闰月的情况,1为大月30天,0为小月29天,第14、15位为闰月的月

// 份,使用10进制表示。最后4位为当年家农历新年-即农历1月1日所在公历

// 的日期,如0131代表1月31日。

// 日期函数使用方式如下i公历年为要输入的公历年,i公历月为公历月,i公历日为

// 公历日,返回值为:属相 ,天干地支,农历年农历月农历日。

 

 

using System;

 

namespace 农历组件

{

    public class 农历

    {

        private string[] m_str农历日历表;

        private string[] m_str天干地支表;

        private string m_str属相表;

        private string m_str农历月表;

        private string m_str农历日表;

        private int m_i最大公历年份;

        private int m_i最小公历年份;

 

        public 农历()

        {

            m_str农历日历表 = new string[] {

                                         "0100101101101080131",

                                         "0100101011100000219",

                                         "1010010101110000208",

                                         "0101001001101050129",

                                         "1101001001100000216",

                                         "1101100101010000204",

                                         "0110101010101040125",

                                         "0101011010100000213",

                                         "1001101011010000202",

                                         "0100101011101020122",

                                         "0100101011100000210",

                                         "1010010011011060130",

                                         "1010010011010000218",

                                         "1101001001010000206",

                                         "1101010101001050126",

                                         "1011010101010000214",

                                         "0101011010100000204",

                                         "1001011011010020123",

                                         "1001010110110000211",

                                         "0100100110111070201",

                                         "0100100110110000220",

                                         "1010010010110000208",

                                         "1011001001011050128",

                                         "0110101001010000216",

                                         "0110110101000000205",

                                         "1010110110101040124",

                                         "0010101101100000213",

                                         "1001010101110000202",

                                         "0100100101111020123",

                                         "0100100101110000210",

                                         "0110010010110060130",

                                         "1101010010100000217",

                                         "1110101001010000206",

                                         "0110110101001050126",

                                         "0101101011010000214",

                                         "0010101101100000204",

                                         "1001001101110030124",

                                         "1001001011100000211",

                                         "1100100101101070131",

                                         "1100100101010000219",

                                         "1101010010100000208",

                                         "1101101001010060127",

                                         "1011010101010000215",

                                          "0101011010100000205",

                                         "1010101011011040125",

                                         "0010010111010000213",

                                         "1001001011010000202",

                                         "1100100101011020122",

                                         "1010100101010000210",

                                         "1011010010101070129",

                                         "0110110010100000217",

                                         "1011010101010000206",

                                         "0101010110101050127",

                                         "0100110110100000214",

                                         "1010010110110000203",

                                         "0101001010111030124",

                                         "0101001010110000212",

                                         "1010100101010080131",

                                         "1110100101010000218",

                                         "0110101010100000208",

                                         "1010110101010060128",

                                         "1010101101010000215",

                                         "0100101101100000205",

                                         "1010010101110040125",

                                         "1010010101110000213",

                                         "0101001001100000202",

                                         "1110100100110030121",

                                         "1101100101010000209",

                                         "0101101010101070130",

                                         "0101011010100000217",

                                         "1001011011010000206",

                                         "0100101011101050127",

                                         "0100101011010000215",

                                         "1010010011010000203",

                                         "1101001001101040123",

                                         "1101001001010000211",

                                         "1101010100101080131",

                                         "1011010101000000218",

                                         "1011011010100000207",

                                         "1001011011010060128",

                                         "1001010110110000216",

                                         "0100100110110000205",

                                         "1010010010111040125",

                                         "1010010010110000213",

                                         "1011001001011100202",

                                         "0110101001010000220",

                                         "0110110101000000209",

                                         "1010110110101060129",

                                         "1010101101100000217",

                                         "1001001101110000206",

                                         "0100100101111050127",

                                         "0100100101110000215",

                                         "0110010010110000204",

                                         "0110101001010030123",

                                         "1110101001010000210",

                                         "0110101100101080131",

                                         "0101101011000000219",

                                         "1010101101100000207",

                                         "1001001101101050128",

                                         "1001001011100000216",

                                         "1100100101100000205",

                                         "1101010010101040124",

                                         "1101010010100000212",

                                         "1101101001010000201",

                                         "0101101010101020122",

                                         "0101011010100000209",

                                         "1010101011011070129",

                                         "0010010111010000218",

                                         "1001001011010000207",

                                         "1100100101011050126",

                                         "1010100101010000214",

                                         "1011010010100000214"  

                                     };

            m_str属相表 = "鼠牛虎兔龙蛇马羊猴鸡狗猪";

            m_str农历月表 = "正二三四五六七八九十寒腊";

            m_str农历日表 = "初一初二初三初四初五初六初七初八初九初十十一十二十三十四十五十六十七十八十九二十廿一廿二廿三廿四廿五廿六廿七廿八廿九三十";

            m_i最大公历年份 = 2011;

            m_i最小公历年份 = 1900;

 

            string str天干 = "甲乙丙丁戊已庚辛壬癸";

            string str地支 = "子丑寅卯辰巳午未申酉戌亥";

            m_str天干地支表 = new string[60];

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

            {

                m_str天干地支表[i] = str天干.Substring(i % 10, 1) + str地支.Substring(i % 12, 1);

            }

 

        }

 

        public string 日期(int i公历年,

            int i公历月,

            int i公历日)

        {

            if ( (i公历年 < m_i最小公历年份) || (i公历年 > m_i最大公历年份) )

            { //如果不是有效公历日期,退出。

                return "无效公历年份";

            }

 

            // 计算农历年

            int i农历年;

            int i农历月;

            int i农历日;

 

 

            i农历年 = i公历年;

            // 农历新年月份

            i农历月 = Convert.ToInt32((m_str农历日历表[i农历年 - m_i最小公历年份].Substring(15, 2)));

            // 农历新年日子

            i农历日= Convert.ToInt32((m_str农历日历表[i农历年 - m_i最小公历年份].Substring(17, 2)));;

  

            if ( (i公历月 < i农历月) || ( (i公历月 == i农历月) && (i公历日 < i农历日)) )

            {

                i农历年--;

                // 农历新年月份

                i农历月 = Convert.ToInt32((m_str农历日历表[i农历年 - m_i最小公历年份].Substring(15, 2)));

                // 农历新年日子

                i农历日= Convert.ToInt32((m_str农历日历表[i农历年 - m_i最小公历年份].Substring(17, 2)));;

            }

  

            // 计算农历月

            DateTime dt公历日期 = new DateTime(i公历年, i公历月, i公历日);

            DateTime dt农历日期 = new DateTime(i农历年, i农历月, i农历日);

            TimeSpan ts日期差 = dt公历日期 - dt农历日期;

            int i天数 = ts日期差.Days;

 

            i农历月 = 1;

            i农历日 = 1;

            bool b闰月 = false;

            for (int i = 0; i < i天数; i++)

            {

                i农历日++;

                if (i农历日 == 30 + Convert.ToInt32(m_str农历日历表[i农历年 - m_i最小公历年份].Substring(i农历月 - 1, 1)) ||

                    (b闰月 && ( i农历日 == 30 + Convert.ToInt32( m_str农历日历表[i农历年 - m_i最小公历年份].Substring(12, 1) ) )) )

                {

                    if ( (b闰月 == false) && (i农历月 == Convert.ToInt32(m_str农历日历表[i农历年 - m_i最小公历年份].Substring(13, 2))) )

                    {

                        b闰月= true;

                    }

                    else

                    {

                        b闰月 = false;

                        i农历月++;

                    }

                    i农历日 = 1;

                }

                else

                {

                }

            }

  

            // 计算农历日

            string str农历日 = m_str农历日表.Substring((i农历日 -1) * 2, 2);

  

            // 计算农历月

            string str农历月 = m_str农历月表.Substring(i农历月 - 1, 1) + "月";

            if (b闰月)

            {

                str农历月 = "闰" + str农历月;

            }

  

            // 农历年

            string str农历年 = Convert.ToString(i农历年, 10) + "年";

  

            // 计算天干地支

            string str天干地支 = m_str天干地支表[ (i农历年 - 4) % 60 ];

 

            // 计算属相

            string str属相 = m_str属相表.Substring((i农历年 - 4) % 12, 1);

  

            // 返回农历日期

            return str属相 + "," + str天干地支 + "," + str农历年 + str农历月 + str农历日;

        }

    }

}

 

 

posted on 2006-07-28 11:38  Yang-S  阅读(2317)  评论(1)    收藏  举报