happyqiang

博客园 首页 新随笔 联系 订阅 管理

 

一共470多例winform 界面特效的源码。

实例030 窗口颜色的渐变

实例说明

在程序设计时,可以通过设置窗体的BackColor属性来改变窗口的背景颜色。但是这个属性改变后整个窗体的客户区都会变成这种颜色,并且非常单调。如果窗体的客户区可以向标题栏一样能够体现颜色的渐变效果,那么窗体风格将会另有一番风味。本例设计了一个颜色渐变的窗体。效果如图1.30所示。

技术要点

C#中可以通过Color.FromArgb( )方法返回一种颜色,下面详细介绍一下该方法。

Color.FromArgb( )方法用来返回Color的颜色值,该方法语法结构如下:

public static Color FromArgb (

    int red,

    int green,

    int blue

)

参数说明如下。

l     red:新Color的红色分量值。有效值为从0~255。

l     green:新Color的绿色分量值。有效值为从0~255。

l     blue:新Color的蓝色分量值。有效值为从0~255。

l     返回值:此方法创建的Color。

该函数就是用3种不同的色值来返回一个颜色,而稍微的调整某一种颜色值就可以使整体的颜色发生细微的变化,在窗体中至上而下每行填充一种稍微调整后的颜色,这样整体看来就会产生渐变的效果。可以利用窗体的Graphics对象对窗体进行绘图,该对象可以完全操控窗体的客户区。

 注意:颜色值在0~255之间。

实现过程

(1)创建一个项目,将其命名为Ex01_30,默认窗体为Form1。

(2)在Form1窗体中添加Button用来使颜色渐变;添加TextBox控件用来输入颜色RGB值。

(3)主要程序代码。

触发重新绘制事件的实现代码如下:

        private void button2_Click(object sender, EventArgs e)

        {

            InvokePaintBackground( );

            this.Hide( );

            this.Visible=true;

        }

重新绘制窗体背景颜色的实现代码如下:

        protected override void OnPaintBackground(PaintEventArgs e)

        {

            int y, dy;

            y = this.ClientRectangle.Location.Y;

            dy = this.ClientRectangle.Height / 256;

            for (int i = 255; i >= 0; i--)

            {

             

                Color c = new Color( );

                c = Color.FromArgb(Convert.ToInt32(textBox1.Text.ToString( )), i,Convert.ToInt32(textBox2.Text.ToString( )));

                SolidBrush sb = new SolidBrush(c);

                Pen p = new Pen(sb, 1);

                e.Graphics.DrawRectangle(p,this.ClientRectangle.X, y, this.Width,y+dy);

                y = y + dy;

            }

        }

举一反三

根据本实例,读者可以开发以下程序。

  把窗体设置成单一的颜色。

  利用Timer组体,使窗体动态改变颜色。

1.9 

本节主要对窗体进行动画设置,在窗体上添加一些动画效果,可以为操作者添加一些乐趣,下面的几个例子将详细介绍窗体动画的相关技术。

实例031 窗体中的滚动字幕

实例说明

普通窗体中的文字位置都是固定的,一些窗体中需要让文字动起来,例如一些广告性较强的界面中需要做一些滚动的字幕。本例实现了一个具有滚动字幕效果的窗体,运行本例,单击【演示】按钮,看到窗口中的文字开始滚动。单击【暂停】按钮,可以使字幕停止滚动。本例运行效果如图1.31所示。

技术要点

滚动字幕的效果其实就是改变了文字的位置,在窗体中显示一串文字最好的办法就是利用Label控件。将Label控件的位置改变就可以实现文字的位置变换,如果该控件的位置不断的向水平方向移动,就会实现文字的滚动效果。改变Label控件的水平位置可以通过改变Label控件的Left的值来实现。用Timer控件对文字的移动进行时间控制。

实现过程

(1)创建一个项目,将其命名为Ex01_31,默认窗体为Form1。

(2)在窗体上添加Label控件用来显示消息;添加Button控件用来控制消息的运动;添加Timer控件用来控制滚动速度。

(3)主要程序代码。

        private void timer1_Tick(object sender, EventArgs e)//用Timer来控制滚动速度

        {

           

            label1.Left -= 2;

            if (label1.Right < 0)

            {

                label1.Left = this.Width;

            }

        }

        private void button1_Click(object sender, EventArgs e)

        {

            timer1.Enabled = true; //开始滚动

        }

        private void button2_Click(object sender, EventArgs e)

        {

            timer1.Enabled = false; //停止滚动

        }

 注意:要特别注意文字滚动的方向问题,向左则减,向右则加。

举一反三

根据本实例,读者可以开发以下程序。

  可以在窗体中设置一个滚动的图片。

  可以在窗体中设置一个滚动的提示信息。

实例032 动画显示窗体

实例说明

当用户启动程序后,普通的程序窗口都是瞬间显示到屏幕上,这样未免有些生硬。如果窗口能够慢慢的展现在用户面前,将会是什么样的效果?本例设计的是一个动画显示的窗体,该程序运行后,窗体是慢慢的以拉伸的效果显示到用户的面前。当关闭时也是一样慢慢的消失。本例运行效果如图1.32所示。

技术要点

Windows提供了一个API函数Animate Window,该函数可以实现窗体的动画效果,AnimateWindow函数在C#中的声明如下。

        [DllImportAttribute("user32.dll")]

        private static extern bool AnimateWindow(IntPtr  hwnd, int  dwTime, int  dwFlags);

参数说明如下。

l     hwnd:目标窗口句柄。

l     dwTime:动画的持续时间,数值越大动画效果的时间就越长。

l     DwFlags:DwFlags参数是动画效果类型选项,该参数在C#中的声明如下:

        public const Int32 AW_HOR_POSITIVE = 0x00000001;

        public const Int32 AW_HOR_NEGATIVE = 0x00000002;

        public const Int32 AW_VER_POSITIVE = 0x00000004;

        public const Int32 AW_VER_NEGATIVE = 0x00000008;

        public const Int32 AW_CENTER = 0x00000010;

        public const Int32 AW_HIDE = 0x00010000;

        public const Int32 AW_ACTIVATE = 0x00020000;

        public const Int32 AW_SLIDE = 0x00040000;

        public const Int32 AW_BLEND = 0x00080000;

DwFlags参数可选值含义如表1.1所示

表1.1                                                                   参数说明

标    志

描    述

AW_SLIDE

使用滑动类型。缺省则为滚动动画类型。当使用AW_CENTER标志时,这个标志就被忽略

AW_ACTIVE

激活窗口。在使用了AW_HIDE标志后不要使用这个标志

AW_BLEND

使用淡入效果。只有当hWnd为顶层窗口的时候才可以使用此标志

AW_HIDE

隐藏窗口,缺省则显示窗口

AW_CENTER

若使用了AW_HIDE标志,则使窗口向内重叠;若未使用AW_HIDE标志,则使窗口向外扩展

AW_HOR_POSITIVE

自左向右显示窗口。该标志可以在滚动动画和滑动动画中使用。当使用AW_CENTER标志时,该标志将被忽略

AW_HOR_NEGATIVE

自右向左显示窗口。当使用了 AW_CENTER 标志时该标志被忽略

AW_VER_POSITIVE

自顶向下显示窗口。该标志可以在滚动动画和滑动动画中使用。当使用AW_CENTER标志时,该标志将被忽略

AW_VER_NEGATIVE

自下向上显示窗口。该标志可以在滚动动画和滑动动画中使用。当使用AW_CENTER标志时,该标志将被忽略

实现过程

(1)创建一个项目,将其命名为Ex01_32,默认窗体为Form1。

(2)在窗体上添加PictureBox控件。

(3)设置PictureBox控件的Image属性。

(4)主要代码如下。

        public Form1( )

        {

            InitializeComponent( );

            AnimateWindow(this.Handle, 300, AW_SLIDE + AW_VER_NEGATIVE);//开始窗体动画

        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)

        {  //结束窗体动画

           AnimateWindow(this.Handle, 300, AW_SLIDE + AW_VER_NEGATIVE + AW_HIDE);

        }

举一反三

根据本实例,读者可以开发以下程序。

  实现窗体的淡入淡出。

  实现窗体从中间扩散显示。

实例033 制作闪烁的窗体

实例说明

Windows系统中,当程序在后台运行时,如果某个窗口的提示信息需要用户浏览,该窗口就会不停的闪烁,这样就会吸引用户的注意。同样,如果在自己的程序中使某个窗口不停的闪烁就会吸引用户的注意。本例设计了一个闪烁的窗体,运行程序,单击【开始闪烁】按钮,窗体就会不停的闪烁,单击【停止】按钮,窗体就会停止闪烁。本例运行效果如图1.33所示。

技术要点

Windows提供了一个API函数FlashWIndow,该函数可以使窗体闪烁一下。FlashWIndow函数在C#中声明如下:

        [System.Runtime.InteropServices.DllImportAttribute("user32.dll")]

        public static extern bool FlashWindow(IntPtr handle, bool bInvert);

参数说明如下。

l     handle:表示将要闪烁的窗体。

l     bInvert:是否恢复状态。

利用该函数只能使窗体闪烁一下,如果让窗口不停地闪烁,就需要用一个Timer控件每隔一段时间就调用该函数使窗体闪烁。

实现过程

(1)创建一个项目,将其命名为Ex01_33,默认窗体为Form1。

(2)在窗体上添加PictureBox控件用来显示窗体;添加Button、Timer控件用来开始和停止闪烁。

(3)设置PictureBox控件的Image属性。

(4)主要程序代码。

timer1的Tick事件处理代码如下:

        private void timer1_Tick(object sender, EventArgs e)

        {

            FlashWindow(this.Handle,true);

        }

【开始闪烁】按钮的单击事件,用来启动窗体闪烁:

        private void button1_Click(object sender, EventArgs e)

        {

            timer1.Enabled = true;

        }

【停止】按钮的单击事件,用来停止窗体的闪烁:

        private void button2_Click(object sender, EventArgs e)

        {

            timer1.Enabled = false;

        }

举一反三

根据本实例,读者可以开发以下程序。

  利用Visible属性制作一个闪烁的图片。

  制作一个闪烁的按钮。

实例034 直接在窗体上绘图

实例说明

含有Graphics对象的控件都能够在其上进行绘图,很多软件就是通过Graphics对象来美化程序的主界面,因为窗体中含有Graphics对象,所以可以将窗体看作一个大画板,一个可以在上面绘图的特殊控件。本例设计了一个简单的绘图软件,该软件就利用了在窗体上绘图的方法,运行本软件可以在窗体上进行绘图。实例效果如图1.34所示。

技术要点

窗体中含有Graphics对象,使用该对象就能够完成大部分绘图功能,Graphics对象已经对Windows底层的一些绘图API进行了封装,使用起来比较方便。下面介绍Graphics对象的常用方法。

Graphics.DrawLine绘图方法用来绘制一条连接由坐标对指定的两个点的线条。其语法结构如下:

public void DrawLine (Pen pen,int x1,int y1,int x2,int y2)

参数说明如下。

l     pen:Pen对象,确定线条的颜色、宽度和样式。

l     x1:第一个点的x坐标。

l     y1:第一个点的y坐标。

l     x2:第二个点的x坐标。

l     y2:第二个点的y坐标。

实现过程

(1)创建一个项目,将其命名为Ex01_34,默认窗体为Form1。

(2)向Form1窗口中添加GroupBox控件,用作RadioButton控件的容器;添加Button控件用来推出程序。

(3)主要程序代码。

在窗体单元的private中添加变量如下:

        int startX,startY;

        Graphics g;

单击鼠标事件。具体代码如下:

        private void Form1_MouseDown(object sender, MouseEventArgs e)

        {

            startX=e.X;

            startY = e.Y;

        }

鼠标在窗体中的移动事件。具体代码如下:

        private void Form1_MouseMove(object sender, MouseEventArgs e)

        {

             g = this.CreateGraphics( );

            Pen p = new Pen(Color.Black, 1);

            if(radioButton2.Checked==true)

            {

                g.DrawRectangle(p, e.X, e.Y, 1, 1);

            }

        }

鼠标抬起事件。具体代码如下:

        private void Form1_MouseUp(object sender, MouseEventArgs e)

        {

            g = this.CreateGraphics( );

            Pen p = new Pen(Color.Black, 2);

            if (radioButton1.Checked == true )

            {

                g.DrawLine(p, startX, startY, e.X, e.Y);

            }

        }

举一反三

根据本实例,读者可以实现以下功能。

  改变画笔的颜色。

  在窗体上绘制矩型。

实例035 动画形式的程序界面

实例说明

在很多的程序界面中,都是以菜单或工具栏的形式显示窗体界面,这种显示方式是以静止状态显示的,界面不够生动。下面介绍一个以动画显示窗体界面的设计方法。运行本例,效果如图1.35所示。

技术要点

在该实例中用到了Microsoft Animation Control 6.0(SP4)COM组件,所以要从工具箱“选择项”中将该组件添加到工具箱,然后继续将该组件从工具箱添加到窗体即可。下面介绍本例中用到的相关方法。

AxAnimation.open方法用来播放动画文件。其结构如下:

Public void AxAnimation.open(string bstrFilename)

参数说明如下。

l     bstrFilename:将要播放的文件名。

 注意:因为使用了AxAnimation类,所以要添加对WMPLib命名空间的引用。

实现过程

(1)创建一个项目,将其命名为Ex01_35,默认窗体为Form1。

(2)在Form1窗体添加PictureBox控件用来显示图片,添加Microsoft Animation Control 6.0 (SP4)COM组件用来播放动画。

(3)主要程序代码。

        private void Form1_Load(object sender, EventArgs e)

        {

            axAnimation1.Open("Electron.avi");

            axAnimation2.Open("zybiao.avi");

            axAnimation3.Open("gd.avi");

        }

举一反三

根据本实例,读者可以实现以下功能。

  制作摸拟网页。

  制作动画播放器。

1.10  标题栏窗体

本节主要是对窗体的标题栏进行设置,标题栏是一个显著的位置,在这个位置添加按钮或进行一些个性化的设置,都会给人一种新奇的感觉。通过以下实例的学习,读者将掌握此技术。

实例036 使窗体标题栏文字右对齐

实例说明

窗口标题栏中的文字是窗口的重要说明,该文字可以标示窗口的功能、状态或名称等信息,一般该文字是居左显示的,在本例中设计一个标题栏文字右对齐的窗口。本实例运行结果如图1.36所示。

技术要点

在C# 2.0中实现这一功能非常容易,只需将窗体的RightToLeft属性设置为Yes即可。

Form. RightToLeft属性用来获取或设置一个值,该值指示是否将控件的元素对齐以支持使用从右向左的字体的区域设置,其语法结构如下:

public virtual RightToLeft RightToLeft { get; set; }

l     属性值:RightToLeft值之一。默认为Inherit。

实现过程

(1)创建一个项目,将其命名为Ex01_36,默认窗体为Form1。

(2)为Form1窗体添加背景图片。

(3)设置RightToLeft属性为Yes。

举一反三

根据本实例,读者可以开发以下程序。

  利用Timer控件使窗体标题栏的文字进行左右闪动。

  制作窗体标题栏滚动播放图片的窗体。

实例037 没有标题栏可义改变大小的窗口

实例说明

隐藏Windows窗口的标题栏之后,窗口只剩下一个客户区域,有点像Panel控件在窗口中的样子,而这样的窗口通常是不能够改变大小的。因为屏蔽其标题栏之后,窗口默认将边框也去除了,本例将用特殊的方法建立一个没有标题栏但是可以改变其大小的窗体。实例运行效果如图1.37所示。

技术要点

窗口的样式是在窗口建立时确定的,在C#中实现窗体没有标题栏但是可以改变大小的窗口,有一个巧妙的方法就是将窗体的Text属性设为空,同时将ControlBox属性设为False。下面介绍一下相关的属性。

ControlBox属性用来获取或设置一个值,该值指示在该窗体的标题栏中是否显示控件框,其语法结构如下:

public bool ControlBox { get; set; }

l     属性值:如果该窗体在窗体的左上角显示控件框,则为True;否则为False。默认为True。

实现过程

(1)创建一个项目,将其命名为Ex01_37,默认窗体为Form1。

(2)在Form1窗口中添加Label、Button控件,用来设计界面。

(3)主要程序代码。

        private void Form1_Load(object sender, EventArgs e)

        {

            ControlBox = false;

        }

 注意:必须将窗体的Text属性设为空。

举一反三

根据本实例,读者可以开发以下程序。

  在窗体显示时最小化。

  在窗体显示时最大化。1.11  设置窗体位置

在许多的软件中,都会对窗体的大小、位置和移动进行限定。在不同分辨率的显示器中如何正确显示窗体、如何设置窗体始终在最上面,这些都需要本节的技术。

实例038 设置窗体在屏幕中的位置

实例说明

在窗体中可以设置窗体居中显示,本例通过设置窗体的Left属性和Top属性可以准确设置窗体的位置。运行本例,效果如图1.38所示。

技术要点

设置窗体在屏幕中的位置,可以通过设置窗体的属性来实现。窗体的Left属性表示窗体距屏幕左侧的距离,Top属性表示窗体距屏幕上方的距离。

实现过程

(1)创建一个项目,将其命名为Ex01_38,默认窗体为Form1。

(2)在窗体上添加Label控件;添加TextBox控件用来输入距屏幕的距离;添加Button控件用来设置窗体在屏幕上的位置。

(3)主要程序代码。

        private void button1_Click(object sender, EventArgs e)

        {

            this.Left = Convert.ToInt32(textBox1.Text);

            this.Top = Convert.ToInt32(textBox2.Text);

        }

举一反三

根据本实例,读者可以开发以下程序。

  根据分辨率的变化动态设置窗体位置。

  用Timer控件实时显示窗体位置。

实例039 始终在最上面的窗体

实例说明

Windows桌面上允许多个窗体同时显示,但是只有一个窗体能够得到焦点,当一个窗体得到焦点后在其上面的窗体会被得到焦点的窗体遮挡,得到焦点的窗体会显示在最上层,这样被覆盖的窗体就不能完全的显示给用户,如果该窗体中具有实时性和比较重要的信息时,需要该窗口始终在最上层。本例就实现了此功能,运行本例后,主窗体会始终在桌面的最上面。实例效果如图1.39所示。

技术要点

在其他开发环境中实现窗体始终在最上面比较复杂,但在C# 2.0中实现非常简单,只要将TopMost属性设为True即可。下面介绍一下TopMost属性。

Form.TopMost属性用来获取或设置一个值,指示该窗体是否应显示为最顶层窗体。其结构如下:

public bool TopMost { get; set; }

l     属性值:如果将窗体显示为最顶层窗体,则为True;否则为False。默认为False。

实现过程

(1)创建一个项目,将其命名为Ex01_39,默认窗体为Form1。

(2)为Form1窗体添加背景图片,并设置窗体TopMost属性为True。

举一反三

根据本实例,读者可以开发以下程序。

  可以将设为最上层的窗体设置成为一个电子表,以便观看时间。

  可以将设为最上层的窗体设置成为一个工作计划表,以便随时提醒自己。

1.12  设置窗体大小

用户打开软件后首先看到的就是窗体和窗体上的控件,如何设置窗体的大小及合理的设置窗体和控件的关系就变得十分重要,下面的实例将介绍这方面的知识。

实例040 限制窗体大小

实例说明

Windows窗体是可以随意改变大小的,然而对于一些要求严格的窗体,开发人员不希望用户随意的改变其大小,例如,定位准确的地图和游戏软件等。遇到这种情况必须对窗口的大小进行一些限制。本例设计一个限制了大小的窗体,用户虽然可以改变其大小,但是,大小的范围是受到限制的。实例效果如图1.40所示。

技术要点

在此C#中实现限制大小非常方便,只要设置窗体的最大和最小范围即可。下面介绍一下相关属性。

Form.MinimumSize属性用来获取或设置窗体可调整到的最小大小,其语法格式如下:

public override Size MinimumSize { get; set; }

l     属性值:Size,表示该窗体的最小大小。

Form.MaximumSize属性用来获取或设置窗体可调整到的最大大小,其语法格式如下:

public override Size MaximumSize{ get; set; }

l     属性值:Size,表示该窗体的最大大小。

实现过程

(1)创建一个项目,将其命名为Ex01_27,默认窗体为Form1。

(2)主要程序代码。

        private void Form1_Load(object sender, EventArgs e)

        {

            MinimumSize = new Size(200, 200);

            MaximumSize = new Size(400, 400);

        }

举一反三

根据本实例,读者可以开发以下程序。

  在窗体显示时规定其大小。

  在窗体运行时规定其大小。

实例041 获取桌面大小

实例说明

获取桌面分辨率可以使用API函数GetDeviceCaps,但API函数参数较多,使用不方便,如何更方便的获取桌面分辨率呢?在本例中,通过读取Screen对象的属性,来获取桌面分辨率信息,以像素为单位。运行本例,效果如图1.41所示。

技术要点

C#中提供了Screen对象,在该对象中封装了屏幕相关信息。可以通过读取Screen对象的相关属性,来获取屏幕的信息,Screen.PrimaryScreen.WorkingArea.Width用于读取桌面宽度;Screen.PrimaryScreen.WorkingArea.Height可以读取桌面的高度。下面介绍一下相关属性。

Screen.PrimaryScreen.WorkingArea属性用于获取显示器的工作区。工作区是显示器的桌面区域,不包括任务栏、停靠窗口和停靠工具栏。其结构如下:

public Rectangle WorkingArea { get; }

l     属性值:一个Rectangle,表示显示器的工作区。

实现过程

(1)创建一个项目,将其命名为Ex01_41,默认窗体为Form1。

(2)在Form1窗体上添加一个Button控件,用来获取桌面大小;添加两个TextBox控件,用来输出所获取的桌面大小。

(3)主要程序代码。

        private void button1_Click(object sender, EventArgs e)

        {

            textBox2.Text = Screen.PrimaryScreen.WorkingArea.Height.ToString( );

            textBox1.Text = Screen.PrimaryScreen.WorkingArea.Width.ToString( );

        }

举一反三

根据本实例,读者可以开发以下程序。

  根据显示器的分辨率信息设置窗体大小及位置。

  根据显示器的分辨率信息调整窗体界面。

实例042 在窗口间移动按扭

实例说明

窗体中每个可视控件都有所有者和父对象两个重要属性,所有者是控件建立时指定的所属对象,该对象可以是不可视控件,而父对象必须是可视控件。因此可以通过窗体中可视控件的Parent属性来判断控件是否在这个窗体中,还可以用Form.Controls.Add( )方法为窗体添加控件。本例以一个可以在窗口间移动的按钮来演示父对象改变后的运行效果。运行本例,在窗口中单击按钮,按钮就会移动到另外一个窗口中。实例效果如图1.42和图1.43所示。

                  

图1.42  在窗口间移动按钮                          图1.43  在窗口间移动按钮

技术要点

可视控件包含一个Parent属性,该属性表示控件的父对象。一般将此属性设置为一个窗口。通过该属性可以控制所属窗体。

实现过程

(1)创建一个项目,将其命名为Ex01_42,默认窗体为Form1。

(2)添加一个窗体,默认窗体的Name属性为Form2。

(3)在Form1窗口中添加一个Button控件。并为Form1和Form2设置背景图片。

(4)主要程序代码。

单击按钮在两个窗体之间移动,具体代码如下:

        private void button1_Click(object sender, EventArgs e)

        {

            if (button1.Parent == this)

            {

                f.Controls.Add(this.button1);

                this.button1.Text = "返回原地";

            }

            else

            {

                this.Controls.Add(button1);

                this.button1.Text = "开始移动";

            }

        }

Form1窗体加载时同时显示Form2窗体,具体代码如下:

        private void Form1_Load(object sender, EventArgs e)

        {

            f = new Form2( );

            f.Show( );

        }

举一反三

根据本实例,读者可以开发以下程序。

  试做从一个窗体将控件拖到另一个窗体。

  试做用一个窗体控制另一个窗体。

实例043 如何实现Office助手

实例说明

用过Office的人都知道,Office助手是一个非常漂亮的小工具,有了它,即使对Office不太熟悉的用户也可以操作自如。本实例使用C#制作了一个类似Office助手的程序,实例效果如图1.44所示。

技术要点

要实现Office助手效果,需要使用Microsoft提供的第3方控件。在工具箱中单击“选择项”,从弹出的对话框中选择COM组件选项卡中的Microsoft Agent Control 2.0组件并加入工具箱中,然后再添加到窗体中。

实现过程

(1)创建一个项目,将其命名为Ex01_43,默认窗体为Form1。

(2)在Form1窗体上添加一个ListBox控件用来让用户选择人物的动作。

(3)主要程序代码。

声明成员变量及字符串数组,具体代码如下:

        IAgentCtlCharacterEx ICCE;

        IAgentCtlRequest ICR;

        string[] ws = new string[10] { "Acknowledge", "LookDown", "Sad", "Alert", "LookDownBlink", "Search", "Announce", "LookUp", "Think", "Blink"};

为ListBox添加选项的实现代码如下:

        private void Form1_Load(object sender, EventArgs e)

        {

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

            {

                listBox1.Items.Add(ws[i]);

            }

            ICR = axAgent1.Characters.Load("merlin", "merlin.acs");

            ICCE = axAgent1.Characters.Character("merlin");

            ICCE.Show(0);

        }

随着选项改变Office表情的实现代码如下:

        private void listBox1_SelectedIndexChanged(object sender, EventArgs e)

        {

            ICCE.StopAll("");

            ICCE.Play(ws[listBox1.SelectedIndex]);

        }

举一反三

根据本实例,读者可以实现以下程序。

  瑞星助手。

  在自己的程序中加入Office助手。

 

实例044 在关闭窗口前加入确认对话框

实例说明

用户对程序进行操作时,难免会有错误操作的情况,例如不小心关闭程序,如果尚有许多资料没有保存,那么损失将非常严重,所以最好使程序具有灵活的交互性。人机交互过程一般都是通过对话框来实现的,对话框中有提示信息,并且提供按钮让用户选择,例如【是】或【否】。这样用户就能够对所做的动作进行确认。正如前面所说的不小心关闭程序,如果在关闭程序之前提示用户将要关闭程序,并且提供用户选择是否继续下去,这样就大大减少了误操作现象。本例程序中的窗口在关闭时会显示一个对话框,该对话框中有两个按钮【是】与【否】代表是否同意关闭程序操作。实例运行结果如图1.45所示。

技术要点

窗口正要关闭但是没有关闭之前会触发FormClosing事件,该事件中的参数FormClosingEventArgs e中包含Cancel属性,如果设置该属性为True,窗口将不会被关闭。所以在该事件处理代码中可以提示用户是否关闭程序,如果用户不想关闭程序,则设置该参数为True。利用MessageBox参数的返回值可以知道用户所选择的按钮。下面详细介绍一下相关属性。

CancelEventArgs.Cancel属性用来获取或设置指示是否应取消事件的值。该属性结构如下:

public bool Cancel { get; set; }

l     属性值:如果应取消事件,则为True;否则为False。

实现过程

(1)创建一个项目,将其命名为Ex01_44,默认窗体为Form1。

(2)主要程序代码。

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)

        {

            if (MessageBox.Show("将要要关闭窗体,是否继续?", "询问", MessageBoxButtons.YesNo) == DialogResult.Yes)

            {

                e.Cancel = false;

            }

            else

            {

                e.Cancel = true;

            }

        }

举一反三

根据本实例,读者可以实现以下程序。

  使窗体的关闭按钮无效。

  使窗体关闭出现在托盘中。

实例045 使用任意组件拖动窗体

实例说明

通常将鼠标按住窗口的标题栏才能够拖动窗口,但是,在没有窗口标题栏的情况下如何拖动窗体呢?本例将会利用窗口中的控件拖动窗口,将鼠标放在按钮上然后按住鼠标左键移动鼠标即可拖动窗体。实例效果如图1.46所示。

技术要点

通过控件移动窗体时,需要判断用户的鼠标动作。用户准备拖动窗体时必须在控件上按住鼠标左键,所以应该在鼠标MouseDown事件处理过程中来实现窗体的拖动。当用户在按钮上将鼠标左键按下时,触发MouseDown事件,在该事件处理代码中,MouseEventArgs e的Button属性记录了当前按下的鼠标按钮,如果按键是鼠标左键,则表示可以移动窗口,鼠标移动时,窗体就可以跟着移动了。

实现过程

(1)创建一个项目,将其命名为Ex01_45,默认窗体为Form1。

(2)在Form1窗体上添加两个Button控件,分别用来拖动窗体和关闭窗体。然后设置窗体的背景颜色。

(3)主要程序代码。

声明记录鼠标按下时初始位置的变量,具体代码如下:

        private int startX, StartY;

鼠标按下事件处理代码,具体代码如下:

        private void button1_MouseDown(object sender, MouseEventArgs e)

        {

            if (e.Button == MouseButtons.Left)

            {

                startX = e.X;

                StartY = e.Y;

            }

        }

鼠标移动事件处理代码,具体代码如下:

        private void button1_MouseMove(object sender, MouseEventArgs e)

        {

            if (e.Button == MouseButtons.Left)

            {

                this.Left += e.X - startX;

                this.Top += e.Y - StartY;

            }

        }

举一反三

根据本实例,读者可以开发以下程序。

  可以用窗体的用户区拖动窗体。

  不可以拖动的窗体。

实例046 修改提示字体及颜色

实例说明

如果设置了控件的ToolTip属性,当鼠标移到该控件后,会提示相关的文本,但没有提供对提示字体及颜色的设置属性,如何改变提示文本的样式和字体呢?本例可以设置提示文本的字体及颜色。运行本例,效果如图1.47所示。

技术要点

C# 2.0中提供了ToolTip控件,可以指定关联控件并为每个控件提供提示文本,其中ToolTipTitle属性指定文本提示盒中的文本。下面介绍相关的属性和方法。

(1)SetToolTip方法

使工具提示文本与指定的控件相关联。其语法结构如下:

public void SetToolTip (Control control,string caption)

参数说明如下。

l     control:要将工具提示文本与其关联的Control。

l     caption:指针位于控件上方时要显示的工具提示文本。

(2)ToolTip.ToolTipTitle属性

获取或设置工具提示窗口的标题。其语法结构如下:

public string ToolTipTitle { get; set; }

l     属性值:包含窗口标题的String。该标题在窗口中作为一行粗体文本显示在标准的工具提示控件说明文本的上方。通常,标题只用于区分窗体上不同类别的控件,或作为较长控件说明的简介。

实现过程

(1)创建一个项目,将其命名为Ex01_46,默认窗体为Form1。

(2)在Form1窗体上添加Button控件用来在其上方显示提示文本;添加ToolTip控件用来设计提示文本。

(3)主要程序代码。

设置提示文本,及提示文本的关联控件,具体代码如下:

        private void Form1_Load(object sender, EventArgs e)

        {

            this.toolTip1.OwnerDraw = true;

            this.toolTip1.SetToolTip(this.button1,"设置提示的字体及颜色");

            this.toolTip1.Draw += new DrawToolTipEventHandler(toolTip1_Draw);

        }

设置文本的提示样式,具体代码如下:

        void toolTip1_Draw(object sender, DrawToolTipEventArgs e)

        {

           // throw new Exception("The method or operation is not implemented.");

            e.DrawBackground( );

            e.DrawBorder( );

            using (StringFormat sf = new StringFormat( ))

            {

                sf.Alignment = StringAlignment.Center;

                sf.LineAlignment = StringAlignment.Center;

                sf.HotkeyPrefix = System.Drawing.Text.HotkeyPrefix.None;

                sf.FormatFlags = StringFormatFlags.NoWrap;

                using (Font f = new Font("宋体", 12))

                {

                    e.Graphics.DrawString(e.ToolTipText, f,

                        SystemBrushes.ActiveCaptionText, e.Bounds, sf);

                }

            }

        }

举一反三

根据本实例,读者可以实现以下功能。

  修改任意控件提示文本的样式。

  提示时加提示的声音。

1.14 

本节主要介绍了如何创建和关闭MDI窗体。在大型项目和产品的开发中常常将系统设计为MDI界面。

实例047 如何为MDI类型窗体设置背景图片

实例说明

MDI窗体是一种应用非常广泛的窗体类型,在一个主窗体内包含多个子窗体,子窗体永远不会显示在主窗体的外面。当子窗体不能完全的显示在主窗体中时,主窗体会显示滚动条来调整可视范围,在其他开发环境中为MDI窗体添加背景图片十分困难。但在C# 2.0中实现非常容易。在本例中实现了一个具有背景的MDI窗体。实例效果如图1.48所示。

技术要点

在C# 2.0中直接提供了BackgroundImage 属性,该属性可以直接设置窗体的背景图片。设置IsMdiContainer属性为True可以使窗体成为MDI主窗体。下面详细介绍一下相关属性。

(1)BackgroundImage属性

获取或设置在控件中显示的背景图像。其语法结构如下:

public virtual Image BackgroundImage { get; set; }

l     属性值:一个Image,表示在控件的背景中显示的图像。

(2)Form.IsMdiContainer属性

获取或设置一个值,该值指示窗体是否为多文档界面(MDI)子窗体的容器。其语法结构如下:

public bool IsMdiContainer { get; set; }

l     属性值:如果该窗体是MDI子窗体的容器,则为True;否则为False。默认为False。

此属性将窗体的显示和行为更改为MDI父窗体。当此属性设置为True时,该窗体显示具有凸起边框的凹陷工作区。所有分配给该父窗体的MDI子窗体都在该父窗体的工作区内显示。

实现过程

(1)创建一个项目,将其命名为Ex01_47,默认窗体为Form1。

(2)添加一个窗体,默认窗体的Name属性为Form2。

(3)为Form1窗体中添加背景图片。

(4)设置Form1窗体的IsMdiContainer属性为True,该窗口作为MDI主窗体。

(5)主要程序代码。

        private void Form1_Load(object sender, EventArgs e)

        {

            Form2 f = new Form2( );

            f.MdiParent = this;

            f.Show( );

        }

举一反三

根据本实例,读者可以开发以下程序。

  为非MDI窗体制作背景。

  为MDI子窗体设定显示区域。

实例048 向提示框中添加图标

实例说明

在开发程序时,为了让用户熟悉操作,经常使用一些提示框,显示提示信息。默认情况下,提示信息框只包含提示信息,未免有些单调,如果在提示信息框中显示一个图标,程序或许就别具风格了。本实例实现了在提示框中添加图标的功能,实例运行结果如图1.49所示。

技术要点

要修改提示信息框的风格,首先需要了解C#中提示信息框的设计原理。在C#中,提示信息框是用ToolTip控件来实现的。ToolTip控件的ToolTipIcon属性可以设置提示时显示的图片,下面详细介绍一下该属性。

ToolTip.ToolTipIcon属性用来获取或设置一个值,该值定义要在工具提示文本旁显示的图标的类型。其语法结构如下:

public ToolTipIcon ToolTipIcon { get; set; }

l     属性值:System.Windows.Forms.ToolTipIcon枚举值之一。

实现过程

(1)创建一个项目,将其命名为Ex01_48,默认窗体为Form1。

(2)在Form1窗口中添加3个Label控件,用来显示文字。

(3)在窗体上添加ToolTip控件用来显示提示内容和提示样式。

(4)主要程序代码。

.        private void Form1_Load(object sender, EventArgs e)

        {

            toolTip1.SetToolTip(label1,"人生格言");

            toolTip1.SetToolTip(label2, "人生格言");

        }

举一反三

根据本实例,读者可以实现以下功能。

  自定义提示信息框。

  制作气泡样式提示信息框。

 

 

 

  串口控制

  加密狗

  IC卡应用

  监控

  语音卡控制

  手机程序开发

  其他程序

 

第13章 硬件相关开发技术

13.1 

串行口是计算机的标准接口,现在的PC机(个人电脑)一般至少有两个串行口COM1和COM2。串行口应用广泛,在数据通信、计算机网络以及分布式工业控制系统中,经常采用串行通信来交换数据和信息。本节通过几个实例,介绍串口应用的技术和方法。

实例418 通过串口发送数据

实例说明

现在大多数硬件设备均采用串口技术与计算机相连,因此串口的应用程序开发越来越普遍。例如,在计算机没有安装网卡的情况下,将本机上的一些信息数据传输到另一台计算机上,那么利用串口通信就可以实现。运行本程序,在“发送数据”文本框中输入要传送的数据,单击【发送】按钮,将传送的数据发送到所选择的端口号中;单击【接收】按钮,传递的数据被接收到“接收数据”文本框中。如图13.1所示。

技术要点

在.NET Framework 2.0中提供了SerialPort类,该类主要实现串口数据通信等。下面主要介绍该类的主要属性(表13.1)和方法(表13.2)。

表13.1                                                     SerialPort类的常用属性

名  称

说  明

BaseStream

获取 SerialPort 对象的基础 Stream 对象

BaudRate

获取或设置串行波特率

BreakState

获取或设置中断信号状态

BytesToRead

获取接收缓冲区中数据的字节数

BytesToWrite

获取发送缓冲区中数据的字节数

CDHolding

获取端口的载波检测行的状态

CtsHolding

获取“可以发送”行的状态

DataBits

获取或设置每个字节的标准数据位长度

DiscardNull

获取或设置一个值,该值指示 Null 字节在端口和接收缓冲区之间传输时是否被忽略

DsrHolding

获取数据设置就绪 (DSR) 信号的状态

DtrEnable

获取或设置一个值,该值在串行通信过程中启用数据终端就绪 (DTR) 信号

Encoding

获取或设置传输前后文本转换的字节编码

Handshake

获取或设置串行端口数据传输的握手协议

IsOpen

获取一个值,该值指示 SerialPort 对象的打开或关闭状态

NewLine

获取或设置用于解释 ReadLine( )和WriteLine( )方法调用结束的值

Parity

获取或设置奇偶校验检查协议

续表

名  称

说  明

ParityReplace

获取或设置一个字节,该字节在发生奇偶校验错误时替换数据流中的无效字节

PortName

获取或设置通信端口,包括但不限于所有可用的 COM 端口

ReadBufferSize

获取或设置 SerialPort 输入缓冲区的大小

ReadTimeout

获取或设置读取操作未完成时发生超时之前的毫秒数

ReceivedBytesThreshold

获取或设置 DataReceived 事件发生前内部输入缓冲区中的字节数

RtsEnable

获取或设置一个值,该值指示在串行通信中是否启用请求发送 (RTS) 信号

StopBits

获取或设置每个字节的标准停止位数

WriteBufferSize

获取或设置串行端口输出缓冲区的大小

WriteTimeout

获取或设置写入操作未完成时发生超时之前的毫秒数

表13.2                                                    SerialPort类的常用方法

方 法 名 称

说  明

Close

关闭端口连接,将 IsOpen 属性设置为False,并释放内部 Stream 对象

Open

打开一个新的串行端口连接

Read

从 SerialPort 输入缓冲区中读取

ReadByte

从 SerialPort 输入缓冲区中同步读取一个字节

ReadChar

从 SerialPort 输入缓冲区中同步读取一个字符

ReadLine

一直读取到输入缓冲区中的 NewLine 值

ReadTo

一直读取到输入缓冲区中指定 value 的字符串

Write

已重载。将数据写入串行端口输出缓冲区

WriteLine

将指定的字符串和 NewLine 值写入输出缓冲区

 注意:用跳线使串口的第2、3针连接,可以在本地计算机上实现串口通信,所以,通过串口的第2、3针的连接可以对程序进行检测。串口截面图如图13.2所示。

 

图13.2  串口截面图

实现过程

(1)新建一个项目,命名为Ex13_01,默认窗体为Form1。

(2)在Form1窗体中,主要添加两个Button控件,分别用于执行发送数据和接受数据,添加两个TextBox控件,用于输入发送数据和显示接收数据。

(3)主要程序代码。

        private void button1_Click(object sender, EventArgs e)

        {

            serialPort1.PortName = "COM1";

            serialPort1.BaudRate = 9600;

            serialPort1.Open();

            byte[] data = Encoding.Unicode.GetBytes(textBox1.Text);

            string str = Convert.ToBase64String(data);

            serialPort1.WriteLine(str);

            MessageBox.Show("数据发送成功!","系统提示");

        }

        private void button2_Click(object sender, EventArgs e)

        {

            byte[] data = Convert.FromBase64String(serialPort1.ReadLine());

            textBox2.Text = Encoding.Unicode.GetString(data);

            serialPort1.Close();

            MessageBox.Show("数据接收成功!","系统提示");

        }

举一反三

根据本实例,读者可以实现以下功能。

  远程监控对方计算机屏幕。

  下位机控制程序。

实例419 通过串口关闭对方计算机

实例说明

在网络应用程序中,主要通过网卡实现数据的传输,因此可以利用套接字技术实现远程关闭计算机。如果计算机中没有安装网卡,该如何实现远程关闭计算机呢?本例实现了利用串口关闭对方计算机,程序运行结果如图13.3所示。

技术要点

本实例使用SerialPort类的属性和方法,请参见实例“通过串口发送数据”。下面主要介绍SerialPort类的DataReceived 事件,DataReceived 事件为本实例的主要使用技术。DataReceived事件表示将处理 SerialPort 对象的数据接收事件的方法。串行接收事件可以由 SerialData 枚举中的任何项引起,是否引发此事件由操作系统决定,所以不一定会报告所有奇偶校验错误。

 注意:本实例从开发到测试,都是由本地计算机完成的,用户只需要使用跳线将串口的第2、3针连接,可以在本地计算机上实现串口通信。跳线连接请参见图13.2。

实现过程

(1)新建一个项目,命名为Ex13_02,默认窗体为Form1。

(2)在Form1窗体中,主要添加两个Button控件,分别用于打开通信串口和关闭对方计算机。

(3)主要程序代码。

        private void button1_Click(object sender, EventArgs e)

        {

            //打开串口

            serialPort1.PortName = "COM1";

            serialPort1.Open();

            button1.Enabled = false;

            button2.Enabled = true;

        }   //数据接收事件,等待接收关机命令

        private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)

        {

            byte[] data = Convert.FromBase64String(serialPort1.ReadLine());

            string str = Encoding.Unicode.GetString(data);

            serialPort1.Close();

            if (str == "关机")

            {

                Process p = new Process();

                p.StartInfo.FileName = "cmd.exe";

                p.StartInfo.UseShellExecute = false;

                p.StartInfo.RedirectStandardInput = true;

                p.StartInfo.RedirectStandardOutput = true;

                p.StartInfo.RedirectStandardError = true;

                p.StartInfo.CreateNoWindow = true;

                p.Start();

                p.StandardInput.WriteLine("shutdown /s");

                p.StandardInput.WriteLine("exit");

            }

        }   //发送关机命令

        private void button2_Click(object sender, EventArgs e)

        {

            if (button2.Text == "关闭计算机")

            {

                //发送关机命令数据

                byte[] data = Encoding.Unicode.GetBytes("关机");

                string str = Convert.ToBase64String(data);

                serialPort1.WriteLine(str);

                button2.Text = "取消关机";

            }

            else

            {

                button2.Text = "关闭计算机";

                button1.Enabled = true;

                button2.Enabled = false;

                //取消关机

                Process p = new Process();

                p.StartInfo.FileName = "cmd.exe";

                p.StartInfo.UseShellExecute = false;

                p.StartInfo.RedirectStandardInput = true;

                p.StartInfo.RedirectStandardOutput = true;

                p.StartInfo.RedirectStandardError = true;

                p.StartInfo.CreateNoWindow = true;

                p.Start();

                p.StandardInput.WriteLine("shutdown /a");

                p.StandardInput.WriteLine("exit");

            }

        }

举一反三

根据本实例,读者可以实现以下功能。

  远程控制对方计算机操作。

  定时控制对方计算机关闭。

13.2  加 密 狗

一些商务管理软件,为了防止盗版,经常使用加密狗将软件加密。下面的两个实例将介绍如何将密码写入加密狗及利用加密狗来设计加密程序。

实例420 密码写入与读出加密狗

实例说明

在使用加密狗时,需要向加密狗中写入或读取数据。例如,将密码写入或读取加密狗,如何实现呢?运行本例,在文本框中设置密码后,单击【写入】按钮,即可将设置的密码写入加密狗,成功写入后,单击【读出】按钮,即可将写入的密码读出并显示在文本框中。如图13.4所示。

技术要点

在购买加密狗时,厂家通常会附带有开发手册和一张光盘。开发手册中介绍了加密狗的使用方法和开发资料。本例使用赛孚耐信息技术有限公司的加密狗产品,该产品提供了.NET中非托管的类库,来完成加密狗的数据读写功能。下面介绍有关加密狗的类库中的读写函数。

  ● DogWrite 函数

该函数将pdogData指向的数据写入加密狗中,从DogAddr地址开始写入,到DogBytes地址停止。

函数声明如下:

    [DllImport("Win32dll.dll", CharSet = CharSet.Ansi)]

    public static unsafe extern uint DogWrite(uint idogBytes, uint idogAddr, byte* pdogData);

参数说明如下。

l     idogAddr:对软件狗读写操作时用户区中的首地址。取值范围为0~99。

l     IdogBytes:对软件狗读写操作时的字节长度。读写时取值范围为1~100,并且与idogAddr之和不能超过100。

l     pdogData:指针型变量。指向读写操作或变换的数据缓冲区。

l     返回值:0表示操作成功,其他值是错误码。

  ● DogRead函数

该函数从加密狗中的idogAddr开始的存储区读出数据,存入pdogData指定的缓冲区,读出字节数为idogBytes。切记,缓冲区大小要足够长。

函数声明如下:

[DllImport("Win32dll.dll", CharSet = CharSet.Ansi)]

    public static unsafe extern uint DogRead(uint idogBytes, uint idogAddr, byte* pdogData);

参数说明如下。

l     idogAddr:对软件狗读写操作时用户区中的首地址。取值范围为0~99。

l     idogBytes:对软件狗读写操作时的字节长度。读写时取值范围为1~100,并且与idogAddr之和不能超过100。

l     pdogData:指针型变量。指向读写操作或变换的数据缓冲区。

l     返回值:0表示操作成功,其他值是错误码。

 注意以下几点。

在使用这个函数之前,必须将随加密狗附带的安装程序安装完整,并将安装目录下的Win32dll.dll文件复制到系统目录下。例如:

在Windows 2003下将安装目录下的“\SafeNet China\SoftDog SDK V3.1\Win32\Win32dll\HighDll\ Win32dll.dll”文件复制到“C:\WINDOWS\system32\”文件夹中。

实现过程

(1)新建一个项目,命名为Ex13_03,默认窗体为Form1。

(2)在Form1窗体中,主要添加两个Button控件,用于执行向加密狗数据的写入与读出数据,添加两个TextBox控件,分别用于填写向加密狗中写入的数据和显示读取加密狗中的数据。

(3)主要程序代码。

设置加密狗类,并且完善加密狗的读写功能,代码如下:

[StructLayout(LayoutKind.Sequential)]

//这个类用于读写加密狗

public unsafe class Dog

{

    public uint DogBytes, DogAddr;  //设置加密狗字节长度和起始地址

    public byte[] DogData;  //设置数据的长度

    public uint Retcode;

    [DllImport("Win32dll.dll", CharSet = CharSet.Ansi)]

    public static unsafe extern uint DogRead(uint idogBytes, uint idogAddr, byte* pdogData);

    [DllImport("Win32dll.dll", CharSet = CharSet.Ansi)]

    public static unsafe extern uint DogWrite(uint idogBytes, uint idogAddr, byte* pdogData);

    public unsafe Dog(ushort num)

    {

        DogBytes = num;

        DogData = new byte[DogBytes]; //设置数据的长度

    }

    public unsafe void ReadDog()

    {

        fixed (byte* pDogData = &DogData[0])

        {

            Retcode = DogRead(DogBytes, DogAddr, pDogData);  //将数据读出加密狗

        }

    }

    public unsafe void WriteDog()

    {

        fixed (byte* pDogData = &DogData[0])

        {

            Retcode = DogWrite(DogBytes, DogAddr, pDogData); //将数据写入加密狗

        }

    }

}

调用加密狗类,进行加密狗的读写功能,代码如下:

        private void button1_Click_1(object sender, EventArgs e)

        {

            Dog dog = new Dog(100);

            dog.DogAddr = 0;        

            dog.DogBytes = 10;   

            string str = textBox1.Text;

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

            {

                dog.DogData[i] = (byte)str[i];

            }

            dog.WriteDog();

            MessageBox.Show("密码已成功写入加密狗!", "成功提示!", MessageBoxButtons.OK, MessageBoxIcon.Information);

            textBox1.ReadOnly = true;

            button1.Enabled = false;

            button2.Enabled = true;

        }

        private void button2_Click_1(object sender, EventArgs e)

        {

            Dog dog = new Dog(100);

            dog.DogAddr = 0;       

            dog.DogBytes = 10;   

            dog.ReadDog();

            if (dog.Retcode == 0)   //开始读加密狗数据

            {

                char[] chTemp = new char[textBox1.Text.Length];

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

                {

                    chTemp[i] = (char)dog.DogData[i];

                }

                String str = new String(chTemp);

                textBox2.Text = str;

            }

            else

            {

                textBox2.Text = "2:" + dog.Retcode;

            }

            textBox1.ReadOnly = false;

            button2.Enabled = false;

            button1.Enabled = true;

        }

 注意:本程序所使用的代码为不安全代码,正常编译是无法通过的,那么需要设置开发环境允许运行不安全代码,设置步骤为:在菜单栏中选择“项目”/“属性”/“生成”子菜单,在“生成”选项卡中选中“允许不安全代码”选项即可。

举一反三

根据本实例,读者可以开发以下程序。

  利用加密狗加密自己的软件。

  利用加密狗设计控制计算机使用的程序。

实例421 使用加密狗进行身份验证

实例说明

在程序开发过程中,对于一些机密的数据,开发人员需要将其有效的保护起来。例如,对于用户的密码,如果从数据库中验证用户密码,很容易被非法人员发现甚至破解。本例实现了利用加密狗进行身份验证。实例运行结果如图13.5所示。

技术要点

本例的关键是从加密狗中读取数据,可以使用ReadDog函数实现。有关该函数的介绍请参考实例“密码写入与读出加密狗”中的“技术要点”部分。

实现过程

(1)新建一个项目,命名为Ex13_03,默认窗体为Form1。

(2)在Form1窗体中,主要添加两个Button控件,用于数据验证和退出程序,添加两个TextBox控件,分别用于输入用户名称和密码。

(3)主要程序代码。

        private void button1_Click(object sender, EventArgs e)

        {

            Dog dog = new Dog(100);

            dog.DogAddr = 0;       

            dog.DogBytes = 6;           

            dog.ReadDog();

            if (dog.Retcode == 0)

            {

                char[] chTemp = new char[6];

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

                {

                    chTemp[i] = (char)dog.DogData[i];

                }

                String str = new String(chTemp);

                if (textBox2.Text==str)

                {

                    MessageBox.Show("OK");

                }

                else

                {

                    MessageBox.Show("error");

                }

            }

        }

举一反三

根据本实例,读者可以实现以下功能。

  利用加密狗设计加密软件。

  使用加密狗控制用户使用权限。

13.3  IC卡应用

IC(Integrated Circuit)卡,也被称作智能卡(Smart Card),具有写入数据和存储数据的功能,IC卡内存储器的内容可以根据需要有条件地供外部读取,完成信息处理和判定。由于其内部具有集成电路,不但可以存储大量信息,具有极强的保密性能,并且还具有抗干扰、无磨损、寿命长等特性。因此在各个领域中得到广泛应用。下面通过两个实例介绍IC卡的简单应用。

实例422 向IC卡中写入数据

实例说明

IC卡是携带应用信息和数据的媒体,空白IC卡是不能立即使用的,必须对IC卡应用系统进行初始化,写入系统IC卡和个人密码,个人专用信息和应用数据。下面介绍如何向IC卡中写入数据。运行本例,在“数据”文本框中输入要存入IC卡中的数据,单击“写数据”按钮,即可将输入的数据写入IC卡中。如图13.6所示。

技术要点

本例使用的是深圳明华生产的明华IC卡读写器,用户在使用时将驱动程序安装完毕后,即可正常使用本系统。

本例通过调用Mwic_32.dll链接库,进行IC卡的读写工作。下面介绍与IC卡写操作相关的几个函数。

(1)auto_init函数

该函数用于初始化IC卡读卡器。语法如下:

public static extern int auto_init(int port, int baud);

参数说明如下。

l     port:标识端口号,Com1对应的端口号为0;Com2对应的端口号为1,依此类推。

l     baud:标识波特率。

l     返回值:如果初始化成功,返回值是IC卡设备句柄;如果初始化失败,返回值小于零。

(2)setsc_md函数

该函数用于设置设备密码模式。语法如下:

public static extern int setsc_md(int icdev, int mode);

参数说明如下。

l     icdev:标识设备句柄,通常是auto_init函数的返回值。

l     mode:标识设备密码模式,如果为0,设备密码有效,设备在加电时必须验证设备密码才能对设备进行操作。如果为1,设备密码无效。

l     返回值:如果函数执行成功返回值为零,否则小于零。

(3)get_status函数

该函数用于获取设备的当前状态。语法如下:

public static extern Int16 get_status(int icdev, Int16* state);

参数说明如下。

l     icdev:标识设备句柄,通常是auto_init函数的返回值。

l     state:用于接收函数返回的结果。如果为0表示读卡器中无卡,为1表示读卡器中有卡。

l     返回值:如果函数执行成功返回值为零,否则小于零。

(4)csc_4442函数

该函数用于核对IC卡密码。语法如下:

public static extern Int16 Csc_4442(int icdev, int len, [MarshalAs(UnmanagedType.LPArray)] byte[] p_string);

参数说明如下。

l     icdev:标识设备句柄,通常是auto_init函数的返回值。

l     len:标识密码长度,其值为3。

l     p_string:标识设置的密码。

l     返回值:如果函数执行成功返回值为零,否则小于零。

(5)swr_4442函数

该函数用于向IC卡中写入数据。语法如下:

public static extern int swr_4442(int icdev, int offset, int len, char* w_string);

参数说明如下。

l     icdev:标识设备句柄,通常是auto_init函数的返回值。

l     offset:标识地址的偏移量,范围是0~255。

l     len:标识字符串长度。

l     w_string:标识写入的数据。

(6)ic_exit函数

该函数用于关闭设备端口。语法如下:

public static extern int ic_exit(int icdev);

参数说明如下。

l     icdev:标识设备句柄,通常是auto_init函数的返回值。

(7)dv_beep函数

该函数使读卡器嗡鸣。语法如下:

public static extern int dv_beep(int icdev, int time);

参数说明如下。

l     icdev:标识设备句柄,通常是auto_init函数的返回值。

l     time:标识嗡鸣持续的时间,单位是10毫秒。

实现过程

(1)新建一个项目,命名为Ex13_05,默认窗体为Form1。

(2)在Form1窗体中,主要添加两个Button控件,用于执行向卡中写入数据和退出程序的操作,添加一个TextBox控件,将TextBox中数据写入IC卡中。

(3)主要程序代码。

将程序所使用的操作IC卡的函数,封装在类IC中。代码如下:

[StructLayout(LayoutKind.Sequential)]

public unsafe class IC

{

    //对设备进行初始化

    [DllImport("Mwic_32.dll", EntryPoint = "auto_init", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]

    public static extern int auto_init(int port, int baud);

    //设备密码格式

    [DllImport("Mwic_32.dll", EntryPoint = "setsc_md", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]

    public static extern int setsc_md(int icdev, int mode);

    //获取设备当前状态

    [DllImport("Mwic_32.dll", EntryPoint = "get_status", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]

    public static extern Int16 get_status(int icdev, Int16* state);

    //关闭设备通讯接口

    [DllImport("Mwic_32.dll", EntryPoint = "ic_exit", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]

    public static extern int ic_exit(int icdev);

    //使设备发出蜂鸣声

    [DllImport("Mwic_32.dll", EntryPoint = "dv_beep", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]

    public static extern int dv_beep(int icdev, int time);

    //向IC卡中写数据

    [DllImport("Mwic_32.dll", EntryPoint = "swr_4442", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]

    public static extern int swr_4442(int icdev, int offset, int len, char* w_string);

    //核对卡密码 

    [DllImport("Mwic_32.dll", EntryPoint = "csc_4442", SetLastError = true, CharSet = CharSet.Auto, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)]

    public static extern Int16 Csc_4442(int icdev, int len, [MarshalAs(UnmanagedType.LPArray)] byte[] p_string);

}

下面代码主要用于将TextBox中数据写入到IC卡中。代码如下:

        private void button1_Click(object sender, EventArgs e)

        {

            //初始化

            int icdev = IC.auto_init(0, 9600);

            if (icdev < 0)

                MessageBox.Show("端口初始化失败,请检查接口线是否连接正确。","错误提示",MessageBoxButtons.OK,MessageBoxIcon.Information);

            int md = IC.setsc_md(icdev, 1); //设备密码格式

            unsafe

            {

                Int16 status = 0;

                Int16 result = 0;

                result = IC.get_status(icdev, &status);

                if (result != 0)

                {

                    MessageBox.Show("设备当前状态错误!");

                    int d1 = IC.ic_exit(icdev);   //关闭设备

                    return;

                }

                if (status != 1)

                {

                    MessageBox.Show("请插入IC卡");

                    int d2 = IC.ic_exit(icdev);   //关闭设备

                    return;

                }

            }

            unsafe

            {

                //卡的密码默认为6个f(密码为:ffffff),1个f的16进制是15,两个f的16进制是255

                byte[] pwd = new byte[3] { 255, 255, 255 };

                //byte[] pwd = new byte[3] { 0xff, 0xff, 0xff };

                //char[] pass=new ch{0xff,0xff,0xff};

                Int16 checkIC_pwd = IC.Csc_4442(icdev, 3, pwd);

                if (checkIC_pwd < 0)

                {

                    MessageBox.Show("IC卡密码错误!");

                    return;

                }

                char str = 'a';

                int write=-1;

                for (int j = 0; j < textBox1.Text.Length; j++)

                {

                    str = Convert.ToChar(textBox1.Text.Substring(j, 1));

                    write = IC.swr_4442(icdev, 33 + j, textBox1.Text.Length, &str);

                }

                if (write == 0)

                {

                    int beep = IC.dv_beep(icdev, 20);  //发出蜂鸣声

                    MessageBox.Show("数据已成功写入IC卡中!");

                }

                else

                    MessageBox.Show("数据写入IC卡失败!");

            }

            int d = IC.ic_exit(icdev);  //关闭设备

        }

举一反三

根据本实例,读者可以实现以下功能。

  在图书借阅中使用IC卡。

  利用IC卡控制上网。

实例423 读取IC卡中的数据

实例说明

向IC卡写入数据后,就可以进行读卡操作了。运行本例,将写入数据的IC卡插入读卡器,单击【读卡】按钮,IC卡中的数据将显示在文本框中。如图13.7所示。

技术要点

本例中主要调用srd_4442函数读取IC卡中的数据,相关函数介绍请参考实例“向IC卡中写入数据”中的“技术要点”部分。这里只介绍读卡函数。

q srd_4442函数

该函数用于读取IC卡中的数据。语法如下:

public static extern int srd_4442(int icdev, int offset, int len, char* r_string);

参数说明如下。

l     icdev:标识设备句柄,通常是auto_init函数的返回值。

l     offset:标识地址的偏移量,范围是0~255。

l     len:标识字符串长度。

l     r_string:用于存储返回的数据。

实现过程

(1)新建一个项目,命名为Ex13_06,默认窗体为Form1。

(2)在Form1窗体中,主要添加两个Button控件,用于读取卡中的数据和退出程序,添加一个TextBox控件,显示卡中的数据。

(3)主要程序代码。

        private void button1_Click(object sender, EventArgs e)

        {

            //初始化

            int icdev = IC.auto_init(0, 9600);

            if (icdev < 0)

                MessageBox.Show("端口初始化失败,请检查接口线是否连接正确。", "错误提示", MessageBoxButtons.OK, MessageBoxIcon.Information);

            int md = IC.setsc_md(icdev, 1); //设备密码格式

            int i = IC.dv_beep(icdev, 10);  //发出蜂鸣声

            unsafe

            {

                Int16 status = 0;

                Int16 result = 0;

                result = IC.get_status(icdev, &status);

                if (result != 0)

                {

                    MessageBox.Show("设备当前状态错误!");

                    int d1 = IC.ic_exit(icdev);   //关闭设备

                    return;

                }

                if (status != 1)

                {

                    MessageBox.Show("请插入IC卡");

                    int d2 = IC.ic_exit(icdev);   //关闭设备

                    return;

                }

            }

            unsafe

            {

                char str = 'a';

                int read = -1;

                for (int j = 0; j < 6; j++)

                {

                    read = IC.srd_4442(icdev, 33 + j, 1, &str);

                    textBox1.Text = textBox1.Text + Convert.ToString(str);

                }

                if (read == 0)

                    MessageBox.Show("IC卡中数据读取成功!");

            }

            int d = IC.ic_exit(icdev);  //关闭设备

        }

举一反三

根据本实例,读者可以开发以下程序。

  读取IC卡电话系统。

  公交车刷卡系统。

实例424 利用IC卡制作考勤程序

实例说明

IC卡广泛应用于各行业,包括银行卡、公交车刷卡系统、读书卡等。下面介绍使用IC卡制作简单的公司考勤系统。运行本例,单击【刷卡】按钮,即可对员工进行考勤。实现效果如图13.8所示。

技术要点

有关IC卡的操作函数请参考实例“向IC卡中写入数据”和“读取IC卡中的数据”中的“技术要点”部分。

下面主要介绍通过IC卡如何实现员工考勤。主要将写入IC卡中的卡号读取出来,然后从数据表中查询员工信息。具体代码请参考实现过程。

实现过程

(1)新建一个项目,命名为Ex13_07,默认窗体为Form1。

(2)在Form1窗体中,主要添加5个TextBox控件和6个Label控件,用途如图13.7所示,添加一个Button控件,执行刷IC卡命令。

(3)主要程序代码。

        private void button1_Click(object sender, EventArgs e)

        {

            //初始化

            int icdev = IC.auto_init(0, 9600);

            if (icdev < 0)

                label6.Text = "端口初始化失败,请检查接口线是否连接正确。";

            unsafe

            {

                Int16 status = -1;

                Int16 result = IC.get_status(icdev, &status);

                int md = IC.setsc_md(icdev, 1);   //设备密码格式

                if (result < 0)

                {

                    int d1 = IC.ic_exit(icdev);  //关闭设备

                    return;

                }

                else if ((result == 0) && (status == 0))

                {

                    int d2 = IC.ic_exit(icdev);  //关闭设备

                    label6.Text = "请插入IC卡";

                    return;

                }

            }

            unsafe

            {

                char str = 'a';

                int read = -1;

                string ic = "";

                for (int j = 0; j < 6; j++)

                {

                    read = IC.srd_4442(icdev, 33 + j, 1, &str);

                    ic = ic + Convert.ToString(str);

                }

                textBox1.Text = ic;

                if (read == 0)

                    label6.Text = "刷卡成功!";

                int beep = IC.dv_beep(icdev, 20);  //发出蜂鸣声

                int d3 = IC.ic_exit(icdev);   //关闭设备

            }

            int d = IC.ic_exit(icdev);  //关闭设备

            //根据卡号,查找相应数据

            OleDbConnection con = new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + "price.mdb" + ";Persist Security Info=False");

            OleDbDataAdapter dap = new OleDbDataAdapter("select * from worker where ICID='"+textBox1.Text+"'", con);

            DataSet ds = new DataSet();

            dap.Fill(ds, "table");

            if (ds.Tables.Count > 0)

            {

                textBox2.Text = ds.Tables[0].Rows[0][0].ToString();

                textBox3.Text = ds.Tables[0].Rows[0][1].ToString();

                textBox4.Text = ds.Tables[0].Rows[0][2].ToString();

                textBox5.Text = ds.Tables[0].Rows[0][3].ToString();

            }

            else

            {

                label6.Text = "不存在该用户!";

            }

        }

举一反三

根据本实例,读者可以开发以下程序。

  代金卡系统。

  工资发放系统。

13.4  监  控

在一些银行、大型商场、办公楼、升降电梯中,为了保障公有财产、商品、办公设备、资料、人身等的安全,都设有监控系统。在出现问题时,用户可以通过监控系统查找原因。下面的几个实例分别实现了摄像头监控与定时监控的功能。

实例425 简易视频程序

实例说明

利用普通的简易摄像头,通过C#语言即可开发成简易视频程序。本实例利用市场上购买的普通摄像头,利用VFW技术,实现单路视频监控系统。运行程序,窗体中将显示舰体摄像头采集的视频信息。如图13.9所示。

技术要点

本实例主要使用了VFW(Video for Windows)技术。VFW 是Microsoft公司为开发Windows平台下的视频应用程序提供的软件工具包,提供了一系列应用程序编程接口(API),用户可以通过这些接口很方便地实现视频捕获、视频编辑及视频播放等通用功能,还可利用回调函数开发比较复杂的视频应用程序。该技术的特点是播放视频时不需要专用的硬件设备,而且应用灵活,可以满足视频应用程序开发的需要。Windows操作系统自身就携带了VFW技术,系统安装时,会自动安装VFW的相关组件。

VFW技术主要由六个功能模块组成,下面进行简单说明。

l     AVICAP32.DLL:包含执行视频捕获的函数,给AVI文件的I/O处理和视频,音频设备驱动程序提供一个高级接口。

l     MSVIDEO.DLL:包含一套特殊的DrawDib函数,用来处理程序上的视频操作。

l     MCIAVI.DRV:包括对VFW的MCI命令解释器的驱动程序。

l     AVIFILE.DLL:包含由标准多媒体I/O(mmio)函数提供的更高级的命令,用来访问.AVI文件。

l     ICM:压缩管理器,用于管理的视频压缩/解压缩的编译码器。

l     ACM:音频压缩管理器,提供与ICM相似的服务,适用于波形音频。

其中13.4节所有的实例主要使用AVICAP32.DLL中的函数和USER32.DLL中的函数,函数语法及结构如下。

(1)capCreateCaptureWindow函数

该函数用于创建一个视频捕捉窗口。语法如下:

        [DllImport("avicap32.dll")]

        public static extern IntPtr capCreateCaptureWindowA(byte[] lpszWindowName, int dwStyle, int x, int y, int nWidth, int nHeight, IntPtr hWndParent, int nID);

参数说明如下。

l     lpszWindowName:标识窗口的名称。

l     dwStyle:标识窗口风格。

l     x、y:标识窗口的左上角坐标。

l     nWidth、nHeight:标识窗口的宽度和高度。

l     hWnd:标识父窗口句柄。

l     nID:标识窗口ID。

l     返回值:视频捕捉窗口句柄。

(2)SendMessage函数

用于向Windows系统发送消息机制。

[DllImport("User32.dll")]

private static extern bool SendMessage(IntPtr hWnd, int wMsg, int wParam, int lParam);

参数说明如下。

l     hWnd:窗口句柄。

l     wMsg:将要发送的消息。

l     wParam、lParam:消息的参数,每个消息都有两个参数,参数设置由发送的消息而定。

实现过程

(1)新建一个项目,命名为Ex13_08,默认窗体为Form1,添加1个类文件(.CS),用于编写视频类。

(2)在Form1窗体中,主要添加1个PictrueBox控件,用于显示视频;添加4个Button控件,用于打开视频、关闭视频、拍摄照片和退出程序。

(3)主要程序代码。

视频类中主要实现打开视频、关闭视频以及通过视频拍摄照片的功能。代码如下:

    public class VideoAPI  //视频API类

    {

        //  视频API调用

        [DllImport("avicap32.dll")]

        public static extern IntPtr capCreateCaptureWindowA(byte[] lpszWindowName, int dwStyle, int x, int y, int nWidth, int nHeight, IntPtr hWndParent, int nID);

        [DllImport("avicap32.dll")]

        public static extern bool capGetDriverDescriptionA(short wDriver, byte[] lpszName, int cbName, byte[] lpszVer, int cbVer);

        [DllImport("User32.dll")]

        public static extern bool SendMessage(IntPtr hWnd, int wMsg, bool wParam, int lParam);

        [DllImport("User32.dll")]

        public static extern bool SendMessage(IntPtr hWnd, int wMsg, short wParam, int lParam);

        //  常量

        public const int WM_USER = 0x400;

        public const int WS_CHILD = 0x40000000;

        public const int WS_VISIBLE = 0x10000000;

        public const int SWP_NOMOVE = 0x2;

        public const int SWP_NOZORDER = 0x4;

        public const int WM_CAP_DRIVER_CONNECT = WM_USER + 10;

        public const int WM_CAP_DRIVER_DISCONNECT = WM_USER + 11;

        public const int WM_CAP_SET_CALLBACK_FRAME = WM_USER + 5;

        public const int WM_CAP_SET_PREVIEW = WM_USER + 50;

        public const int WM_CAP_SET_PREVIEWRATE = WM_USER + 52;

        public const int WM_CAP_SET_VIDEOFORMAT = WM_USER + 45;

        public const int WM_CAP_START = WM_USER;

        public const int WM_CAP_SAVEDIB = WM_CAP_START + 25;

    }

    public class cVideo     //视频类

    {

        private IntPtr lwndC;       //保存无符号句柄

        private IntPtr mControlPtr; //保存管理指示器

        private int mWidth;

        private int mHeight;

        public cVideo(IntPtr handle, int width, int height)

        {

            mControlPtr = handle; //显示视频控件的句柄

            mWidth = width;      //视频宽度

            mHeight = height;    //视频高度

        }

        /// <summary>

        /// 打开视频设备

        /// </summary>

        public void StartWebCam()

        {

            byte[] lpszName = new byte[100];

            byte[] lpszVer = new byte[100];

            VideoAPI.capGetDriverDescriptionA(0, lpszName, 100, lpszVer, 100);

            this.lwndC = VideoAPI.capCreateCaptureWindowA(lpszName, VideoAPI.WS_CHILD | VideoAPI.WS_VISIBLE, 0, 0, mWidth, mHeight, mControlPtr, 0);

            if (VideoAPI.SendMessage(lwndC, VideoAPI.WM_CAP_DRIVER_CONNECT, 0, 0))

            {

                VideoAPI.SendMessage(lwndC, VideoAPI.WM_CAP_SET_PREVIEWRATE, 100, 0);

                VideoAPI.SendMessage(lwndC, VideoAPI.WM_CAP_SET_PREVIEW, true, 0);

            }

        }

        /// <summary>

        /// 关闭视频设备

        /// </summary>

        public void CloseWebcam()

        {

            VideoAPI.SendMessage(lwndC, VideoAPI.WM_CAP_DRIVER_DISCONNECT, 0, 0);

        }

        ///   <summary>  

        ///   拍照

        ///   </summary>  

        ///   <param   name="path">要保存bmp文件的路径</param>  

        public void GrabImage(IntPtr hWndC, string path)

        {

            IntPtr hBmp = Marshal.StringToHGlobalAnsi(path);

            VideoAPI.SendMessage(lwndC, VideoAPI.WM_CAP_SAVEDIB, 0, hBmp.ToInt32());

        } 

    }

Form1窗体中通过调用视频类中的方法来实现相应的功能。

在【打开视频】按钮的Click事件中添加如下代码:

        private void button1_Click(object sender, EventArgs e)

        {

            btnPlay.Enabled = false;

            btnStop.Enabled = true;

            btnPz.Enabled = true;

            video = new cVideo(pictureBox1.Handle, pictureBox1.Width, pictureBox1.Height);

            video.StartWebCam();

        }

在【关闭视频】按钮的Click事件中添加如下代码:

        private void b_stop_Click(object sender, EventArgs e)

        {

            btnPlay.Enabled = true;

            btnStop.Enabled = false;

            btnPz.Enabled = false;

            video.CloseWebcam();

        }

在【拍摄照片】按钮的Click事件下添加如下代码:

        private void btnPz_Click(object sender, EventArgs e)

        {

            video.GrabImage(pictureBox1.Handle, "d:\\a.bmp");

        }

举一反三

根据本实例,读者可以开发以下程序。

  无人值班视频实时监控系统。

  车库安全实时监控系统。

实例426 摄像头监控录像

实例说明

本例是为通过摄像头来实现监控录像的程序。运行本例后,单击【开始监控】按钮,程序将自动开始录像,录像文件(lx.avi)将保存在D盘根目录下。运行程序,效果如图13.10所示。

技术要点

在实例“简易视频程序”的技术要点中,使用的技术和相关函数已经介绍过。在这里主要介绍如何将捕获的视频制作成 .AVI媒体文件。实现技术为主要通过SendMessage函数发送Windows消息机制,消息值WM_CAP_FILE_SET_CAPTURE_FILEA和WM_CAP_SEQUENCE,分别用来设置视频捕捉的文件名称和初始化视频流,捕捉视频信息到文件:

        private const int WM_USER = 0x400;

        private const int WM_CAP_START = WM_USER;

        private const int WM_CAP_SEQUENCE = WM_CAP_START + 62;

        private const int WM_CAP_FILE_SET_CAPTURE_FILEA = WM_CAP_START + 20;

实现关键代码如下:

            IntPtr hBmp = Marshal.StringToHGlobalAnsi(path);

            SendMessage(hWndC,WM_CAP_FILE_SET_CAPTURE_FILEA,0, hBmp.ToInt32());

            SendMessage(hWndC, WM_CAP_SEQUENCE, 0, 0);

实现过程

(1)新建一个项目,命名为Ex13_09,默认窗体为Form1,添加一个类文件(.CS),用于编写视频类。

(2)在Form1窗体中,主要添加一个PictrueBox控件,用于显示视频;添加4个Button控件,用于开始监控、停止监控和监控程序。

(3)视频类中主要程序代码如下:

        ///   <summary>  

        ///   开始录像

        ///   </summary>  

        ///   <param   name="path">要保存录像的路径</param>  

        public void StarKinescope(string path)

        {

            IntPtr hBmp = Marshal.StringToHGlobalAnsi(path);

            SendMessage(hWndC,WM_CAP_FILE_SET_CAPTURE_FILEA,0, hBmp.ToInt32());

            SendMessage(hWndC, WM_CAP_SEQUENCE, 0, 0);

        }

        /// <summary>

        /// 停止录像

        /// </summary>

        public void StopKinescope()

        {

            SendMessage(hWndC, WM_CAP_STOP, 0, 0);

        }

(4)Form1窗体中主要程序代码如下:

//开始录像

        private void button1_Click(object sender, EventArgs e)

        {

            btnStar.Enabled = false;

            btnStop.Enabled = true;

            video.StarKinescope(@"d:\lx.avi");

        }

//停止录像

        private void button2_Click(object sender, EventArgs e)

        {

            btnStar.Enabled = true;

            btnStop.Enabled = false;

            video.StopKinescope();

        }

举一反三

根据本实例,读者可以开发以下程序。

  小区视频监控录像系统。

  公司财务室视频监控系统。

实例427 超市摄像头定时监控系统

实例说明

本实例实现超市摄像头定时监控系统。运行本例后,在“定时监控设置”处设置监控的星期及时间,单击【保存】按钮,将“定时设置”参数数据保存到数据库中。系统在运行到定时时间后,程序将自动进行监控。如图13.11所示。另外,监控的录像文件和图片文件保存在D盘根目录中,命名格式为系统当前日期。

 

图13.11  超市摄像头定时监控

技术要点

相关技术要点请参见实例“摄像头监控录像”。另外,本实例利用Timer控件中的定时执行功能,进行数据的定时录像工作。

实现过程

(1)新建一个项目,命名为Ex13_10,默认窗体为Form1,添加一个类文件(.CS),用于编写视频类。

(2)在Form1窗体中,主要添加一个PictrueBox控件,用于显示视频;其他控件的添加如图13.11所示。

(3)主要程序代码。

        private void timer1_Tick(object sender, EventArgs e)

        {

            string strTime="";

            //星期一

            if (chk1.Checked && Convert.ToInt32(DateTime.Now.DayOfWeek)==1)

            {

                strTime = DateTime.Now.ToString("HH:mm");

                DateTime date = Convert.ToDateTime(mtxt1.Text);

                if (strTime == date.ToString("HH:mm"))

                    video.StarKinescope(@"d:\" + DateTime.Today.Month.ToString() + DateTime.Today.Day.ToString() + DateTime.Now.Hour.ToString() + DateTime.Now.Minute.ToString() + DateTime.Now.Second.ToString() + ".avi");

            }

            //星期二

            if (chk1.Checked && Convert.ToInt32(DateTime.Now.DayOfWeek) == 2)

            {

                strTime = DateTime.Now.ToString("HH:mm");

                DateTime date = Convert.ToDateTime(mtxt2.Text);

                if (strTime == date.ToString("HH:mm"))

                    video.StarKinescope(@"d:\" + DateTime.Today.Month.ToString() + DateTime.Today.Day.ToString() + DateTime.Now.Hour.ToString() + DateTime.Now.Minute.ToString() + DateTime.Now.Second.ToString() + ".avi");

            }

            //星期三

            if (chk1.Checked && Convert.ToInt32(DateTime.Now.DayOfWeek) == 3)

            {

                strTime = DateTime.Now.ToString("HH:mm");

                DateTime date = Convert.ToDateTime(mtxt3.Text);

                if (strTime == date.ToString("HH:mm"))

                    video.StarKinescope(@"d:\" + DateTime.Today.Month.ToString() + DateTime.Today.Day.ToString() + DateTime.Now.Hour.ToString() + DateTime.Now.Minute.ToString() + DateTime.Now.Second.ToString() + ".avi");

            }

            //星期四

            if (chk1.Checked && Convert.ToInt32(DateTime.Now.DayOfWeek) == 4)

            {

                strTime = DateTime.Now.ToString("HH:mm");

                DateTime date = Convert.ToDateTime(mtxt4.Text);

                if (strTime == date.ToString("HH:mm"))

                    video.StarKinescope(@"d:\" + DateTime.Today.Month.ToString() + DateTime.Today.Day.ToString() + DateTime.Now.Hour.ToString() + DateTime.Now.Minute.ToString() + DateTime.Now.Second.ToString() + ".avi");

            }

            //星期五

            if (chk1.Checked && Convert.ToInt32(DateTime.Now.DayOfWeek) == 5)

            {

                strTime = DateTime.Now.ToString("HH:mm");

                DateTime date = Convert.ToDateTime(mtxt5.Text);

                if (strTime == date.ToString("HH:mm"))

                    video.StarKinescope(@"d:\" + DateTime.Today.Month.ToString() + DateTime.Today.Day.ToString() + DateTime.Now.Hour.ToString() + DateTime.Now.Minute.ToString() + DateTime.Now.Second.ToString() + ".avi");

            }

            //星期六

            if (chk1.Checked && Convert.ToInt32(DateTime.Now.DayOfWeek) == 6)

            {

                strTime = DateTime.Now.ToString("HH:mm");

                DateTime date = Convert.ToDateTime(mtxt6.Text);

                if (strTime == date.ToString("HH:mm"))

                    video.StarKinescope(@"d:\" + DateTime.Today.Month.ToString() + DateTime.Today.Day.ToString() + DateTime.Now.Hour.ToString() + DateTime.Now.Minute.ToString() + DateTime.Now.Second.ToString() + ".avi");

            }

            //星期日

            if (chk1.Checked && Convert.ToInt32(DateTime.Now.DayOfWeek) == 7)

            {

                strTime = DateTime.Now.ToString("HH:mm");

                DateTime date = Convert.ToDateTime(mtxt7.Text);

                if (strTime == date.ToString("HH:mm"))

                    video.StarKinescope(@"d:\" + DateTime.Today.Month.ToString() + DateTime.Today.Day.ToString() + DateTime.Now.Hour.ToString() + DateTime.Now.Minute.ToString() + DateTime.Now.Second.ToString() + ".avi");

            }

举一反三

根据本实例,读者可以开发以下程序。

  车站定时监控系统。

  公司定时安防系统。

13.5  语音卡控制

随着语音技术的不断发展,语音卡在通信行业应用非常广泛。本节通过几个典型实例介绍语音卡程序的开发。

实例428 语音卡电话呼叫系统

实例说明

随着科学技术的不断发展,语音卡被广泛地应用于商业软件中。本例实现了利用语音卡实现电话呼叫的功能。实例运行结果如图13.12所示。

技术要点

本例采用东进公司开发的8路模拟语音卡,该卡采用灵活的模式化设计,可按需配置外线、内线两种模块。该语音卡可实现坐席、会议、FSK数据收发、语音合成等多种功能,并提供SDK开发工具包。

在安装完驱动程序后,相应的动态链接库(NewSig.dll和Tc08a32.dll文件)会复制到Windows的系统目录下。在语音卡的开发过程中,主要通过调用NewSig.dll和Tc08a32.dll来实现相应的功能。下面介绍这两个动态库中的主要使用函数。

(1)LoadDRV函数

该函数用于加载动态链接库。语法如下:

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

        public static extern long LoadDRV();

返回值:返回值为0表示成功;−1表示打开设备驱动程序错误。−2表示在读取TC08A-V.INI文件时发生错误;−3表示INI文件的设置与实际的硬件不一致时发生错误。

(2)FreeDRV函数

该函数用于关闭驱动程序。语法如下:

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

        public static extern long EnableCard(short wusedCh, short wFileBufLen);

(3)EnableCard函数

该函数用于初始化语音卡硬件,并为每个通道分配语音缓冲区。语法如下:

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

        public static extern long EnableCard(short wusedCh, short wFileBufLen);

参数说明如下。

l     wUsedCh:标识通道数量。

l     WFileBufLen:标识分配的缓冲区大小。

(4)CheckValidCh函数

该函数检测在当前机器内可用的通道总数。语法如下:

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

        public static extern short CheckValidCh();

l     返回值:通道总数量。

(5)CheckChType函数

该函数用于测试某个通道的类型。语法如下:

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

        public static extern short CheckChType(short wChnlNo);

参数说明如下。

l     wChnlNo:标识通道号。

l     返回值:为0表示内线;为1表示外线;为2表示悬空。

(6)PUSH_PLAY函数

该函数用于维持文件录放音的持续进行,需在处理函数的大循环中调用。语法如下:

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

        public static extern void PUSH_PLAY();

(7)SetBusyPara函数

该函数用于设置要检测的挂机忙音的参数。语法如下:

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

        public static extern void SetBusyPara(short BusyLen);

参数说明:

l     BusyLen:标识忙音的时间长度,单位为毫秒。

(8)RingDetect函数

该函数用于测试外线是否振铃或内线是否提机。语法如下:

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

        public static extern bool RingDetect(short wChnlNo);

参数说明如下。

l     wChnlNo:标识通道号。

返回值:如果为1,对于外线表示有振铃信息;对于内线,表示提机。如果为0,对于外线,表示无振铃信息;对于内线,表示挂机。

(9)OffHook函数

该函数用于外线提机。对于内线,不起作用。语法如下:

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

        public static extern void OffHook(short wChnlNo);

参数说明如下。

l     wChnlNo:标识外线通道。

(10)HangUp函数

该函数用于外线挂机。对于内线,不起作用。语法如下:

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

        public static extern void HangUp(short wChnlNo);

参数说明如下。

l     wChnlNo:标识外线通道。

(11)Sig_Init函数

该函数用于完成信号音检测的初始化工作。语法如下:

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

        public static extern void Sig_Init(int Times);

参数说明如下。

l     wPara:缺省值为0,不起作用。

(12)Sig_CheckBusy函数

清空忙音检测的缓冲区以及内部计数。当检测对方挂机的忙音后,必须调用本函数。语法如下:

        [DllImport("NewSig.DLL", CharSet = CharSet.Auto)]

        public static extern void Sig_ResetCheck(short wChlNo);

参数说明如下。

l     wChNo:标识通道号。

l     返回值:为1表示检测到忙音;为0,表示没有检测到忙音。

(13)Sig_ResetCheck函数

该函数用于清空忙音检测的缓冲区以及内部计数。当检测对方挂机的忙音后,必须调用本函数。语法如下:

        [DllImport("NewSig.DLL", CharSet = CharSet.Auto)]

        public static extern void Sig_ResetCheck(short wChlNo);

参数说明如下。

l     wChNo:标识通道号。

(14)Sig_StartDial函数

该函数用于拨打电话号码。开始某通道的呼出过程。该函数只是设置通道的呼出缓冲区,真正的呼出过程需要循环调用Sig_CheckDial函数来逐步完成。语法如下:

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

        public static extern int Sig_StartDial(short wChNo, [MarshalAs(UnmanagedType.LPArray)] byte[] DialNum, [MarshalAs(UnmanagedType.LPArray)] byte[] PreDialNum, short wMode);

参数说明如下。

l     wChNo:标识通道号。

l     DialNum:标识呼出号码。

l     PreDialNum:标识前导号码。

l     wMode:呼出检测的模式。

(15)Sig_CheckDial函数

该函数用于检测呼出结果。

在调用函数Sig_StartDial启动拨号过程后,就可以循环调用Sig_CheckDial函数维持拨号过程,并检测呼出的结果,直至得到结果为止。

拨号的一般过程如下。

1.如果参数PreDialNum不为空,则延迟1秒后拨出PreDialNum,如果参数PreDialNum为空,则直接进入步骤3。

2.检测PreDialNum是否已发完。如已发完转至步骤3。

3.检测是否有拨号音,如拨号音长度达到配置项DialToneAfterOffHook的数值,则发送DialNum码串,并转至步骤4。如在此步骤已等待配置项NoDialToneAfterOffHook定义的时间长度仍未检测到拨号音,则返回0x10。

4.检测DialNum码串是否发完,如已发完则延迟StartDelay配置项的时间长度后进入步骤5。

5.如果从进入此步骤起已经过配置项RingLen定义的时间长度,拨号音仍未停止则返回0x10;如果在此步骤已等待配置项NoRingLen定义的时间长度仍未检测到回铃音则返回0x10;如果检测到占线忙音数达到配置项BusySigCount定义的数字,则返回0x21;如果检测到对方摘机,则返回0x14;如果进入此步骤已经过配置项Ringback_NoAnswerTime定义的时间长度,并且已检测到回铃音,则返回0x13;其他情况返回0x10。

 注意:在进行呼出结果检测之前必须调用函数StartSigCheck启动信号音采集过程,并且在进行呼出结果检测时,要循环调用FeedSigFunc函数维持信号音采集过程。

语法如下:

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

        public static extern int Sig_CheckDial(short wChNo);

参数说明如下。

l     wChNo:标识通道号。

l     返回值包括以下几种情况。

l     16(0x10):尚未得出结果。

l     15(0x0F):没有拨号音。

l     33(0x21):检测到对方占线的忙音。

l     20(0x14):对方摘机,可以进行通话。

l     19(0x13):振铃若干次,无人接听电话。

l     21(0x15):没有信号音。

 注意:关于语音卡其他函数语法请参见光盘中的本实例文件D161A.CS,该文件给出大部分语音卡的函数语法。

实现过程

(1)新建一个项目,命名为Ex13_11,默认窗体为Form1。

(2)在Form1窗体中,主要添加两个Button控件,用于执行电话拨号和电话挂机,添加一个DataGridView控件,显示语音卡各通道及通道状态,添加Timer组件实现电话的呼出过程,添加一个TextBox控件,用于输入呼出电话号码。

(3)主要程序代码。

在窗体装载事件中,主要进行初始化语音卡驱动程序,并且检测通道总数及状态,为每一条通道分配语音缓冲区。代码如下:

        private void Form1_Load(object sender, EventArgs e)

        {

           

            //初始化驱动程序

            long load = DJ160API.LoadDRV();

            //检测通道总数,并为每个通道分配语音缓冲区

            short wuseCh = DJ160API.CheckValidCh();

            short wFileBufLen = 16 * 1024;

            long card = DJ160API.EnableCard(wuseCh, wFileBufLen);

            //设置表格通道的行数

            dataGridView1.RowCount = wuseCh;

            //检测每个通道类型

            short chanelTpye = 0; //定义通道类型变量

            string strType = "";

            for (short i = 0; i < wuseCh; i++)

            {

                chanelTpye = DJ160API.CheckChType(i);

                dataGridView1[0, i].Value = i;

                switch (chanelTpye)

                {

                    case 0:

                        strType = "内线";

                        break;

                    case 1:

                        strType = "外线";

                        break;

                    case 2:

                        strType = "悬空";

                        break;

                }

                dataGridView1[1, i].Value = strType;

                dataGridView1[2, i].Value = "空闲";

            }

        }

在DataGridView控件中选择一个外线空闲通道,单击【拨号】按钮,进行电话拨号,并且将拨号过程中的状态显示在相应的DataGirdView表格中。代码如下:

        private void button1_Click(object sender, EventArgs e)

        {

            short wuseCh = DJ160API.CheckValidCh();

            short wFileBufLen = 16 * 1024;

            long card = DJ160API.EnableCard(wuseCh, wFileBufLen);

            DJ160API.Sig_Init(chanel);

            //检查(外线)是否有振铃信号或(内线)是否有提机

            bool ring = DJ160API.RingDetect(chanel);

            //外线提机

            DJ160API.OffHook(chanel);

            byte[] ss =new byte[textBox1.Text.Length];

            byte[] s ={ 0 };

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

            {

                ss[i] = Convert.ToByte(textBox1.Text.Substring(i, 1));

            }

            DJ160API.Sig_StartDial(chanel, ss, s, 0);

            timer1.Enabled = true;

            dataGridView1[2, chanel].Value = "拨号中...";

            dataGridView1[3, chanel].Value = textBox1.Text;

        }

单击【挂机】按钮,实现电话挂机功能。代码如下:

        private void button2_Click(object sender, EventArgs e)

        {

            DJ160API.HangUp(chanel);

            DJ160API.Sig_ResetCheck(chanel);

            DJ160API.StartSigCheck(chanel);

            timer1.Enabled = false;

            dataGridView1[2, chanel].Value = "空闲";

            dataGridView1[3, chanel].Value = "";

        }

Sig_StartDial函数用于拨打电话号码。开始某通道的呼出过程。该函数只是设置通道的呼出缓冲区,真正的呼出过程需循环调用Sig_CheckDial函数来逐步完成。代码如下:

        private void timer1_Tick(object sender, EventArgs e)

        {

            DJ160API.Sig_CheckDial(chanel);

        }

单击DataGridView控件的相应行记录相应的通道号,代码如下:

   private void dataGridView1_CellClick(object sender, DataGridViewCellEventArgs e)

        {

            chanel = (short)e.RowIndex;

        }

举一反三

根据本实例,读者可以开发以下程序。

  实现电话自助服务系统。

  实现电话自动报警系统。

实例429 客户来电查询系统

实例说明

随着市场竞争的加剧,企业越来越重视客户服务和市场反馈。本例实现了电话来电的显示支持功能。当有客户打入电话时,会读取客户的电话号码,根据电话号码可以提取客户的相关信息,方便客服人员有针对性地进行服务。实例运行结果如图13.13所示。

 

图13.13  客户来电查询

技术要点

其他相关函数介绍请参见实例“语音卡电话呼叫系统”,本实例主要介绍GetCallerIDStr函数,该函数用于获取主叫号码。语法如下:

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

        public static extern short GetCallerIDStr(short wChnlNo, byte[] IDStr);

参数说明如下。

l     wChnlNo:标识通道号。

l     IDStr:用于接收读取的号码。

l     返回值:为0,表示未收到任何信息;为1,表示正在接收头信息;为2表示正在接收ID号码;为3表示接收完毕,校验正确;为4表示接收完毕,校验错误。

在调用GetCallerIDStr函数时,只有返回值为3或4才表示已经正确接收了主机号码。

实现过程

(1)新建一个项目,命名为Ex13_12,默认窗体为Form1。

(2)在Form1窗体中,主要添加一个DataGridView控件,显示语音卡各通道和通道状态,并在来电时显示来电号码;添加一个Timer控件用于时刻检测来电信息;添加其他控件及用途如图13.13所示。

(3)主要程序代码。

private void timer1_Tick(object sender, EventArgs e)

        {

            byte[] ss = new byte[100];

                for (short i = 0; i < 8; i++)

                {

                    DJ160API.StartSigCheck(i);

                    if(open_close==false)

                        DJ160API.ResetCallerIDBuffer(i);

                    if (DJ160API.RingDetect(i))

                    {

                        open_close = true;

                        //获取来电号码

                        result = DJ160API.GetCallerIDStr(i, ss);

                        if (result == 3 || result == 4)

                        {

                            string str = Encoding.UTF8.GetString(ss);

                            txtTel.Text = str;

                            txtTel.Text = txtTel.Text.Substring(txtTel.Text.Length - 8, 8);

                            dataGridView1[2, i].Value = txtTel.Text;

                            //查询客户资料

                            this.getMessage(txtTel.Text);

                        }

                    }

                }

        }

        private void getMessage(string str)

        {

            OleDbConnection con = new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + "db_csell.mdb" + ";Persist Security Info=False");

            OleDbDataAdapter dap = new OleDbDataAdapter("SELECT * FROM 个人名录表 WHERE 电话='" + str + "'", con);

            DataSet ds = new DataSet();

            dap.Fill(ds);

            if (ds.Tables[0].Rows.Count > 0)

            {

                txtName.Text = ds.Tables[0].Rows[0]["姓名"].ToString();

                txtDuty.Text = ds.Tables[0].Rows[0]["职务"].ToString();

                txtAddress.Text = ds.Tables[0].Rows[0]["地址"].ToString();

                txtMobile.Text = ds.Tables[0].Rows[0]["手机"].ToString();

                txtCompany.Text = ds.Tables[0].Rows[0]["公司名称"].ToString();

                txtPostId.Text = ds.Tables[0].Rows[0]["邮编"].ToString();

            }

            else

            {

                labStatus.Text = "非本单位会员客户。。。。";

            }

        }

举一反三

根据本实例,读者可以开发以下程序。

  用语音卡实现客户某项费用的查询。

  用语音卡实现客户在某个时间的留言信息。

  用语音卡实现客户购买物品的查询。

  实现客户反馈电话录音系统。

实例430 语音卡实现电话录音

实例说明

如今的许多电话都具有电话录音的功能。本例实现了该功能,当有电话打入时,即刻将双方的对话信息进行录音。实例运行结果如图13.14所示。

技术要点

其他相关函数介绍请参见实例“语音卡电话呼叫系统”,本实例主要介绍StartRecordFile函数和StopRecordFile函数。

(1)StartRecordFile函数用于开始文件录音。停止该方式的录音一定要用StopRecordFile函数。检查录音是否结束,用CheckRecordEnd函数。StartRecordFile函数语法如下:

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

        public static extern bool StartRecordFile(short wChnlNo, byte[] FileName, long dwRecordLen);

参数说明如下。

l     wChnINo:标识录音的通道号。

l     FileName:标识录音的文件名。

l     dwRecordLen:标识文件大小。

(2)StopRecordFile函数用于停止录音。该函数语法如下:

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

        public static extern void StopRecordFile(short wChnlNo);

参数说明如下。

l     wChnINo:标识要停止的录音通道。

(3)CheckRecordEnd函数检查指定通道录音是否结束(缓冲区已满)。

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

        public static extern int CheckRecordEnd(int ChannelNo);

参数说明如下。

l     wChnINo:标识录音的通道号。

l     返回值:0表示未结束;1代表结束。

实现过程

(1)新建一个项目,命名为Ex13_13,默认窗体为Form1。

(2)在Form1窗体中,主要添加一个DataGridView控件,显示语音卡各通道和通道状态,并在来电时显示来电号码;添加一个Timer控件用于实时检测来电信息,如果来电,程序将自动摘机并且实现录音;添加其他控件及用途如图13.14所示。

(3)主要程序代码。

        private void timer1_Tick(object sender, EventArgs e)

        {

            //维持文件录音持续执行

            DJ160API.PUSH_PLAY();

            for (short i = 0; i < 8; i++)

            {

                DJ160API.StartSigCheck(i);

                if (open_close == false)

                    DJ160API.ResetCallerIDBuffer(i);

                if (DJ160API.RingDetect(i))

                {

                    open_close = true;

                    //摘机

                    DJ160API.OffHook(i);

                    DJ160API.StartSigCheck(i);

                    //是否挂机

                    if (DJ160API.ReadCheckResult(i, 2) != 33)

                    {

                        bool bl = DJ160API.StartRecordFile(i, Encoding.UTF8.GetBytes(@"D:\ly.001"), 600 * 1024);

                        dataGridView1[2, i].Value = "已接来电,开始录音";

                    }

                    else

                    {

                        DJ160API.StopRecordFile(i);

                        open_close = false;

                        DJ160API.Sig_ResetCheck(i);

                        dataGridView1[2, i].Value = "";

                    }

                    if (DJ160API.CheckRecordEnd(i)==1)

                    {

                        DJ160API.StopRecordFile(i);

                        open_close = false;

                        dataGridView1[2, i].Value = "";

                    }

                }

            }

        }

举一反三

根据本实例,读者可以实现以下功能。

  用语音卡实现电话点播歌曲,通过按键的选择点播自己喜爱的歌曲。

  用语音卡实现产品报价,通过按键实现某些产品的报价。

  利用语音卡实现产品报价。

13.6  手机程序开发

如今手机已成为大众的交流工具。有关手机的程序开发越来越广泛,本节通过几个典型实例介绍如何利用短信猫发送、接收短信、远程控制计算机、业务员销售数据采集和短信息娱乐互动平台。

实例431 利用短信猫收发短信息

实例说明

短信猫是利用SIM卡发送短信的硬件设备,通过串口或USB接口(根据设备型号而定)与计算机相连。在程序中可以利用短信猫发送或接收短信。本例实现了利用短信猫收发短信息的功能。实例运行结果如图13.15所示。

技术要点

本例使用的是北京人大金仓信息技术有限公司的串口短信猫。在购买短信猫时会附带包括SDK的开发包,其中提供了操作短信猫的函数(封装在dllforvc.dll动态库中)。下面介绍操作短信猫的主要函数。

(1)GSMModemGetSnInfoNew函数

该函数获取短信猫注册需要的信息,代码如下:

    [DllImport("dllforvc.dll",

         EntryPoint = "GSMModemGetSnInfoNew",

         CharSet = CharSet.Ansi,

         CallingConvention = CallingConvention.StdCall)]

    public static extern string GSMModemGetSnInfoNew(string device, string baudrate);

参数说明如下。

l     device:通信端口,为null时系统会自动检测。

l     baudrate:通讯波特率,为null时系统会自动检测。

(2)GSMModemGetDevice函数

该函数获取当前的通讯端口,代码如下:

    [DllImport("dllforvc.dll",

         EntryPoint = "GSMModemGetDevice",

         CharSet = CharSet.Ansi,

         CallingConvention = CallingConvention.StdCall)]

    public static extern string GSMModemGetDevice();

(3)GSMModemGetBaudrate函数

该函数获取当前的通讯波特率,代码如下:

    [DllImport("dllforvc.dll",

         EntryPoint = "GSMModemGetBaudrate",

         CharSet = CharSet.Ansi,

         CallingConvention = CallingConvention.StdCall)]

    public static extern string GSMModemGetBaudrate();

(4)GSMModemInitNew函数

该函数用于初始化短信猫。语法如下:

    [DllImport("dllforvc.dll",

         EntryPoint = "GSMModemInitNew",

         CharSet = CharSet.Ansi,

         CallingConvention = CallingConvention.StdCall)]

    public static extern bool GSMModemInitNew(

        string device,

        string baudrate,

        string initstring,

        string charset,

        bool swHandshake,

        string sn);

参数说明如下。

l     device:标识通信端口,如果为NULL,系统会自动检测。

l     baudrate:标识通讯波特率,如果为NULL,系统会自动检测。

l     initstring:标识初始化命令,为NULL即可。

l     charset:标识通讯字符集,为NULL即可。

l     swHandshake:标识是否进行软件握手,为False即可。

l     sn:标识短信猫的授权号,需要根据实际情况填写。

(5)GSMModemSMSsend函数

该函数用于发送手机短信。语法如下:

    [DllImport("dllforvc.dll",

         EntryPoint = "GSMModemSMSsend",

         CharSet = CharSet.Ansi,

         CallingConvention = CallingConvention.StdCall)]

    public static extern bool GSMModemSMSsend(

        string serviceCenterAddress,

        int encodeval,

        string text,

        int textlen,

        string phonenumber,

        bool requestStatusReport);

参数说明如下。

l     serviceCenterAddress:标识短信中心号码,为NULL即可。

l     encodeval:标识短信息编码格式,如果为8,表示中文短信编码。

l     text:标识短信内容。

l     textlen:标识短信内容的长度。

l     phonenumber:标识接收短信的电话号码。

l     requestStatusReport:标识状态报告。

(6)GSMModemSMSReadAll函数

该函数取得所有短信息,包括SIM卡和手机中的短信息。返回的短信内容格式为电话号码1|短信内容1||电话号码2|短信内容2||:

    //接收短信息返回字符串格式为:手机号码|短信内容||手机号码|短信内容||

    //RD_opt为1表示接收短信息后不做任何处理,为0表示接收后删除信息

    [DllImport("dllforvc.dll",

         EntryPoint = "GSMModemSMSReadAll",

         CharSet = CharSet.Ansi,

         CallingConvention = CallingConvention.StdCall)]

    public static extern string GSMModemSMSReadAll(int RD_opt);

参数说明如下。

l     RD_opt:对读取后的短信息进行处理,0表示删除,1表示不做处理。

实现过程

(1)新建一个项目,命名为Ex13_14,默认窗体为Form1。

(2)在Form1窗体中,主要添加TextBox控件和Label控件,控件的数量及用途如图13.15所示,添加两个Button控件,分别用于发送短信息和接收短信息。

(3)主要程序代码。

将所使用的函数封装在GMS类中。代码如下:

class GMS

{

    //初始化gsm modem,并连接gsm modem

    [DllImport("dllforvc.dll",

         EntryPoint = "GSMModemInitNew",

         CharSet = CharSet.Ansi,

         CallingConvention = CallingConvention.StdCall)]

    public static extern bool GSMModemInitNew(

        string device,

        string baudrate,

        string initstring,

        string charset,

        bool swHandshake,

        string sn);

    //获取短信猫新的标识号码

    [DllImport("dllforvc.dll",

         EntryPoint = "GSMModemGetSnInfoNew",

         CharSet = CharSet.Ansi,

         CallingConvention = CallingConvention.StdCall)]

    public static extern string GSMModemGetSnInfoNew(string device, string baudrate);

    //获取当前通讯端口

    [DllImport("dllforvc.dll",

         EntryPoint = "GSMModemGetDevice",

         CharSet = CharSet.Ansi,

         CallingConvention = CallingConvention.StdCall)]

    public static extern string GSMModemGetDevice();

    //获取当前通讯波特率

    [DllImport("dllforvc.dll",

         EntryPoint = "GSMModemGetBaudrate",

         CharSet = CharSet.Ansi,

         CallingConvention = CallingConvention.StdCall)]

    public static extern string GSMModemGetBaudrate();

    //断开连接并释放内存空间       

    [DllImport("dllforvc.dll",

         EntryPoint = "GSMModemRelease",

         CharSet = CharSet.Ansi,

         CallingConvention = CallingConvention.StdCall)]

    public static extern void GSMModemRelease();

    //取得错误信息   

    [DllImport("dllforvc.dll",

         EntryPoint = "GSMModemGetErrorMsg",

         CharSet = CharSet.Ansi,

         CallingConvention = CallingConvention.StdCall)]

    public static extern string GSMModemGetErrorMsg();

    //发送短信息

    [DllImport("dllforvc.dll",

         EntryPoint = "GSMModemSMSsend",

         CharSet = CharSet.Ansi,

         CallingConvention = CallingConvention.StdCall)]

    public static extern bool GSMModemSMSsend(

        string serviceCenterAddress,

        int encodeval,

        string text,

        int textlen,

        string phonenumber,

        bool requestStatusReport);

    //接收短信息返回字符串格式为:手机号码|短信内容||手机号码|短信内容||

    //RD_opt为1接收短信息后不做任何处理,0为接收后删除信息

    [DllImport("dllforvc.dll",

         EntryPoint = "GSMModemSMSReadAll",

         CharSet = CharSet.Ansi,

         CallingConvention = CallingConvention.StdCall)]

    public static extern string GSMModemSMSReadAll(int RD_opt);

}

在装载Form1窗体时,获取设备信息。代码如下:

        private void Form1_Load(object sender, EventArgs e)

        {

            //机器号码,当参数为空时,函数自动获取设备信息

            txtJQHM.Text = GMS.GSMModemGetSnInfoNew(txtCOM.Text, txtBTL.Text);

            txtCOM.Text = GMS.GSMModemGetDevice();  //COM1

            txtBTL.Text= GMS.GSMModemGetBaudrate();  //波特率

        }

发送短信息。代码如下:

        private void btnSend_Click(object sender, EventArgs e)

        {

               if(txtSJHM.Text == "")

               {

           MessageBox.Show("手机号码不能为空!","提示", MessageBoxButtons.OK);

                   txtSJHM.Focus();

                   return;

               }

               if(txtContent.Text=="")

               {

           MessageBox.Show("短信内容不能为空!", "提示", MessageBoxButtons.OK);

                   txtContent.Focus();

                   return;

               }

               //连接设备

               if(GMS.GSMModemInitNew(txtCOM.Text, txtBTL.Text, null, null, false, txtPower.Text)==false)

               {

                   MessageBox.Show("设备连接失败!" + GMS.GSMModemGetErrorMsg(),"提示", MessageBoxButtons.OK);

                   return;

               }

               // 发送短信

               if (GMS.GSMModemSMSsend(null, 8, txtContent.Text, Encoding.Default.GetByteCount(txtContent.Text),txtSJHM.Text, false) == true)

                   MessageBox.Show("短信发送成功!", "提示", MessageBoxButtons.OK);

               else

                   MessageBox.Show("短信发送失败!" + GMS.GSMModemGetErrorMsg(), "提示", MessageBoxButtons.OK);

        }

接收短信息。代码如下:

        private void btnResvice_Click(object sender, EventArgs e)

        {

            //1)连接设备

            if (GMS.GSMModemInitNew(txtCOM.Text, txtBTL.Text, null, null, false, txtPower.Text) == false)

            {

                MessageBox.Show("连接失败!" + GMS.GSMModemGetErrorMsg(), "提示", MessageBoxButtons.OK);

                return;

            }

            //2)接收短信

            txtContent.Text = GMS.GSMModemSMSReadAll(1);

            txtSJHM.Text = txtContent.Text.Substring(0, 13);

            txtContent.Text = txtContent.Text.Substring(13, txtContent.Text.Length-13);

        }

举一反三

根据本实例,读者可以开发以下程序。

  利用短信猫群发短信。

  办公自动化系统,办公短信通知、短信日程提醒、应急信息短信发布和短信审批等。

实例432 利用短信远程关闭计算机

实例说明

本例实现了利用短信远程关闭计算机的功能。运行程序,首先,进行关机信息设置;然后,开启服务;最后,通过手机向短信猫发送“关机”数据。片刻之后,指定的计算机将会自动关机。程序如图13.16所示。

技术要点

相关函数请参见实例“利用短信猫收发短信息”中的技术要点。

实现过程

(1)新建一个项目,命名为Ex13_15,默认窗体为Form1。

(2)在Form1窗体中,主要添加TextBox控件和Label控件,控件的数量及用途如图13.16所示,添加一个Button控件,用于开启或停止远程关闭计算机服务。

(3)主要程序代码。

        private void Form1_Load(object sender, EventArgs e)

        {

           //机器号码

            txtJQHM.Text = GMS.GSMModemGetSnInfoNew(txtCOM.Text, txtBTL.Text); 

            txtCOM.Text = GMS.GSMModemGetDevice();  //COM1

            txtBTL.Text = GMS.GSMModemGetBaudrate();  //波特率

            labStatus.Text = "服务关闭中。。。。。";

        }

        private void Close_Windows()

        {

            try

            {

                //指定生成 WMI 连接所需的所有设置

                ConnectionOptions op = new ConnectionOptions();

                op.Username = txtUser.Text;  //远程计算机用户名称

                op.Password = txtPWD.Text;   //远程计算机用户密码

                //设置操作管理范围

         ManagementScope scope = new ManagementScope("\\\\" + txtIP.Text + "\\root\\cimv2", op);

                scope.Connect();  //将此 ManagementScope 连接到实际的 WMI 范围。

                ObjectQuery oq = new ObjectQuery("SELECT * FROM Win32_OperatingSystem");

             ManagementObjectSearcher query = new ManagementObjectSearcher(scope, oq);

                //得到WMI控制

                ManagementObjectCollection queryCollection = query.Get();

                foreach (ManagementObject obj in queryCollection)

                {

                  obj.InvokeMethod("ShutDown", null); //执行关闭远程计算机

                }

            }

            catch(Exception ex)

            {

                Process p = new Process();

                p.StartInfo.FileName = "cmd.exe";

                p.StartInfo.UseShellExecute = false;

                p.StartInfo.RedirectStandardInput = true;

                p.StartInfo.RedirectStandardOutput = true;

                p.StartInfo.RedirectStandardError = true;

                p.StartInfo.CreateNoWindow = true;

                p.Start();

                p.StandardInput.WriteLine("shutdown /s");

                p.StandardInput.WriteLine("exit");

            }

        }

        private void timer1_Tick(object sender, EventArgs e)

        {

            //连接设备

            if (GMS.GSMModemInitNew(txtCOM.Text, txtBTL.Text, null, null, false, txtPower.Text) == false)

            {

                MessageBox.Show("连接失败!" + GMS.GSMModemGetErrorMsg(), "提示", MessageBoxButtons.OK);

                return;

            }

            //接收短信

            string str = GMS.GSMModemSMSReadAll(1);

            if (str==null)

                return;

            if (str.Substring(str.IndexOf("|")+1, 2) == "关机")

            {

                this.Close_Windows();

            }

        }

        private void button1_Click(object sender, EventArgs e)

        {

            if (button1.Text == "开启服务")

            {

                timer1.Enabled = true;

                labStatus.Text = "关机命令采集中。。。。。";

                button1.Text = "停止服务";

            }

            else

            {

                timer1.Enabled = false;

                button1.Text = "开启服务";

                labStatus.Text = "服务关闭中。。。。。";

            }

        }

举一反三

根据本实例,读者可以实现以下功能。

  利用短信实现客户资料查询。

  保险行业中:保单查询、续费提醒、客户生日提醒和保费计算等。

实例433 短信息采集烟草销售数据

实例说明

在各类销售行业中,产品销售数据量是企业不可缺少的一项数据。当销售人员在外地出差并且在没有计算机的情况下,如何及时的将销售数据汇报到公司中呢?

本例实现利用短信息采集烟草销售数据的功能。销售人员根据规定的格式编辑短信发送到短信息猫中即可。运行程序,单击【烟草销售信息采集】按钮采集烟草销售数据,然后单击【统计】按钮,将销售数据整理出来。如图13.17所示。

技术要点

相关函数请参见实例“利用短信猫收发短信息”中的技术要点。

另外,程序规定的编辑短信息格式为,以冒号“:”分隔并结束。例如“张三:业务员:12:长春:3400:”。其中,主要使用String.Split( )方法将信息数据拆分。

 

图13.17  短信息采集烟草销售数据

String.Split( )方法:返回包含此实例中的子字符串(由指定 Char 数组的元素分隔)的 String 数组。

语法:

public string[] Split (

    params char[] separator

)

参数说明如下。

l     separator:分隔此实例中子字符串的 Unicode 字符数组,不包含分隔符的空数组或空引用。

l     返回值:一个数组,其元素包含此实例中的子字符串,这些子字符串由 separator 中的一个或多个字符分隔。

实现过程

(1)新建一个项目,命名为Ex13_16,默认窗体为Form1。

(2)在Form1窗体中,主要添加TextBox控件和Label控件,控件的数量及用途如图13.17所示,添加3个Button控件,分别用于发送短信息、采集销售数据和整理采集数据,添加两个DataGridView表格,分别用于显示短信息内容和整理后的销售数据。

(3)主要程序代码。

单击【烟草销售信息采集】按钮,接受短信息并保存到数据库中。代码如下:

          private void btnResvice_Click(object sender, EventArgs e)

        {

            //连接设备

            if (GMS.GSMModemInitNew(txtCOM.Text, txtBTL.Text, null, null, false, txtPower.Text) == false)

            {

                MessageBox.Show("连接失败!" + GMS.GSMModemGetErrorMsg(), "提示", MessageBoxButtons.OK);

                return;

            }

            //接收短信

            string content = GMS.GSMModemSMSReadAll(0);

            if (content ==null)

            {

                this.getMessage();

                return;

            }

            content= content.Replace("||", "|"); // 替换||为|

            string[] str_sp = content.Split('|');// 进行分离

            int k=0;

            dataGridView1.ColumnCount = 2;

            dataGridView1.RowCount = str_sp.Length / 2;

            dataGridView1.Columns[0].HeaderText = "手机号码";

            dataGridView1.Columns[1].HeaderText = "短信息";

            for (int i = 0; i < str_sp.Length / 2; i++)

            {

                for (int j = 0; j < 2; j++)

                {

                    dataGridView1[j, i].Value = str_sp[k];

                    if (k % 2 != 0)

                        this.InsertMessage("insert into RecivedBox (Mobile,Content,reciveTime)values('" + Convert.ToString(dataGridView1[0, i].Value) + "','" + Convert.ToString(dataGridView1[1, i].Value) + "','" + DateTime.Now.ToString() + "') ");

                    k++;

                }

            }

            this.getMessage();

        }

自定义方法getSplitMessage()用来拆分短信息并且整理为正规数据。代码如下:

        private void getSplitMessage()

        {

            string content = "";

            for (int i = 0; i < dataGridView1.Rows.Count; i++)

            {

                content = content + Convert.ToString(dataGridView1["短信息", i].Value);

            }

            string[] str_sp = content.Split(':');// 进行分离

            int k = 0;

            dataGridView2.ColumnCount = 5;

            dataGridView2.RowCount = str_sp.Length/5 ;

            dataGridView2.Columns[0].HeaderText = "姓名";

            dataGridView2.Columns[1].HeaderText = "职务";

            dataGridView2.Columns[2].HeaderText = "月份";

            dataGridView2.Columns[3].HeaderText = "销售地区";

            dataGridView2.Columns[4].HeaderText = "销售数量";

            for (int i = 0; i < str_sp.Length / 5; i++)

            {

                for (int j = 0; j < 5; j++)

                {

                    dataGridView2[j, i].Value = str_sp[k];

                    k++;

                }

            }

        }

自定义InsertMessage()方法,将接受的短信息保存到数据库中。代码如下:

        private void InsertMessage(string strSql)

        {

            //将短信息内容添加到数据库中

            OleDbConnection con = new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + "message.mdb" + ";Persist Security Info=False");

            con.Open();

            OleDbCommand cmd = new OleDbCommand(strSql, con);

            cmd.ExecuteNonQuery();

            con.Close();

        }

自定义getMessage()方法,获取数据库中所有的短信息数据。代码如下:

        private void getMessage()

        {

            OleDbConnection con = new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + "message.mdb" + ";Persist Security Info=False");

            OleDbDataAdapter dap = new OleDbDataAdapter("select mobile as 手机号码,content as 短信息,reciveTime as 日期 from RecivedBox", con);

            DataSet ds = new DataSet();

            dap.Fill(ds);

            dataGridView1.DataSource = ds.Tables[0].DefaultView;

        }

调用自定义getSplitMessage()方法,将整理后的销售数据显示在DataGridView表格中。代码如下:

        private void btnFind_Click(object sender, EventArgs e)

        {

            if(dataGridView1.Rows.Count>0)

            this.getSplitMessage();

        }

举一反三

根据本实例,读者可以实现以下功能。

  利用短信实现销售业绩查询。

  利用短信实现订购商品。

实例434 “春晚”节目评比短信息互动平台

实例说明

在观看娱乐电视节目中,主持人经常带动场外的电视观众参与到活动当中。例如,在春节联欢晚会中,通过编辑短信来选取观众最喜欢的春晚节目,那么本实例将实现为“春晚”节目评比的短信息互动平台。电视观众根据规定的格式编辑短信发送到短信息互动平台进行节目评比。运行程序,单击【获取参与观众短信】按钮,即可得到观众的投票,如图13.18所示。

技术要点

相关函数请参见实例“利用短信猫收发短信息”中的技术要点。

实现过程

(1)新建一个项目,命名为Ex13_17,默认窗体为Form1。

(2)在Form1窗体中,主要添加TextBox控件和Label控件,控件的数量及用途如图13.18所示,添加一个Button控件,用于获取参与的观众短信息,添加一个DataGridView控件,用于显示观众的投票信息。

(3)主要程序代码。

        private void btnResvice_Click(object sender, EventArgs e)

        {

            //连接设备

            if (GMS.GSMModemInitNew(txtCOM.Text, txtBTL.Text, null, null, false, txtPower.Text) == false)

            {

                MessageBox.Show("连接失败!" + GMS.GSMModemGetErrorMsg(), "提示", MessageBoxButtons.OK);

                return;

            }

            //接收短信

            string content = GMS.GSMModemSMSReadAll(0);

            if (content == null)

            {

                this.getMessage();

                return;

            }

            content = content.Replace("||", "|"); // 替换||为|

            string[] str_sp = content.Split('|');// 进行分离

            int k = 0;

            string[,] str_Sp = new string[2, str_sp.Length / 2];

            for (int i = 0; i < str_sp.Length / 2; i++)

            {

                for (int j = 0; j < 2; j++)

                {

                    str_Sp[j, i] = str_sp[k];

                    if (k % 2 != 0)

                        this.InsertMessage("insert into RecivedBox (Mobile,Content,reciveTime)values('" + Convert.ToString(str_Sp[0, i]) + "','" + Convert.ToString(str_Sp[1, i]) + "','" + DateTime.Now.ToString() + "') ");

                    k++;

                }

            }

            this.getMessage();

        }

        private void getMessage()

        {

            OleDbConnection con = new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + "message.mdb" + ";Persist Security Info=False");

            OleDbDataAdapter dap = new OleDbDataAdapter("select mobile as 手机号码,content as 短信息,reciveTime as 日期 from RecivedBox", con);

            DataSet ds = new DataSet();

            dap.Fill(ds);

            dataGridView1.DataSource = ds.Tables[0].DefaultView;

        }

        private void InsertMessage(string strSql)

        {

            //将短信息内容添加到数据库中

            OleDbConnection con = new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + "message.mdb" + ";Persist Security Info=False");

            con.Open();

            OleDbCommand cmd = new OleDbCommand(strSql, con);

            cmd.ExecuteNonQuery();

            con.Close();

        }

举一反三

根据本实例,读者可以实现以下功能。

  手机短信息平台订购商品。

  定时向手机发送天气预报

13.7 

实例435 条形码扫描器销售商品

实例说明

如今,许多超市都利用条形码销售商品。微机操作员利用扫描器在商品的条形码处进行扫描,商品的详细信息就会显示在屏幕中。本例实现了利用条形码销售商品的功能。效果如图13.19所示。

技术要点

当利用扫描器扫描条形码时,条形码数据会显示在当前获得焦点的窗口控件中。例如,如果当前编辑框获得焦点,那么条形码数据会显示在TextBox文本框中。然后会向TextBox文本框发送回车键按下时的消息。

在程序中只要触发TextBox文本框的KeyDown事件,判断当前按键是否是回车键,如果是,读取TextBox文本框中的条形码数据,并从数据表中根据条形码查询商品信息,将其显示在DataGridView列表中。

实现过程

(1)新建一个项目,命名为Ex13_18,默认窗体为Form1。

(2)在Form1窗体中,主要添加TextBox控件,用于接收条形码;添加一个DataGridView控件,用于显示扫描器扫描条形码的商品销售信息。

(3)主要程序代码。

        private void textBox1_KeyDown(object sender, KeyEventArgs e)

        {

            if (e.KeyValue == 13)

            {

                OleDbConnection con = new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + "price.mdb" + ";Persist Security Info=False");

                OleDbDataAdapter dap = new OleDbDataAdapter("select * from MerchandiseInfo where barcode='" + textBox1.Text + "'", con);

                DataSet ds = new DataSet();

                dap.Fill(ds);

                if (ds.Tables[0].Rows.Count == 0)

                {

                    MessageBox.Show("该商品不存在!", "系统提示", MessageBoxButtons.OK, MessageBoxIcon.Information);

                    return;

                }

                for (int i = 0; i < dataGridView1.RowCount; i++)

                {

                    if (Convert.ToString(dataGridView1[0, i].Value) == "")

                    {

                        dataGridView1[0, i].Value = ds.Tables[0].Rows[0][0].ToString();

                        dataGridView1[1, i].Value = ds.Tables[0].Rows[0][1].ToString();

                        dataGridView1[2, i].Value = ds.Tables[0].Rows[0][2].ToString();

                        dataGridView1[3, i].Value = ds.Tables[0].Rows[0][3].ToString();

                        return;

                    }

                }

            }

        }

举一反三

根据本实例,读者可以开发以下程序。

  超市条形码扫描系统。

  公司工具条形码扫描系统。

实例436 利用神龙卡制作练歌房程序

实例说明

在开发酒店、宾馆的点歌系统时,使用神龙DVD解压卡可以方便地进行媒体控制。神龙DVD解压卡(以下简称神龙卡)是一款专门针对中国大陆市场而开发出来的DVD硬解压卡,神龙卡与好莱坞卡的基本功能相近,两卡比较具有以下几个区别。

l     神龙卡可播放中国大陆全区码DVD碟,好莱坞卡可播放全球1~6区码影碟,可无数次解区码。

l     神龙卡支持1~5M码流播放,好莱坞卡可支持1~15M的视频流播放。

l     神龙卡支持全屏播放,好莱坞卡支持全屏及窗口(即可缩放窗口)播放。

l     神龙卡支持Win 9x下工作环境,好莱坞卡可支持Win 9x及WinNT下工作环境。

本例利用神龙卡实现了音乐的控制功能。运行程序,结果如图13.20所示。

 

图13.20  利用神龙卡制作练歌房程序

技术要点

本程序主要通过一个第三方NNSREALmagicCtrl.ocx控件实现。在.NET下使用第三方控件,首先,需要进行Windows注册,注册命令为“REgsvr32 路径\NNSREALmagicCtrl.ocx”;其次,将注册成功的控件添加到Microsoft Visual Studio 2005开发环境中,实现步骤为:选择菜单“工具”/“选择工具箱”,弹出“选择工具箱”窗口,在该窗口中选择“COM组件”选项卡,在列表中选择注册的第三方控件,单击【确定】按钮即可,如图13.21所示。

 

图13.21  添加第三方控件

实现过程

(1)新建一个项目,命名为Ex13_19,默认窗体为Form1。

(2)在Form1窗体中,主要添加DataGridView控件,用于选择播放影音;添加其他控件及用途如图13.20所示。

(3)主要程序代码。

        private void button1_Click(object sender, EventArgs e)

        {

            if (strFileName == "")

            {

                MessageBox.Show("请在列表中选择播放文件!","系统提示");

                return;

            }

            axREALmagicCtrl1.Filename = strFileName;  //指定播放文件

            axREALmagicCtrl1.Play(); //播放

        }

        private void btnPause_Click(object sender, EventArgs e)

        {

            axREALmagicCtrl1.Pause(); //暂停播放

        }

        private void btnStop_Click(object sender, EventArgs e)

        {

            axREALmagicCtrl1.Stop();  //停止播放

        }

        private void btnSpeed_Click(object sender, EventArgs e)

        {

            axREALmagicCtrl1.CurrentFrame = axREALmagicCtrl1.CurrentFrame + 125; //快进

        }

        private void btnRecede_Click(object sender, EventArgs e)

        {

                axREALmagicCtrl1.CurrentFrame = axREALmagicCtrl1.CurrentFrame - 125; //快退

        }

        private void rdoLeftTrack_Click(object sender, EventArgs e)

        {

            axREALmagicCtrl1.AudioChannel = NNSREALmagicCtrl.TAudChannel.acLEFT; //左声道

        }

        private void rdoRightTrack_Click(object sender, EventArgs e)

        {

            axREALmagicCtrl1.AudioChannel = NNSREALmagicCtrl.TAudChannel.acRIGHT; //右声道

        }

        private void rdoStereo_Click(object sender, EventArgs e)

        {

            axREALmagicCtrl1.AudioChannel = NNSREALmagicCtrl.TAudChannel.acSTEREO; //立体声

        }

        private void tbVolume_Scroll(object sender, EventArgs e)

        {

            axREALmagicCtrl1.Volume = tbVolume.Value;   //音量

        }

        private void rdoTV_Click(object sender, EventArgs e)

        {

            axREALmagicCtrl1.DisplayDevice = NNSREALmagicCtrl.TDisDev.ddTV; //TV输出模式

        }

        private void rdoVGA_Click(object sender, EventArgs e)

        {

            axREALmagicCtrl1.DisplayDevice = NNSREALmagicCtrl.TDisDev.ddVGA; //VGA输出模式

        }

        private void Form1_Load(object sender, EventArgs e)

        {

            if (!axREALmagicCtrl1.OpenDriver())  //打开驱动

            {

                MessageBox.Show("打开驱动失败!!", "系统提示");

                this.groupBox1.Enabled = false;

                this.groupBox2.Enabled = false;

                this.groupBox3.Enabled = false;

                this.groupBox4.Enabled = false;

                return;

            }

            OleDbConnection con = new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + "DataBase.mdb" + ";Persist Security Info=False");

            OleDbDataAdapter dap = new OleDbDataAdapter("select G_name as 影音名称,G_YC as 原唱,G_wjlx as 文件格式 from g_music_name", con);

            DataSet ds = new DataSet();

            dap.Fill(ds); //显示影音文件的相关属性

            dataGridView1.DataSource = ds.Tables[0].DefaultView;

        }

        private void dataGridView1_CellClick(object sender, DataGridViewCellEventArgs e)

        {  //选择播放的影音

            strFileName = @"\vod\" + dataGridView1[0, e.RowIndex].Value.ToString() + "." + dataGridView1[2, e.RowIndex].Value.ToString();

        }

举一反三

根据本实例,读者可以开发以下程序。

  练歌房卡拉OK系统。

  单机卡拉OK系统。

第16章         加密、安全与软件注册

16.1  数据加密与解密

在企业的计算机中,往往存有大量的机密文件,这些机密文件与企业的发展有紧密联系,如果这些机密文件保管不善,将会使企业遭受巨大的损失。本节通过几个典型实例详细介绍一下C#中的加密与解密技术。

实例463 数据加密技术

实例说明

本实例实现对文件的机密数据进行加密的功能。运行程序,在文本框中输入要加密的数据,单击【加密】按钮,对数据进行加密,并将加密后的数据显示在“加密后的字符”文本框中。实例运行结果如图16.1所示。

技术要点

实现本实例功能主要用到了System.Security.Cryptography命名空间下的MD5Crypto- ServiceProvider类的ComputeHash( )方法、System.Text命名空间下的ASCIIEncoding类的ASCII属性、GetBytes( )方法和GetString( )方法。下面分别进行介绍。

(1)System.Security.Cryptography命名空间

System.Security.Cryptography 命名空间提供加密服务(包括安全的数据编码和解码)以及许多其他操作,例如散列法、随机数字生成和消息身份验证。

(2)MD5CryptoServiceProvider类

此类使用加密服务提供程序(CSP)提供的实现,计算输入数据的MD5哈希值。

语法格式为:

public sealed class MD5CryptoServiceProvider : MD5

 注意:MD5CryptoServiceProvider类的哈希大小为128位。

(3)ComputeHash( )方法

此方法计算指定字节数组的哈希值。

语法格式为:

public byte[] ComputeHash (byte[] buffer)

参数说明如下。

l     buffer:要计算其哈希代码的字节数组。

l     返回值:计算所得的哈希代码。

(4)System.Text命名空间

表示 ASCII、Unicode、UTF-7和UTF-8字符编码的类;是用于将字符块转换为字节块和将字节块转换为字符块的抽象基类。

(5)ASCIIEncoding类

此类表示Unicode字符的ASCII字符编码。

语法格式为:

    public class ASCIIEncoding : Encoding

(6)ASCII属性

此属性获取 ASCII(7位)字符集的编码。

语法格式为:

public static Encoding ASCII { get; }

l     属性值:ASCII(7 位)字符集的Encoding。

(7)GetBytes( )方法

此方法将指定的String中的所有字符编码为一个字节序列。

语法格式为:

public virtual byte[] GetBytes (string s)

参数说明如下。

l     s:包含要编码的字符的String。

(8)GetString( )方法

此方法将指定字节数组中的所有字节解码为一个字符串。

语法格式为:

public virtual string GetString (byte[] bytes)

参数说明如下。

l     bytes:包含要解码的字节序列的字节数组。

l     返回值:包含指定字节序列解码结果的String。

 注意:使用MD5CryptoServiceProvider类必须引用System.Security.Cryptography命名空间。

实现过程

(1)新建一个Windows应用程序,将其命名为Ex16_01,默认窗体为Form1。

(2)在Form1窗体中,主要添加两个TextBox控件,用来输入和显示字符串;添加一个Button控件,用来执行加密操作。

(3)主要程序代码。

        private void button1_Click(object sender, EventArgs e)

        {

            if (textBox1.Text == "")

            { MessageBox.Show("请输入加密数据"); return; }

            MD5CryptoServiceProvider M5 = new MD5CryptoServiceProvider();

            textBox2.Text = ASCIIEncoding.ASCII.GetString(M5.ComputeHash(ASCIIEncoding.ASCII.GetBytes(textBox1.Text)));

        }

举一反三

根据本实例,读者可以实现以下功能。

  对机密的文件夹进行加密与解密。

  在数据传输中使用,以便保证传输信息不被泄露。

实例464 文本文件加密与解密

实例说明

本实例实现对计算机中的一些非常机密的文本文件进行加密与解密操作。运行程序,单击【选择文件】按钮,选择要进行加密或解密的文本文件(.txt格式的文件),单击【加密】或【解密】按钮,即可完成对文本文件的加密或解密操作。实例运行结果如图16.2所示。

技术要点

实现本实例功能主要用到了System.Security.Cryptography命名空间下的RijndaelManaged类的CreateDecryptor( )方法、CreateEncryptor( )方法、CryptoStream类的Write( )方法、FlushFinalBlock( )方法、Close( )方法、System.IO命名空间下的FileStream类、StreamReader类的ReadToEnd( )方法、StreamWriter类的Write( )方法、Flush( )方法和BinaryReader类的ReadBytes( )方法。System.Security.Cryptography命名空间在第16章实例463中已经做过详细介绍,这里不再详细描述,下面对本实例中用到的其他知识进行详细介绍。

(1)RijndaelManaged类

此类表示Rijndael对称加密算法的所有实现必须从其继承的基类中获得。

语法格式为:

public abstract class Rijndael SymmetricAlgorithm

 注意:此算法支持128、192或256位的密钥长度。

(2)CreateDecryptor( )方法

此方法使用指定的Key和初始化向量(IV)创建对称的Rijndael解密器对象。

语法格式为:

public override IcryptoTransform CreateDecryptor (byte[] rgbKey,byte[] rgbIV)

参数说明如下。

l     rgbKey:用于对称算法的机密密钥。

l     rgbIV:用于对称算法的IV。

l     返回值:对称的Rijndael解密器对象。

(3)CreateEncryptor( )方法

此方法使用指定的Key和初始化向量(IV)创建对称的Rijndael加密器对象。

语法格式为:

public override ICryptoTransform CreateEncryptor (byte[] rgbKey,byte[] rgbIV)

参数说明如下。

l     rgbKe:用于对称算法的机密密钥。

l     rgbIV:用于对称算法的IV。

l     返回值:对称的Rijndael加密器对象。

(4)CryptoStream类

此类定义将数据流链接到加密转换的流。

语法格式为:

public CryptoStream (Stream stream,ICryptoTransform transform,CryptoStreamMode mode)

参数说明如下。

l     stream:对其执行加密转换的流。

l     Transform:要对流执行的加密转换。

l     Mode:CryptoStreamMode值之一。CryptoStreamMode值及明说如表16.1所示。

表16.1                                                  CryptoStreamMode值及说明

说  明

Read

对加密流的读访问

Write

对加密流的写访问

(5)CryptoStream类的Write( )方法

此方法将一个字节序列写入当前CryptoStream类中,并从当前位置写入指定的字节数。

语法格式为:

public override void Write (byte[] buffer,int offset,int count)

参数说明如下。

l     buffer:字节数组。此方法将count个字节从buffer复制到当前流。

l     offset:buffer中的字节偏移量,从此偏移量开始将字节复制到当前流。

l     count:要写入当前流的字节数。

(6)FlushFinalBlock( )方法

此方法用缓冲区的当前状态更新基础数据源或储存库,随后清除缓冲区。

语法格式为:

public void FlushFinalBlock ()

(7)Close( )方法

关闭当前流并释放与之关联的所有资源(如套接字和文件句柄)。

语法格式为:

public virtual void Close ()

(8)System.IO命名空间

System.IO命名空间包含允许读写文件和数据流的类型以及提供基本文件和目录支持的类型。

(9)FileStream类

此类公开以文件为主的Stream,既支持同步读写操作,也支持异步读写操作。

语法格式为:

public FileStream (string path,FileModemode,FileAccessaccess)

参数说明如下。

l     path:当前FileStream类对象封装文件的相对路径或绝对路径。

l     Mode:FileMode常数,确定如何打开或创建文件。FileMode常数的值及说明如表16.2所示。

l     Access:FileAccess常数,确定FileStream对象访问文件的方式。这将获取FileStream对象的CanRead和CanWrite属性。如果path指定磁盘文件,则CanSeek为True。FileAccess常数的值及说明如表16.3所示。

表16.2                                                   FileMode常数的值及说明

常 数 值

说  明

Append

打开现有文件并查找到文件尾,或创建新文件。FileMode.Append只能同FileAccess.Write一起使用。任何读尝试都将失败并引发ArgumentException

Create

指定操作系统应创建新文件。如果文件已存在,它将被改写。这要求FileIOPermissionAccess.Write和System.IO.FileMode.Create等效于这样的请求:如果文件不存在,则使用CreateNew;否则使用Truncate

CreateNew

指定操作系统应创建新文件。此操作需要FileIOPermissionAccess.Write。如果文件已存在,则将引发IOException

Open

指定操作系统应打开现有文件。打开文件的能力取决于FileAccess所指定的值。如果该文件不存在,则引发System.IO.FileNotFoundException

OpenOrCreate

指定操作系统应打开文件(如果文件存在);否则,应创建新文件。如果用FileAccess.Read打开文件,则需要FileIOPermissionAccess.Read。如果文件访问为FileAccess.Write或FileAccess.ReadWrite,则需要FileIOPermissionAccess.Write。如果文件访问为FileAccess.Append,则需要FileIOPermissionAccess.Append

Truncate

指定操作系统应打开现有文件。文件一旦打开,就将被截断为零字节大小。此操作需要FileIOPermissionAccessWrite。试图从使用Truncate打开的文件中进行读取将导致异常

表16.3                                                  FileAccess常数的值及说明

常 数 值

说  明

Read

对文件的读访问。可从文件中读取数据。同Write组合即构成读写访问权

ReadWrite

对文件的读访问和写访问。可从文件读取数据和将数据写入文件

Write

文件的写访问。可将数据写入文件。同Read组合即构成读/写访问权

(10)StreamReader类

此类实现一个TextReader,使其以一种特定的编码从字节流中读取字符。

语法格式为:

public StreamReader (Stream stream)

参数说明如下。

l     stream:要读取的流。

(11)ReadToEnd( )方法

此方法从流的当前位置到末尾读取流。

public override string ReadToEnd ()

l     返回值:字符串形式的流的其余部分(从当前位置到末尾)。如果当前位置位于流的末尾,则返回空字符串(“”)。

(12)StreamWriter类

此类实现一个TextWriter,使其以一种特定的编码向流中写入字符。

语法格式为:

public StreamWriter (Stream stream)

参数说明如下。

l     stream:要写入的流。

(13)StreamWriter类的Write( )方法

此方法将字符写入流。

语法格式为:

public override void Write (char value)

参数说明如下。

l     value:要写入文本流中的字符。

(14)Flush( )方法

此方法清理当前编写器的所有缓冲区,并使所有缓冲数据写入基础流。

语法格式为:

public override void Flush ()

(15)BinaryReader类

此类用特定的编码将基元数据类型读作二进制值。

语法格式为:

public BinaryReader (Stream input)

参数说明如下。

l     Input:流。

(16)ReadBytes( )方法

此方法从当前流中将count个字节读入字节数组,并使当前位置提升count个字节。

语法格式为:

public virtual byte[] ReadBytes (int count)

参数说明如下。

l     count:要读取的字节数。

l     返回值:包含从基础流中读取的数据的字节数组。如果到达了流的末尾,则该字节数组可能小于所请求的字节数。

实现过程

(1)新建一个Windows应用程序,将其命名为Ex16_02,默认窗体为Form1。

(2)在Form1窗体中,主要添加一个TextBox控件,用来显示文件路径;添加一个OpenFileDialog控件,用来选择要加密或解密的文件;添加3个Button控件,用来执行加密、解密和选择文件操作。

(3)主要程序代码。

加密文本文件的实现代码如下:

private void button5_Click(object sender, EventArgs e)

        {

            if (textBox1.Text == "")

            { MessageBox.Show("请选择要加密的文件"); }

            else

            {

               try{

                string strPath = textBox1.Text;//加密文件的路径

                int intLent=strPath.LastIndexOf("\\")+1;

                int intLong = strPath.Length;

            //要加密的文件名称

                string strName = strPath.Substring(intLent,intLong-intLent);

                int intTxt = strName.LastIndexOf(".");

                int intTextLeng = strName.Length;

               //取出文件的扩展名

                string strTxt = strName.Substring(intTxt,intTextLeng-intTxt);

                strName = strName.Substring(0,intTxt);

                //加密后的文件名及路径

                string strOutName = strPath.Substring(0, strPath.LastIndexOf("\\") + 1) + strName + "Out" + strTxt;

//加密文件密钥

                byte[] key = { 24, 55, 102, 24, 98, 26, 67, 29, 84, 19, 37, 118, 104, 85, 121, 27, 93, 86, 24, 55, 102, 24, 98, 26, 67, 29, 9, 2, 49, 69, 73, 92 };

                byte[] IV ={ 22, 56, 82, 77, 84, 31, 74, 24, 55, 102, 24, 98, 26, 67, 29, 99 };

                RijndaelManaged myRijndael = new RijndaelManaged();

                FileStream fsOut = File.Open(strOutName, FileMode.Create, FileAccess.Write);

                FileStream fsIn = File.Open(strPath, FileMode.Open, FileAccess.Read);

                //写入加密文本文件

                CryptoStream csDecrypt = new CryptoStream(fsOut, myRijndael.CreateEncryptor(key, IV), CryptoStreamMode.Write);

                //读加密文本

                BinaryReader br = new BinaryReader(fsIn);

                csDecrypt.Write(br.ReadBytes((int)fsIn.Length), 0, (int)fsIn.Length);

                csDecrypt.FlushFinalBlock();

                csDecrypt.Close();

                fsIn.Close();

                fsOut.Close();

                if (MessageBox.Show(strOutName, "提示:加密成功!加密后的文件名及路径为:\n"+"是否删除源文件", MessageBoxButtons.YesNo) == DialogResult.Yes)

                {

                    File.Delete(strPath);

                    textBox1.Text = "";

                }else

                { textBox1.Text = ""; }

            }

            catch (Exception ee)

            {

                MessageBox.Show(ee.Message);

            }

            }

        }

解密文本文件的实现代码如下:

   private void button4_Click(object sender, EventArgs e)

        {

            if (textBox1.Text == "")

            {

                MessageBox.Show("请选择要解密的文件路径");

            }

            else

            {

                string strPath = textBox1.Text;//加密文件的路径

                int intLent = strPath.LastIndexOf("\\") + 1;

                int intLong = strPath.Length;

                //要加密的文件名称

                string strName = strPath.Substring(intLent, intLong - intLent);

                int intTxt = strName.LastIndexOf(".");

                int intTextLeng = strName.Length;

                strName = strName.Substring(0, intTxt);

                if (strName.LastIndexOf("Out") != -1)

                {

                    strName = strName.Substring(0, strName.LastIndexOf("Out"));

                }

                else

                {

                    strName = strName + "In";

                }

                //加密后的文件名及路径

                string strInName = strPath.Substring(0, strPath.LastIndexOf("\\") + 1) + strName + ".txt"; //解密文件密钥

                byte[] key = { 24, 55, 102, 24, 98, 26, 67, 29, 84, 19, 37, 118, 104, 85, 121, 27, 93, 86, 24, 55, 102, 24, 98, 26, 67, 29, 9, 2, 49, 69, 73, 92 };

                byte[] IV ={ 22, 56, 82, 77, 84, 31, 74, 24, 55, 102, 24, 98, 26, 67, 29, 99 };

                RijndaelManaged myRijndael = new RijndaelManaged();

                FileStream fsOut = File.Open(strPath, FileMode.Open, FileAccess.Read);

                CryptoStream csDecrypt = new CryptoStream(fsOut, myRijndael.CreateDecryptor(key, IV), CryptoStreamMode.Read);

                StreamReader sr = new StreamReader(csDecrypt);//把文件读出来

                StreamWriter sw = new StreamWriter(strInName);//解密后文件写入一个新的文件

                sw.Write(sr.ReadToEnd());

                sw.Flush();

                sw.Close();

                sr.Close();

                fsOut.Close();

                if (MessageBox.Show(strInName, "提示:解密成功!解密后的文件名及路径为:" + "是否删除源文件", MessageBoxButtons.YesNo) == DialogResult.Yes)

                {

                    File.Delete(strPath);

                    textBox1.Text = "";

                }

                else

                {

                    textBox1.Text = "";

                }

            }

        }

举一反三

根据本实例,读者可以实现以下功能。

  对重要公文进行加密。

  对网络中传输的文件进行加密与解密。

实例465 利用图片加密文件

实例说明

本实例中,利用图片生成密钥,然后对文本文件进行加密和解密操作。运行程序,单击【打开图片】按钮,选择密钥图片,然后单击【打开文本文件】按钮,选择要加密或解密的文件,单击【加密】或【解密】按钮完成文本文件的加密或解密操作。解密时的密钥图片要与加密时的密钥图片相同,否则解密不能成功。实例运行结果如图16.3所示。

技术要点

实现本实例功能主要用到了System.Security.Cryptography命名空间下的RC2CryptoServiceProvider类的CreateDecryptor( )方法、CreateEncryptor( )方法、CryptoStream类的Write( )方法、FlushFinalBlock( )方法、Close( )方法、System.IO命名空间下的FileStream类、BinaryReader类的ReadBytes( )方法、BinaryWriter类的Write( )方法、File类的Delete( )方法和Copy( )方法。以上大部分知识在第16章实例464中已经做过详细介绍,这里不再详细讲解。下面主要对RC2CryptoServiceProvider类、BinaryWriter类的Write( )方法、File类的Delete( )方法和Copy( )方法进行介绍。

(1)RC2CryptoServiceProvider类

此类定义访问RC2算法的加密服务提供程序(CSP)实现的包装对象。

(2)BinaryWriter类

此类以二进制形式将基元类型写入流,并支持用特定的编码写入字符串。

语法格式为:

public BinaryWriter (Streamoutput)

参数说明如下。

l     output:输出流。

(3)BinaryWriter类的Write( )方法

此方法将一个无符号字节写入当前流,并将流的位置提升1个字节。

语法格式为:

public virtual void Write (byte value)

参数说明如下。

l     value:要写入的无符号字节。

(4)File类

此类提供用于创建、复制、删除、移动和打开文件的静态方法,并协助创建FileStream对象。

(5)Delete( )方法

此方法删除指定的文件。如果指定的文件不存在,则引发异常。

语法格式为:

public static void Delete (string path)

参数说明如下。

l     path:要删除的文件的名称。

(6)Copy( )方法

此方法将现有文件复制到新文件,不允许改写同名的文件。

语法格式为:

public static void Copy (string sourceFileName,string destFileName)

参数说明如下。

l     sourceFileName:要复制的文件。

l     destFileName:目标文件的名称,不能是一个目录或现有文件。

实现过程

(1)新建一个Windows应用程序,将其命名为Ex16_03,默认窗体为Form1。

(2)在Form1窗体中,主要添加一个TextBox控件,用来显示加密或解密文件的路径;添加一个OpenFileDialog控件,用来选择要加密或解密的文件和打开密钥的图片;添加4个Button控件,用来执行加密、解密、打开文件和打开图片操作;添加一个PictureBox控件,用于显示密钥图片。

(3)主要程序代码。

利用图片加密文本文件的实现代码如下:

private void button3_Click(object sender, EventArgs e)

        {

            try

            {

                if (pictureBox1.ImageLocation==null)

                { MessageBox.Show("请选择一幅图片用于加密"); return; }

                if (textBox1.Text == "")

                { MessageBox.Show("请选择加密文件路径"); return; }

                //图片流

                FileStream fsPic = new FileStream(pictureBox1.ImageLocation, FileMode.Open, FileAccess.Read);

                //加密文件流

                FileStream fsText = new FileStream(textBox1.Text, FileMode.Open, FileAccess.Read);

                //初始化Key IV

                byte[] bykey = new byte[16];

                byte[] byIv = new byte[8];

                fsPic.Read(bykey, 0, 16);

                fsPic.Read(byIv, 0, 8);

                //临时加密文件

                string strPath = textBox1.Text;//加密文件的路径

                int intLent = strPath.LastIndexOf("\\") + 1;

                int intLong = strPath.Length;

                string strName = strPath.Substring(intLent, intLong - intLent);//要加密的文件名称

                string strLinPath = "C:\\" + strName;//临时加密文件路径

                FileStream fsOut = File.Open(strLinPath, FileMode.Create, FileAccess.Write);

                //开始加密

            RC2CryptoServiceProvider desc = new RC2CryptoServiceProvider();//desc进行加密

                BinaryReader br = new BinaryReader(fsText);//从要加密的文件中读出文件内容

                CryptoStream cs = new CryptoStream(fsOut, desc.CreateEncryptor(bykey, byIv), CryptoStreamMode.Write);//写入临时加密文件

                cs.Write(br.ReadBytes((int)fsText.Length), 0, (int)fsText.Length);//写入加密流

                cs.FlushFinalBlock();

                cs.Flush();

                cs.Close();

                fsPic.Close();

                fsText.Close();

                fsOut.Close();

                File.Delete(textBox1.Text.TrimEnd());//删除原文件

                File.Copy(strLinPath, textBox1.Text);//复制加密文件

                File.Delete(strLinPath);//删除临时文件

                MessageBox.Show("加密成功");

                pictureBox1.ImageLocation = null;

                textBox1.Text = "";

            }

            catch (Exception ee)

            {

                MessageBox.Show(ee.Message);

            }

        }

利用图片解密文本文件的实现代码如下:

  private void button4_Click(object sender, EventArgs e)

        {

            try

            {

                //图片流

        FileStream fsPic = new FileStream(pictureBox1.ImageLocation, FileMode.Open, FileAccess.Read);

                //解密文件流

            FileStream fsOut = File.Open(textBox1.Text, FileMode.Open, FileAccess.Read);

                //初始化Key IV

                byte[] bykey = new byte[16];

                byte[] byIv = new byte[8];

                fsPic.Read(bykey, 0, 16);

                fsPic.Read(byIv, 0, 8);

                //临时解密文件

                string strPath = textBox1.Text;//加密文件的路径

                int intLent = strPath.LastIndexOf("\\") + 1;

                int intLong = strPath.Length;

      string strName = strPath.Substring(intLent, intLong - intLent);//要加密的文件名称

                string strLinPath = "C:\\" + strName;//临时解密文件路径

               FileStream fs = new FileStream(strLinPath, FileMode.Create, FileAccess.Write);

                //开始解密

          RC2CryptoServiceProvider desc = new RC2CryptoServiceProvider();//desc进行解密

           CryptoStream csDecrypt = new CryptoStream(fsOut, desc.CreateDecryptor(bykey, byIv), CryptoStreamMode.Read);//读出加密文件

                BinaryReader sr = new BinaryReader(csDecrypt);//从要加密流中读出文件内容

                BinaryWriter sw = new BinaryWriter(fs);//写入解密流

                sw.Write(sr.ReadBytes(Convert.ToInt32(fsOut.Length)));//

                sw.Flush();

                sw.Close();

                sr.Close();

                fs.Close();

                fsOut.Close();

                fsPic.Close();

                csDecrypt.Flush();

                File.Delete(textBox1.Text.TrimEnd());//删除原文件

                File.Copy(strLinPath, textBox1.Text);//复制加密文件

                File.Delete(strLinPath);//删除临时文件

                MessageBox.Show("解密成功");

                pictureBox1.ImageLocation = null;

                textBox1.Text = "";

            }

            catch (Exception ee)

            {

                MessageBox.Show(ee.Message);

            }

        }

举一反三

根据本实例,读者可以实现以下功能。

  利用各种图片加密文件。

  使用图片批量解密文件。

16.2  Access数据库安全

由于Access数据库操作简单,使用方便,所以一些中小型的应用软件经常采用Access数据库。为了保证数据库的安全,经常要加密或锁定数据库,如果数据库由于某种原因遭到破坏,就需要对数据库进行修复。下面的几个实例分别实现了加密、锁定和修复Access数据库的功能。

实例466 如何编程修复Access数据库

实例说明

Access数据库操作简单,使用方便,是中小型企业经常采用的数据库,但Access数据库容易遭到破坏,并随着时间的增加,数据库文件会变得非常大,该如何解决这些问题呢?本实例通过压缩数据库的方法重新组织修复数据库,减少了数据库占用的空间。运行程序,单击【打开】按钮,找到要修复的数据库,单击【开始修复】按钮,即可完成修复数据库操作。实例运行结果如图16.4所示。

技术要点

实现本实例功能主要用到了JRO命名空间下JetEngineClass对象的CompactDatabase( )方法、System.IO命名空间下File类的Copy( )方法和Delete( )方法。下面分别进行介绍。

(1)CompactDatabase( )方法

CompactDatabase( )方法压缩并回收本地数据库中的浪费空间。

语法格式为:

CompactDatabase(strng SourceConnection, string DestConnection)

参数说明如下。

l     SourceConnection:字符串值,指定与要压缩的源数据库的连接。

l     DestConnection:字符串值,指定与要通过压缩创建的目标数据库的连接。

 注意:必须引用C:\Program Files\Common Files\System\ado\msjro.dll,该DLL包含JRO命名空间。

(2)Copy( )方法

此方法将现有文件复制到新文件,不允许改写同名的文件。

语法格式为:

public static void Copy (string sourceFileName,string destFileName)

参数说明如下。

l     sourceFileName:要复制的文件。

l     destFileName:目标文件的名称,不能是一个目录或现有文件。

(3)Delete( )方法

此方法删除指定的文件。

语法格式为:

public static void Delete (string path)

参数说明如下。

l     path:要删除的文件的名称。

实现过程

(1)新建一个Windows应用程序,将其命名为Ex16_04,默认窗体为Form1。

(2)在Form1窗体中,主要添加一个TextBox控件,用来显示修复数据库文件的路径;添加一个OpenFileDialog控件,用来选择要修复的数据库文件;添加3个Button控件,用来执行修复、退出和打开数据库文件操作。

(3)主要程序代码。

开始压缩数据库的实现代码如下:

        string strPathMdb = null;//数据库路径

        private void button2_Click(object sender, EventArgs e)

        {

            if (!File.Exists(strPathMdb)) //检查数据库是否已存在

            {

                MessageBox.Show("目标数据库不存在,无法压缩","操作提示");

                return;

            }

            //声明临时数据库的名称

            string temp = DateTime.Now.Year.ToString();

            temp += DateTime.Now.Month.ToString();

            temp += DateTime.Now.Day.ToString();

            temp += DateTime.Now.Hour.ToString();

            temp += DateTime.Now.Minute.ToString();

            temp += DateTime.Now.Second.ToString() + ".bak";

            temp = strPathMdb.Substring(0, strPathMdb.LastIndexOf("\\") + 1) + temp;

            //定义临时数据库的连接字符串

            string temp2 = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + temp;

            //定义目标数据库的连接字符串

            string strPathMdb2 = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + strPathMdb;

            //创建一个JetEngineClass对象的实例

            JRO.JetEngineClass jt = new JRO.JetEngineClass();

            //使用JetEngineClass对象的CompactDatabase方法压缩修复数据库

            jt.CompactDatabase(strPathMdb2, temp2);

            //复制临时数据库到目标数据库(覆盖)

            File.Copy(temp, strPathMdb, true);

            //最后删除临时数据库

            File.Delete(temp);

            MessageBox.Show("修复完成");

        }

举一反三

根据本实例,读者可以实现以下功能。

  定时数据库压缩。

  定时数据库备份。

实例467 访问带验证模式的Sqlserver 2000数据库

实例说明

本实例实现了访问带验证模式的SQL Server 2000数据库的功能。用户登录数据库时,必须输入用户名和密码。运行程序,输入计算机的名称或地址、访问数据库的用户名、密码和数据库名称,单击【登录】按钮,即可登录数据库。实例运行结果如图16.5所示。

技术要点

实现本实例功能主要用到了ADO.NET的SqlConnection对象的Open( )方法、Close( )方法和ConnectionState枚举。下面分别进行介绍。

(1)SqlConnection对象

此对象表示SQL Server数据库的一个打开的连接。

语法格式为:

public SqlConnection (string connectionString)

参数说明如下。

l     connectionString:用于打开SQL Server数据库的连接。

(2)Open( )方法

此方法使用ConnectionString所指定的属性设置打开的数据库连接。

语法格式为:

public override void Open ()

(3)Close( )方法

此方法关闭与数据库的连接。这是关闭任何打开连接的首选方法。

语法格式为:

public override void Close ()

(4)ConnectionState枚举

此枚举描述与数据源连接的当前状态。

语法格式为:

public enum ConnectionState

ConnectionState枚举值及说明如表16.4所示。

表16.4                                               ConnectionState枚举值及说明

枚 举 值

说  明

Broken

与数据源的连接中断。只有在连接打开之后才可能发生这种情况。可以关闭处于这种状态的连接,然后重新打开

Closed

连接处于关闭状态

Connecting

连接对象正在与数据源连接

Executing

连接对象正在执行命令

Fetching

连接对象正在检索数据

Open

连接处于打开状态

 注意:使用SqlConnection对象必须引用System.Data.SqlClient命名空间。

实现过程

(1)新建一个Windows应用程序,将其命名为Ex16_05,默认窗体为Form1。

(2)在Form1窗体中,主要添加4个TextBox控件,用于输入登录信息;添加3个Button控件,用来执行登录、断开连接和退出操作。

(3)主要程序代码。

登录数据库的实现代码如下:

  public SqlConnection con = null;//实义数据库连接对象

        private void button1_Click(object sender, EventArgs e)

        {

            if (textBox1.Text == "")

            {

                MessageBox.Show(textBox1.Tag.ToString()+"不能为空","提示");

                textBox1.Focus();

                return;

            }

            if (textBox2.Text == "")

            {

                MessageBox.Show(textBox2.Tag.ToString() + "不能为空", "提示");

                textBox2.Focus();

                return;

            }

            if (textBox4.Text == "")

            {

                MessageBox.Show(textBox4.Tag.ToString() + "不能为空", "提示");

                textBox4.Focus();

                return;

            }

            try

            {

                string strcon = "server='" + textBox1.Text.Trim() + "';uid='" + textBox2.Text.Trim() + "';pwd='" + textBox3.Text + "';database='" + textBox4.Text.Trim() + "'";

                con = new SqlConnection(strcon);//实例SqlConnect对象

                con.Open();

                MessageBox.Show("登录成功");

            }

            catch (Exception ee)

            {

                MessageBox.Show(ee.Message);

            }

        }

断开连接退出数据库登录的实现代码如下:

        private void button2_Click(object sender, EventArgs e)

        {

            if (con.State == ConnectionState.Open)

            {

                con.Close();

                MessageBox.Show("连接已断开");

            }

            else

            {

                MessageBox.Show("还没有连接数据库");

            }

        }

举一反三

根据本实例,读者可以实现以下功能。

  对数据库添加用户。

  对数据库添加用户权限。

16.3  软件注册与加密

为了使开发的软件能被更广泛地使用,开发者希望更多的用户能试用软件,而另一方面,又不想让用户长时间免费使用未经授权的软件,这就需要设计软件注册程序。下面通过几个典型实例介绍保护软件安全的方法。

实例468 利用INI文件对软件进行注册

实例说明

本实例实现使用INI文件对软件的用户信息进行注册的功能。运行程序,输入登录名称、登录口令和注册码,单击【注册】按钮进行注册,如果注册成功,则给出提示;如果信息已注册,系统给出提示信息。实例运行结果如图16.6所示。

技术要点

实现本实例功能主要用到API函数WritePrivateProfileString和GetPrivateProfileString函数。下面分别进行介绍。

(1)WritePrivateProfileString函数

此函数实现对INI文件的写操作。

函数声明如下。

[ DllImport ( "kernel32" ) ]

   private static extern long WritePrivateProfileString ( string section ,string key , string val , string filePath ) ;

参数说明如下。

l     section:INI文件中的段落。

l     key:INI文件中的关键字。

l     val:INI文件中关键字的数值。

l     filePath:INI文件完整的路径和名称。

(2)GetPrivateProfileString函数

此函数实现对INI文件的读操作。

函数声明如下。

[DllImport("kernel32")]

        private static extern int GetPrivateProfileString(string section, string key, string def, StringBuilder retVal, int size, string filePath);

参数说明如表16.5所示。

表16.5                                                                参数说明

参 数 值

说  明

section

INI文件中的段落名称

key

INI文件中的关键字

def

无法读取时候的缺省数值

retVal

读取数值

size

数值的大小

filePath

INI文件的完整路径和名称

 注意:使用API函数必须引用System.Runtime.InteropServices命名空间。

实现过程

(1)新建一个Windows应用程序,将其命名为Ex16_07,默认窗体为Form1。

(2)在Form1窗体中,主要添加3个TextBox控件,用于输入注册信息;添加两个Button控件,用来执行注册和退出操作。

(3)主要程序代码。

注册用户信息的实现代码如下:

  private void Form1_Load(object sender, EventArgs e)

        {

            FileStream c = new FileStream("C:\\desck.ini",FileMode.OpenOrCreate,FileAccess.Write);

        }

        [ DllImport ( "kernel32" ) ]

        private static extern long WritePrivateProfileString ( string section ,string key , string val , string filePath ) ;

        [DllImport("kernel32")]

        private static extern int GetPrivateProfileString(string section, string key, string def, StringBuilder retVal, int size, string filePath);

        private void button1_Click(object sender, EventArgs e)

        {

            StringBuilder temp = new StringBuilder(200);

            string FileName = "C:\\desck.ini";//NI文件的完整的路径和名称。

            foreach (object ct in Controls)

            {

                if (ct.GetType().ToString() == "System.Windows.Forms.TextBox")

                {

                    TextBox tx = (TextBox)ct;

                    if (tx.Text == "")

                    {

                        MessageBox.Show(tx.Tag.ToString()+"不能为空");

                    }

                }

            }

            string section = textBox3.Text;//INI文件中的段落

            string key = textBox1.Text;//INI文件中的关键字

            string keyValue = textBox2.Text;//INI文件中的关键字

            int i = GetPrivateProfileString(section, key, "无法读取对应数值!", temp, 200, FileName);//判断是否注册过

            if (temp.ToString() == "无法读取对应数值!")

            {

                WritePrivateProfileString(section, key, keyValue, FileName);

                MessageBox.Show("注册成功写入INI文件!", "信息");

            }

            else

            {

                MessageBox.Show("此信息已注册过了");

            }

        }

举一反三

根据本实例,读者可以实现以下功能。

  对INI文件加密保存注册信息。

  对组合INI文件加密保存注册信息。

实例469 利用注册表设计软件注册程序

实例说明

大多数应用软件会将用户输入的注册信息写进注册表中,程序运行过程中,可以将这些信息从注册表中读出。本实例主要实现在程序中对注册表进行操作的功能,运行程序,单击【注册】按钮,会将用户输入的信息写入注册表中。实例运行结果如图16.7所示。

技术要点

实现本实例功能主要用到了Microsoft.Win32命名空间下的Registry类的CurrentUser属性、RegistryKey类的OpenSubKey( )方法、GetSubKeyNames( )方法、SetValue( )方法和CreateSubKey( )方法。下面分别进行介绍。

(1)Microsoft.Win32命名空间

Microsoft.Win32命名空间提供两种类型的类:处理由操作系统引发的事件的类和操作系统注册表的类。

(2)RegistryKey类

此类表示Windows注册表中的项级节点,此类是注册表封装。

语法格式为:

public sealed class RegistryKey : MarshalByRefObject, IDisposable

 注意:要获取RegistryKey实例,需要使用Registry类的静态成员之一。

(3)Registry类

此类提供表示Windows注册表中的根项的RegistryKey对象,并提供访问项/值对的static( )方法。

语法格式为:

public static class Registry

(4)CurrentUser属性

此属性包含有关当前用户首选项的信息,该字段读取Windows 注册表中的HKEY_ CURRENT_USER注册表项。

语法格式为:

public static readonly RegistryKey CurrentUser

 注意:存储在此项中的信息包括环境变量的设置和有关程序组、颜色、打印机、网络连接和应用程序首选项的数据,此项使建立当前用户的设置更容易。在此项中,软件供应商存储要在其应用程序中使用的当前用户特定的首选项。

(5)OpenSubKey( )方法

此方法检索指定的子项。

语法格式为:

public RegistryKey OpenSubKey (string name,bool writable)

参数说明如下。

l     name:要打开的子项的名称或路径。

l     writable:如果需要项的写访问权限,则设置为True。

l     返回值:请求的子项;如果操作失败,则为空引用。

(6)CreateSubKey( )方法

此方法创建一个新子项或打开一个现有子项以进行写访问。字符串subkey不区分大小写。

语法格式为:

public RegistryKey CreateSubKey (string subkey)

参数说明如下。

l     subkey:要创建或打开的子项的名称或路径。

l     返回值:RegistryKey对象,表示新建的子项或空引用。如果为subkey指定了零长度字符串,则返回当前的RegistryKey对象。

(7)GetSubKeyNames( )方法

此方法检索包含所有子项名称的字符串数组。

语法格式为:

public string[] GetSubKeyNames ()

l     返回值:包含当前项的子项名称的字符串数组。

(8)SetValue( )方法

此方法设置指定的名称/值对。

语法格式为:

public void SetValue (string name,Object value)

参数说明如下。

l     name:要存储的值的名称。

l     value:要存储的数据。

 注意:对注册表操作使用RegistryKey类和Registry类时,必须引用Microsoft.Win32 命名空间。

实现过程

(1)新建一个Windows应用程序,将其命名为Ex16_07,默认窗体为Form1。

(2)在Form1窗体中,主要添加3个TextBox控件,用于输入注册信息;添加两个Button控件,用来执行注册和退出操作。

(3)主要程序代码。

     private void button1_Click(object sender, EventArgs e)

        {

            if(textBox1.Text=="")

            {

                MessageBox.Show("公司名称不能为空"); return;

            }

                if(textBox2.Text=="")

                { MessageBox.Show("用户名称不能为空"); return; }

                if (textBox3.Text == "")

                { MessageBox.Show("注册码不能为空"); return; }

                //实例RegistryKey 类对象

            Microsoft.Win32.RegistryKey retkey1 = Microsoft.Win32.Registry.CurrentUser.OpenSubKey("software").OpenSubKey("ZHY").OpenSubKey("ZHY.INI", true);

            foreach (string strName in retkey1.GetSubKeyNames())//判断注册码是否过期

            {

                if (strName == textBox3.Text)

                {

                    MessageBox.Show("此注册码已经过期");

                    return;

                }

            }//开始注册信息

            Microsoft.Win32.RegistryKey retkey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey("software", true).CreateSubKey("ZHY").CreateSubKey("ZHY.INI").CreateSubKey(textBox3.Text.TrimEnd());

            retkey.SetValue("UserName", textBox2.Text);

            retkey.SetValue("capataz", textBox1.Text);

            retkey.SetValue("Code", textBox3.Text);

            MessageBox.Show("注册成功,您可以使用本软件");

            Application.Exit();

        }

举一反三

根据本实例,读者可以实现以下功能。

  注册信息加密后存入注册表。

  记录用户试用次数的注册程序。

实例470 利用网卡序列号设计软件注册程序

实例说明

本实例实现了利用本机网卡序列号生成软件注册码的功能。运行程序,自动获得本机网卡序列号,单击【生成注册码】按钮,生成软件注册码,将注册码依次输入下面的文本框,单击【注册】按钮实现软件注册功能。实例运行结果如图16.8所示。

技术要点

实现本实例功能主要用到了Microsoft.Win32命名空间下的Registry类的CurrentUser属性、RegistryKey类的OpenSubKey( )方法、GetSubKeyNames( )方法、SetValue( )方法、CreateSubKey( )方法、System.Management命名空间下的ManagementClass类的GetInstances( )方法、ManagementObjectCollection类和ManagementObject类。Microsoft.Win32命名空间下的类和方法在第16章实例469中已经做过介绍,这里不再详细说明,下面主要对System.Management命名空间及该命名空间下的类进行详细介绍。

(1)System.Management命名空间

提供对大量管理信息和管理事件集合的访问,这些信息和事件是与根据Windows管理规范 (WMI)结构对系统、设备和应用程序设置检测点有关的。

(2)ManagementClass类

表示公共信息模型(CIM)管理类。管理类是一个WMI类,如Win32_LogicalDisk和Win32_Process,前者表示磁盘驱动器,后者表示进程(如Notepad.exe)。

语法格式为:

public class ManagementClass : ManagementObject

(3)GetInstances( )方法

返回该类的所有实例的集合。

语法格式为:

public ManagementObjectCollection GetInstances ()

l     返回值:表示该类实例的ManagementObject对象的集合。

(4)ManagementObjectCollection类

基于指定的查询检索管理对象的集合。此类是用于检索管理信息的较为常用的入口点之一。例如,可以用于枚举系统中的所有磁盘驱动器、网络适配器、进程及更多管理对象,或者用于查询所有处于活动状态的网络连接以及暂停的服务等。

(5)ManagementObject类

表示 WMI 实例。

实现过程

(1)新建一个Windows应用程序,将其命名为Ex16_08,默认窗体为Form1。

(2)在Form1窗体中添加4个TextBox控件、3个Button控件和6个Label控件。其中,TextBox控件用输入注册码,Button控件用来执行注册、退出和生成注册码操作,Label控件用于显示计算机名称、网卡序列号、软件注册码和一些提示信息等。

(3)主要程序代码。

获得网卡序列号和计算机名称的实现代码如下:

  private void Form1_Load(object sender, EventArgs e)

        {

            label2.Text = Environment.MachineName.ToString();//得到计算机名

            label4.Text = GetNetCardMacAddress();//得到网卡信息

        }

        //获得网卡信息函数

       public string GetNetCardMacAddress()

        {     

           ManagementClass mc = new ManagementClass("Win32_NetworkAdapterConfiguration");

           ManagementObjectCollection moc = mc.GetInstances();

            string str = "";

            foreach (ManagementObject mo in moc)

            {

                if ((bool)mo["IPEnabled"] == true)

                    str = mo["MacAddress"].ToString();

            }

            return str;

        }

生成注册码的实现代码如下:

  string[] strLanCode = new string[12];// 网卡信息存储

        string[] strkey ={ "Q", "W", "7", "E", "D", "F", "2", "G", "R", "T", "Y", "8", "P", "N", "B", "V", "C", "X", "Z", "0", "9", "I", "8", "6", "U", "O", "P", "M", "5", "4", "3", "1", "A", "S", "H", "J", "K", "L" };

        //生成注册码

        public int intRand = 0;//判断随机生成次数

        private void button1_Click(object sender, EventArgs e)

        {

            //把网卡信息转换成字符串

            string strCode = GetNetCardMacAddress();//调用函数获取网卡信息

            strCode = strCode.Substring(0, 2) + strCode.Substring(3, 2) + strCode.Substring(6, 2) + strCode.Substring(9, 2) + strCode.Substring(12, 2) +strCode.Substring(15, 2);

            string strb = strCode.Substring(0, 4) + strCode.Substring(4, 4) + strCode.Substring(8, 4);//网卡信息存储

            for (int i = 0; i < strLanCode.Length; i++)//把网卡信息存入数组

            {

                strLanCode[i] = strb.Substring(i, 1);

            }

            Random ra = new Random();

            switch (intRand)//随机生成注册码的顺序

            {

                case 0:

                    label5.Text = strCode.Substring(0, 4) + "-" + strCode.Substring(4, 4) + "-" + strCode.Substring(8, 4) + "-" + strkey[ra.Next(0, 37)].ToString() + strkey[ra.Next(0, 37)].ToString() + strkey[ra.Next(0, 37)].ToString() + strkey[ra.Next(0, 37)].ToString();

                    intRand = 1;

                    break;

                case 1:

                    label5.Text = strCode.Substring(0, 4) + "-" + strCode.Substring(4, 4) + "-" + strLanCode[ra.Next(0, 11)] + strLanCode[ra.Next(0, 11)] + strLanCode[ra.Next(0, 11)] + strLanCode[ra.Next(0, 11)] + "-" + strkey[ra.Next(0, 37)].ToString() + strkey[ra.Next(0, 37)].ToString() + strkey[ra.Next(0, 37)].ToString() + strkey[ra.Next(0, 37)].ToString();

                    intRand = 2;

                    break;

                case 2:

                    label5.Text = strCode.Substring(0, 4) + "-" + strLanCode[ra.Next(0, 11)] + strLanCode[ra.Next(0, 11)] + strLanCode[ra.Next(0, 11)] + strLanCode[ra.Next(0, 11)] + "-" + strLanCode[ra.Next(0, 11)] + strLanCode[ra.Next(0, 11)] + strLanCode[ra.Next(0, 11)] + strLanCode[ra.Next(0, 11)] + "-" + strkey[ra.Next(0, 37)].ToString() + strkey[ra.Next(0, 37)].ToString() + strkey[ra.Next(0, 37)].ToString() + strkey[ra.Next(0, 37)].ToString();

                    intRand = 3;

                    break;

                case 3:

                    label5.Text = strLanCode[ra.Next(0, 11)] + strLanCode[ra.Next(0, 11)] + strLanCode[ra.Next(0, 11)] + strLanCode[ra.Next(0, 11)] + "-" + strLanCode[ra.Next(0, 11)] + strLanCode[ra.Next(0, 11)] + strLanCode[ra.Next(0, 11)] + strLanCode[ra.Next(0, 11)] + "-" + strLanCode[ra.Next(0, 11)] + strLanCode[ra.Next(0, 11)] + strLanCode[ra.Next(0, 11)] + strLanCode[ra.Next(0, 11)] + "-" + strkey[ra.Next(0, 37)].ToString() + strkey[ra.Next(0, 37)].ToString() + strkey[ra.Next(0, 37)].ToString() + strkey[ra.Next(0, 37)].ToString();

                    intRand = 0;

                    break;

            }

        }

注册软件的实现代码如下。

   private void button2_Click(object sender, EventArgs e)

        {

            if (label5.Text == "")

            { MessageBox.Show("请生成注册码"); }

            else

            {

                string strNameKey = textBox1.Text.TrimEnd() + textBox2.Text.TrimEnd() + textBox3.Text.TrimEnd() + textBox4.Text.TrimEnd();

                string strNumber = label5.Text.Substring(0, 4) + label5.Text.Substring(5, 4) + label5.Text.Substring(10, 4) + label5.Text.Substring(15, 4);

                if (strNameKey == strNumber)

                {

                    Microsoft.Win32.RegistryKey retkey1 = Microsoft.Win32.Registry.CurrentUser.OpenSubKey("software").OpenSubKey("ZHY").OpenSubKey("ZHY.INI", true);

                foreach (string strName in retkey1.GetSubKeyNames())//判断注册码是否过期

                    {

                        if (strName == strNameKey)

                        {

                            MessageBox.Show("此注册码已经过期");

                            return;

                        }

                    }//开始注册信息

                    Microsoft.Win32.RegistryKey retkey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey("software",true).CreateSubKey("ZHY").CreateSubKey("ZHY.INI").CreateSubKey(strNumber.TrimEnd());

                    retkey.SetValue("UserName", "明日科技");

                    MessageBox.Show("注册成功!", "提示");

                    Application.Exit();

                }

                else

                { MessageBox.Show("注册码输入错误"); }

            }

        }

举一反三

根据本实例,读者可以实现以下功能。

  应用组件的注册使用。

  销售的软件产品进行授权。

实例471 根据cpu序列号、磁盘序列号设计软件注册程序

实例说明

本实例根据计算机的cpu号和硬盘序列号经过简单的计算自动生成一组无规律的注册码来实现应用程序的注册。运行程序,单击【生成机器码】按钮,生成24位的机器码,单击【生成注册码】按钮,根据生成的机器码自动转换出24位注册码,将注册码输入文本框中,单击【注册】按扭,完成软件注册功能。实例运行对果如图16.9所示。

技术要点

实现本实例功能主要用到了Microsoft.Win32命名空间下的Registry类的CurrentUser属性、RegistryKey类的OpenSubKey( )方法、GetSubKeyNames( )方法、SetValue( )方法、CreateSubKey( )方法、System.Management命名空间下的ManagementClass类的GetInstances( )方法、ManagementObjectCollection类和ManagementObject类、Char字符、Random类的Next( )方法。Microsoft.Win32和System.Management命名空间下的类和方法在第16章实例469和470中已经做过介绍,这里不再详细讲解。下面对本实例中用到的其他知识进行详细介绍。

(1)Char字符

Char类型的常数可以写成字符、十六进制换码序列或Unicode表示形式,用户也可以显式转换整数字符代码。

(2)Random类

表示伪随机数生成器,一种能够产生满足某些随机性统计要求的数字序列的设备。

(3)Next方法

返回一个指定范围内的随机数。

语法格式为:

public virtual int Next (int minValue,int maxValue)

参数说明如下。

l     minValue:返回的随机数的下界(随机数可取该下界值)。

l     maxValue:返回的随机数的上界(随机数不能取该上界值)。maxValue必须大于或等于minValue。

l  返回值:一个大于或等于minValue且小于maxValue的32位带符号整数,即返回的值范围包括minValue但不包括maxValue。如果minValue等于maxValue,则返回minValue。

实现过程

(1)新建一个Windows应用程序,将其命名为Ex16_08,默认窗体为Form1。

(2)在Form1窗体中,主要添加一个TextBox控件,用来输入注册码;添加4个Button控件,用来执行注册、退出、生成注册码和生成机器码操作;添加3个Label控件,用于显示软件注册码和机器码等信息。

(3)主要程序代码。

获得CPU序列号和硬盘序列号的实现代码如下:

        public string GetDiskVolumeSerialNumber()取得设备硬盘的卷标号

        {

            ManagementClass mc = new ManagementClass("Win32_NetworkAdapterConfiguration");

            ManagementObject disk = new ManagementObject("win32_logicaldisk.deviceid=\"d:\"");

            disk.Get();

            return disk.GetPropertyValue("VolumeSerialNumber").ToString();

        }

        public string getCpu()获得CPU的序列号

        {

            string strCpu = null;

            ManagementClass myCpu = new ManagementClass("win32_Processor");

            ManagementObjectCollection myCpuConnection = myCpu.GetInstances();

            foreach( ManagementObject myObject in myCpuConnection)

            {

                strCpu = myObject.Properties["Processorid"].Value.ToString();

                break;

            }

            return strCpu;

        }

生成机器码的实现代码如下:

   private void button1_Click(object sender, EventArgs e)

        {

          label2.Text = getCpu() + GetDiskVolumeSerialNumber();//获得24位CPU和硬盘序列号

            string[] strid = new string[24];

            for (int i = 0; i < 24; i++)//把字符赋给数组

            {

                strid[i] = label2.Text.Substring(i, 1);

            }

            label2.Text = "";

            Random rdid = new Random();

            for (int i = 0; i < 24; i++)//从数组随机抽取24个字符组成新的字符生成机器码

            {

                label2.Text += strid[rdid.Next(0, 24)];

            }

        }

生成注册码的实现代码如下:

public int[] intCode = new int[127];//用于存密钥

        public void setIntCode()//给数组赋值小于10个的随机数

        {

            Random ra = new Random();

            for (int i = 1; i < intCode.Length;i++ )

            {

                intCode[i] = ra.Next(0, 9);

            }

        }

        public int[] intNumber = new int[25];//用于存机器码的AscII值

        public char[] Charcode = new char[25];//存储机器码字

        //生成注册码

        private void button2_Click(object sender, EventArgs e)

        {

            if (label2.Text != "")

            {

                //把机器码存入数组中

                setIntCode();//初始化127位数组

                for (int i = 1; i < Charcode.Length; i++)//把机器码存入数组中

                {

                    Charcode[i] = Convert.ToChar(label2.Text.Substring(i - 1, 1));

                }

            for (int j = 1; j < intNumber.Length; j++)//把字符的ASCII值存入一个整数组中

                {

     intNumber[j] = intCode[Convert.ToInt32(Charcode[j])] + Convert.ToInt32(Charcode[j]);

                }

                string strAsciiName = null;//用于存储机器码

                for (int j = 1; j < intNumber.Length; j++)

                {

                    //MessageBox.Show((Convert.ToChar(intNumber[j])).ToString());

               if (intNumber[j] >= 48 && intNumber[j] <= 57)//判断字符ASCII值是否在0~9之间

                    {

                        strAsciiName += Convert.ToChar(intNumber[j]).ToString();

                    }

         else if (intNumber[j] >= 65 && intNumber[j] <= 90)//判断字符ASCII值是否在A~Z之间

                    {

                        strAsciiName += Convert.ToChar(intNumber[j]).ToString();

                    }

        else if (intNumber[j] >= 97 && intNumber[j] <= 122)//判断字符ASCII值是否在a~z之间

                    {

                        strAsciiName += Convert.ToChar(intNumber[j]).ToString();

                    }

                    else//判断字符ASCII值不在以上范围内

                    {

                        if (intNumber[j] > 122)//判断字符ASCII值是否大于z

                        { strAsciiName += Convert.ToChar(intNumber[j] - 10).ToString(); }

                        else

                        {

                            strAsciiName += Convert.ToChar(intNumber[j] - 9).ToString();

                        }

                    }

                    label3.Text = strAsciiName;//得到注册码

                }

            }

            else

            { MessageBox.Show("请选生成机器码","注册提示"); }

        }

举一反三

根据本实例,读者可以实现以下功能。

  获取CPU信息。

  进行远程软件产品的注册。

 

 

---恢复内容结束---

posted on 2019-04-10 17:27  妙堂传奇  阅读(2011)  评论(0编辑  收藏  举报