1
第 12章 多媒体应用程序的设计
2
12.1 利用音频函数实现多媒体
程序设计
为了介绍多媒体程序的设计,我们
先介绍一个非常简单的例子,希望
读者能够通过这个简单的例子,了
解音频文件的播放方法
3
12.1.1 一个简单的应用实例
【 例 12-1】 设计一个简单的音频播放程序,程
序启动时,播放 windows系动启动时候的音乐
1.创建工程文件 MCIStart;打开 Stdafx.h文件,在 #ifndef
_AFX_NO_AFXCMN_SUPPORT语句的上一行顶头加入
语句 #include <mmsystem.h>
2.将 winmm.lib
与应用程序链
接起来
3.在 MCIStartDlg.cpp的 OnInitDialog()
函数中的 return TRUE之前加上代码:
sndPlaySound("SystemStart",SND_ASYNC);
4
PlaySound()
sndPlaySound()
12.1.2 几个常用的音频函数
(1) MessageBeep()函数,用来播放系统提示音
(2) sndPlaySound()函数,播放 wav音频
(3) PlaySound()函数,播放来自资源中的声音
MessageBeep()
功能包
含关系
5
12.1.3 用 MCI控制波形声音的播放
MCIERROR mciSendCommand(
MCIDEVICEID IDDevice,//接收命令消息的 MCI设备 ID
UINT uMsg,//发送的命令消息
DWORD fdwCommand,//命令消息的标志集
DWORD_PTR dwParam) //消息参数的结构体地址
常用的 MCI设备消息 见教材表 12-5
6
在调用 MCI设备时可用 mciGetErrorString()检
测错误,该函数的原型如下:
BOOL mciGetErrorString(
DWORD fdwError,//错误代码
LPTSTR lpszErrorText,//指向错误内容字串的指针
UINT cchErrorText //错误内容的缓冲区容量
)
7
【 例 12-2】 编写一个音频播放器程序,可以选
择音频文件,并控制其播放、暂停播放、暂
停后的继续播放以及停止播放的功能
IDC_OPEN_BUTTON IDC_CLOSE_BUTTON
IDC_START_BUTTON IDC_PAUSE_BUTTON IDC_STOP_BUTTON
IDC_EXIT_BUTTON
8
1,将头文件 mmsystem.h加入到文件 Stdafx.h中,
将多媒体函数库 winmm.lib通过 project菜单中的
seetings命令来与程序链接起来
2.在 CMCIPlayerDlg类上增加 Protected类型的成员变量,
具体如下:
BOOL m_PSign(作为判断正在播放的标识)
BOOL m_ASign(作为判断正在播放的标识)、
DWORD dwError(用来储存错误代码)、
MCIDEVICEID m_MCIDeviceID( 用来储存打开设备的 ID值 )
charszErrorBuf[MAXERRORLENGTH]( 用来储存出错内容)
9
BOOL CMCIPlayerDlg::OnInitDialog()
{ CDialog::OnInitDialog();
…
// TODO,Add extra initialization here
m_PSign=FALSE; //初始化正在播放标识
m_ASign=FALSE; //初始化正在暂停标识
m_MCIDeviceID=0; //初始化设备标识
return TRUE;
}
10
void CMCIPlayerDlg::OnOpenButton() //打开一个文件
{ CString filename; //定义 CString类的 filename用来存储文件名
CString fileext; //定义 CString类的 fileext用来存储文件扩展名
MCI_OPEN_PARMS mciOpenParms;//定义结构体变量用来存储打
开文件的信息和返回的设备标识信息
DWORD dwError; //定义 dwError用来储存返回的错误标识
static char szFilter[]="波形音频文件 (*.wav)|*.wav|MIDI序列 (*.mid)|*.mid\0";
CFileDialog
dlg(TRUE,NULL,NULL,OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT,szFilter);
//通过打开按钮时显示的内容
11
if (dlg.DoModal()==IDOK)
{ filename=dlg.GetFileName(); //获取打开的文件名
fileext=dlg.GetFileExt(); //获取打开的文件扩展名
if (m_PSign) //如果程序正在播放, 则关闭
{ dwError=mciSendCommand(m_MCIDeviceID,MCI_CLOSE,0,NULL);
//关闭正在播放的声音
if (dwError) //如果关闭不成功, 则显示出错的原因
{if(mciGetErrorString(dwError,(LPSTR)szErrorBuf,MAXERRORLENGTH))
MessageBox(szErrorBuf,"MCI出错 ",MB_ICONWARNING);
else
MessageBox("不明错误标识 ","MCI出错 ",MB_ICONWARNING);
//给出相应报告
return;
} }
12
//如果没有声音正在播放, 则获取打开文件的后辍, 并
根据后辍决定相应的打开类型
if (!strcmp("wav",fileext)) //当后辍为 wav时
mciOpenParms.lpstrDeviceType="waveaudio";
else if (!strcmp("mid",fileext))//当后辍为 mid时
mciOpenParms.lpstrDeviceType="sequencer";
mciOpenParms.lpstrElementName=filename;
//将打开的文件名存入 mciOpenParms结构体中
dwError=mciSendCommand(0,MCI_OPEN,MCI_OPEN_TYPE|MCI_OP
EN_ELEMENT,(DWORD)(LPVOID)&mciOpenParms);
//发送打开文件命令, MCI_OPEN_TYPE参数说明设备类
型名包含在 mciOpenParms结构体中,
//MCI_OPEN_ELEMENT参数说明要打开的文件名包含在
mciOpenParams结构体中
13
if (dwError)//如果打开不成功, 则显示出错的原因
{if (mciGetErrorString(dwError,(LPSTR)szErrorBuf,MAXERRORLENGTH))
MessageBox(szErrorBuf,"MCI出错 ",MB_ICONWARNING);
else
MessageBox("不明错误标识 ","MCI出错 ",MB_ICONWARNING);
return;
}
m_MCIDeviceID=mciOpenParms.wDeviceID;//将获取的设备
ID值赋给全局变量 m_MCIDeviceID
m_PSign=FALSE;//设置正在播放标识为 FALSE
m_ASign=FALSE;//设置正在暂停标识为 FALSE
} }
14
void CMCIPlayerDlg::OnStartButton() //从头开始播放
{ MCI_PLAY_PARMS mciPlayParms;
//结构体变量存储播放相关信息
if (!m_PSign) //如果没有正在播放的声音
{mciPlayParms.dwCallback=(long)GetSafeHwnd();
//为发送 MM_MCINOTIFY消息指定窗口句柄
mciPlayParms.dwFrom=0;//设置播放位置从头开始
dwError=mciSendCommand(m_MCIDeviceID,MCI_PLAY,MCI_
FROM|MCI_NOTIFY,(DWORD)(LPVOID)&mciPlayParms);
//开始播放声音,参数 MCI_FROM说明开始播放的位置
包含在 mciPlayParms结构体中
//参数 MCI_NOTIFY的意义是播放完后发送 MM_MCINOTIFY
消息
15
if (dwError)
{if
(mciGetErrorString(dwError,(LPSTR)szErrorBuf,MAXERRORLENGTH))
MessageBox(szErrorBuf,"MCI出错 ",MB_ICONWARNING);
else
MessageBox("不明错误标识 ","MCI出错 ",MB_ICONWARNING);
return;
}
m_PSign=TRUE;//设置正在播放标识为 TRUE
} }
16
void CMCIPlayerDlg::OnPauseButton()
{if (m_PSign)//如果有正在播放的声音
{if (!m_ASign)//如果不是暂停状态
{dwError=mciSendCommand(m_MCIDeviceID,MCI_PAUSE,0,NULL);
//则暂停播放
if (dwError)
{if
(mciGetErrorString(dwError,(LPSTR)szErrorBuf,MAXERRORLENGTH))
MessageBox(szErrorBuf,"MCI出错 ",MB_ICONWARNING);
else
MessageBox("不明错误标识 ","MCI出错 ",MB_ICONWARNING);
return;
}
m_ASign=TRUE; //设置正在暂停标识为 TRUE
}
17
else //如果已经是暂停状态
{dwError=mciSendCommand(m_MCIDeviceID,MCI_RESUME,0,NULL);
//则继续播放
if (dwError)
{if (mciGetErrorString(dwError,(LPSTR)szErrorBuf,MAXERRORLENGTH))
MessageBox(szErrorBuf,"MCI出错 ",MB_ICONWARNING);
else
MessageBox("不明错误标识 ","MCI出错 ",MB_ICONWARNING);
return;
}
m_ASign=FALSE;//设置正在暂停标识为 FALSE
} } }
18
void CMCIPlayerDlg::OnStopButton()
{dwError=mciSendCommand(m_MCIDeviceID,MCI_STOP,MCI_WAIT,NULL);
//发送停止命令消息, 参数 MCI_WAIT说明当命令执行结束后函数才返回值
if (dwError)
{if (mciGetErrorString(dwError,(LPSTR)szErrorBuf,MAXERRORLENGTH))
MessageBox(szErrorBuf,"MCI出错 ",MB_ICONWARNING);
else
MessageBox("不明错误标识 ","MCI出错 ",MB_ICONWARNING);
return;
}
m_PSign=FALSE; //设置正在播放标识为 FALSE
m_ASign=FALSE; //设置正在暂停标识为 FALSE
MessageBox("如要播放新的文件, 请在打开前先关闭现有文件 ","注意 ",MB_ICONQUESTION);
//提请用户注意先关闭现有文件
}
19
void CMCIPlayerDlg::OnCloseButton() //关闭当前设备
{if (m_MCIDeviceID) //若什么文件都没有打开过, 就不执行关闭操作
{dwError=mciSendCommand(m_MCIDeviceID,MCI_STOP,MCI_WAIT,NU
LL);
if (dwError)
{if (mciGetErrorString(dwError,(LPSTR)szErrorBuf,MAXERRORLENGTH))
MessageBox(szErrorBuf,"MCI出错 ",MB_ICONWARNING);
else
MessageBox("不明错误标识 ","MCI出错 ",MB_ICONWARNING);
return; }
dwError=mciSendCommand(m_MCIDeviceID,MCI_CLOSE,0,NULL);
if (dwError)
{if (mciGetErrorString(dwError,(LPSTR)szErrorBuf,MAXERRORLENGTH))
MessageBox(szErrorBuf,"MCI出错 ",MB_ICONWARNING);
else
MessageBox("不明错误标识 ","MCI出错 ",MB_ICONWARNING);
return; }
m_MCIDeviceID=0; //关闭文件后将变量设为 0
} }
20
void CMCIPlayerDlg::OnExitButton()
//点击确定的响应
{
OnCloseButton();
//先执行关闭文件的操作
CDialog::OnOK(); //关闭窗口
}
21
手动加入 MM_MCINOTIFY消息的处理函数
在 MCIPlayerDlg.h中的函数
class CMCIPlayerDlg, public CDialog( ) 的
,//}}AFX_MSG”
和
,DECLARE_MESSAGE_MAP()”
语句之间加入如下代码:
afx_msg LRESULT MciNotify(WPARAM wParam,LPARAM lParam);
22
在 MCIPlayerDlg.cpp 中的消息映射入口处加入如下代码:
ON_MESSAGE(MM_MCINOTIFY,MciNotify)。
样式如下:
BEGIN_MESSAGE_MAP(CMCIPlayerDlg,CDialog)
//{{AFX_MSG_MAP(CMCIPlayerDlg)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(IDC_OPEN_BUTTON,OnOpenButton)
ON_BN_CLICKED(IDC_START_BUTTON,OnStartButton)
ON_BN_CLICKED(IDC_PAUSE_BUTTON,OnPauseButton)
ON_BN_CLICKED(IDC_STOP_BUTTON,OnStopButton)
ON_BN_CLICKED(IDC_CLOSE_BUTTON,OnCloseButton)
//}}AFX_MSG_MAP
ON_MESSAGE(MM_MCINOTIFY,MciNotify)
END_MESSAGE_MAP()
23
将函数 MciNotify加入应用程序中,
LRESULT CMCIPlayerDlg::MciNotify(WPARAM wParam,LPARAM lParam)
{ if (wParam==MCI_NOTIFY_SUCCESSFUL)
//成功播放完成后重置标识
{ m_PSign=FALSE;//设置正在播放标识为 FALSE
m_ASign=FALSE;//设置正在暂停标识为 FALSE
return 0;
}
return -1;//否则返回错误
}
24
12.2 利用 Windows Media Player
控件实现多媒体程序设计
【 例 12-3】 编
写应用程序,
使得用户可
以分别选择
一个视频文
件和一个音
频文件来同
时播放或者
分别播放
25
为 CPlayMediaView类添加四个成员,两个用来播放多
媒体文件,两个用来保存待播放的文件名,修改后的
CPlayMediaView.h文件如下:
class CWMPPlayer4; // 前示声明
class CPlayMediaView, public CView
{ protected,// create from serialization only
CPlayMediaView();
DECLARE_DYNCREATE(CPlayMediaView)
// Attributes
public:
CPlayMediaDoc* GetDocument();
CWMPPlayer4 * m_Video; // 用来播放视频
CWMPPlayer4 * m_Music; // 用来播放音频
CString m_strVideo; // 视频文件名
CString m_strMusic; // 音频文件名
// Operations
…
};
26
初始化和释放指针, 修改 PlayMedia.cpp文件:
#include "wmpplayer4.h"
#include "wmpcontrols.h"
#include "wmpsettings.h"
CPlayMediaView::CPlayMediaView()
{
// TODO,add construction code here
m_Video = new CWMPPlayer4;
m_Music = new CWMPPlayer4;
}
CPlayMediaView::~CPlayMediaView()
{ delete m_Video; delete m_Music; }
27
对于 m_Video和 m_Music两个变量,
由于对应的是 ActiveX控件, 因此 不
但需要 new来初始化指针, 还需要使
用它的 Create 函 数 来 创 建 初 始 化
ActiveX控件 。
由于两个控件是作为 View的子窗
口运行的, 通常它们的创建是在
View的 OnCreate函数中完成, 因此需
要响应 View的 WM_CREATE消息, 并
在其中创建两个控件:
28
int CPlayMediaView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{ if (CView::OnCreate(lpCreateStruct) == -1)
return -1;
// TODO,Add your specialized creation code here
HCURSOR hCursor=::LoadCursor(NULL,IDC_ARROW);
// 获得标准箭头鼠标指针
m_Video->Create(AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS | CS_PARENTDC,
hCursor,0,0),NULL,WS_VISIBLE|WS_CHILD,CRect(0,0,0,0),this,0);
// 创建视频窗口
// 由于使用单独的音频来播放,将视频设置为静音模式
m_Video->GetSettings().SetMute(TRUE);
m_Music->Create(AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS | CS_PARENTDC,hCursor,0,0),
NULL,WS_VISIBLE|WS_CHILD,CRect(0,0,0,0),this,0);
// 创建音频窗口
m_Music->ShowWindow(SW_HIDE); // 将音频窗口设置为不可见
return 0; }
29
们添加, 操作, 及相关菜单
项
ID_OPER_OPENV
ID_OPER_PLAYV
ID_OPER_STOPV
ID_OPER_OPENA
ID_OPER_PLAYA
ID_OPER_STOPA
30
void CPlayMediaView::OnOperOpenv()
{ static char BASED_CODE szFilter[] =
"Video Files (*.avi;*.mpg;*.mpeg)|*.avi;*.mpg;*.mpeg|";
// 只允许打开视频文件
CFileDialog fileDlg(TRUE,NULL,NULL,OFN_HIDEREADONLY,szFilter);
if(fileDlg.DoModal() == IDOK)
m_strVideo = fileDlg.GetPathName();// 获得视频文件名
}
void CPlayMediaView::OnOperPlayv()
{
m_Video->GetControls().stop(); // 首先停止正在播放的
m_Video->GetSettings().SetAutoStart(TRUE); // 准备播放
m_Video->SetUrl(m_strVideo); // 载入视频文件自动播放
}
31
void CPlayMediaView::OnOperStopv()
{ m_Video->GetControls().stop(); // 停止播放 }
void CPlayMediaView::OnOperOpena()
{ // 只允许打开音频文件
static char BASED_CODE szFilter[] =
"Audio Files (*.wav;*.mp3)|*.wav;*.mp3||";
CFileDialog fileDlg(TRUE,NULL,NULL,
OFN_HIDEREADONLY,szFilter);
if(fileDlg.DoModal() == IDOK)
m_strMusic = fileDlg.GetPathName();// 获得音频文件名
}
32
void CPlayMediaView::OnOperPlaya()
{
m_Music->GetControls().stop(); // 首先停止正在播放的
m_Music->GetSettings().SetAutoStart(TRUE); // 准备播放
m_Music->SetUrl(m_strMusic); // 载入视频文件自动播放
}
void CPlayMediaView::OnOperStopa()
{
m_Music->GetControls().stop(); // 停止播放
}
33
有了以上代码,程序还不能够正常运行,需要为播
放视频的控件设置窗口尺寸。为此需要响应 View的
WM_SIZE消息,该消息响应函数如下:
void CPlayMediaView::OnSize(UINT nType,int cx,int cy)
{ CView::OnSize(nType,cx,cy);
// TODO,Add your message handler code here
CRect rect;
GetClientRect(rect);
m_Video->MoveWindow(rect);
}
在播放视频时,该控件会自动显示出 MediaPlayer的
控制面板,可以通过在 OnCreate函数中,在 m_Video创
建之后调用 SetMode函数来隐藏该控件的控制面板。
m_Video->SetUiMode("none");
34
12.3常见格式图片的显示
Windows程序中经常要显示各种图片,对于普
通的 BMP,DIB等位图格式文件,GDI的
LoadImage,LoadBitmap函数已经提供了支持,
但是对于网页中常见的 PNP,JPG,GIF以及
矢量格式的 WMF图片,Visual C++自带了一
个实现这个功能的函数 —— OleLoadPicture。
但是由于 MSDN中只提到该函数支持 BMP、
ICO,WMF格式,因此该函数经常被大家忽
视,这里将介绍如何使用该函数来显示各种格
式的图片。
35
【 例 12-4】 使用 AppWizard创建 MFC SDI应用程序,用来
装载并显示图片。
创建 ImageViewer工程文件。为显示图片,为
CImageViewerView添加成员 m_pPicture以装载图形,并
定义载入图片的函数的声明:
LPPICTURE m_pPicture;
private,void LoadPicture(CString strFile);
接下来为 m_pPicture成员添加初始化和释放的代码:
CImageViewerView::CImageViewerView()
{ m_pPicture = NULL; }
CImageViewerView::~CImageViewerView()
{ if (m_pPicture) m_pPicture->Release(); }
36
为实现打开文件,我们添加“操作 &O”菜单,增加菜单
项“载入图片”,其 ID为 ID_OPER_OPEN,然后映射消
息响应函数:
void CImageViewerView::OnOperOpen()
{ TCHAR szFile[MAX_PATH]; // 保存文件名的缓冲
ZeroMemory(szFile,MAX_PATH); // 初始化该缓冲
OPENFILENAME ofn; // 打开文件的关键结构
ZeroMemory(&ofn,sizeof(OPENFILENAME)); // 初始化该结构
ofn.lStructSize= sizeof(OPENFILENAME);// 设置该结构的大小
// 设置属性:文件必须存在、路径必须存在、隐藏只读文件
ofn.Flags= OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST
| OFN_HIDEREADONLY;
ofn.hwndOwner= m_hWnd; // 设置该文件框的父窗口
37
ofn.lpstrFilter= _T("Supported Files
Types(*.bmp;*.gif;*.jpg;*.ico;*.emf;*.wmf)\0
*.bmp;*.gif;*.jpg;*.ico;*.emf;*.wmf\0
Bitmaps (*.bmp)\0*.bmp\0
GIF Files (*.gif)\0*.gif\0JPEG Files (*.jpg)\0
*.jpg\0Icons (*.ico)\0*.ico\0
Enhanced Metafiles (*.emf)\0*.emf\0
Windows Metafiles (*.wmf)\0*.wmf\0\0");
// 设置支持的文件扩展名
ofn.lpstrTitle= _T("选择图片 "); // 对话框标题
ofn.lpstrFile = szFile; // 设置返回文件名的缓冲
ofn.nMaxFile = MAX_PATH; // 设置缓冲的长度
if (IDOK == GetOpenFileName(&ofn)) // 调用对话框
LoadPicture(szFile); // 载入该文件
}
38
下面实现关键函数 ——LoadPicture:
void CImageViewerView::LoadPicture(CString strFile)
{ HANDLE
hFile=CreateFile(strFile,GENERIC_READ,0,NULL,OPEN_EXISTING,0,NULL);
//打开文件
_ASSERTE(INVALID_HANDLE_VALUE != hFile);
DWORD dwFileSize = GetFileSize(hFile,NULL); // 获得文件大小
_ASSERTE(-1 != dwFileSize);
LPVOID pvData = NULL;
HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE,dwFileSize);
// 分配全局内存,获得内存句柄
_ASSERTE(NULL != hGlobal);
pvData = GlobalLock(hGlobal); //锁定内存,获得内存指针
_ASSERTE(NULL != pvData);
出错时产生调试信息
39
DWORD dwBytesRead = 0;
BOOL bRead = ReadFile(hFile,pvData,dwFileSize,
&dwBytesRead,NULL); // 读取文件
_ASSERTE(FALSE != bRead);
GlobalUnlock(hGlobal);
CloseHandle(hFile);
LPSTREAM pstm = NULL;
HRESULT hr = CreateStreamOnHGlobal(hGlobal,
TRUE,&pstm); //从内存数据创建 IStream*
_ASSERTE(SUCCEEDED(hr) && pstm);
40
if (m_pPicture) // 创建 IPicture
m_pPicture->Release();
// 从 IStream接口中载入图片到 IPicture中
hr =,:OleLoadPicture(pstm,dwFileSize,FALSE,
IID_IPicture,(LPVOID *)&m_pPicture);
_ASSERTE(SUCCEEDED(hr) && m_pPicture);
pstm->Release(); // 释放 IStream接口
Invalidate(); // 强制重新绘制窗口
} 通过以上调用,我们
的程序已经将位图文
件成功载入到
m_pPicture变量中了
41
下面就是显示的步骤,与一般的绘图程序
类似,显示代码也是在 OnDraw中完成。
#define HIMETRIC_INCH 2540
void CImageViewerView::OnDraw(CDC* pDC)
{ CImageViewerDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO,add draw code for native data here
if(m_pPicture)
{long hmWidth;
long hmHeight;
m_pPicture->get_Width(&hmWidth);
m_pPicture->get_Height(&hmHeight);
42
// convert himetric to pixels
int nWidth= MulDiv(hmWidth,GetDeviceCaps(pDC->GetSafeHdc(),
LOGPIXELSX),HIMETRIC_INCH);
int nHeight= MulDiv(hmHeight,GetDeviceCaps(pDC->GetSafeHdc(),
LOGPIXELSY),HIMETRIC_INCH);
CRect rc;
GetClientRect(&rc);
// display picture using IPicture::Render
m_pPicture->Render(pDC->GetSafeHdc(),0,0,
nWidth,nHeight,0,hmHeight,hmWidth,-hmHeight,&rc);
} }
43
【 例 12-5】 在上例的基础上对所载入的图片进行
50%压缩显示 。
添加菜单项“压缩 50%”,其 ID为 ID_OPER_SIZE,为
了控制显示模式,为 View类添加一个控制变量:
BOOL m_bScale;
在 View的实现中添加对该变量的初始化,以及
对应菜单项的处理:
CImageViewerView::CImageViewerView()
{ m_pPicture = NULL;
m_bScale = FALSE;
}
44
void CImageViewerView::OnOperSize()
{
m_bScale = TRUE;
Invalidate();
}
void CImageViewerView::OnUpdateOperSize(CCmdUI* pCmdUI)
{
pCmdUI->SetCheck(m_bScale);
}
45
下面需要实现显示函数,GDI提供的 StrechBlt可以实
现图片的缩放显示。在上例的 OnDraw函数的最后一行
前面加入如下内容:
if(m_pPicture)
{ long hmWidth;
long hmHeight;
m_pPicture->get_Width(&hmWidth);
m_pPicture->get_Height(&hmHeight);
// convert himetric to pixels
int nWidth= MulDiv(hmWidth,GetDeviceCaps(pDC->GetSafeHdc(),
LOGPIXELSX),HIMETRIC_INCH);
int nHeight= MulDiv(hmHeight,GetDeviceCaps(pDC->GetSafeHdc(),
LOGPIXELSY),HIMETRIC_INCH);
46
CRect rc; GetClientRect(&rc);
if(m_bScale) // 缩放
{ CDC memdc; // 创建内存 DC
memdc.CreateCompatibleDC(pDC);
CBitmap bmp; // 创建位图
bmp.CreateCompatibleBitmap(pDC,nWidth,nHeight);
memdc.SelectObject(bmp); // 将位图选入内存 DC
m_pPicture->Render(memdc.GetSafeHdc(),0,0,nWidth,
nHeight,0,hmHeight,hmWidth,-hmHeight,&rc);
//将图片以原始尺寸绘制到内存 DC中
pDC->StretchBlt(0,0,nWidth/2,nHeight/2,&memdc,0,0,
nWidth,nHeight,SRCCOPY);
// 从内存 DC缩放拷贝到显示 DC
}
else //原始尺寸显示
m_pPicture->Render(pDC->GetSafeHdc(),0,0,nWidth,
nHeight,0,hmHeight,hmWidth,-hmHeight,&rc);
}
第 12章 多媒体应用程序的设计
2
12.1 利用音频函数实现多媒体
程序设计
为了介绍多媒体程序的设计,我们
先介绍一个非常简单的例子,希望
读者能够通过这个简单的例子,了
解音频文件的播放方法
3
12.1.1 一个简单的应用实例
【 例 12-1】 设计一个简单的音频播放程序,程
序启动时,播放 windows系动启动时候的音乐
1.创建工程文件 MCIStart;打开 Stdafx.h文件,在 #ifndef
_AFX_NO_AFXCMN_SUPPORT语句的上一行顶头加入
语句 #include <mmsystem.h>
2.将 winmm.lib
与应用程序链
接起来
3.在 MCIStartDlg.cpp的 OnInitDialog()
函数中的 return TRUE之前加上代码:
sndPlaySound("SystemStart",SND_ASYNC);
4
PlaySound()
sndPlaySound()
12.1.2 几个常用的音频函数
(1) MessageBeep()函数,用来播放系统提示音
(2) sndPlaySound()函数,播放 wav音频
(3) PlaySound()函数,播放来自资源中的声音
MessageBeep()
功能包
含关系
5
12.1.3 用 MCI控制波形声音的播放
MCIERROR mciSendCommand(
MCIDEVICEID IDDevice,//接收命令消息的 MCI设备 ID
UINT uMsg,//发送的命令消息
DWORD fdwCommand,//命令消息的标志集
DWORD_PTR dwParam) //消息参数的结构体地址
常用的 MCI设备消息 见教材表 12-5
6
在调用 MCI设备时可用 mciGetErrorString()检
测错误,该函数的原型如下:
BOOL mciGetErrorString(
DWORD fdwError,//错误代码
LPTSTR lpszErrorText,//指向错误内容字串的指针
UINT cchErrorText //错误内容的缓冲区容量
)
7
【 例 12-2】 编写一个音频播放器程序,可以选
择音频文件,并控制其播放、暂停播放、暂
停后的继续播放以及停止播放的功能
IDC_OPEN_BUTTON IDC_CLOSE_BUTTON
IDC_START_BUTTON IDC_PAUSE_BUTTON IDC_STOP_BUTTON
IDC_EXIT_BUTTON
8
1,将头文件 mmsystem.h加入到文件 Stdafx.h中,
将多媒体函数库 winmm.lib通过 project菜单中的
seetings命令来与程序链接起来
2.在 CMCIPlayerDlg类上增加 Protected类型的成员变量,
具体如下:
BOOL m_PSign(作为判断正在播放的标识)
BOOL m_ASign(作为判断正在播放的标识)、
DWORD dwError(用来储存错误代码)、
MCIDEVICEID m_MCIDeviceID( 用来储存打开设备的 ID值 )
charszErrorBuf[MAXERRORLENGTH]( 用来储存出错内容)
9
BOOL CMCIPlayerDlg::OnInitDialog()
{ CDialog::OnInitDialog();
…
// TODO,Add extra initialization here
m_PSign=FALSE; //初始化正在播放标识
m_ASign=FALSE; //初始化正在暂停标识
m_MCIDeviceID=0; //初始化设备标识
return TRUE;
}
10
void CMCIPlayerDlg::OnOpenButton() //打开一个文件
{ CString filename; //定义 CString类的 filename用来存储文件名
CString fileext; //定义 CString类的 fileext用来存储文件扩展名
MCI_OPEN_PARMS mciOpenParms;//定义结构体变量用来存储打
开文件的信息和返回的设备标识信息
DWORD dwError; //定义 dwError用来储存返回的错误标识
static char szFilter[]="波形音频文件 (*.wav)|*.wav|MIDI序列 (*.mid)|*.mid\0";
CFileDialog
dlg(TRUE,NULL,NULL,OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT,szFilter);
//通过打开按钮时显示的内容
11
if (dlg.DoModal()==IDOK)
{ filename=dlg.GetFileName(); //获取打开的文件名
fileext=dlg.GetFileExt(); //获取打开的文件扩展名
if (m_PSign) //如果程序正在播放, 则关闭
{ dwError=mciSendCommand(m_MCIDeviceID,MCI_CLOSE,0,NULL);
//关闭正在播放的声音
if (dwError) //如果关闭不成功, 则显示出错的原因
{if(mciGetErrorString(dwError,(LPSTR)szErrorBuf,MAXERRORLENGTH))
MessageBox(szErrorBuf,"MCI出错 ",MB_ICONWARNING);
else
MessageBox("不明错误标识 ","MCI出错 ",MB_ICONWARNING);
//给出相应报告
return;
} }
12
//如果没有声音正在播放, 则获取打开文件的后辍, 并
根据后辍决定相应的打开类型
if (!strcmp("wav",fileext)) //当后辍为 wav时
mciOpenParms.lpstrDeviceType="waveaudio";
else if (!strcmp("mid",fileext))//当后辍为 mid时
mciOpenParms.lpstrDeviceType="sequencer";
mciOpenParms.lpstrElementName=filename;
//将打开的文件名存入 mciOpenParms结构体中
dwError=mciSendCommand(0,MCI_OPEN,MCI_OPEN_TYPE|MCI_OP
EN_ELEMENT,(DWORD)(LPVOID)&mciOpenParms);
//发送打开文件命令, MCI_OPEN_TYPE参数说明设备类
型名包含在 mciOpenParms结构体中,
//MCI_OPEN_ELEMENT参数说明要打开的文件名包含在
mciOpenParams结构体中
13
if (dwError)//如果打开不成功, 则显示出错的原因
{if (mciGetErrorString(dwError,(LPSTR)szErrorBuf,MAXERRORLENGTH))
MessageBox(szErrorBuf,"MCI出错 ",MB_ICONWARNING);
else
MessageBox("不明错误标识 ","MCI出错 ",MB_ICONWARNING);
return;
}
m_MCIDeviceID=mciOpenParms.wDeviceID;//将获取的设备
ID值赋给全局变量 m_MCIDeviceID
m_PSign=FALSE;//设置正在播放标识为 FALSE
m_ASign=FALSE;//设置正在暂停标识为 FALSE
} }
14
void CMCIPlayerDlg::OnStartButton() //从头开始播放
{ MCI_PLAY_PARMS mciPlayParms;
//结构体变量存储播放相关信息
if (!m_PSign) //如果没有正在播放的声音
{mciPlayParms.dwCallback=(long)GetSafeHwnd();
//为发送 MM_MCINOTIFY消息指定窗口句柄
mciPlayParms.dwFrom=0;//设置播放位置从头开始
dwError=mciSendCommand(m_MCIDeviceID,MCI_PLAY,MCI_
FROM|MCI_NOTIFY,(DWORD)(LPVOID)&mciPlayParms);
//开始播放声音,参数 MCI_FROM说明开始播放的位置
包含在 mciPlayParms结构体中
//参数 MCI_NOTIFY的意义是播放完后发送 MM_MCINOTIFY
消息
15
if (dwError)
{if
(mciGetErrorString(dwError,(LPSTR)szErrorBuf,MAXERRORLENGTH))
MessageBox(szErrorBuf,"MCI出错 ",MB_ICONWARNING);
else
MessageBox("不明错误标识 ","MCI出错 ",MB_ICONWARNING);
return;
}
m_PSign=TRUE;//设置正在播放标识为 TRUE
} }
16
void CMCIPlayerDlg::OnPauseButton()
{if (m_PSign)//如果有正在播放的声音
{if (!m_ASign)//如果不是暂停状态
{dwError=mciSendCommand(m_MCIDeviceID,MCI_PAUSE,0,NULL);
//则暂停播放
if (dwError)
{if
(mciGetErrorString(dwError,(LPSTR)szErrorBuf,MAXERRORLENGTH))
MessageBox(szErrorBuf,"MCI出错 ",MB_ICONWARNING);
else
MessageBox("不明错误标识 ","MCI出错 ",MB_ICONWARNING);
return;
}
m_ASign=TRUE; //设置正在暂停标识为 TRUE
}
17
else //如果已经是暂停状态
{dwError=mciSendCommand(m_MCIDeviceID,MCI_RESUME,0,NULL);
//则继续播放
if (dwError)
{if (mciGetErrorString(dwError,(LPSTR)szErrorBuf,MAXERRORLENGTH))
MessageBox(szErrorBuf,"MCI出错 ",MB_ICONWARNING);
else
MessageBox("不明错误标识 ","MCI出错 ",MB_ICONWARNING);
return;
}
m_ASign=FALSE;//设置正在暂停标识为 FALSE
} } }
18
void CMCIPlayerDlg::OnStopButton()
{dwError=mciSendCommand(m_MCIDeviceID,MCI_STOP,MCI_WAIT,NULL);
//发送停止命令消息, 参数 MCI_WAIT说明当命令执行结束后函数才返回值
if (dwError)
{if (mciGetErrorString(dwError,(LPSTR)szErrorBuf,MAXERRORLENGTH))
MessageBox(szErrorBuf,"MCI出错 ",MB_ICONWARNING);
else
MessageBox("不明错误标识 ","MCI出错 ",MB_ICONWARNING);
return;
}
m_PSign=FALSE; //设置正在播放标识为 FALSE
m_ASign=FALSE; //设置正在暂停标识为 FALSE
MessageBox("如要播放新的文件, 请在打开前先关闭现有文件 ","注意 ",MB_ICONQUESTION);
//提请用户注意先关闭现有文件
}
19
void CMCIPlayerDlg::OnCloseButton() //关闭当前设备
{if (m_MCIDeviceID) //若什么文件都没有打开过, 就不执行关闭操作
{dwError=mciSendCommand(m_MCIDeviceID,MCI_STOP,MCI_WAIT,NU
LL);
if (dwError)
{if (mciGetErrorString(dwError,(LPSTR)szErrorBuf,MAXERRORLENGTH))
MessageBox(szErrorBuf,"MCI出错 ",MB_ICONWARNING);
else
MessageBox("不明错误标识 ","MCI出错 ",MB_ICONWARNING);
return; }
dwError=mciSendCommand(m_MCIDeviceID,MCI_CLOSE,0,NULL);
if (dwError)
{if (mciGetErrorString(dwError,(LPSTR)szErrorBuf,MAXERRORLENGTH))
MessageBox(szErrorBuf,"MCI出错 ",MB_ICONWARNING);
else
MessageBox("不明错误标识 ","MCI出错 ",MB_ICONWARNING);
return; }
m_MCIDeviceID=0; //关闭文件后将变量设为 0
} }
20
void CMCIPlayerDlg::OnExitButton()
//点击确定的响应
{
OnCloseButton();
//先执行关闭文件的操作
CDialog::OnOK(); //关闭窗口
}
21
手动加入 MM_MCINOTIFY消息的处理函数
在 MCIPlayerDlg.h中的函数
class CMCIPlayerDlg, public CDialog( ) 的
,//}}AFX_MSG”
和
,DECLARE_MESSAGE_MAP()”
语句之间加入如下代码:
afx_msg LRESULT MciNotify(WPARAM wParam,LPARAM lParam);
22
在 MCIPlayerDlg.cpp 中的消息映射入口处加入如下代码:
ON_MESSAGE(MM_MCINOTIFY,MciNotify)。
样式如下:
BEGIN_MESSAGE_MAP(CMCIPlayerDlg,CDialog)
//{{AFX_MSG_MAP(CMCIPlayerDlg)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(IDC_OPEN_BUTTON,OnOpenButton)
ON_BN_CLICKED(IDC_START_BUTTON,OnStartButton)
ON_BN_CLICKED(IDC_PAUSE_BUTTON,OnPauseButton)
ON_BN_CLICKED(IDC_STOP_BUTTON,OnStopButton)
ON_BN_CLICKED(IDC_CLOSE_BUTTON,OnCloseButton)
//}}AFX_MSG_MAP
ON_MESSAGE(MM_MCINOTIFY,MciNotify)
END_MESSAGE_MAP()
23
将函数 MciNotify加入应用程序中,
LRESULT CMCIPlayerDlg::MciNotify(WPARAM wParam,LPARAM lParam)
{ if (wParam==MCI_NOTIFY_SUCCESSFUL)
//成功播放完成后重置标识
{ m_PSign=FALSE;//设置正在播放标识为 FALSE
m_ASign=FALSE;//设置正在暂停标识为 FALSE
return 0;
}
return -1;//否则返回错误
}
24
12.2 利用 Windows Media Player
控件实现多媒体程序设计
【 例 12-3】 编
写应用程序,
使得用户可
以分别选择
一个视频文
件和一个音
频文件来同
时播放或者
分别播放
25
为 CPlayMediaView类添加四个成员,两个用来播放多
媒体文件,两个用来保存待播放的文件名,修改后的
CPlayMediaView.h文件如下:
class CWMPPlayer4; // 前示声明
class CPlayMediaView, public CView
{ protected,// create from serialization only
CPlayMediaView();
DECLARE_DYNCREATE(CPlayMediaView)
// Attributes
public:
CPlayMediaDoc* GetDocument();
CWMPPlayer4 * m_Video; // 用来播放视频
CWMPPlayer4 * m_Music; // 用来播放音频
CString m_strVideo; // 视频文件名
CString m_strMusic; // 音频文件名
// Operations
…
};
26
初始化和释放指针, 修改 PlayMedia.cpp文件:
#include "wmpplayer4.h"
#include "wmpcontrols.h"
#include "wmpsettings.h"
CPlayMediaView::CPlayMediaView()
{
// TODO,add construction code here
m_Video = new CWMPPlayer4;
m_Music = new CWMPPlayer4;
}
CPlayMediaView::~CPlayMediaView()
{ delete m_Video; delete m_Music; }
27
对于 m_Video和 m_Music两个变量,
由于对应的是 ActiveX控件, 因此 不
但需要 new来初始化指针, 还需要使
用它的 Create 函 数 来 创 建 初 始 化
ActiveX控件 。
由于两个控件是作为 View的子窗
口运行的, 通常它们的创建是在
View的 OnCreate函数中完成, 因此需
要响应 View的 WM_CREATE消息, 并
在其中创建两个控件:
28
int CPlayMediaView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{ if (CView::OnCreate(lpCreateStruct) == -1)
return -1;
// TODO,Add your specialized creation code here
HCURSOR hCursor=::LoadCursor(NULL,IDC_ARROW);
// 获得标准箭头鼠标指针
m_Video->Create(AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS | CS_PARENTDC,
hCursor,0,0),NULL,WS_VISIBLE|WS_CHILD,CRect(0,0,0,0),this,0);
// 创建视频窗口
// 由于使用单独的音频来播放,将视频设置为静音模式
m_Video->GetSettings().SetMute(TRUE);
m_Music->Create(AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS | CS_PARENTDC,hCursor,0,0),
NULL,WS_VISIBLE|WS_CHILD,CRect(0,0,0,0),this,0);
// 创建音频窗口
m_Music->ShowWindow(SW_HIDE); // 将音频窗口设置为不可见
return 0; }
29
们添加, 操作, 及相关菜单
项
ID_OPER_OPENV
ID_OPER_PLAYV
ID_OPER_STOPV
ID_OPER_OPENA
ID_OPER_PLAYA
ID_OPER_STOPA
30
void CPlayMediaView::OnOperOpenv()
{ static char BASED_CODE szFilter[] =
"Video Files (*.avi;*.mpg;*.mpeg)|*.avi;*.mpg;*.mpeg|";
// 只允许打开视频文件
CFileDialog fileDlg(TRUE,NULL,NULL,OFN_HIDEREADONLY,szFilter);
if(fileDlg.DoModal() == IDOK)
m_strVideo = fileDlg.GetPathName();// 获得视频文件名
}
void CPlayMediaView::OnOperPlayv()
{
m_Video->GetControls().stop(); // 首先停止正在播放的
m_Video->GetSettings().SetAutoStart(TRUE); // 准备播放
m_Video->SetUrl(m_strVideo); // 载入视频文件自动播放
}
31
void CPlayMediaView::OnOperStopv()
{ m_Video->GetControls().stop(); // 停止播放 }
void CPlayMediaView::OnOperOpena()
{ // 只允许打开音频文件
static char BASED_CODE szFilter[] =
"Audio Files (*.wav;*.mp3)|*.wav;*.mp3||";
CFileDialog fileDlg(TRUE,NULL,NULL,
OFN_HIDEREADONLY,szFilter);
if(fileDlg.DoModal() == IDOK)
m_strMusic = fileDlg.GetPathName();// 获得音频文件名
}
32
void CPlayMediaView::OnOperPlaya()
{
m_Music->GetControls().stop(); // 首先停止正在播放的
m_Music->GetSettings().SetAutoStart(TRUE); // 准备播放
m_Music->SetUrl(m_strMusic); // 载入视频文件自动播放
}
void CPlayMediaView::OnOperStopa()
{
m_Music->GetControls().stop(); // 停止播放
}
33
有了以上代码,程序还不能够正常运行,需要为播
放视频的控件设置窗口尺寸。为此需要响应 View的
WM_SIZE消息,该消息响应函数如下:
void CPlayMediaView::OnSize(UINT nType,int cx,int cy)
{ CView::OnSize(nType,cx,cy);
// TODO,Add your message handler code here
CRect rect;
GetClientRect(rect);
m_Video->MoveWindow(rect);
}
在播放视频时,该控件会自动显示出 MediaPlayer的
控制面板,可以通过在 OnCreate函数中,在 m_Video创
建之后调用 SetMode函数来隐藏该控件的控制面板。
m_Video->SetUiMode("none");
34
12.3常见格式图片的显示
Windows程序中经常要显示各种图片,对于普
通的 BMP,DIB等位图格式文件,GDI的
LoadImage,LoadBitmap函数已经提供了支持,
但是对于网页中常见的 PNP,JPG,GIF以及
矢量格式的 WMF图片,Visual C++自带了一
个实现这个功能的函数 —— OleLoadPicture。
但是由于 MSDN中只提到该函数支持 BMP、
ICO,WMF格式,因此该函数经常被大家忽
视,这里将介绍如何使用该函数来显示各种格
式的图片。
35
【 例 12-4】 使用 AppWizard创建 MFC SDI应用程序,用来
装载并显示图片。
创建 ImageViewer工程文件。为显示图片,为
CImageViewerView添加成员 m_pPicture以装载图形,并
定义载入图片的函数的声明:
LPPICTURE m_pPicture;
private,void LoadPicture(CString strFile);
接下来为 m_pPicture成员添加初始化和释放的代码:
CImageViewerView::CImageViewerView()
{ m_pPicture = NULL; }
CImageViewerView::~CImageViewerView()
{ if (m_pPicture) m_pPicture->Release(); }
36
为实现打开文件,我们添加“操作 &O”菜单,增加菜单
项“载入图片”,其 ID为 ID_OPER_OPEN,然后映射消
息响应函数:
void CImageViewerView::OnOperOpen()
{ TCHAR szFile[MAX_PATH]; // 保存文件名的缓冲
ZeroMemory(szFile,MAX_PATH); // 初始化该缓冲
OPENFILENAME ofn; // 打开文件的关键结构
ZeroMemory(&ofn,sizeof(OPENFILENAME)); // 初始化该结构
ofn.lStructSize= sizeof(OPENFILENAME);// 设置该结构的大小
// 设置属性:文件必须存在、路径必须存在、隐藏只读文件
ofn.Flags= OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST
| OFN_HIDEREADONLY;
ofn.hwndOwner= m_hWnd; // 设置该文件框的父窗口
37
ofn.lpstrFilter= _T("Supported Files
Types(*.bmp;*.gif;*.jpg;*.ico;*.emf;*.wmf)\0
*.bmp;*.gif;*.jpg;*.ico;*.emf;*.wmf\0
Bitmaps (*.bmp)\0*.bmp\0
GIF Files (*.gif)\0*.gif\0JPEG Files (*.jpg)\0
*.jpg\0Icons (*.ico)\0*.ico\0
Enhanced Metafiles (*.emf)\0*.emf\0
Windows Metafiles (*.wmf)\0*.wmf\0\0");
// 设置支持的文件扩展名
ofn.lpstrTitle= _T("选择图片 "); // 对话框标题
ofn.lpstrFile = szFile; // 设置返回文件名的缓冲
ofn.nMaxFile = MAX_PATH; // 设置缓冲的长度
if (IDOK == GetOpenFileName(&ofn)) // 调用对话框
LoadPicture(szFile); // 载入该文件
}
38
下面实现关键函数 ——LoadPicture:
void CImageViewerView::LoadPicture(CString strFile)
{ HANDLE
hFile=CreateFile(strFile,GENERIC_READ,0,NULL,OPEN_EXISTING,0,NULL);
//打开文件
_ASSERTE(INVALID_HANDLE_VALUE != hFile);
DWORD dwFileSize = GetFileSize(hFile,NULL); // 获得文件大小
_ASSERTE(-1 != dwFileSize);
LPVOID pvData = NULL;
HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE,dwFileSize);
// 分配全局内存,获得内存句柄
_ASSERTE(NULL != hGlobal);
pvData = GlobalLock(hGlobal); //锁定内存,获得内存指针
_ASSERTE(NULL != pvData);
出错时产生调试信息
39
DWORD dwBytesRead = 0;
BOOL bRead = ReadFile(hFile,pvData,dwFileSize,
&dwBytesRead,NULL); // 读取文件
_ASSERTE(FALSE != bRead);
GlobalUnlock(hGlobal);
CloseHandle(hFile);
LPSTREAM pstm = NULL;
HRESULT hr = CreateStreamOnHGlobal(hGlobal,
TRUE,&pstm); //从内存数据创建 IStream*
_ASSERTE(SUCCEEDED(hr) && pstm);
40
if (m_pPicture) // 创建 IPicture
m_pPicture->Release();
// 从 IStream接口中载入图片到 IPicture中
hr =,:OleLoadPicture(pstm,dwFileSize,FALSE,
IID_IPicture,(LPVOID *)&m_pPicture);
_ASSERTE(SUCCEEDED(hr) && m_pPicture);
pstm->Release(); // 释放 IStream接口
Invalidate(); // 强制重新绘制窗口
} 通过以上调用,我们
的程序已经将位图文
件成功载入到
m_pPicture变量中了
41
下面就是显示的步骤,与一般的绘图程序
类似,显示代码也是在 OnDraw中完成。
#define HIMETRIC_INCH 2540
void CImageViewerView::OnDraw(CDC* pDC)
{ CImageViewerDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO,add draw code for native data here
if(m_pPicture)
{long hmWidth;
long hmHeight;
m_pPicture->get_Width(&hmWidth);
m_pPicture->get_Height(&hmHeight);
42
// convert himetric to pixels
int nWidth= MulDiv(hmWidth,GetDeviceCaps(pDC->GetSafeHdc(),
LOGPIXELSX),HIMETRIC_INCH);
int nHeight= MulDiv(hmHeight,GetDeviceCaps(pDC->GetSafeHdc(),
LOGPIXELSY),HIMETRIC_INCH);
CRect rc;
GetClientRect(&rc);
// display picture using IPicture::Render
m_pPicture->Render(pDC->GetSafeHdc(),0,0,
nWidth,nHeight,0,hmHeight,hmWidth,-hmHeight,&rc);
} }
43
【 例 12-5】 在上例的基础上对所载入的图片进行
50%压缩显示 。
添加菜单项“压缩 50%”,其 ID为 ID_OPER_SIZE,为
了控制显示模式,为 View类添加一个控制变量:
BOOL m_bScale;
在 View的实现中添加对该变量的初始化,以及
对应菜单项的处理:
CImageViewerView::CImageViewerView()
{ m_pPicture = NULL;
m_bScale = FALSE;
}
44
void CImageViewerView::OnOperSize()
{
m_bScale = TRUE;
Invalidate();
}
void CImageViewerView::OnUpdateOperSize(CCmdUI* pCmdUI)
{
pCmdUI->SetCheck(m_bScale);
}
45
下面需要实现显示函数,GDI提供的 StrechBlt可以实
现图片的缩放显示。在上例的 OnDraw函数的最后一行
前面加入如下内容:
if(m_pPicture)
{ long hmWidth;
long hmHeight;
m_pPicture->get_Width(&hmWidth);
m_pPicture->get_Height(&hmHeight);
// convert himetric to pixels
int nWidth= MulDiv(hmWidth,GetDeviceCaps(pDC->GetSafeHdc(),
LOGPIXELSX),HIMETRIC_INCH);
int nHeight= MulDiv(hmHeight,GetDeviceCaps(pDC->GetSafeHdc(),
LOGPIXELSY),HIMETRIC_INCH);
46
CRect rc; GetClientRect(&rc);
if(m_bScale) // 缩放
{ CDC memdc; // 创建内存 DC
memdc.CreateCompatibleDC(pDC);
CBitmap bmp; // 创建位图
bmp.CreateCompatibleBitmap(pDC,nWidth,nHeight);
memdc.SelectObject(bmp); // 将位图选入内存 DC
m_pPicture->Render(memdc.GetSafeHdc(),0,0,nWidth,
nHeight,0,hmHeight,hmWidth,-hmHeight,&rc);
//将图片以原始尺寸绘制到内存 DC中
pDC->StretchBlt(0,0,nWidth/2,nHeight/2,&memdc,0,0,
nWidth,nHeight,SRCCOPY);
// 从内存 DC缩放拷贝到显示 DC
}
else //原始尺寸显示
m_pPicture->Render(pDC->GetSafeHdc(),0,0,nWidth,
nHeight,0,hmHeight,hmWidth,-hmHeight,&rc);
}