建立osgMFC项目
VS2010新建一空项目osgMFC
将osg示例Samples下的osgViewerMFC文件拷贝到当前项目的osgMFC文件下
项目→添加现有项,把osgMFC文件夹下文件都添加到当前项目。并在使用共享DLL中使用MFC。
然后配置osg的头文件和库文件目录,添加链接器输入。
OpenThreadsd.lib
osgd.lib
osgDBd.lib
osgFXd.lib
osgGAd.lib
osgManipulatord.lib
osgParticled.lib
osgShadowd.lib
osgSimd.lib
osgTerraind.lib
osgTextd.lib
osgUtild.lib
osgViewerd.lib
出现错误:
“1>LINK : fatal error LNK1561: 必须定义入口点 ”,修改如下
运行,打开一个osg文件,如图
以下见FreeSouth《Step Into OSG》
修改鼠标
原代码为
BOOL CMFC_OSG_MDIView::PreCreateWindow(CREATESTRUCT& cs)
{
return CView::PreCreateWindow(cs);
}
//在使用OSG进行渲染时将取消背景填充色,通过下面的代码重新注册窗口类可以改变当前的鼠标与背景色和窗口风格,还有一种比较麻烦的方法是重新注册WNDCLASS结构体。为了保证是宽字符串,需要在"res/StarCraftArrow.ani"前加上L,或者是TEXT都可以,VS8几乎取消了对窄字符的支持!GetStockObject函数,可以返回一个画刷,字体或PEN,具体参数如下:
HGDIOBJ GetStockObject( int fnObject);
/*BLACK_BRUSH 黑色画刷,值为4
DKGRAY_BRUSH 暗灰色画刷
GRAY_BRUSH 灰色画刷
HOLLOW_BRUSH 空画刷,显示为背景
LTGRAY_BRUSH 亮灰色画刷
NULL_BRUSH 空画刷,显示为背景
WHITE_BRUSH 白色画刷
BLACK_PEN 黑色Pen
WHITE_PEN 白色pen.
NULL_PEN 空画笔
SYSTEM_FONT 得到系统字体,默认情况下中国就是宋体,是在程序中菜单上,对话框等上出现的字体
DEFAULT_PALETTE 得到系统调色板 */
BOOL CMFC_OSG_MDIView::PreCreateWindow(CREATESTRUCT& cs)
{
if (!CView::PreCreateWindow (cs)) return FALSE ;
cs.lpszClass = AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW,
LoadCursorFromFile("res/StarCraftArrow.ani"), (HBRUSH)GetStockObject(4)) ;
return TRUE;
}
重新注册窗口类可以
改变当前的鼠标与背景色和窗口风格
函数: HCURSOR LoadCursorFromFile(LPCTSTR lpFileName); 此函数可以从文件中读入一个鼠标,返回HCURSOR。失败时返回NULL.失败的情况一般为.ANI文件不存在或是做过非法修改!
HCURSOR LoadCursor( HINSTANCE hInstance, LPCTSTR lpCursorName); 此函数也可以用做取鼠标,不同的是它取的是系统默认的鼠标,第一个参数一般设置为NULL,经常使用的语句是:SetCursor(LoadCursor(NULL, IDC_WAIT));其中第二个参数IDC_WAIT可以是IDC_ARROW:箭头,IDC_UPARROW向上箭头,IDC_CROSS十字架等等!
为啥鼠标没变呢?
透明对话框
各种设计透明物体的方法在经过实践的选择以后,alpha混合技术成为主导。它的原理是如果一个物体需要对它后面的物体透明,这就需要将透明物的颜色和其后面物体的颜色进行混合。与每个象素相关联的有一个RGB颜色值和一个Z缓冲器深度值,另外一个成份就是alpha值,可以根据需要生成并存储,可以使用alpha描述给定象素处物体的透明程度(Archie注:更确切的说是不透明程度),alpha为1表示不透明,为0表示全透明。
在VS中为我们提供了一个函数,专门用来计算层叠窗口的透明度与不透明度!
下面我们把上个例子中的关于对话框做成透明的:
BOOL CAboutDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// TODO: 在此添加额外的初始化
//在窗口样式中添加WS_EX_LAYERED
SetWindowLong(this->GetSafeHwnd(),GWL_EXSTYLE,GetWindowLong(this->GetSafeHwnd(),GWL_EXSTYLE)^WS_EX_LAYERED);
//需要装入User32.DLL
HINSTANCE hInst = LoadLibrary("User32.DLL");
if(hInst)
{
typedef BOOL (WINAPI *MYFUNC)(HWND,COLORREF,BYTE,DWORD);
MYFUNC fun = NULL; //取得SetLayeredWindowAttributes函数指针
CHAR str[] = "SetLayeredWindowAttributes" ;
fun=(MYFUNC)GetProcAddress(hInst, str);
if(fun)
fun(this->GetSafeHwnd(),0,128,2);
FreeLibrary(hInst);
}
return TRUE; // return TRUE unless you set the focus to a control
// 异常: OCX 属性页应返回 FALSE
}
函数:
BOOL SetLayeredWindowAttributes( HWND hwnd, COLORREF crKey, BYTE bAlpha, DWORD dwFlags);
参数:
HWND hWnd :指的是需要透明的窗口的句柄,需要指明的是该窗口必须具有WS_EX_LAYERED的扩展风格才能具备透明度,可以使用SetWindowLong来对扩展风格进行处理,这是个极常用的函数,等一会儿详细介绍。
COLORREF crKey:顾明思义,这里指的是要透明的颜色,使用的是RGB宏(0~255)
BYTE bAlpha:指的是透明度。0指全透明,255指不透明。
DWORD dwFlags:指定一种行为,可以是以下两值: LWA_COLORKEY 把crKey 当作透明色 LWA_ALPHA 使用bAlpha来指定不透明度
函数: LONG SetWindowLong( HWND hWnd, int nIndex, LONG dwNewLong);
功能:设置窗口风格
参数:
HWND hWnd:一般指要修改的窗口的句柄
Int nIndex:要设置的风格类型,一般取下列值之一:
GWL_EXSTYLE :设置扩展风格
GWL_STYLE :设置新的风格
GWL_ID :给窗口设置一个新的ID
GWL_USERDATA :给窗口重新定义一个32位长的数字,一般窗口都有一个唯一32位长的数字来描述其各种属性
LONG dwNewLong:给窗口设置新值(风格),一般是通过GetWindowLong取得同样值后与需要的值取位与运算,或是位且取反运算来去掉该值!GetWindowLong只有前两个参数,返回值为风格(LONG)
这里再对动态链接库进行一下简单介绍。(Archie)
动态链接库(dynamics link library,DLL)是一个包含了若干个导出函数的可执行文件。与静态链接库(static link library,SLL)不同主要是连接时机不同。SLL是在编译、链接应用程序时就同程序相链接,链接由LINK完成,称为“静态链接”;而DLL则在程序运行时才同程序链接,由Windows操作系统来完成,称为“动态链接”。
使用静态链接库,必须执行Tool|Options命令,在Directories页面设置Library files静态链接库文件所在的路径。
使用DLL有隐式链接和显示链接,隐式链接是指在程序运行时,由Windows操作系统将要使用的DLL自动加载到应用程序,显示链接是指应用程序在执行的过程中,程序自己通过专门的函数(如LoadLibrary)调用来动态加载DLL。
采用隐式链接时,除DLL,还需提供DLL的导入库文件(即LIB文件),并且需要在VC开发环境中设置Project有关选项,在Project|Settings命令,Link页面Object/library modules输入库的路径和文件名。
采用显示链接,不需要LIB文件,DLL中的导出函数必须在模块定义文件(DEF文件)中进行EXPORTS说明。在程序中通过函数调用动态加载和卸载DLL,并通过函数指针调用导出函数。
Archie注:VC6 中 只要加入这段代码在顶部 就可以使用SetLayeredWindowAttributes了#define WS_EX_LAYERED 0x00080000 #define LWA_COLORKEY 0x00000001 #define LWA_ALPHA 0x00000002 typedef BOOL (FAR WINAPI *LAYERFUNC)(HWND,COLORREF,BYTE,DWORD); BOOL SetLayeredWindowAttributes(HWND hwnd,COLORREF crKey,BYTE bAlpha,DWORD dwFlags) { LAYERFUNC SetLayer; HMODULE hmod = LoadLibrary("user32.dll"); SetLayer=(LAYERFUNC)GetProcAddress(hmod,"SetLayeredWindowAttributes"); BOOL bReturn = SetLayer(hwnd,crKey,bAlpha,dwFlags); FreeLibrary(hmod); return bReturn; }
这也是一个显示使用DLL的例子。
typedef BOOL (FAR WINAPI *LAYERFUNC)(HWND,COLORREF,BYTE,DWORD);关于typedef此句理解
声明一个指向函数的指针类型,返回值为BOOL。
这句使用了typedef也就是说为声明重新定义了一个别名LAYERFUNC。
LAYERFUNC SetLayer;//声明一个指向函数的指针变量
SetLayer=(LAYERFUNC)GetProcAddress(hmod,"SetLayeredWindowAttributes");
获取函数的地址GetProcAddress函数检索指定的动态链接库(DLL)中的输出库函数地址。
函数原型:
FARPROC GetProcAddress(
HMODULE hModule, // DLL模块句柄
LPCSTR lpProcName // 函数名
);
函数参数类型分别为HWND,COLORREF,BYTE,DWORD
HMODULE可以用HINSTANCE替换HMODULE 是代表应用程序载入的模块,win32系统下通常是被载入模块的线性地址。
HINSTANCE 在win32下与HMODULE是相同的东西,在Win32下还存在主要是因为win16程序使用HINSTANCE来区别task。在头文件中HMODULE定义如下:typedef HINSTANCE HMODULE;再看看HINSTANCE定义,typedef HANDLE HINSTANCE;再看看HANDLE定义,typedef PVOID HANDLE;再看看PVOID定义,typedef void *PVOID;
函数指针
指针变量也可以指向一个函数。一个函数在编译时被分配给一个入口地址。这个函数入口地址就称为函数的指针。可以用一个指针变量指向函数,然后通过该指针变量调用此函数。
指向函数的指针变量的一般定义形式为
函数类型 (*指针变量名p)(函数形参表);
在定义指向函数的指针变量p时(*p)两侧的括号不可省略()优先级高于*,表示p先于*结合,它是指针变量,然后再与后面的()结合,表示此指针变量指向函数,这个函数值(即函数返回的值)为函数类型。
求函数地址时,只需将函数名赋给函数指针即可,不能带参数形式。函数名代表函数入口地址,而带参数则是函数调用。比如求两个数最大值原型
int max(int x,int y);
定义函数指针
int (*p)(int, int);
int a,b,m;
p=max; //函数名代表函数入口地址,入口地址赋给指针变量p
cin>>a>>b;
m=p(a,b);
此句和m=max(a, b)等价。
4、客户区全屏
单文档全屏
CMainFrame::CMainFrame()
: m_bFullScreen(false)
{
m_bAutoMenuEnable = false;
}
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CMDIFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;
……
FullScreen(false) ;
return 0;
}
void CMainFrame::FullScreen(BOOL)
{
LONG style = ::GetWindowLong(this->m_hWnd,GWL_STYLE);
//得到当前wnd
CWnd *pWnd = AfxGetMainWnd();
if (m_bFullScreen) // 全屏显示
{
pWnd->DrawMenuBar();
// 去掉边框
style&=~(WS_DLGFRAME | WS_THICKFRAME);
SetWindowLong(this->m_hWnd,GWL_STYLE,style);
// 最大化视窗
this->ShowWindow(SW_SHOWMAXIMIZED);
CRect rect;
//得到当前视窗口并稍做移动,保证彻底全屏,可能有的机器不同,会保留任务栏,大多数都 会全屏
this->GetWindowRect(&rect);
::SetWindowPos(this->m_hWnd,HWND_TOPMOST,rect.left, rect.top, rect.right-rect.left + 3, rect.bottom-rect.top + 3, SWP_FRAMECHANGED);
}
else//窗口显示
{
//得到菜单
CMenu menu;
//取菜单
menu.LoadMenu(IDR_MAINFRAME);
//设置菜单
pWnd->SetMenu(&menu);
pWnd->DrawMenuBar();
menu.Detach();
style |= WS_DLGFRAME | WS_THICKFRAME;
SetWindowLong(this->m_hWnd, GWL_STYLE, style);
this->ShowWindow(SW_SHOWNORMAL);
} ;
}
多文档全屏
先在构造函数中初始化m_bFullScreen:
CMainFrame::CMainFrame():m_bFullScreen( false){...}
PreCreateWindow()和OnCreate()函数可以不修改,用默认的.
void CMainFrame::FullScreen(BOOL)
{
if (m_bFullScreen) // 全屏显示
{ //保存旧窗口坐标位置
GetWindowRect( &m_rcOldWnd);
//隐藏菜单栏,状态栏等,以便全屏
m_wndStatusBar.ShowWindow( SW_HIDE);
m_wndToolBar.ShowWindow( SW_HIDE);
// SetMenu(NULL);//设置菜单为空 如果有此句,全屏时将看不到菜单栏
ModifyStyle( 0 ,WS_POPUP );
ModifyStyle( WS_CAPTION, 0);
ModifyStyle(WS_BORDER,0);
ModifyStyle(WS_THICKFRAME,0);
//将窗口大小设为全屏,
MoveWindow( 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN));
MDIGetActive()->ShowWindow( SW_SHOWMAXIMIZED);//将视图区占满整个客户区
}
else//窗口显示
{
//显示菜单栏,状态栏等
m_wndStatusBar.ShowWindow( SW_SHOW);
m_wndToolBar.ShowWindow( SW_SHOW);
//恢复到旧位置
MoveWindow( &m_rcOldWnd);
ModifyStyle( 0, WS_CAPTION );
ModifyStyle( 0, WS_BORDER);
ModifyStyle( 0, WS_THICKFRAME );
//注意,此处不能用WS_MAXIMIZE,否则位置会变为第一次窗口的位置
ModifyStyle( 0, WS_OVERLAPPED | FWS_ADDTOTITLE |WS_MINIMIZEBOX | WS_MAXIMIZEBOX /* |WS_MAXIMIZE*/ );
//重新加载菜单 当菜单栏也需要隐藏和显示时,在恢复菜单时加上此句,比如用键盘控制全屏
SetMenu( CMenu::FromHandle( ::LoadMenu( ::AfxGetApp()->m_hInstance ,MAKEINTRESOURCE( IDR_MFC_OSG_MDITYPE))));
//修改子窗口属性
MDIGetActive()->ModifyStyle( 0, WS_CHILD | WS_VISIBLE | WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU
| FWS_ADDTOTITLE /*| WS_THICKFRAME*/ | WS_MINIMIZEBOX | WS_MAXIMIZEBOX /*| WS_MAXIMIZE*/);
MDIGetActive()->ShowWindow( SW_SHOWNORMAL);//将子窗口视图区正常显示
ShowWindow(SW_SHOWNORMAL);//显示正常的主窗口
} ;
}
接着,在子窗口的ChildFrame.cpp里设置窗口属性:
BOOL CChildFrame::PreCreateWindow(CREATESTRUCT& cs)
{
// TODO: Modify the Window class or styles here by modifying the CREATESTRUCT cs
if( !CMDIChildWnd::PreCreateWindow(cs) )
return FALSE;
//注意,要全屏千万不能有WS_THICKFRAME
cs.style = WS_CHILD | WS_VISIBLE | WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU
| FWS_ADDTOTITLE /*| WS_THICKFRAME */| WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_MAXIMIZE;
return TRUE;
}
总结:要实现全屏效果,可隐藏标题,菜单和工具,状态栏等,然后把子窗口最大化即可.本文还有一些缺陷,如没有考虑到滚动条等.
键盘控制全屏
由于视图类处理键盘和鼠标消息更为方便,我们把处理函数放在View类中,有几种处理方式
第一种比如按键F等字母键,直接在OnKeyDown消息中处理,这里全屏去掉了菜单项
#include "MainFrm.h"
void CMFC_OSG_MDIView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
// Pass Key Presses into OSG
mOSG->getViewer()->getEventQueue()->keyPress(nChar);
switch(nChar)
{
case VK_ESCAPE:
{
GetParent()->SendMessage(WM_CLOSE);
break;
}
case 'F': //全屏控制
{
((CMainFrame*)::AfxGetMainWnd())->OnFullscreen();
break;
}
}
}
第二种在PreTranslateMessage()中处理链接
或在OnChar等中处理
音乐播放
音乐播放最好放在CMainFrame中,因为菜单频繁的控制VIEWER类,有几率可能会使音乐中断且在框类中调用
在程序开始创建时便开始播放音乐,并不是在打开模型后或是在打开模型时,在换场景等等对VIEWER类的操作中,不会使音乐受到任何影响,有时候需要重新加载VIEWER类来释放内存,那么音乐将会中断,而在CMainFrame中将不会出现这种情况!
---------------------------
Archie注:关于MCI可以参考利用MCI制作音乐播放器
MCI ( Media Control Interface ) ,即媒体控制接口,向基于Windows操作系统的应用程序提供了高层次的控制媒体设备接口的能力。
相关程序
MCIERROR mciSendString(
LPCTSTR lpszCommand, //MCI命令字符串
LPTSTR lpszReturnString, //存放反馈信息的缓冲区
UINT cchReturn, //缓冲区的长度
HANDLE hwndCallback //回调窗口的句柄,一般为NULL
); //若成功则返回0,否则返回错误码。
BOOL mciGetErrorString(
DWORD fdwError, //函数mciSendString或mciSendCommand返回的错误码
LPTSTR lpszErrorText, //接收描述错误的字符串的缓冲区
UINT cchErrorText //缓冲区的长度
);
下面是使用mciSendString函数的一个简单例子:
char buf[50];
MCIERROR mciError;
mciError=mciSendString(“open cdaudio”,NULL,0,NULL);
if(mciError)
{
mciGetErrorString(mciError,buf,sizeof(buf));
AfxMessageBox(buf);
return;
}
----------------------------------
下面来介绍本节中使用的“发声函数”,在MCI中,往往会创立一个窗口,我们不需要此窗口,故而需要把窗口来隐藏掉:
函数: HWND MCIWndCreate( HWND hwndParent, HINSTANCE hInstance, DWORD dwStyle, LPSTR szFile);
功能:该函数创建MCI窗口 ,(MCI:Media Control Interface).
参数:
hwndParent:父窗口句柄,将被挂名在此窗口下
hInstance:创建一个实例与MCIWindow相关!
dwStyle:指定风格,类似与CreateWindowEx的功能,也可以用之实现,该标识一般取下列值:
MCIWNDF_NOAUTOSIZEWINDOW MCI:具有此风格窗口的大小将不会改变。
MCIWNDF_NOAUTOSIZEMOVIE :同上。
MCIWNDF_NOERRORDLG :继承ERROR对话框给USER MCIWNDF_NOMENU :隐藏菜单,一般丢失的菜单上的选项可以在系统菜单上找到 MCIWNDF_NOOPEN :在面板上隐藏打开菜单,将从系统菜单上增加打开选项
MCIWNDF_NOPLAYBAR :隐藏工具栏
MCIWNDF_NOTIFYANSI :在向父窗口发送设置参数修改消息时,让MCIWnd使用ANSI字符替换Unicode字符只能与 MCIWNDF_NOTIFYMODE 配合使用在NT及2000以上的系统之上。
MCIWNDF_NOTIFYMODE 向系统发送MCIWNDM_NOTIFYMODE 消息,无论当前在进行什么操作,且在lparam中指定新值如 MCI_MODE_STOP。 MCIWNDF_NOTIFYPOS 当对播放器指定回放操作时将会发生此消息且记下当前回放位置
MCIWNDF_NOTIFYMEDIA当打开文件时会发送该消息,lparam中存有新文件之路径串
MCIWNDF_NOTIFYSIZE 当窗口大小改变时会发送此消息
MCIWNDF_NOTIFYERROR 向主窗口发送错误消息
MCIWNDF_NOTIFYALL 使所有的NODIFY风格被使用
MCIWNDF_RECORD 在面板上增加Record键以完成其功能
MCIWNDF_SHOWALL 使所有的MCIWNDF_SHOW 风格被使用
MCIWNDF_SHOWMODE 当前配置模式被显示在WINDOWS标题栏中,当前模式可以通过MCIWndGetMode宏得到
MCIWNDF_SHOWNAME 在MCIWindow的标题栏中显示当前文件名
MCIWNDF_SHOWPOS 在MCIWindow的标题栏中显示当前播放进度 LPSTR szFile:指定要播放的文件名字串,为平凡字串(意思为非宽字符)
函数: BOOL ShowWindow( HWND hWnd, int nCmdShow);
功能:隐藏或显示指定窗口
参数:
HWND hWnd:指定要隐藏或要显示的窗口
Int nCmdShow: 参数一般习惯为取TRUE|true|1为显示,取FALSE|false|0为隐藏,而实际上01只是它们的前两个值,一般它可以取下列值:
SW_HIDE :隐藏窗口,同时激活其它窗口
SW_SHOW :显示窗口且激活其在当前位置
SW_SHOWNA:显示窗口正常如果在激活仍旧在激活,一般不使用这个
SW_SHOWNORMAL :正常显示窗口,如果在最小化之中将会被赋以焦点从而以正常尺寸在当前位置显示,这个一般在第一次创建窗口时使用, 关于MCI的更多内容请查阅MSDN:
下面我们来播放音乐,在上一章例子的基础上做一些改动:
第一步
把要播放的音乐(建议使用.MP3,.WAV过大)拷至文件根目录下,否则需要在播放文件名前加路径,在调试程序编译运行时的根目录指的是放有很多.CPP,.H的这个当前目录,而独立运行指的是可执行程序所在目录。
第二步
点击菜单:项目属性页,在弹出的对话框中,配置项默认为对当前活动Debug进行配置,如果不调试,而发布Release则需要对Release进行配置,一般来讲Debug版本中包含更多的信息,但是Release适合发布,一般文件比较小。
点击下面列表中的链接器,按如图配置:
在附加选项中添加vfw32.lib这是链接文件,在OSG中最经常使用与制做的功能模块便是WIN32 DLL,更加方便的复用,ActriveX控件适合做成品的功能模块,而DLL更适合某些具体的功能模块,如某DLL可到处实现对任何OSG程序任何场景的漫游,只需要添加不多的几行代码,而ActriveX更适合已经做成的小漫游场景用在VS的其它类型的程序框中,最经常见到的是VB与WEB。
下面的配置也可以使用另一种方法,可以在属性页(如下图)的链接器输入右边面板的附加依赖项中添加vfw32.lib这样的话所加内容将会出现在如图示的右边所有选项的灰色框中,读者可自己下去试一下。
也可以通过语句添加,在stdafx.h中加入如下语句#pragma comment(lib, "vfw32.lib")便可动态的加入vfw32.lib文件。