Lesson7:定制应用程序外观
应用程序外观是用户体验中一个重要因素,优美的程序外观可以提升程序逼格。本文主要讲解了MFC中应用程序外观的修改,主要包括任务栏、工具栏、启动画面等。
因为应用程序的外观主要是在程序的框架窗口上,通过框架类体现的,所以在定制程序外观的时候,主要是在框架类进行修改。如果要修改应用程序框架外观大小,通常在窗口创建之前。可以在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类里使用。
如果我们已经创建了窗口,那么在窗口创建之后如果要修改窗口外观,需要在CMainFrame类和View类里的OnCreate()里通过SetWindowLong()函数进行操作,下面是两个例子的具体代码。
SetWindowLong(m_hWnd,GWL_STYLE, WS_OVERLAPPEDWINDOW); //取消了窗口名称
SetWindowLong(m_hWnd,GWL_STYLE, GetWindowLong(m_hWnd, GWL_STYLE) & ~WS_MAXIMIZEBOX); //取消了最大化
如果我们需要在窗口显示中有几幅图标轮流显示,该怎么做呢?首先设计我们要加载的图标,然后加载图标到图标数组中。假设我们添加了三幅图标,这里需要添加含有三个元素的图标数组,
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); }
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); }
显示系统时间同样是在窗口建立之后的动作,所以需要在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); }
添加启动画面时,需要准备一幅位图资源,当我们操作资源的时候,一般是和类相关联的。
①添加一个类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); }