Download presentation
Presentation is loading. Please wait.
1
第十八章 图形和多媒体
2
1 理解图形上下文(提供图形环境)和图形对象(提供图形功 能)。 2 学会处理颜色和字体。
本章的学习内容包括六个方面: 1 理解图形上下文(提供图形环境)和图形对象(提供图形功 能)。 2 学会处理颜色和字体。 3 理解并学会运用 GDI+ Graphics 方法绘制直线、矩形、String 和 图像等。 4 学会使用类操纵和显示图像。 5 学会使用 GraphicsPath 类,以简单形状为基础描述复杂形状。 6 学会在 C++CLI 应用程序中使用多媒体播放器 Windows Media Player 和微软代理 Microsoft Agent。
3
Visual C++ .NET 出色的图形支持使得程序设计者可以大大改进
18.1 概述 Visual C++ .NET 出色的图形支持使得程序设计者可以大大改进 Windows 应用程序的视觉表现。 FCL 在 System::Drawing 命名空间 中以及其他命名空间中包含了大量复杂的绘图功能,它们共同 构成了 .NET 的 GDI+ 资源。 GDI+ 是对 Windows 的传统 “图形设备接口”(GDI)的一个扩 展,是一套特殊的 “应用程序编程接口”(API)。它提供的类可 用于创建二维矢量图形、操纵字体以及插入图像。GDI+ 在扩展 GDI 的同时,还简化了编程模型,并添加了一些新特性,比如图 形路径、扩展图像文件支持以及颜色的 alpha 混合等。
4
使用 GDI+ API,程序员可更加灵活地创建图像,而不必关心
图形硬件和运行平台的细节。 GDI+ 中包含了大量组件,最常用的组件位于 System::Drawing 和 System::Drawing::Drawing2D 命名空间中。其中本章将要涉及的 组件类和结构类如下图所示:
5
System::Drawing Font FontFamily Color Graphics Point Icon Rectangle Pen Size Region Image Brush SolidBrush TextureBrush
6
Graphics 类型包含了在一个控件上绘制字符文本、各种矢量 图形和图象的方法,以及其他相关方法和属性。
其中: Graphics 类型包含了在一个控件上绘制字符文本、各种矢量 图形和图象的方法,以及其他相关方法和属性。 Pen 类型对象用于绘制矢量图形的轮廓线。为此提供了相关 的方法和属性。 System::Drawing::Drawing2D HatchBrush LinearGradientBrush PathGradientBrush
7
Brush 类型用于矢量图形的填充。为此提供了相关的方法和属
性。 Color 结构类型包含了用来设置各种图形颜色的 static 属性和 允许用户创建新颜色的方法。 Font 类型包含了定义字体的各种属性和操作方法。 FontFamily 类型定义有着相似的基本设计但在形式上有某些差 异的一组字体。该类型为 sealed 类型,不可继承。
8
要在 C++/CLI 图形界面中绘图,首先要理解 GDI+ 的坐标系统。
所谓 “坐标系统”,是对屏幕上的每个点进行标识的一种方案。 GUI 组件(比如 Panel 或 Form)左上角的坐标默认为(0, 0)。 点的坐标含有 x 位置值和 y 位置值。x 位置值是与左上角的水平 距离,方向向右。y 位置值是与左上角的垂直距离,方向向下。 +x (0, 0) X 轴 + y (x, y) Y 轴
9
所有的图形绘制操作都需要指定确定文本或图形的形状、尺 寸和位置的 (x, y) 坐标,以便将文本、图形和图象定位到屏幕上。
坐标单位是像素(图形元素),即显示器能分辨的最小单元。 在 System::Drawing 命名空间中还提供了描述图形的几何位置 和大小的结构类型 Point、Rectangle 和 Size: Point 结构代表一个点在二维平面上的位置; Rectangle 结构定义了一个矩形区域的位置和大小; Size 结构指定了一个形状或区域的大小。
10
18.2 图形上下文和图形对象 “图形上下文”(graphics context)代表一个绘图表面,可以使
18.2 图形上下文和图形对象 “图形上下文”(graphics context)代表一个绘图表面,可以使 用它在屏幕上绘图。Graphics 类型对象通过控制信息的描述来管 理图形上下文。Graphics 类型中包含了用于绘图、字体处理、颜 色处理,以及进行各种矢量图形和图象相关操作的方法。 从 System::Windows::Forms::Form 派生的每个 Windows 应用程序 窗体和控件都继承了一个事件处理方法 OnPaint,该方法被声明 为 virtual 方法,它负责执行大多数图形操作。传给 OnPaint 方法 的参数是一个 PaintEventArgs 类型对象的 ^指针,从该参数中可 以获得一个用于绘图操作的 Graphics 对象。
11
每次调用 OnPaint 方法时,都必须重新获取 Graphics 对象,因
指示一个控件准备绘制或者重新绘制它的图形外貌。 注意,在派生类中在重新定义 OnPaint 方法的覆盖版本中,都 应该首先调用它直接基类(__super)的 OnPaint 方法,然后从参 数中提取 Graphics 对象。典型的示范代码如下:
12
程序员很少直接调用 OnPaint 方法,因为图形绘制是一个由事 件(覆盖窗口、重新显示窗口或者改变窗口大小)驱动的方法。
virtual void OnPaint( PaintEventArgs ^paintEvent ) override { __super::OnPaint( paintEvent ); // call base OnPaint method Graphics ^graphicsObject = paintEvent->Graphics; // get graphics … } 程序员很少直接调用 OnPaint 方法,因为图形绘制是一个由事 件(覆盖窗口、重新显示窗口或者改变窗口大小)驱动的方法。 与之类似,任何控件(如 TextBox 或 Label)显示时,程序会调 用那个控件的 Paint 方法。
13
如果程序员需要 OnPaint 方法被调用,则可以调用需要绘制图
形外貌的窗体或控件对象的 Invalidate 方法(从 Control 继承), 使该窗体或控件的客户区域的原有图形外貌作废,并隐式调用 OnPaint 方法,完成重新绘制其图形外貌的操作。 调用 OnPaint 方法会引发所属窗口或控件发出 Paint 事件。程 序设计者可以为所包含控件添加 Paint 事件处理方法,而不是覆 盖该控件的 OnPaint 方法。控件的 Paint 事件处理方法原型的一 般形式如下:
14
任何控件在显示时程序都会调用那个控件的 Paint 事件处理方法。 所以,如果希望定义一个方法,让它只更新一个特定的控件
void className::controlName_Paint( Object ^sender, PaintEventArgs ^e); 任何控件在显示时程序都会调用那个控件的 Paint 事件处理方法。 所以,如果希望定义一个方法,让它只更新一个特定的控件 (而不是整个窗体),则可以只为那个控件的 Paint 事件提供一 个事件处理方法。 注意,Label 和 Button 等控件没有自己的图形上下文(换言 之,一般不能在它们的表面上绘图)。但如果需要在这些控件 上绘图,则可以首先调用控件类的 CreateGraphics 方法来创建它 的 Graphics 对象: Graphics ^graphicsObject = controlName->CreateGraphics(); 之后,程序员就能使用 Graphics 类所提供的方法在控件绘图。
15
18.2 颜色控制 Color 结构提供了用于处理颜色的常量属性和方法。由于它是 一个轻量级对象,只执行少数操作,而且存储数据的成员多为
18.2 颜色控制 Color 结构提供了用于处理颜色的常量属性和方法。由于它是 一个轻量级对象,只执行少数操作,而且存储数据的成员多为 静态成员,所以将 Color 定义为结构更为方便、高效。 每种颜色都可以通过组合 alpha、red、green 和 blue “分量值” 来创建。这些分量统称为 ARGB 值,每个 ARGB 分量值都是 byte 类型,表示范围为 0~255 之间的整数值。其中 RGB 分量值表示 组合颜色的三基色,值越大,表示相应的基色比例越高。alpha 分量值决定了颜色的亮度。例如,alpha 值为 0,可产生一种透 明颜色;值为 255,则产生完全不透明的颜色。所以 0~255 之间 的 alpha 值则造成颜色的 RGB 值和当前任何背景色的 RGB 值产 生一种加权混合效果,营造一种具有不同透明状态的颜色。
16
Visual C++ .NET 允许程序设计者从大约 1700 万种颜色中选择。
如果计算机不能显示所有这些颜色,就显示最接近的,或者通 过 “抖动”(dithering)技术来模拟它。所谓 “抖动” 是指使用现有 的颜色来构成一个图案,从而模拟出所需的颜色。Color 结构的 常用属性、方法和预定义颜色常量如下: 常用属性: A 0~255 的 byte 类型值,表示 alpha 分量值 R 0~255 的 byte 类型值,表示 red 分量值 G 0~255 的 byte 类型值,表示 green 分量值 B 0~255 的 byte 类型值,表示 blue 分量值
17
FromArgb 方法根据参数所指定或缺省指定的 ARGB 分量值来 创建一个 Color 结构对象。该方法有四种重载版本:
常用静态方法: FromArgb 方法根据参数所指定或缺省指定的 ARGB 分量值来 创建一个 Color 结构对象。该方法有四种重载版本: static Color FromArgb( int); 根据参数给出的 32 位 ARGB 值(从高至低每 8 位指定 alpha、red、green 和 blue)创建并返回一个 Color 结构对象。 static Color FromArgb( int, Color); 根据第一参数给出的 alpha 值和第二参数给出的 Color 类型对象(RGB 值)创建并返回一个 Color 结构对象。 static Color FromArgb( int, int, int);
18
static Color FromArgb( int, int, int, int);
根据三个参数给出的 red、green 和 blue 值和缺省的 alpha 值(255)创建并返回一个 Color 结构对象。 static Color FromArgb( int, int, int, int); 根据四个参数给出的 alpha、red、green 和 blue 值创建并返回 一个 Color 结构对象。 FromName 方法根据参数所指定的颜色名称来创建一个 Color 结构对象。该方法的原型如下: static Color FromName( String^); 根据参数 String^ 所指向的颜色名称字串创建并返回一个 Color 结构对象。
19
数据成员名就是颜色名,可以作为参数传递给方法 FromName, 以便创建一个具有相应颜色的 Color 类型对象。常用静态预定义
颜色数据成员: 成员名 RGB 分量值 成员名 RGB 分量值 Orange 255, 200, 0 White 255, 255, 255 Pink 255, 175, 175 Gray 28, 128, 128 Cyan 0, 255, 255 DarkGray 64, 64, 64 Magenta 255, 0, 255 Red 255, 0, 0 Yellow 255, 255, 0 Green 0, 255, 0 Black 0, 0, 0 Blue 0, 0, 255
20
绘制各种矢量图形都需要使用画笔 Pen 和画刷 Brush,以便确 定所绘制图形的轮廓和内部填充图形。
笔的颜色和线条宽度。在 System::Drawing 命名空间中还定义了 一个 Pens 集合类型,其中包含多个预定义的 Pen 类型对象。这 些预定义 Pen 类型对象可以通过相应的静态属性获取,例如, 使用如下代码: Pen ^myPen = Pens::Red; 就可以获得一个线宽为 1 个象素的红色画笔。
21
Brush 类型是一个抽象类型,而它的派生类型可以为图形内部 的着色操作创建画刷对象。例如,SolidBrush 类型的构造函数可
以根据参数传递的 Color 对象(它描述了要绘制的颜色),创建 一个画刷对象。Brush 派生的各种画刷类型及其用途如下: HatchBrush 使用矩形画刷在一个区域中填充图案。图案由 HatchStyle 枚举的成员、一个前景色以及一个背景色来定义。 LinearGradientBrush 用渐变颜色来填充一个区域。线性渐变是 沿着一条直线来定义的。要定义它,可以指定两种颜色、渐 变角度以及矩形的宽度或者两个点。 SolidBrush 用单色来填充一个区域。颜色由一个 Color 对象来 定义。
22
本节的实例1 ShowColors 演示如何使用 Color 的方法和属性。程
TextureBrush 沿着表面重复指定的 Image,从而填充一个区域。 本节的实例1 ShowColors 演示如何使用 Color 的方法和属性。程 序允许用户通过输入 alpha 和 RGB 分量值指定前景色,通过输 入预定义颜色常量名指定背景色,并将两种颜色显示在两个矩 形中。 本节中的第一个实例是命名为 ShowColors 的托管类图形界面 程序。设计该实例的目的有三个: ① 学会如何创建和应用 Color 类型对象; ② 学会如何在绘图窗口类型中重新定义 OnPaint 的重载版本; ③ 学会如何在事件处理方法或其他方法中,对窗口客户区中需要修改的部分进行重绘操作。
23
为此,本实例的 GUI 可以设计如下: 窗口客户区的上部用于显示绘图颜色对象改变时,图形的变化。
24
客户区的下部放置两个组框(GroupBox)控件,用于容纳修改
绘图颜色对象操作的控件。这些控件的功能如下: ⑴ 组框(Set Back Color Name)中 ① 文本框(TextBox)控件用于接收用户输入的背景颜色名。 ② 按钮(Button)控件用于触发修改绘图背景色的事件。 ⑵ 组框(Set Front Color Value)中 ① 文本框(TextBox)控件用于接收用户输入的前景颜色名。 ② 按钮(Button)控件用于触发修改绘图前景色的事件。 编程要点: ① 由于需要在窗口 Form1 的客户区中绘制反映颜色变化的图形,因此必须重定义 Form1 的 OnPaint 方法。定义如下:
25
virtual void OnPaint( PaintEventArgs ^paintEvent ) override
{ __super::OnPaint( paintEvent ); // call base OnPaint method Graphics ^graphicsObject = paintEvent->Graphics; // get graphics // create text brush SolidBrush ^textBrush = gcnew SolidBrush( Color::Black ); // create solid brush SolidBrush ^brush = gcnew SolidBrush( Color::White ); // draw white background graphicsObject->FillRectangle( brush, 4, 4, 275, 180 ); // display name of backColor graphicsObject->DrawString( backColor.Name, Font, textBrush, 40, 5 );
26
// set brush color and display back rectangle
brush->Color = backColor; graphicsObject->FillRectangle( brush, 45, 20, 150, 120 ); // display ARGB value of front color graphicsObject->DrawString( String::Concat( L"Alpha: ", frontColor.A.ToString(), L" Red: ", frontColor.R.ToString(), L" Green: ", frontColor.G.ToString(), L" Blue: ", frontColor.B.ToString()), Font, textBrush, 55, 165 ); // set brush color and display front rectangle brush->Color = frontColor; graphicsObject->FillRectangle( brush, 65, 35, 170, 130 ); } // end method OnPaint
27
OnPaint 方法的第一次被调用是在程序开始运行的时候,主窗
体 Form1 被装载事件 Load 的处理方法 Form1_Load 中调用方法 Invalidate 发生的。该方法的定义代码如下: System::Void Form1_Load( System::Object^ sender, System::EventArgs^ e ) { // set frontColor and backColor frontColor = Color::FromName( L"Black" ); backColor = Color::FromName( L"Gray" ); // set colorNameTextBox colorNameTextBox->Text = L"Gray"; // set alphaTextBox, redTextBox, greenTextBox and blueTextbox
28
在程序运行中,用户修改主窗体中所绘制图形的颜色是通过
alphaTextBox->Text = L"255"; redTextBox->Text = L"0"; greenTextBox->Text = L"0"; blueTextBox->Text = L"0"; // Redraw Form Invalidate( Rectangle( 4, 4, 275, 180 )); } 在程序运行中,用户修改主窗体中所绘制图形的颜色是通过 主窗体中所提供的 “Set Back Color Name” 和 “Set Front Color Value” 两组控件交互完成的。 ① 修改背景色 在文本框中输入需要的颜色名后,按<Set Color Name> 按钮。
29
在四个文本框中顺序输入所需要颜色的 ARGB 值( alpha, red,
按钮的点击事件处理方法如下: System::Void colorNameButton_Click( System::Object^ sender, System::EventArgs^ e ) { // set backColor to color specified in text box backColor = Color::FromName( colorNameTextBox->Text ); Invalidate( Rectangle( 4, 4, 275, 180 )); // refresh Form } // end method colorNameButton_Click ② 修改前景色 在四个文本框中顺序输入所需要颜色的 ARGB 值( alpha, red, green, blue)名后,按<Set Color Value> 按钮。
30
按钮的点击事件处理方法如下: System::Void colorValueButton_Click( System::Object^ sender, System::EventArgs^ e ) { try // obtain new front color from text boxes frontColor = Color::FromArgb( Convert::ToInt32( alphaTextBox->Text ), Convert::ToInt32( redTextBox->Text ), Convert::ToInt32( greenTextBox->Text ), Convert::ToInt32( blueTextBox->Text )); // refresh Form
31
Invalidate( Rectangle( 4, 4, 275, 180 ));
} // end try catch( FormatException ^formatException ) { MessageBox::Show( formatException->Message, L"Error", MessageBoxButtons::OK, MessageBoxIcon::Error ); } // end catch catch( ArgumentException ^argumentException ) MessageBox::Show( argumentException->Message, L"Error", } // end method colorValueButton_Click
32
运行结果如下:
33
本节的实例2 ShowColorsComplex 演示如何使用预定义 GUI 组
件 ColorDialog。该组件是一个对话框,它允许用户从包括所有颜 色的一个调色板上选择一种颜色。该对话框还提供了创建自定 义颜色的选项。一旦用户选择或创建了某种颜色并单击 <OK> 按钮,应用程序就能通过 ColorDialog 的 Color 属性获取用户的选 择。本实例中使用用户选择的颜色来改变 GUI 界面的前景色和 背景色。该程序的 GUI 设计如下:
34
由于本例不需要在主窗体的客户区中绘制特定图形,改变客 户区的前景色和背景色只需要重新设置相应的属性 ForeColor 和
编程要点: 由于本例不需要在主窗体的客户区中绘制特定图形,改变客 户区的前景色和背景色只需要重新设置相应的属性 ForeColor 和 BackColor 即可。因此,无须重新定义主窗体的 OnPaint 方法,也 无须添加窗体的 Load 事件处理函数。 修改前景色的操作是通过按 <Change Text Color> 按钮,在处 理鼠标单击 Click 事件方法 textColorButton_Click 中完成的。修改 背景色的操作是通过按 <Change Background Color> 按钮,在处理 鼠标单击 Click 事件方法 backgroundColorButton_Click 中完成的。 textColorButton_Click 方法定义如下:
35
System::Void textColorButton_Click( System::Object^ sender,
System::EventArgs^ e ) { // create ColorDialog object ColorDialog ^colorChooser = gcnew ColorDialog(); System::Windows::Forms::DialogResult result; // get chosen color result = colorChooser->ShowDialog(); if( result == System::Windows::Forms::DialogResult::Cancel ) return; // assign forecolor to result of dialog ForeColor = colorChooser->Color; } // end method textColorButton_Click backgroundColorButton_Click 方法定义如下:
36
System::Void backgroundColorButton_Click( System::Object^ sender,
System::EventArgs^ e ) { // create ColorDialog object ColorDialog ^colorChooser = gcnew ColorDialog(); Windows::Forms::DialogResult result; // show ColorDialog ang get result colorChooser->FullOpen = true; result = colorChooser->ShowDialog(); if( result == System::Windows::Forms::DialogResult::Cancel ) return; // set background color BackColor = colorChooser->Color; } // end method backgroundColorButton_Click
37
实例的运行结果如下:
38
18.3 字体控制 本节介绍定义在 System::Drawing 命名空间中的字体类型 Font
18.3 字体控制 本节介绍定义在 System::Drawing 命名空间中的字体类型 Font 的应用。Font 对象一旦创建,其属性就不能更改。如果需要不 同的字体,只能创建新 Font 对象。Font 类的常用属性如下: Bold 确定字体是否为加粗字形。true 为粗体,false 为非粗体。 FontFamily 获取 Font 的 FontFamily (对字体进行组织的一种 结构,定义了它们的类似属性) Height 确定字体高度。 Italic 确定字体是否为斜字形。true 为斜体,false 为非斜体。 Name 确定表示字体名称的 String 类型对象。 Size 确定字符的大小(float 类型),单位为字体的度量单位。
39
如果按图形 “度量单位(GraphicsUnit)” 来指定 Size 属性,表
SizeInPoints 确定字符的大小(float 类型),单位为打印机点。 Strikeout 确定字体是否为有删除线的字体。 true 为有删除 线,false 为无删除线。 Underline 确定字体是否为有下划线的字体。 true 为有下划 线,false 为无下划线。 如果按图形 “度量单位(GraphicsUnit)” 来指定 Size 属性,表 明字体大小可采取多种度量单位来指定,比如英寸或毫米等。 Font 构造函数的某些版本接受一个 GraphicsUnit 参数(指定字 号度量单位的枚举)。GraphicsUnit 枚举成员包括:Point(打印 机点1/72 英寸)、Display(1/75 英寸)、Document(1/300 英 寸)、Millimeter(毫米)、Inch(英寸)和 Pixel(设备像素)。
40
如果为构造函数提供了该参数,Size 属性就采取指定度量单位的
字号,而 SizeInPoints 属性会将该字号转换为打印机点为单位。 例如,创建一个字号为 1 的 Font ,并指定用 GraphicsUnit::Inch 来度量字体,那么Size 属性是 1,而 SizeInPoints 属性是 72。如 果构造函数不允许将 GraphicsUnit 作为成员,字号的默认度量单 位是 GraphicsUnit::Point(此时,Size 和 SizeInPoints 属性值相等)。 Font 类提供了多个版本的构造函数,这里不一一列举。 其中有许多版本都要求提供一个 String^ 类型的参数,用于提 供 “字体名称”(Font Name),指定一种系统目前支持的字体。 构造函数通常还要求提供 “字体大小”(Font Size)参数。
41
构造函数通常还要求提供 “字体样式”(Font Style),它是一
个 FontStyle 枚举成员(Bold, Italic, Regular, Strikeout 和 Underline 等)。 FontStyle 枚举成员可用 + 运算符进行组合。例如: FontStyle::Italic + FontStyle::Bold。 本节的实例1 UsingFonts 的设计目的是演示如何创建所需要的 字体,并使用新创建的字体在主窗体的客户区的指定位置显示 指定的字符串。程序中使用同一种 Font 构造函数版本创建 4 种 不同字体,创建时需要传递三个参数: ① String^ 类型的字体名称:Arial, Times New Poman, Courier New 和 Tahoma; ② float 类型的字体大小;
42
③ FontStyle 类型成员:Bold, Regular, Bold+Italic 和 Strikeout。
由于只使用这些字体分别在主窗体的客户区的指定位置上显 示相应的字符串,而无须进行任何用户的交互操作,因此无须 在主窗体添加控件。该程序的 GUI 如下:
43
显示四种不同字体的字符串的操作应在主窗体客户区的重绘 方法中实现,因此需要重新定义主窗体的 OnPaint 方法。
编程要点 显示四种不同字体的字符串的操作应在主窗体客户区的重绘 方法中实现,因此需要重新定义主窗体的 OnPaint 方法。 virtual void OnPaint( PaintEventArgs^ paintEvent ) override { __super::OnPaint( paintEvent ); // call base OnPaint method Graphics ^graphicsObject = paintEvent->Graphics; SolidBrush ^brush = gcnew SolidBrush( Color::DarkBlue ); // arial, 12 pt regular FontStyle style = FontStyle::Bold; Drawing::Font ^arial = gcnew Drawing::Font( L"Arial", 12, style ); … graphicsObject->DrawString( String::Concat( arial->Name, L" 12 point bold.“ ), arial, brush, 10, 10 ); } // end method OnPaint
44
运行结果如下:
45
创建一个 Font 对象时,可以指定精确的字体 “度量”(或“属
性”)信息,比如高度、下沉(字符相对于基线的下降幅度)、 上升(字符相对于基线的上升幅度)以及行距(一行的下沉和 下一行的上升之间的距离)。 FontFamily 类型为一组相关字体定义了公共属性。利用该类型 提供的几个方法,可以判断由特定字体家族的成员共享的字体 属性。这些方法的功能如下: GetCellAscent 获取用字体度量单位表示的字体下沉量。 GetCellDescent 获取用字体度量单位表示的字体上升量。 GetEmHeight 获取用字体度量单位表示的字体高度。 GetLineSpacing 获取用字体度量单位表示的字体的行距。
46
本节的实例2 UsingfontMetrics 中创建了两种字体。一种是 12
象素点的普通风格 Arial 字体,另一种是 14 象素点的斜体风格 Microsoft Scans Serif 字体。并通过字体类型 Font 的 FontFamily 属 性来获取相应字体的 FontFamily 对象。然后再使用 FontFamily 类 型提供的获取字体精细结构数据的方法,分别获取所创建字体 的上升、下沉、高度和行距值,并将所获取的每种字体的一组 精细结构数据显示在主窗体的客户区中。 与实例1 的相同,程序只使用所创建的字体分别在主窗体的 客户区的指定位置上显示相应的字体精细结构数据信息,而无 须进行任何用户的交互操作,因此无须在主窗体添加控件。
47
实例2 的运行结果如下:
48
18.4 线、矩形和椭圆的绘制 本节介绍 Graphics 类型提供的绘制线、矩形和椭圆矢量图形
18.4 线、矩形和椭圆的绘制 本节介绍 Graphics 类型提供的绘制线、矩形和椭圆矢量图形 的方法。实现这些绘制操作的方法一般都有多个重载版本,以 适应不同的操作需要。关于这些方法的详细信息请查阅 MSDN 相关文档,这里只给出常用的方法原型: DrawLine( Pen ^p, int x1, int y1, int x2, int y2 ); 画一条从坐标点 (x1, y1) 到 (x2, y2) 的直线。画笔 p 决定了所画直 线的颜色、样式和宽度。 DrawRectangle( Pen ^p, int x, int y, int width, int height ); 画一个左上角坐标为 (x, y),宽为 width,高为 height 的无填充图 形的矩形。画笔 p 决定矩形的颜色、样式和边框线的线宽。
49
画一个左上角坐标为 (x, y),宽为 width,高为 height 的有填充图 形的矩形。画刷 b 决定矩形内部的填充图案。
FillRectangle( Brush ^b, int x, int y, int width, int height ); 画一个左上角坐标为 (x, y),宽为 width,高为 height 的有填充图 形的矩形。画刷 b 决定矩形内部的填充图案。 DrawEllipse( Pen ^p, int x, int y, int width, int height ); 画一个无填充图形的椭圆。该椭圆的外接矩形由后四个参数确 定:左上角坐标为 (x, y),宽为 width,高为 height 。画笔 p 决定 椭圆轮廓线的颜色、样式和线宽。 FillEllipse( Brush ^b, int x, int y, int width, int height ); 画一个有填充图形的椭圆。该椭圆的外接矩形由后四个参数确 定:左上角坐标为 (x, y),宽为 width,高为 height 。画刷 b 决定 椭圆内部的填充图案。
50
本节的实例 LineRectangleOvel 演示了上述图形绘制方法的调
用。这些调用是在主窗体客户区重绘方法 OnPaint 中实现的。该 程序运行时也无须进行用户的交互操作,所以主窗体中也无须 添加任何控件。 编程要点 ⑴ 绘制无填充的图形的一般步骤: ① 创建用于绘制图形轮廓线的画笔对象。例如: Pen ^pen = gcnew Pen( Color::AliceBlue ); ② 使用新创建的画笔对象绘制需要的图形。例如: graphicsObject->DrawLine( pen, 90, 30, 110, 40 ); graphicsObject->DrawRectangle( pen, 110, 40, 150, 90 );
51
① 创建用于绘制填充图形的画刷对象。例如:
graphicsObject->DrawEllipse( pen, 280, 30, 100, 50 ); ⑵ 绘制有填充的图形的一般步骤: ① 创建用于绘制填充图形的画刷对象。例如: SolidBrush ^ brush = gcnew SolidBrush( Color::Blue ); ② 使用新创建的画刷对象绘制需要的图形。例如: graphicsObject->FillRectangle( brush, 90, 30, 150, 90 ); brush->Color = Color::Red; graphicsObject->FillEllipse( brush, 280, 75, 100, 50 );
52
实例的运行结果如下:
53
18.5 弧线和饼图的绘制 弧线是椭圆轮廓的一部分,而饼图是椭圆的一部分。这两种
18.5 弧线和饼图的绘制 弧线是椭圆轮廓的一部分,而饼图是椭圆的一部分。这两种 图形都是从 “起始角” 开始,并旋转指定度数(称为 “仰角”)。 如果顺时针方向旋转,则需要指定正度数;若逆时针旋转,则 需要指定负度数。Graphics 类提供了绘制弧线和饼图的多个版本 的方法,以满足多种调用参数的需要,常用的方法如下: DrawArc( Pen ^p, int x, int y, int width, int height, int startAngle, int sweepAngle ); 从起始角 startAngle (单位:度),旋转 sweepAngle 度,绘制椭 圆的部分弧线。该椭圆的约束矩形的左上角坐标为 (x, y),宽为 width,高为 height。 画笔对象 p 决定弧线的颜色、样式和线宽。
54
从起始角 startAngle (以度为单位),旋转 sweepAngle 度,绘制
DrawPie( Pen ^p, int x, int y, int width, int height, int startAngle, int sweepAngle ); 从起始角 startAngle (以度为单位),旋转 sweepAngle 度,绘制 椭圆的部分扇形。该椭圆的约束矩形的左上角坐标为 (x, y),宽 为 width,高为 height。 画笔对象 p 决定扇形轮廓线的颜色、样 式和线宽。 FillPie( Brush ^p, int x, int y, int width, int height, 功能类似于 DrawPie ,只是对扇形进行图形填充。 画刷对象 b 决定扇形内部的填充图案。
55
关于绘制弧线和饼图方法的其他版本的详细信息请查阅 MSDN 文档的相关内容。
本节的实例 DrawArcs 中演示了上述图形绘制方法的调用。这 些调用是在主窗体客户区重绘方法 OnPaint 中实现的。该程序运 行时也无须进行用户的交互操作,所以主窗体中也无须添加任 何控件。 编程要点 ⑴ 绘制弧线和无填充的饼图的一般步骤: ① 创建用于绘制弧线或扇形轮廓线的画笔对象。例如: SolidBrush ^brush1 = gcnew SolidBrush( Color::Firebrick ); Pen ^pen1 = gcnew Pen( brush1, 1 );
56
② 使用新创建的画笔对象绘制弧线或扇形。例如:
Rectangle rectangle = Rectangle( 15, 35, 80, 80 ); graphicsObject->DrawArc( pen1, rectangle, 0, 150 ); graphicsObject->DrawPie( pen1, rectangle, 0, 150 ); ⑵ 绘制有填充的扇形的一般步骤: ① 创建用于绘制填充图形的画刷对象。例如: SolidBrush ^brush2 = gcnew SolidBrush( Color::DarkBlue ); ② 使用新创建的画刷对象绘制需要的图形。例如: graphicsObject->FillPie( pen1, rectangle, 0, 150 );
57
实例的运行结果如下:
58
18.6 多边形和折线的绘制 Graphics 类型提供的折线和多边形绘制方法也有多种版本。这 两种方法的常用版本的原型如下所示:
18.6 多边形和折线的绘制 Graphics 类型提供的折线和多边形绘制方法也有多种版本。这 两种方法的常用版本的原型如下所示: DrawLines( Pen ^p, array<Point> ^points ); 依据第二个参数 Point 坐标数组 points 中元素给出的点坐标,绘 制指定的折线。每两个顺序点之间的连接直线确定了折线的一 段构成线段。如果最后一个点不与第一个点重合,折线的形状 就不是闭合的。画笔对象 p 决定折线的颜色、样式和线宽。 DrawPolygon( Pen ^p, array<Point> ^points ); 制指定的多边形。每两个顺序点之间的连接直线确定了多边形
59
的一条边。无论最后一个点与第一个点是否重合,所确定的多 边形总是闭合的。画笔对象 p 决定多边形轮廓线的颜色、样式 和线宽。
FillPolygon( Brush ^b, array<Point> ^points ); 依据第二个参数 Point 坐标数组 points 中元素给出的点坐标,绘 制指定的具有填充图形的多边形。每两个顺序点之间的连接直 线确定了多边形的一条边。无论最后一个点与第一个点是否重 合,所确定的多边形总是闭合的。画刷对象 b 决定多边形内部 的填充图形的颜色、样式。
60
本节的实例 DrawPolygons 演示了依据同一个 Point 数组提供的
点位置数据,分别调用 DrawLines、DrawPolygon 和 FillPolygon 方 法时,得到的不同绘制效果。为此,本例的设计要点: ① 使用 ArrayList 类型存放图形顶点位置数据元素 Point 对象。 ② 在主窗体的绘图区域中,每当鼠标左键被按下一次,就交互 产生一个点位置数据,并以 Point 类型对象存入 ArrayList 类型 数组。同时强制主窗体绘图区域中的多边形或折线图形依据 更新后的数组中的顶点位置数据重新绘制。 ③ 为了方便实现用户能以交互方式绘图、变换绘图方法和改变所绘图形的颜色,需要在主窗体客户区中添加如下控件: 一个面板(Panel)控件,提供主窗体的绘图区域;
61
一个包含三个单选按钮(RadioButton)控件的组框(Group)控件,提供变换绘图方法的操作界面;
两个按钮(Button)控件,提供清除当前数组的全部点位置数据和改变所绘图形颜色的界面。程序的 GUI 设计如下:
62
1 添加到 ArrayList 类数组的元素必须是托管类对象。因此,像 Point 这类结构对象必须 “装箱” 成托管类对象后加入数组。
编程要点: 1 添加到 ArrayList 类数组的元素必须是托管类对象。因此,像 Point 这类结构对象必须 “装箱” 成托管类对象后加入数组。 private: static ArrayList ^points = gcnew ArrayList(); … // add mouse position to vertex list points->Add(( Object^ )Point( e->X, e->Y )); 2 在面板控件确定的绘图区域中实现重绘操作是在该控件的 Paint 事件处理方法中完成。注意,不要直接调用 Paint 事件处 理方法,而应该调用面板 Panel 控件的 Invalidate 方法,从而 间接调用 Paint 事件处理方法。
63
程序中需要绘图区域中的图形被重新绘制的情况包括:鼠标 左键按下、图形种类选择、图形颜色改变和清除图形四种。
例如,在鼠标左键按下的事件处理方法: System::Void drawPanel_MouseDown( System::Object^ sender, System::Windows::Forms::MouseEventArgs^ e ) { // add mouse position to vertex list points->Add( (Object^)Point( e->X, e->Y ) ); drawPanel->Invalidate(); } // end method drawPanel_MouseDown 面板控件的 Paint 事件处理方法定义如下:
64
System::Void drawPanel_Paint(System::Object^ sender,
System::Windows::Forms::PaintEventArgs^ e) { // get graphics object for panel Graphics ^graphicsObject = e->Graphics; // if arraylist has 2 or more points, display shape if( points->Count > 1 ) // create array of points array<Point> ^pointArray = gcnew array<Point>( points->Count ); // add each point to the array for( int i = 0; i < points->Count; i++ ) pointArray[i] = *( dynamic_cast<Point^>( points[i] ) );
65
if( polygonOption->Checked )
graphicsObject->DrawPolygon( pen, pointArray ); // draw polygon else if( lineOption->Checked ) graphicsObject->DrawLines( pen, pointArray ); // draw line else if( filledPolygonOption->Checked ) graphicsObject->FillPolygon( brush, pointArray ); // draw filled polygon } // end if } // end method drawPanel_Paint
66
其中,调用绘制折线、多边形的方法中传递确定图形的点位置数据需要通过使用 array<Point> ^ 数组类型的参数 pointArray。而程序中动态存储 Point 对象的 ArrayList 类型对象 points 不能直接作为调用绘图方法的实参。因此,必须进行如下的转换: array<Point> ^pointArray = gcnew array<Point>( points->Count ); // add each point to the array for( int i = 0; i < points->Count; i++ ) pointArray[i] = *( dynamic_cast<Point^>( points[i] ) );
67
实例的运行结果如下:
68
18.7 高级图形功能 .NET 还提供了许多其他图形功能。例如,在画刷 Brush 的派
18.7 高级图形功能 .NET 还提供了许多其他图形功能。例如,在画刷 Brush 的派 生类层次中,除 SolidBrush 外,还有其他派生类,如定义名域空 间 System::Drawing::Drawing2D 中的 HatchBrush、TextureBrush 以及 LinearGradientBrush。 HatchBrush 定义了具有阴影线填充风格的画刷类型。该类型的构 造函数有两个版本,它们的原型如下: HatchBrush( HatchStyle, Color ); 创建一个具有指定阴影线填充风格的画刷类型对象。第一参数 指定了 HatchStyle 枚举类型的阴影线风格,该枚举类型中定义了 55 种阴影线风格;第二参数指定了画刷的阴影线颜色。
69
创建一个具有指定阴影线填充风格的画刷类型对象。第一参数 指定了 HatchStyle 枚举类型的阴影线风格,该枚举类型中定义了
HatchBrush( HatchStyle, Color, Color ); 创建一个具有指定阴影线填充风格的画刷类型对象。第一参数 指定了 HatchStyle 枚举类型的阴影线风格,该枚举类型中定义了 55 种阴影线风格;第二和第三参数指定了画刷的阴影线颜色和 背景颜色。 LinearGradientBrush 定义了具有颜色线性变化填充风格的画刷类 型。该类型的构造函数有 8 个版本,其中最常用的版本如下: LinearGradientBrush( Rectangle, Color, Color, LinearGradientMode ); 创建具有颜色线性变化填充的画刷类型对象。 第一参数指定了画刷颜色变化的矩形限定区域; 第二、三参数指定了填充的起始颜色和结束颜色;
70
第四参数指定了 LinearGradientMode 枚举类型颜色变化斜率。
TextureBrush 定义了具有图形纹理填充风格的画刷类型。该类型 的构造函数也有 8 个不同版本,其中最常用的版本如下: TextureBrush( Image ); 创建使用参数 Image 类型对象指定的图形为填充纹理的画刷。 成员名称 含义 Horizontal 指定从左到右的渐变 Vertical 指定从上到下的渐变 ForwardDiagonal 指定从左上到右下的渐变 BackwardDiagonal 指定从右上到左下的渐变
71
本节的实例1 DrawShapes 演示了使用粗实线和粗虚线绘制直
线段、矩形和饼形,以及使用阴影线填充、双色渐变填充和图 形纹理填充等功能。这些功能的实现均在主窗体客户区的重绘 方法 OnPaint 中完成。因此,主窗体类型需要添加 OnPaint 方法 的重新定义版本。而无须为主窗体添加用于交互操作的控件。 编程要点: 在程序代码中,创建纹理填充的画刷(TextureBrush 类对象) 所使用的 Image 类对象是一个位图 Bitmap 类对象。 创建该 Bitmap 类型对象的过程是:先创建一个空白位图,再 获取该位图的 Graphics 对象,然后通过该 Graphics 对象在位图中 绘制需要的图形。实现代码如下:
72
// bitmap texture Bitmap ^textureBitmap = gcnew Bitmap(10, 10); // get bitmap graphics Graphics ^graphicsObject2 = Graphics::FromImage( textureBitmap ); // brush and pen used throughout program SolidBrush ^solidColorBrush = gcnew SolidBrush( Color::Red ); Pen ^coloredPen = gcnew Pen( solidColorBrush ); // fill textureBitmap with yellow solidColorBrush->Color = Color::Yellow; graphicsObject2->FillRectangle( solidColorBrush, 0, 0, 10, 10 ); // draw small black rectangle in textureBitmap
73
coloredPen->Color = Color::Black;
graphicsObject2->DrawRectangle( coloredPen, 1, 1, 6, 6 ); // draw small blue rectangle in textureBitmap solidColorBrush->Color = Color::Blue; graphicsObject2->FillRectangle( solidColorBrush, 1, 1, 3, 3 ); // draw small red square in textureBitmap solidColorBrush->Color = Color::Red; graphicsObject2->FillRectangle( solidColorBrush, 4, 4, 3, 3 ); // create textured brush and display textured rectangle TextureBrush ^textureBrush = gcnew TextureBrush( textureBitmap ); graphicsObject->FillRectangle( textureBrush, 155, 30, 75, 100 );
74
该实例的运行结果如下:
75
GDI+ 提供的另一个非常有用的图形功能是绘制图形的 “图形 路径”。图形路径描述的是一个复杂形状的图形,它可以由一系
列直线、曲线、封闭图形构成。 图形路径是使用定义在 System::Drawing::Drawing2D 命名空间中 的 GraphicsPath 类型创建的一个对象。 GraphicsPath 类型是一个不允许继承的类型。该类型的构造函 数有多个版本,允许创建由指定的图形和填充模式进行初始化 的图形路径,也允许创建无指定图形和缺省填充模式(Alternate )的图形路径。所谓填充方式是用枚举类型 FillMode 描述的。该 枚举有两个成员: Alternate 表示交替填充模式,Winding 表示环 绕填充模式。
76
的方法,例如, AddLine、AddArc、AddBezier、AddCurve、AddPie、
GraphicsPath 类型提供了向一个图形路径中添加各种所需图形 的方法,例如, AddLine、AddArc、AddBezier、AddCurve、AddPie、 AddPath 等等。 方法 CloseFigure 的功能是闭合当前图形路径并开始新的图形 路径。如果当前图形路径包含一系列相互连接的直线和曲线, 该方法通过连接一条从终结点到起始点的直线,闭合该环回。 方法 StartFigure 的功能是不闭合当前图形路径,即开始一个 新图形路径。后面添加到该路径的所有点都被添加到此新图形 路径中。
77
本节的实例2 DrawStars 演示了使用图形路径绘制五角星图形 的操作过程。同时演示了如何实现指定图形的平移和旋转。这
些功能的实现均在主窗体客户区的重绘方法 OnPaint 中完成。因 此,主窗体类型需要添加 OnPaint 方法的重新定义版本。而无须 为主窗体添加用于交互操作的控件。 编程要点: ① 使用 GraphicsPath 创建五角星图形的代码如下: // X and Y points of the path array<int> ^xPoints = gcnew array<int>{ 55, 67, 109, 73, 83, 55, 27, 37, 1, 43 }; array<int> ^yPoints = gcnew array<int>{ 0, 36, 36, 54, 96, 72, 96, 54, 36, 36 };
78
② 实现图形的平移和旋转的操作是调用 Graphics 类型的方法
// create graphics path for star GraphicsPath ^star = gcnew GraphicsPath(); // create star from series of points for( int i = 0; i <= 8; i += 2 ) star->AddLine( xPoints[i], yPoints[i], xPoints[i+1], yPoints[i+1] ); // close the shape star->CloseFigure(); ② 实现图形的平移和旋转的操作是调用 Graphics 类型的方法 TranslateTransform 和 RotateTransform 完成的。方法原型如下: TranslateTransform( float, float ); 通过使此 Graphics 的变换矩阵左乘参数指定的 X 和 Y 方向平 移量来更改坐标系统的原点。
79
RotateTransform( float );
将参数指定的旋转(角度)应用于此 Graphics 的变换矩阵。程序实现图形平移和旋转的代码如下: // translate the origin to (150, 150) graphicsObject->TranslateTransform( 150, 150 ); // rotate the origin and draw stars in random colors for( int i = 1; i <= 18; i++ ) { graphicsObject->RotateTransform( 20 ); brush->Color = Color::FromArgb( random->Next( 200, 256 ), random->Next( 256 ), random->Next( 256 ), random->Next( 256 )); graphicsObject->FillPath( brush, star ); } // end for
80
实例的运行结果如下:
81
18.8 多媒体概述 Visual C++ .NET 提供了许多方便的途径向程序中添加图象和动
18.8 多媒体概述 Visual C++ .NET 提供了许多方便的途径向程序中添加图象和动 画。几十年前,人们开始使用计算机时,主要用它执行数学计 算。随着技术的进步,人们开始认识到计算机数据处理能力的 重要性。多媒体编程是一个充满趣味和创新的领域,但也带来 了大量挑战。 今天,廉价的速度极快的处理器使基于多媒体的应用程序得 以迅速普及。随着多媒体市场的飞速发展,用户可获得更快的 处理器、更充足的内存以及更大的网络带宽来支持多媒体应用 程序。这使计算机和通信业尝到了甜头,它们积极提供硬件、 软件和相关服务,确保了多媒体技术的良性发展。
82
本章的剩余部分将通过 5 个实例介绍如何使用和处理图形以及多媒体特性及功能。
83
18.9 加载、显示和缩放图片 本节的实例 DisplayLogowin 演示了如何添加一个 Image 类型
18.9 加载、显示和缩放图片 本节的实例 DisplayLogowin 演示了如何添加一个 Image 类型 (在 System::Drawing 命名空间中定义) 对象。该对象可以加载 多种类型的指定图象,并提供了在窗体客户区或控件表面的指 定位置,以希望的高度和宽度显示已经加载的图象。 本例设计为用户能通过交互界面任意指定一个被显示的图象 的宽和高,并将指定了宽和高的图象显示在主窗体客户区的指 定位置。为此,需要在主窗体中添加如下控件: ① 两个文本框控件,用于接收用户指定的图象显示的宽和高; ② 一个按钮控件,用于发出按指定的宽和高重新显示特定图象 的事件。
84
实例窗体的 GUI 设计如下:
85
1 由于本程序的 Image 对象所包含的图象内容在程序运行中无 须变化,因此可在 Image 对象的定义的同时,完成指定图象
编程要点: 1 由于本程序的 Image 对象所包含的图象内容在程序运行中无 须变化,因此可在 Image 对象的定义的同时,完成指定图象 的加载。代码如下: private: static Image ^image=Image::FromFile( L"images\\Logowin.gif" ); 2 Image 对象中已经加载图象的显示操作应在窗体客户区的重画操作中进行,以保持图象显示的持续性和一致性。因此,需要为主窗体重新定义 OnPaint 方法。代码如下: virtual void OnPaint( PaintEventArgs^ paintEvent ) override { …
86
3 为了在程序开始运行时能按图象原有宽和高进行显示,应为 主窗体添加 Load 事件处理方法,在该方法中完成上述操作。
graphicsObject->DrawImage( image, 5, 5, imageShowWidth, imageShowHeight ); } 3 为了在程序开始运行时能按图象原有宽和高进行显示,应为 主窗体添加 Load 事件处理方法,在该方法中完成上述操作。 System::Void Form1_Load( System::Object^ sender, System::EventArgs^ e) { imageShowWidth = image->Width; imageShowHeight = image->Height; widthTextBox->Text = image->Width.ToString(); heightTextBox->Text = image->Height.ToString();
87
4 添加按钮 Set 的单击事件处理方法 setButton_Click 用于实现按 照用户输入的图象尺寸重新显示图象的操作。
System::Void setButton_Click( System::Object^ sender, System::EventArgs^ e ) { try // get user input imageShowWidth = Convert::ToInt32( widthTextBox->Text ); imageShowHeight = Convert::ToInt32( heightTextBox->Text ); } // end try catch( FormatException ^formatException ) MessageBox::Show( formatException->Message, L"Error", MessageBoxButtons::OK, MessageBoxIcon::Error ); return; } // end catch
88
catch( OverflowException ^overflowException )
{ MessageBox::Show( overflowException->Message, L"Error", MessageBoxButtons::OK, MessageBoxIcon::Error ); return; } // end catch // if dimensions specified are too large display problem if( imageShowWidth > 375 || imageShowHeight > 225 ) MessageBox::Show( String::Concat( L"Height or Width too large\n", L"Maximum Width equal or less then 375\n", L"Maximum Height equal or less then 225" ), L"Problem", MessageBoxButtons::OK, MessageBoxIcon::Question ); } // end if Invalidate(); } // end method setButton_Click
89
实例的运行结果如下:
91
18.10 将一系列图象变成动画 在本节的实例 LogoAnimator 中演示了通过一组相关图象按时
将一系列图象变成动画 在本节的实例 LogoAnimator 中演示了通过一组相关图象按时 间间隔轮换显示而形成的动画。主要支持技术仍然是 Image 类 型提供的属性和方法。为此,主窗体需要进行如下添加: ① 一个 Arraylist 类型对象,用于存放一组加载了相应图象的 Image 类型对象。 ② 一个图片框 PictureBox 类型控件,用于显示动画图象。 ③ 一个定时器 Timer 组件,用于产生显示动画序列中两幅相邻画面之间的时间间隔。 本实例主窗体的 GUI 设计如下:
92
编程要点: ① 用于存放动画的画面序列的 ArrayList 类型对象 images 应该在主窗体 Form1 的构造函数中被初始化。 images 的定义和初始化代码如下:
93
private: static ArrayList ^images = gcnew ArrayList();
… Form1( void ) { InitializeComponent(); for(int i = 0; i < 30; i++) images->Add( Image::FromFile( String::Concat( L"images\\logowin", i.ToString(), L".gif“ ) ) ); // load first image logoPictureBox->Image = dynamic_cast<Image^>( images[0] ); // set PictureBox to be the same size as Image logoPictureBox->Size = logoPictureBox->Image->Size; }
94
② Timer 组件类型也定义在 System::Windows::Forms 命名空间中,
它可以按照设定的时间间隔发出 “嘀答” (Tick)事件。在 Tick 事件处理方法中可以安排需要按排时间间隔重复的操作。 本例中设计每 50 毫秒就显示一幅动画画面,因此需要将定时 器 timer 的时间间隔 Interval 属性设定为 50 毫秒(缺省值为 100 毫秒),并添加 Tick 事件处理方法。该方法的定义代码 如下: private: static int count = -1; … System::Void timer_Tick( System::Object^ sender, System::EventArgs^ e )
95
{ // increment counter count = ( count + 1 ) % 30; // laod next image logoPictureBox->Image = dynamic_cast<Image^>( images[count] ); } // end method timer_Tick
96
实例的运行结果如下:
97
18.11 碰撞检测和区域无效 本节的实例 ChessGame 是一个能模拟国际象棋的棋盘布局操
碰撞检测和区域无效 本节的实例 ChessGame 是一个能模拟国际象棋的棋盘布局操 作以及能实现移动棋子操作的程序。其中涉及 “二维碰撞检测” 和 “区域无效” 等技术。 所谓二维碰撞检测实际是检测两个形状是否重合。本例演示 了最简单的碰撞检测方法,也就是判断一个长方形(棋子图象) 中是否包含某个点(鼠标单击的位置)。 所谓区域无效就是只刷新屏幕上被指定为无效区域的部分, 以增强绘图的性能和效果。本例演示的区域无效发生在棋子移 动操作过程中,只刷新棋子移出和移入的棋盘格区域。
98
程序设计 实现上述需求,需要从界面、结构和资源进行如下设计: 1 界面设计 程序的主要交互界面是棋盘画面和棋子图形。 其中棋盘是一个 8 行 8 列黑白格相间的矩形画面; 棋子图形有 32 个,每个图形对应红黑双方棋子中的一个,其 大小与棋盘画面中的棋盘格相同。 程序执行过程中,棋盘画面是静止的。而棋子图形是可以移 动的,但移动范围是在棋盘画面之内,并且每次移动必须是 从一个棋盘格到另一个棋盘格。
99
不难看出,棋盘画面和棋子图形应该分别绘制在两个画面层
中。我们可以将棋盘画面绘制在主窗体的客户区中,而将棋 子图形绘制一个图片框控件。 为此,需要在主窗体中添加一个图片框控件,将该控件的大 小设置成与棋盘画面相同,并且位置重合。同时,在该棋子 图片框的右边还需要添加另一个图片框,用于绘制被吃掉的 棋子图形。 另外,为了在程序在运行中,允许用户重新布局棋盘,需要 为主窗体添加一个菜单,为用户提供交互界面。 程序的 GUI 设计如下:
101
2 结构设计 程序的结构设计应基于功能实现的合理性、程序的可维护性 和可扩展性。显然,在本例中,设计需求的棋盘布局功能和 棋子功能均应在主窗体类型实现比较方便、合理。其中棋子 应定义为一个独立的类型更有利于在程序中多个棋子对象的 创建、定位、棋子图形的绘制的代码重用,可维护性和可扩 展性的实现。 ⑴ 添加棋子类型 ChessPiece 描述棋子的属性的数据成员有四个,分别用于指定: ① 棋子种类:红方或黑方的 KING、QUEEN、BISHOP、 KNIGHT、ROOK 和 PAWN。
102
② 棋子图形:对应棋子种类的图形,祥见资源设计。
③ 棋子位置:描述棋子在棋盘中位置的矩形区域。 ④ 棋子颜色:描述棋子的颜色。 对外提供的操作方法应该包括: ① 构造函数:构造一个指定种类、图形和位置的棋子。 ② 图形绘制函数:在当前位置绘制棋子图形。 ③ 获取位置函数:获取描述棋子位置的矩形区域。 ④ 设置位置函数:设置描述棋子位置的矩形区域坐标。 ⑤ 颜色只读属性:获取棋子的颜色值。
103
⑵ 主窗体类型 Form1 添加数据成员和方法 除了控件对象外,Form1 中还需要添加的数据成员包括: ① 棋盘格图形数组:静态成员,用于存放绘制棋盘的棋 盘格图形。 ② 棋盘格索引数组:静态成员,用于存放绘制棋盘的棋 盘格图形的索引。 ③ 棋子数组:静态成员,用于存放当前棋盘上的棋子类 型对象。 ④ 被吃棋子数组:静态成员,用于存放当前棋盘上被吃 掉的棋子类型对象。
104
⑤ 选中棋子索引:静态成员,用于指示当前棋盘上被选
中的棋子在棋子数组中的索引。 ⑥ 被吃掉棋子索引:静态成员,用于指示当前棋盘上被 吃掉的棋子在棋子数组中的索引。 ⑦ 棋盘格尺寸:静态常量成员,用于指定正方形棋盘格 的边长(单位:象素)
105
在 Form1 中需要添加的方法都是围绕着实现棋盘布局和棋
子移动功能考虑的。其中棋子移动功能应设计成由用户使 用鼠标通过交互操作实现,具体的交互操作如下: 鼠标在要移动的棋子处按下鼠标左键,选中棋子; 保持鼠标左键按下,移动鼠标,将选中棋子移动到目标棋 盘格; 释放鼠标左键,如果目标棋盘格是空棋盘格,则选中棋子 被放置在该棋盘格中;如果目标棋盘格不是空棋盘格且不 是本方棋子,则该棋盘格中的原有棋子被 “吃掉” ,即从棋 子数组中删去,同时选中棋子被放置在该棋盘格中。 为此,在 Form1 中需要添加的方法包括:
106
⑴ 棋盘布局复位方法:重新绘制棋盘画面,初始化棋子
在棋盘上的放置。 ⑵ 重绘方法 OnPaint 的重定义版本:重新绘制棋盘画面。 ⑶ 棋子图片框控件的 Paint 事件的处理方法:绘制棋子的 图形。该方法会在所属窗口重绘操作时被自动调用。 ⑷ 被吃棋子图片框控件的 Paint 事件的处理方法:绘制被 吃棋子的图形。 ⑸ 窗体装载 Load 事件的处理方法:从磁盘装载棋盘格图 形,并将相应的图形对象添加到棋盘格图形数组;调 用棋盘布局复位方法;使主窗体客户区无效,从而导 致客户区重绘方法和图片框控件的 Paint 事件的处理方 法被调用。
107
⑹ 获取指定棋子对象方法:依据参数指定的索引,返回
棋子数组中相应的棋子对象。 ⑺ 检测被选中棋子索引方法:依据当前的光标位置检测 是否有棋子被选中。如果有,则返回该棋子在棋子数 组中的索引;否则返回 -1。 ⑻ 图片框控件的 MouseDown 事件处理方法:依据鼠标左 键按下时的光标位置调用检测被选中棋子索引方法, 并将返回的索引值赋给被选中棋子索引。
108
⑼ 图片框控件的 MouseMove 事件处理方法:如果被选中
棋子索引值大于 -1(有棋子被选中),则依据鼠标移 动中的光标位置修改被选中棋子的位置坐标,并使被 选中棋子周围 16 个棋盘格的区域无效,从而导致该 区域内的画面被重新绘制。 ⑽ 图片框控件的 MouseUp 事件处理方法:如果被选中棋 子索引值大于 -1(有棋子被选中),则依据鼠标左键 释放时的光标位置找到最靠近的棋盘格位置,并确保 该位置坐标合法(棋盘画面内合法棋盘格位置)。
109
然后依据修正后的鼠标左键释放时的光标位置检测被
选中棋子的索引,将选中棋子的位置设置为鼠标左键 释放时的光标位置。如果该位置原来有棋子,则原有 的棋子被吃掉,被吃掉的棋子将绘制到右侧的相应位 置。最后,使选中棋子的区域无效,导致选中棋子的 图形被重绘。 ⑾ 菜单项 new 选中事件的处理方法:调用棋盘布局复位 方法;使主窗体客户区无效,从而导致客户区重绘方 法和图片框控件的 Paint 事件的处理方法被调用。
110
3 资源设计 除去在界面设计中已经添加的界面资源外,还需要为实现棋盘画面和棋子图形添加相应的图象文件。图象文件的格式可以根据程序的需求选择,本例中选择使用轻便网络图象文件格式 .png。 由于棋盘画面是由 64 个棋盘格组成的,棋盘格的大小一致, 颜色有四种(两个浅色,两个深色),因此只需要添加四个 棋盘格图象文件:lightTile1.png、lightTile2.png、darkTile1.png 和 darkTile2.png。它们所包含的图象如下:
111
棋子分为黑红两种,每种包含 1 个 KING,1 个 QUEEN,2 个
BISHOP,2 个 KNIGHT,2 个 ROOK 和 8 个 PAWN ,6 种不同 的图形。为了便于管理和装载,可以把黑红棋子的 6 种图形 分别放在 blackPieces.png 和 whitePieces.png 图象文件中,这两 个文件中包含的图象如下:
112
棋子对象必须具有确定的种类 currentType 、图形 pieceImage
编程要点 1 棋子的构造方法 棋子对象必须具有确定的种类 currentType 、图形 pieceImage 颜色 color 和位置 targetRectangle,因此,没有无参数的缺省版 本。棋子的构造方法的代码如下: ChessPiece::ChessPiece( int type, int colorValue,int xLocation, int yLocation, Bitmap ^sourceImage ) { // set current type currentType = type; // set current color color = colorValue; // set current Location targetRectangle = Rectangle( xLocation, yLocation, 75, 75 );
113
注意,代码中双重 for 循环中语句: // obtain pieceImage from section of sourceImage
sourceImage->Clone( Rectangle( type * 75, 0, 75, 75 ), Drawing::Imaging::PixelFormat::Format32bppArgb ); for( int i = 0; i < 75; i++ ) for( int j = 0; j < 75; j++ ) { Color color = pieceImage->GetPixel( i, j ); if( color.R == 255 && color.G == 255 && color.B == 255 ) pieceImage->SetPixel( i, j, Color::FromArgb( 0, color.R, color.G, color.B )); } } // end constructor 注意,代码中双重 for 循环中语句:
114
操作是扫描指定棋子图形的每个象素,将矩形图形中描绘棋 子形状以外的象素点均重新设置为全透明象素,从而使棋子
Color color = pieceImage->GetPixel(i, j); if(color.R == 255 && color.G == 255 && color.B == 255) pieceImage->SetPixel(i, j, Color::FromArgb(0, color.R, color.G, color.B)); 操作是扫描指定棋子图形的每个象素,将矩形图形中描绘棋 子形状以外的象素点均重新设置为全透明象素,从而使棋子 图形在棋盘格中被绘制时,棋子形状以外的部分不会遮挡棋 盘格图形。 2 棋子图形的绘制方法 棋子类型本身并不包含 GDI+ 的图形类型 Graphics 对象,绘制 棋子图形所需要的 Graphics 对象是通过参数获得的。这也意 味着棋子图形可以被绘制任何窗体或控件中。代码如下:
115
方法中首先将组成棋盘画面的四个图象文件加载到棋盘格图 形数组 chessTile 中。再调用棋盘布局复位方法 ResetBoard,
void ChessPiece::Draw( Graphics ^graphicsObject ) { graphicsObject->DrawImage( pieceImage, targetRectangle ); } // end method Draw 3 Form1 的装载事件 Load 的处理方法 方法中首先将组成棋盘画面的四个图象文件加载到棋盘格图 形数组 chessTile 中。再调用棋盘布局复位方法 ResetBoard, 完成棋盘画面的构造和确定棋子放置位置。最后调用 Form1 的 Invalidate 使整个客户区无效,从而导致 OnPaint 方法被调 用,重绘客户区。操作代码如下: private: static ArrayList ^chessTile = gcnew ArrayList(); …
116
4 ResetBoard 方法包含了构造棋盘画面和确定棋子放置位置两部 分功能:
System::Void Form1_Load( System::Object^ sender, System::EventArgs^ e ) { // load chess board tiles chessTile->Add( Bitmap::FromFile( L"lightTile1.png" ) ); chessTile->Add( Bitmap::FromFile( L"lightTile2.png" ) ); chessTile->Add( Bitmap::FromFile( L"darkTile1.png" ) ); chessTile->Add( Bitmap::FromFile( L"darkTile2.png" ) ); ResetBoard(); // initialize board Invalidate(); // refresh form } // end method Form1_Load 4 ResetBoard 方法包含了构造棋盘画面和确定棋子放置位置两部 分功能:
117
构造棋盘画面功能是按照棋盘画面的构成规则,为 8 行 8 列棋盘格数组 board 的每一元素分配相应的棋盘格图形在棋盘格图形数组 chessTile 中的索引。代码如下:
private: static array<int, 2> ^board = gcnew array<int, 2>( 8, 8 ); … void ResetBoard() { Random ^random = gcnew Random(); bool light = true; int type; for( int row = 0; row <= board->GetUpperBound( 0 ); row++ ) for( int column = 0; column <= board->GetUpperBound( 1 ); column++ )
118
{ … type = random->Next( 0, 2 ); if( light ) { // set light tile board[row, column] = type; light = false; } // end if else { // set dark tile board[row, column] = type + 2; light = true; } // end else } // end inner for // account for new row tile color switch light = !light; } // end outer for }
119
确定棋子放置位置功能是按照棋子初始布局时的位置规则, 依据每个位置上棋子的种类、图形和位置描述创建一个棋子
对象,并将其存放到棋子数组 chessPieces 中。代码如下: private: static ArrayList ^chessPieces = gcnew ArrayList(); … void ResetBoard() { int current = -1; int color = -1; ChessPiece ^piece; int type;
120
// ensure whitepieces image
Bitmap ^whitePieces = dynamic_cast<Bitmap^>( Image::FromFile( L"whitePieces.png" ) ); // load blackpieces image Bitmap ^blackPieces = dynamic_cast<Bitmap^>( Image::FromFile( L"blackPieces.png" ) ); // set blackpieces draw first Bitmap ^selected = blackPieces; color = Convert::ToInt32( ChessPiece::Color::BLACK ); // traverse board row in outer loop for( int row = 0; row <= board->GetUpperBound( 0 ); row++ ) {
121
// if at bottom rows, set to black pieces images
if( row > 5 ) { selected = whitePieces; color = Convert::ToInt32( ChessPiece::Color::WHITE ); } // traverse board columns in inner loop for( int column = 0; column <= board->GetUpperBound( 1 ); column++ ) // if first or last row, organize pieces if( row == 0 || row == 7 )
122
{ switch( column) case 0: case 7: // set current piece to rook current = Convert::ToInt32( ChessPiece::Types::ROOK ); break; case 1: case 6: // set current piece to knight ChessPiece::Types::KNIGHT );
123
case 2: case 5: // set current piece to bishop current = Convert::ToInt32( ChessPiece::Types::BISHOP ); break; case 3: // set current piece to queen ChessPiece::Types::QUEEN ); case 4: // set current piece to king ChessPiece::Types::KING );
124
default: break; } // end switch // create current piece at start position piece = gcnew ChessPiece( current, color, column * TILESIZE, row * TILESIZE, selected ); // add piece to arraylist chessPieces->Add( piece ); } // end if // if second or seventh row organize pawns if( row == 1 || row == 6 ) {
125
piece = gcnew ChessPiece(
Convert::ToInt32( ChessPiece::Types::PAWN ), color, column * TILESIZE, row * TILESIZE, selected ); // add piece to arraylist chessPieces->Add( piece ); } // end if … } } // end method ResetBoard
126
该方法除了继承基类型的重绘操作外,新增的操作是按照 8 行 8 列的结构,将棋盘格数组中的棋盘格图形索引指示的棋
5 Form1 的重绘方法 OnPaint 该方法除了继承基类型的重绘操作外,新增的操作是按照 8 行 8 列的结构,将棋盘格数组中的棋盘格图形索引指示的棋 盘格图形数组中所对应的图形顺序绘制在客户区指定位置。 代码如下: virtual void OnPaint( PaintEventArgs^ paintEvent ) override { __super::OnPaint( paintEvent ); // call base OnPaint method // obtain graphics object Graphics ^graphicsObject = paintEvent->Graphics; graphicsObject->TranslateTransform( 0, 26 );
127
for( int row = 0; row <= board->GetUpperBound( 0 ); row++ )
{ for(int column = 0; column <= board->GetUpperBound( 1 ); column++ ) // draw image specified in board array graphicsObject->DrawImage( dynamic_cast<Image^>( chessTile[board[row, column]]), Point( TILESIZE * column, TILESIZE * row ) ); } // end inner for } // end outer for } // end method OnPaint
128
6 棋子图片框的事件 Paint 处理函数 pieceBox_Paint
Form1 的 Invalidate 操作还会向 Form1 中的控件发出绘制事件 Paint 导致控件重绘操作。本例中与主窗体客户区重合的 pictureBox 控件需要承担绘制棋子图形的操作。为此,需要为 该 pictureBox 控件的 Paint 事件添加处理方法,该方法的操作 是顺序绘制保存在棋子数组中的棋子的图形。代码如下: System::Void pieceBox_Paint( System::Object^ sender, System::Windows::Forms::PaintEventArgs^ e ) { for( int i = 0; i < chessPieces->Count; i++ ) GetPiece( i )->Draw( e->Graphics );
129
7 被吃棋子图片框的事件 Paint 处理函数 removedPieceBox_Paint
if( selectedIndex != -1 ) GetPiece( selectedIndex )->Draw( e->Graphics ); } // end method pieceBox_Paint 7 被吃棋子图片框的事件 Paint 处理函数 removedPieceBox_Paint 该方法的操作是顺序绘制保存在被吃棋子数组中的棋子的图 形。代码如下: System::Void removedPieceBox_Paint( System::Object^ sender, System::Windows::Forms::PaintEventArgs^ e ) { for( int i = 0; i < removedPieces->Count; i++ ) dynamic_cast<ChessPiece^>( removedPieces[i] )-> Draw( e->Graphics );
130
该方法的操作是确定鼠标左键按下时选中的棋子在棋子数组 中的索引。代码如下:
8 图片框的 MouseDown 事件处理函数 该方法的操作是确定鼠标左键按下时选中的棋子在棋子数组 中的索引。代码如下: System::Void pieceBox_MouseDown( System::Object^ sender, System::Windows::Forms::MouseEventArgs^ e ) { // determine selected piece selectedIndex = CheckBounds( Point( e->X, e->Y ), -1 ); // save original location of selected chess piece original = selectedInde != -1 ? GetPiece( selectedIndex )-> GetBounds() : Rectangle( -1, -1, 0, 0 ); } // end method pieceBox_MouseDown
131
为此,需要定义一个辅助方法 CheckBounds,以便依据鼠标左键按下时的位置坐标检测是否选中了某个棋子。代码如下:
int CheckBounds( Point point, int exclude ) { Rectangle rectangle; // current bounding rectangle for( int i = 0; i < chessPieces->Count; i++ ) // get piece rectangle rectangle = GetPiece( i )->GetBounds(); rectangle.Inflate( -14, 0 ); // check if rectangle contains point if( rectangle.Contains( point ) && i != exclude ) return i; } // end if return -1; } // end method ChekBounds
132
该方法的操作是在确保鼠标左键按下的状态下,随着鼠标的 移动而移动棋子,并即时更新棋子移过的画面。代码如下:
9 图片框的 MouseMove 事件处理函数 该方法的操作是在确保鼠标左键按下的状态下,随着鼠标的 移动而移动棋子,并即时更新棋子移过的画面。代码如下: System::Void pieceBox_MouseMove( System::Object^ sender, System::Windows::Forms::MouseEventArgs^ e ) { if( selectedIndex > -1 ) Rectangle region = Rectangle( e->X - TILESIZE * 2, e->Y - TILESIZE * 2, TILESIZE * 4, TILESIZE * 4 ); // set piece center to mouse
133
该方法的操作是确保鼠标左键被释放时,依据鼠标左键被释 放时的位置坐标,将选中并移动的棋子放置到合法的棋盘格
GetPiece( selectedIndex )->SetLocation( e->X - TILESIZE / 2, e->Y - TILESIZE / 2 ); // refresh immediate are pieceBox->Invalidate( region ); } // end if } // end method pieceBox_MouseMove 10 图片框的 MouseUp 事件处理函数 该方法的操作是确保鼠标左键被释放时,依据鼠标左键被释 放时的位置坐标,将选中并移动的棋子放置到合法的棋盘格 中。如果目标棋盘格中已经有棋子,则原有棋子被 “吃掉”, 即从棋子数组中删除。代码如下:
134
System::Void pieceBox_MouseUp( System::Object^ sender,
System::Windows::Forms::MouseEventArgs^ e ) { int remove = -1; int maxLocation = 7 * TILESIZE; // if chess piece was selected if( selectedIndex > -1 ) Point current = Point( e->X, e->Y ); Point newPoint = Point( current.X - ( current.X % TILESIZE ), current.Y - ( current.Y % TILESIZE )); // ensure that new point is within bounds of board
135
if( newPoint.X < 0 ) newPoint.X = 0; else if( newPoint.X > maxLocation ) newPoint.X = maxLocation; if( newPoint.Y < 0 ) newPoint.Y = 0; else if( newPoint.Y > maxLocation ) newPoint.Y = maxLocation; // check bounds with point, exclude selected piece remove = CheckBounds( newPoint, selectedIndex ); if( remove != -1 ) // snap piece into center of closest square GetPiece( selectedIndex )-> SetLocation( newPoint.X, newPoint.Y);
136
else if( GetPiece( selectedIndex )->ChessColor !=
GetPiece( remove )->ChessColor ) { // snap piece into center of closest square GetPiece( selectedIndex )-> SetLocation( newPoint.X, newPoint.Y); // draw the taken piece Graphics ^graphics = removedPieceBox->CreateGraphics(); int x = ( removedIndex % 4 ) * TILESIZE; int y = ( removedIndex / 4 ) * TILESIZE; GetPiece( remove )->SetLocation( x, y );
137
GetPiece( remove )->Draw( graphics );
removedpieces->Add( GetPiece( remove ) ); removedIndex ++; // remove taken piece chessPieces->RemoveAt( remove ); } else { // recover the original location of selected chess piece if( original.X != -1 ) GetPiece( selectedIndex )-> SetLocation( original.X, original.Y );
138
// deselect piece selectedIndex = -1; } // end if // refresh pieceBox to ensure artifact removal pieceBox->Invalidate(); } // end method pieceBox_MouseUp
139
11 菜单项 Game->new 的处理函数:
该方法在该菜单项时,为开始新的一局游戏进行如下操作:清除棋子数组和被吃棋子数组中的所有元素;被吃棋子的位置索引归零;棋盘和棋子位置复原;使主窗体和被吃棋子图片框的绘图区域无效,导致所有相关的画面重新绘制,满足新游戏开始的需要。代码如下: System::Void newGameItem_Click( System::Object^ sender, System::EventArgs^ e ) { chessPieces->Clear(); // clear chess array
140
该实例的运行结果如下: removedPieces->Clear(); // clear removed chess array
removedIndex = 0; ResetBoard(); // reinitialize board Invalidate(); // refresh form removedPieceBox->Invalidate(); } // end method newGameItem_Click 该实例的运行结果如下:
145
18.12 Windows Media Player 在应用程序中可以使用 Windows Media Player 控件来播放多种
格式的视频和音频文件。其中包括 MPEG 音频和视频文件、AVI 视频文件、WAVE 声音文件以及 MIDI 声音文件。这类文件可以 在 Internet 上找到,也可以用声音和图象处理软件自行制作。 Windows Media Player 控件是一个 ActiveX 控件。ActiveX 控件是 可重用的 GUI 组件。操作系统配备了大量的预安装的 ActiveX 控 件,Windows Media Player 控件只是其中之一。 要在应用程序的窗体中添加 Windows Media Player 控件,首先 需要把它添加到工具箱中。具体添加方法如下图所示:
146
1 在工具箱中按鼠标右键弹出的菜单中选择 “Choose Items…” 。
在弹出的选项卡的 [COM Components] ( COM 组件)中进行如 下选择:
147
选择确定后,便会在工具箱中增加了相应的控件图标,用于
向程序添加 Windows Media Player 控件。
148
本节的实例 MyMediaPlayer 中演示了如何在应用程序中添加和
使用媒体播放器 Windows Media Player 和打开文件对话框 Open File Dialog。为此,程序还添加了一个菜单,用于生成打开媒体 文件、退出应用程序和显示关于信息。程序的 GUI 设计如下:
150
该方法打开 openMediaPlayerDialog 对话框,从磁盘上查询需要播放的媒体文件,并播放选中的媒体文件。代码如下:
编程要点 1 添加菜单项 Open 的选中事件处理方法 该方法打开 openMediaPlayerDialog 对话框,从磁盘上查询需要播放的媒体文件,并播放选中的媒体文件。代码如下: System::Void openItem_Click( System::Object^ sender, System::EventArgs^ e ) { System::Windows::Forms::DialogResult result = openMediaFileDialog->ShowDialog(); if( result == System::Windows::Forms::DialogResult::Cancel ) return; player->FileName = openMediaFileDialog->FileName;
151
该方法的操作是退出程序的运行。代码如下:
// adjust the size of the Media Player control and // the Form according to the size of the image player->Size.Width = player->ImageSourceWidth; player->Size.Height = player->ImageSourceHeight; this->Size.Width = player->Size.Width + 20; this->Size.Height = player->Size.Height + 60; } // end method openItem_Click 2 添加菜单项 Exit 的选中事件处理方法 该方法的操作是退出程序的运行。代码如下: System::Void exitItem_Click(System::Object^ sender, System::EventArgs^ e) { Application::Exit(); } // end method exitItem_Click 该实例的运行结果如下:
152
该方法的操作是通过信息对话框 AboutBox 显示相关信息。代 码如下:
System::Void aboutMessageItem_Click(System::Object^ sender, System::EventArgs^ e) { player->AboutBox(); } // end method aboutMessageItem_Click 该实例的运行结果如下:
155
18.13 Microsoft Agent Microsoft Agent(微软助手)是为各类应用程序添加 “交互式动
并能响应用户的指示。例如,微软在 Word、Excel 和 PowerPoint 中都采用了 Microsoft Agent 技术。在这些程序中,一些虚拟助手 可以为用户解答问题,并帮助他们理解应用程序的功能。 Microsoft Agent 控件允许用户访问 4 个预设角色: Genie(妖 怪)、Merlin(巫师)、Peedy(鹦鹉)和 Robby(机器人)。每 个角色都有一套独特的动作,程序员可用它们来讲解程序的特 点和特定功能。
156
使用 Microsoft Agent,用户可通过人类最自然的沟通方式 —— 语音,与各类应用程序进行交互。当用户对着麦克风讲话时,控
件会使用一个 “语音识别引擎”,将来自麦克风的语音输入转换成 计算机能理解的语言。Microsoft Agent 控件还使用一个 “文字 - 语 音转换” 引擎,使动画角色通过说话来响应用户操作。引擎也能 将输入的文字转换成声音,并通过耳机或音箱播放出来。 程序员甚至能使用 Microsoft Agent Character Editor 和 Microsoft Linguistic Sound Editing Tool 创建自己的动画角色。 本节实例中要使用的 Microsoft Agent 控件的基本功能以及上述 有关 Microsoft Agent 的知识和工具都可以从微软相关的网页上下载 得到。
157
本节的实例 MicrosoftAgent 演示了如何利用 Microsoft Agent 控件
构建一个简单程序。该程序包含两个下拉列表,用户可从中选 择不同的 Microsoft Agent 角色和角色动画。所选的角色会自动出 现,并表演指定的动画。如果你的计算机安装了语音识别和语 音合成以及相应的语音设备,则应用程序使用它们来控制角色 的动画和语音:用户可按 Scroll Lock 键,然后对着麦克风说出动 画名称(英语),从而指示角色要表演相应的动画。 该实例还允许用户说出一个角色的名字(英语),从而切换 到一个新角色。另外,程序还创建了名为 MoveToMouse 的自定 义命令:当用户输出该命令名(英文)时,角色会移动到当前 光标位置附近。
158
为了能将 Microsoft Agent 控件添加到应用程序中,首先应该将
Microsoft Agent 控件 [Microsoft Agent Control 2.0](该组件位于 C:\Windows\msagent\agentctl.dll 中)添加到工具箱中: 添加后的工具箱中会出现 Microsoft Agent 控件的图标:
159
在应用程序的主窗体中添加 Microsoft Agent 控件后,该程序的
GUI 界面被设计如下:
160
界面中除 Microsoft Agent 控件 mainAgent 外,其他控件如下:
① 处于下部的 characterGroup 组框中包括文本框 locationTextBox (用于输入 Microsoft Agent 角色的磁盘路径名)和组合下拉框 characterCombo(用于选择 Microsoft Agent 角色)。
161
② 处于右边中间的组合下拉框 actionsCombo 用于选择 Microsoft
Agent 角色各种动作。 ③ 处于右边上部的按钮 speakButton 用于发出使 Microsoft Agent 角色发出指定语音信息的事件。 ④ 处于左边上部的文本框 speechTextBox 用于输入要求 Microsoft Agent 角色发出语音信息文本。 编程要点 1 添加文本框 locationTextBox 的回车键按下事件处理方法 locationTextBox_KeyDown 方法的操作是依据用户输入的磁盘路 径加载指定的 Microsoft Agent 角色。
162
2 添加组合下拉框 characterCombo 的条目选择更改事件处理方法
characterCombo_SelectIndexChanged 方法的操作是根据选中的 条目,加载新选定的 Microsoft Agent 角色。 3 添加组合下拉框 actionsCombo 的条目选择更改事件处理方法 actionsCombo_SelectIndexChanged 方法的操作是根据选中的条 目,使 Microsoft Agent 角色执行选定的预定义动作。 4 添加按钮 speakButton 的单击事件处理方法 speakButton_Click 方法的操作是使 Microsoft Agent 角色发出在 speechTextBox 文本框指定文本的语音信息。如 speechTextBox 文本框无指定文本,则发出语音信息:
163
“Please, type the words you want me to speak ”。
5 添加 Microsoft Agent 控件 mainAgent 的单击事件处理方法 mainAgent_Click 方法的操作是使 Microsoft Agent 角色作出困惑 不解的动作,并发出语音信息:“Why are you poking me?”。 6 为自定义动作 MoveTo 添加 Microsoft Agent 角色命令方法 mainAgent_Command 方法的操作依据用户发出的语音命令 “MoveToMouse”,使 Microsoft Agent 角色作出相应的动作。
164
实例的运行结果如下: 程序开始运行时状态如下: 唯一启用 Microsoft Agent 控件的方法是在文本框 locationTextBox 中输入 Microsoft Agent 角色的磁盘路径 “C:\Windows\msagent\chars\” 后,按回车键。
Similar presentations