Lesson7:定制应用程序外观
应用程序外观是用户体验中一个重要因素,优美的程序外观可以提升程序逼格。本文主要讲解了MFC中应用程序外观的修改,主要包括任务栏、工具栏、启动画面等。
1. 程序窗口的图标、背景、光标修改
1.1 窗口创建之前修改
因为应用程序的外观主要是在程序的框架窗口上,通过框架类体现的,所以在定制程序外观的时候,主要是在框架类进行修改。如果要修改应用程序框架外观大小,通常在窗口创建之前。可以在CMainFrame类里的PreCreateWindow()函数中实现。如果我们要修改窗口的图标、背景、光标,应该怎么修改?窗口的类型,大小是在创建窗口设定的,但前面Windows程序运行机制我们了解了如何创建一个窗口,它是通过一个窗口类,在这个类里设计窗口的时候,就设计好了图标、背景、光标,现有的窗口类的设计是MFC帮我们在底层已经设计好了,虽然我们不能改变这个底层代码,但我们可以重新设计一个窗口类,然后用自己的这个窗口类进行修改图标、背景、光标。
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT&cs) //窗口创建之前修改应用程序框架大小
{
if( !CFrameWnd::PreCreateWindow(cs) )
return FALSE;
// TODO: 在此处通过修改
// CREATESTRUCT cs 来修改窗口类或样式
/********************* 窗口创建之前修改应用程序框架大小 *****************/
cs.cx = 300; //修改窗口大小(300*400大小)
cs.cy = 400;
//cs.style = ~FWS_ADDTOTITLE; //修改标题栏名字,因为会选用默认的名字,所以将默认的选项取反
//cs.style = cs.style & ~FWS_ADDTOTITLE; //这种方式和上面方式的效果一样,选一种即可
cs.style = WS_OVERLAPPEDWINDOW; //这种方式和上面方式的效果一样,选一种即可
cs.lpszName = "http"; //窗口名称
/********************** 自定义窗口类,然后注册使用 *****************/
WNDCLASS wndcls;
wndcls.cbClsExtra = 0; //类的额外内存不需要
wndcls.cbWndExtra = 0; //窗口的类的额外内存不需要
wndcls.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); //窗口背景
wndcls.hCursor=LoadCursor(NULL,IDC_HELP); //窗口光标
wndcls.hIcon = LoadIcon(NULL,IDI_ERROR); //窗口tubiao
wndcls.hInstance = AfxGetInstanceHandle(); //获得当前应用程序的句柄
wndcls.lpfnWndProc = ::DefWindowProc; //窗口过程函数
wndcls.lpszClassName ="SUNXING"; //类的名称
wndcls.lpszMenuName = NULL; //菜单的名称,虽然设置为NULL,但会用到MFC自带的菜单
wndcls.style = CS_HREDRAW | CS_VREDRAW; //窗口类的类型,水平和垂直重绘
RegisterClass(&wndcls); //注册窗口
cs.lpszClass = "SUNXING"; //修改cs结构体成员变量的类名,用它来重新设置窗口样式
//cs.lpszClass = AfxRegisterWndClass(CS_HREDRAW| CS_VREDRAW, 0, 0, LoadIcon(NULL, IDI_WARNING)); //不用注册窗口类,直接修改窗口样式
return TRUE;
}
上面我们通过窗口类重新设计与注册使用后发现,只有图标发生了改变,而光标和背景没有改变,这是因为,程序在外观上是view类覆盖在frame类之上的,光标和背景消息首先被view类在获得响应,所以我们在frame类里修改后看不到效果。所以我们在view类里的PreCreateWindow()函数里添加一句代码,修改窗口类为我们以设计的类,就可以看到效果。 cs.lpszClass = "SUNXING";
如果我们仅仅需要修改图标、光标、背景而去重新设计窗口类,那就太麻烦了,MFC提供了取件函数进行直接修改。
LPCTSTR AFXAPIAfxRegisterWndClass( UINT nClassStyle, HCURSORhCursor= 0, HBRUSH hbrBackground = 0,HICON hIcon = 0 );
上面注释起来的就是这个函数的应用,同理这个函数可以在view类里使用。
1.2 窗口创建之后修改
如果我们已经创建了窗口,那么在窗口创建之后如果要修改窗口外观,需要在CMainFrame类和View类里的OnCreate()里通过SetWindowLong()函数进行操作,下面是两个例子的具体代码。
SetWindowLong(m_hWnd,GWL_STYLE, WS_OVERLAPPEDWINDOW); //取消了窗口名称
SetWindowLong(m_hWnd,GWL_STYLE, GetWindowLong(m_hWnd, GWL_STYLE) & ~WS_MAXIMIZEBOX); //取消了最大化
1.3 循环显示窗口的图标
如果我们需要在窗口显示中有几幅图标轮流显示,该怎么做呢?首先设计我们要加载的图标,然后加载图标到图标数组中。假设我们添加了三幅图标,这里需要添加含有三个元素的图标数组,
HICON m_hIcons[3]。并通过添加timer函数定时地不断地切换图标。循环显示图标显然是在窗口建立了以后进行的,所以理所当然的在CMainFrame类的OnCreate()函数中添加代码。
int CMainFrame::OnCreate(LPCREATESTRUCTlpCreateStruct)
{
//三种方式加载图标,三选一就行
m_hIcons[0] = LoadIcon(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDI_ICON1));
m_hIcons[1] = LoadIcon(theApp.m_hInstance,MAKEINTRESOURCE(IDI_ICON2));//这里需要将另一个源文件定义的theApp变量声明一下,延伸作用域。
m_hIcons[2] = LoadIcon(AfxGetApp()->m_hInstance,MAKEINTRESOURCE(IDI_ICON3));
SetClassLong(m_hWnd, GCL_HICON, (LONG)m_hIcons[0]);//先将第一幅图标加载上
SetTimer(1,1000,NULL); //设置定时器 调用定时器函数timer()
return 0;
}
添加timer函数。
void CMainFrame::OnTimer(UINT_PTRnIDEvent)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
static int index = 2; //index定义为静态变量
SetClassLong(m_hWnd, GCL_HICON, (LONG)m_hIcons[index]);
index = (++index) % 3;
CFrameWnd::OnTimer(nIDEvent);
}
2. 状态栏中显示鼠标位置和系统时间
2.1 显示鼠标位置
View类窗口覆盖在frame类窗口之上,鼠标是在view类窗口里动作的,所以这里捕获鼠标消息首先要在view类里添加一个鼠标移动的消息,来获得鼠标的位置。
void CStyleView::OnMouseMove(UINTnFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
CString str;
str.Format("x=%d,y=%d",point.x,point.y);
//几种在状态栏里获得放置鼠标位置的方式
//((CMainFrame*)GetParent())->m_wndStatusBar.SetWindowText(str);//通过获取框架类窗口的指针,调用框架类定义的状态栏对象,用对象的方法设定文本
//((CMainFrame*)GetParent())->SetMessageText(str);
//((CMainFrame*)GetParent())->GetMessageBar()->SetWindowText(str);
GetParent()->GetDescendantWindow(AFX_IDW_STATUS_BAR)->SetWindowText(str);
CView::OnMouseMove(nFlags, point);
}
2.2 显示系统时间
显示系统时间同样是在窗口建立之后的动作,所以需要在OnCreate()函数里不断的调用timer()函数,这里只需要在OnCreate()函数里添加一句调用定时器的代码,其余代码都是向导自动生成的。SetTimer(1, 1000, NULL); //设置定时器, 调用定时器函数timer()
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if(CFrameWnd::OnCreate(lpCreateStruct) == -1)
return-1;
if(!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_GRIPPER| CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
{
TRACE0("未能创建工具栏\n");
return-1; // 未能创建
}
if(!m_wndStatusBar.Create(this))
{
TRACE0("未能创建状态栏\n");
return-1; // 未能创建
}
m_wndStatusBar.SetIndicators(indicators,sizeof(indicators)/sizeof(UINT));
//TODO: 如果不需要可停靠工具栏,则删除这三行
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(&m_wndToolBar);
SetTimer(1,1000, NULL); //设置定时器, 调用定时器函数timer()
return0;
}
添加定时器函数,在这个函数里进行响应
void CMainFrame::OnTimer(UINT_PTR nIDEvent)
{
//TODO: 在此添加消息处理程序代码和/或调用默认值
CTimet = CTime::GetCurrentTime();
CStringstr = t.Format("%H:%M:%S");
CClientDCdc(this); //定义一个dc对象,用来获得str的尺寸,调整状态栏里时钟显示框的大小
CSizesz = dc.GetTextExtent(str);
//intindex = m_wndStatusBar.CommandToIndex(IDS_TIMER); //如果不知道时钟在状态栏的第几个窗格,可以这样找到
m_wndStatusBar.SetPaneInfo(1,IDS_TIMER, SBPS_NORMAL, sz.cx);
m_wndStatusBar.SetPaneText(1,str); //1就是前面的可以用index代替的数,通过状态栏的对象调用函数,其中函数有缺省的参数值
CFrameWnd::OnTimer(nIDEvent);
}
3. 添加启动画面
添加启动画面时,需要准备一幅位图资源,当我们操作资源的时候,一般是和类相关联的。
①添加一个类CWzdSplash,基类是CWnd,并在类里定义一个位图变量CBitmapm_bitmap;
②通过向导添加消息响应函数OnPaint()和 手动添加自己的函数Create()。
准备工作做好后我们来实现启动画面三部曲。
①在frame类的OnCreate()函数里添加代码
int CMainFrame::OnCreate(LPCREATESTRUCTlpCreateStruct)
{
CWzdSplashwndSplash; //创建启动窗口类的实例
wndSplash.Create(); //CWzdSplash类里定义的函数
wndSplash.CenterWindow(); //启动画面出现在屏幕中间
wndSplash.UpdateWindow(); //send WM_PAINT
Sleep(2000); //画面停留时间2s
wndSplash.DestroyWindow(); //销毁初始画面窗口
}
②在CWzdSplash的OnPaint()函数中添加代码
void CWzdSplash::OnPaint()
{
CPaintDCdc(this); // device context for painting
//TODO: 在此处添加消息处理程序代码
//不为绘图消息调用 CWnd::OnPaint()
BITMAPbitmap;
m_bitmap.GetBitmap(&bitmap);
CDCdcComp;
dcComp.CreateCompatibleDC(&dc);
dcComp.SelectObject(&m_bitmap);
//draw bitmap
dc.BitBlt(0,0, bitmap.bmWidth, bitmap.bmHeight, &dcComp, 0, 0, SRCCOPY);
}
③在CWzdSplash的Create()函数中添加代码
void CWzdSplash::Create()
{
m_bitmap.LoadBitmap(IDB_BITMAP1);
BITMAPbitmap;
m_bitmap.GetBitmap(&bitmap);
//CreateEx(0,AfxRegisterWndClass(0),"",WS_POPUP|WS_VISIBLE|WS_BORDER,0,0,bitmap.bmWidth,bitmap.bmHeight,NULL,0);
CreateEx(0,AfxRegisterWndClass(0, AfxGetApp()->LoadStandardCursor(IDC_ARROW)),
NULL,WS_POPUP | WS_VISIBLE, 0, 0, bitmap.bmWidth, bitmap.bmHeight, NULL, NULL);
}