Lesson 9 修改应用程序的外观,工具栏、状态栏编程
 
第一部分 改变应用程序的外观
一、 问题:要修改一个应用程序的外观,应该在应用程序创建之前还是在创建之后修改呢?
修改一幢楼房应在建成之前,应在窗口创建之前修改。要改变一个框架窗口的外观,应在CMainFrame::PreCreateWindow()中去改变,
 
CREATESTRUCT cs结构体的类型和个数与创建窗口的CreateWindowEx()的个数和类型是完全一致的。只是顺序正好相反。
PreCreateWindow(cs)的参数cs被声明为一个引用类型,如果在子类中修改了cs的值,这种改变会反应到MFC的底层代码中。
 
1、修改窗口的大小:
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
       if( !CFrameWnd::PreCreateWindow(cs) )
              return FALSE;
       // TODO: Modify the Window class or styles here by modifying
       // the CREATESTRUCT cs
       cs.cx=300;
       cs.cy=200;
       return TRUE;
}
2、修改窗口的标题
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
       if( !CFrameWnd::PreCreateWindow(cs) )
              return FALSE;
       // TODO: Modify the Window class or styles here by modifying
       // the CREATESTRUCT cs
       cs.lpszName="http://www.sunxin.org";
       return TRUE;
}
标题并没有被改变。
分析:程序是一个SDI应用程序,打开Word时,它的标题栏显示的一个“文档1”标题,
在MSDN中,Window Styles
在一个SDI应用程序中,缺省的窗口类型是WS_OVERLAPPEDWINDOW 和FWS_ADDTOTITLE的组合。
FWS_ADDTOTITLE 用来增加文档的标题到窗口的标题
去掉FWS_ADDTOTITLE属性。
cs.style&=~FWS_ADDTOTITLE;   //取反,并进行与操作。
cs.style=WS_OVERLAPPEDWINDOW; //直接赋值
 
二、问题:在窗口创建之后能不能修改外观和大小呢?
The SetWindowLong function changes an attribute of the specified window
LONG SetWindowLong(         
HWND hWnd,
    int nIndex,     GWL_STYLE
    LONG dwNewLong
);
在OnCreate函数中去改变
 
在所有的窗口对象(C++对象)中都有一个公有的成员变量m_hWnd,保存了与对象相关的窗口的句柄。
 
SetWindowLong(m_hWnd,GWL_STYLE,WS_OVERLAPPEDWINDOW);
问题:在使用SetWindowLong设置窗口类型里,想获得现有的窗口类型,怎么进行?
LONG GetWindowLong(         
HWND hWnd, //窗口句柄
    int nIndex       //常量,指定窗口的那种信息 如:GWL_STYLE
);
获取现有类型,去掉最大化按钮
SetWindowLong(m_hWnd,GWL_STYLE,GetWindowLong(m_hWnd,GWL_STYLE)
 & ~WS_MAXIMIZEBOX);
三、              如果要修改窗口的图标、光标和背景,应该如何去修改?
图标、光标和背景是在设计窗口类时被指定的,窗口类的设计和注册是由MFC的底层代码自动实现的,不可能去修改MFC的底层代码,如何修改?

一、自己编写窗口类,并注册,让随后的窗口的创建按照我们设计好的窗口类去创建。
在PreCreateWindow()中编写一个窗口类并注册之。
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);
       wndcls.hInstance=AfxGetInstanceHandle();//注意用全局函数获取应用程序hInstance句柄
       wndcls.lpfnWndProc=::DefWindowProc;//必须指定为API函数,不同于CWnd中的DefWindowProc(参数个数不同)
       wndcls.lpszClassName="sunxin.org";
       wndcls.lpszMenuName=NULL;//菜单的创建并不是在设计窗口类时创建的,由MFC在构造pDocTemplate = new CSingleDocTemplate(IDR_MAINFRAME,…) 被创建。
       wndcls.style=CS_HREDRAW | CS_VREDRAW;
 
       RegisterClass(&wndcls);
 
       cs.lpszClass="sunxin.org";//precreatewindow()中判lpszClass是否为空,不为空说明已注册
编译并运行,发现只有图标被改变,背景、光标未改变,为什么?
答:view始终履盖在frame窗口上,必须在view类的precreatewindow中,指定窗口类为刚才自己编写的这个窗口类。
BOOL CStyleView::PreCreateWindow(CREATESTRUCT& cs)
{
      
       cs.lpszClass="sunxin.org"; //这个窗口类已经注册了,只要指定即可
      
       return CView::PreCreateWindow(cs);
}

四、在框架窗口中只能修改图标,为了修改图标需要重写窗口类,太麻烦,MFC中提供了一个全局函数,AfxRegisterWndClass()修改、设定一个窗口类的类型,光标、背景、图标。返回一个注册成功的窗口类类名。如果只修改类的类型(wndcls.style),只要第一个参数,其它取默认值。
 
LPCTSTR AFXAPI AfxRegisterWndClass(
   UINT nClassStyle,
   HCURSOR hCursor = 0,
   HBRUSH hbrBackground = 0,
   HICON hIcon = 0
);
1、 改变框架窗口的图标
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
       if( !CFrameWnd::PreCreateWindow(cs) )
              return FALSE;
cs.lpszClass=AfxRegisterWndClass(CS_HREDRAW
 | CS_VREDRAW,0,0,LoadIcon(NULL,IDI_WARNING));//
       return TRUE;
}
2、改变光标和背景,在view中
BOOL CStyleView::PreCreateWindow(CREATESTRUCT& cs)
{
       cs.lpszClass=AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW,
       LoadCursor(NULL,IDC_CROSS),(HBRUSH)GetStockObject(BLACK_BRUSH),0);
              return CView::PreCreateWindow(cs);
}
3、只给AfxRegisterWndClass()的第一个参数赋值,其余取缺省值,cs.lpszClass=AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW);
发现:图标变成wave-flag,光标是箭头形状,背景变成透明。
  
MSDN:
hCursor
Specifies a handle to the cursor resource to be installed in each window created from the window class. If you use the default of 0, you will get the standard IDC_ARROW cursor.
hbrBackground
Specifies a handle to the brush resource to be installed in each window created from the window class. If you use the default of 0, you will have a NULL background brush, and your window will, by default, not erase its background while processing WM_ERASEBKGND.
hIcon
Specifies a handle to the icon resource to be installed in each window created from the window class. If you use the default of 0, you will get the standard, waving-flag Windows logo icon.

五、以上都是在窗口创建之前,在窗口创建之后,还能不能修改它的图标、光标背景?
DWORD SetClassLong(         
HWND hWnd,
    int nIndex,
    LONG dwNewLong
);
The SetClassLong function replaces the specified 32-bit (long) value at the specified offset into the extra class memory or the WNDCLASSEX structure for the class to which the specified window belongs.
DWORD GetClassLong(         
HWND hWnd,
    int nIndex
);
The GetClassLong function retrieves the specified 32-bit (long) value from the WNDCLASSEX structure associated with the specified window
1、 在框架窗口中修改图标,在CMainFrame:: OnCreate() 函数中:
SetClassLong(m_hWnd,GCL_HICON,(LONG)LoadIcon(NULL,IDI_ERROR));
2、 在View中修改光标、背景,在CStyleView:: OnCreate() 函数中
SetClassLong(m_hWnd,GCL_HBRBACKGROUND,(LONG)GetStockObject(BLACK_BRUSH));
       SetClassLong(m_hWnd,GCL_HCURSOR,(LONG)LoadCursor(NULL,IDC_HELP));
3、 利用SetClassLong()实现不断变化的图标(图标的动画效果)
在CMainFrame:: OnCreate()安装一个定时器。
初始化一个存储图标的句柄的数组(在CMainFrame增加m_hIcons[3]成员变量),注意MAKEINTRESOURCE宏的使用,以及三种获当前实例句柄的方法。
m_hIcons[0]=LoadIcon(AfxGetInstanceHandle(),MAKEINTRESOURCE(IDI_ICON1));
m_hIcons[1]=LoadIcon(theApp.m_hInstance,MAKEINTRESOURCE(IDI_ICON2));
m_hIcons[2]=LoadIcon(AfxGetApp()->m_hInstance,MAKEINTRESOURCE(IDI_ICON3));
 
LoadIcon()的第二个参数要求一个字符指针,但我们只有图标的ID号,ID号转换为LPTSTR指针类型,MAKEINTRESOURCE()
 
LPTSTR MAKEINTRESOURCE(
    WORD wInteger
);
 
The MAKEINTRESOURCE macro converts an integer value to a resource type compatible with the resource-management functions. This macro is used in place of a string containing the name of the resource.
 
 
添架定时器的响应函数CMainFrame::OnTimer(UINT nIDEvent)
void CMainFrame::OnTimer(UINT nIDEvent)
{
static int index=1;
SetClassLong(m_hWnd,GCL_HICON,(LONG)m_hIcons[index]);
index=++index%3;
当Index为0时,模3,商为0,余数0-0=0,
当Index为1时,模3,商为0,余数1-0=1
当Index为2时,模3,商为0,余数2-0=2
当Index为3时,模3,商为1,余数3-3=0
 
CTime t=CTime::GetCurrentTime();
CString str=t.Format("%H:%M:%S");
CClientDC dc(this);
CSize sz=dc.GetTextExtent(str);
m_wndStatusBar.SetPaneInfo(1,IDS_TIMER,SBPS_NORMAL,sz.cx);
m_wndStatusBar.SetPaneText(1,str);
 
m_progress.StepIt();
 
CFrameWnd::OnTimer(nIDEvent);
}
 

第二部分
一、工具栏的编程:
在一些按钮与按钮之间有一些分隔符,如何产生这种分隔符,拖动一个按钮向右移动一段距离
如何删除一个按钮,把这个按钮拖出工具栏即可。Del键是删除按钮上的图像。
创建工具栏的步骤:(两种方法)
Visual C++ provides you with two methods to create a toolbar. To create a toolbar resource using the Resource Editor, follow these steps:
 
1、Create a toolbar resource.
2、Construct the CToolBar object.
3、Call the Create (or CreateEx) function to create the Windows toolbar and attach it to the CToolBar object.
4Call LoadToolBar to load the toolbar resource.
Otherwise, follow these steps:
 
1、Construct the CToolBar object.
2、Call the Create (or CreateEx) function to create the Windows toolbar and attach it to the CToolBar object.
3、Call LoadBitmap to load the bitmap that contains the toolbar button p_w_picpaths.
4、Call SetButtons to set the button style and associate each button with an p_w_picpath in the bitmap.
两个EnableDocking函数的比较:
CControlBar::EnableDocking 让工具栏可以停靠。
Call this function to enable a control bar to be docked.
 
void EnableDocking(
   DWORD dwDockStyle
);
 
CFrameWnd::EnableDocking 让框架窗口可以被停靠。
CFrameWnd Overview | Class Members | Hierarchy Chart | CControlBar::EnableDocking | CFrameWnd::DockControlBar | CFrameWnd::FloatControlBar
Call this function to enable dockable control bars in a frame window.
 
void EnableDocking(
   DWORD dwDockStyle
);
 
创建工具栏:
1、 构造一个工具栏对象
protected:
CToolBar    m_wndToolBar;
CToolBar       m_newToolBar;
 
2、创建工具栏对象,加载工具栏资源
if (!m_newToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
        !m_ newToolBar.LoadToolBar(IDR_TOOLBAR1)
{
        TRACE0("Failed to create toolbar\n");
        return -1;      // fail to create
}
m_newToolBar..EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(&m_ newToolBar); //停靠在创建时规定的默认位置
3、 创建一个菜单项,响应工具栏的显示和隐藏。
①第一种显示工具栏的方法:
菜单项的响应函数void CMainFrame::OnViewNewtool()
{
// TODO: Add your command handler code here
/*if(m_newToolBar.IsWindowVisible())
{
        m_newToolBar.ShowWindow(SW_HIDE);
}
else
{
        m_newToolBar.ShowWindow(SW_SHOW);
}
RecalcLayout();
DockControlBar(&m_newToolBar);*/ ShowControlBar(&m_newToolBar,!m_newToolBar.IsWindowVisible(),FALSE);
}
 
分析:RecalcLayout() 不调用这个函数,只会隐藏工具栏上的按钮,而不会隐藏工具栏,当工具栏被显示或隐藏之后,其它的控制栏的位置可能会有所变动,需要调用一个函数RecalcLayout()重新调整它们的位置,
DockControlBar(&m_newToolBar)的调用分析:
如果把工具栏拖放使其处于浮动状态,隐藏,发现只会隐藏工具栏上的按钮,而不会隐藏工具栏,当工具栏显示或隐藏后,需要再次调用DockControlBar()。但这样做,只会使工具栏停靠在top位置,而不会停靠在原来浮动的位置。
RecalcLayout()
Called by the framework when the standard control bars are toggled on or off or when the frame window is resized.
 
virtual void RecalcLayout(
   BOOL bNotify = TRUE
);
Parameters
bNotify
Determines whether the active in-place item for the frame window receives notification of the layout change. If TRUE, the item is notified; otherwise FALSE.
 
如果把工具栏拖放使其处于浮动状态,隐藏,再显示它,发现它不会在原来浮动的位置显示,而是在TOP位置显示,怎么让它仍在原来浮动的位置显示呢?
ShowControlBar函数的使用可以解决这个问题:
②第二种显示工具栏的方法:(最简单)
void CMainFrame::OnViewNewtool()
{
        ShowControlBar(&m_newToolBar,!m_newToolBar.IsWindowVisible(),FALSE);
}
 
ShowControlBar(&m_newToolBar,!m_newToolBar.IsWindowVisible(),FALSE);
MSDN:
CFrameWnd::ShowControlBar
Call this member function to show or hide the control bar.
 
void ShowControlBar(
   CControlBar* pBar,
   BOOL bShow,
   BOOL bDelay
);
Parameters
pBar
Pointer to the control bar to be shown or hidden.
bShow
If TRUE, specifies that the control bar is to be shown. If FALSE, specifies that the control bar is to be hidden.
bDelay
If TRUE, delay showing the control bar. If FALSE, show the control bar immediately
 
4、 生成菜单项的命令更新响应函数:
void CMainFrame::OnUpdateViewNewtool(CCmdUI* pCmdUI)
{
pCmdUI->SetCheck(m_newToolBar.IsWindowVisible());
}
 
第三部分 状态栏的编程
状态栏中分为两类,第一类是最左边长的这一条为提示行,
第二类为以窗格形式排列,主要用于显示一些按键的开关状态,称为状态指示器。
状态栏的创建:
1、 为CMainFrmae增加一个成员变量
CStatusBar m_wndStatusBar;
2、 在OnCreate()函数中创建状态栏
if (!m_wndStatusBar.Create(this) ||
        !m_wndStatusBar.SetIndicators(indicators,
         sizeof(indicators)/sizeof(UINT)))
{
        TRACE0("Failed to create status bar\n");
        return -1;      // fail to create
}
分析:SetIndicators(indicators, sizeof(indicators)/sizeof(UINT)))
这个函数使用了一个indicators数组,该数组在CMainFram.cpp中被定义为静态变量:
static UINT indicators[] =
{
ID_SEPARATOR,           // status line indicator表示了状态栏中最长的那一部分
IDS_TIMER,
IDS_PROGRESS,
ID_INDICATOR_CAPS,
ID_INDICATOR_NUM,
ID_INDICATOR_SCRL,
};
如果要修改状态栏中窗格的数目,要在string table中定义这些字符串资源,再在incicators数组中添加字符串资源的ID。
一、以增加时钟为例:
IDS_TIMER
IDS_PROGRESS
1、 定义这两个资源ID。添加到indicators数组中。
2、 设置一个定时器,添加这个定时器的响应函数
void CMainFrame::OnTimer(UINT nIDEvent)
{
       CTime t=CTime::GetCurrentTime();//类的静态员函数
       CString str=t.Format("%H:%M:%S");
       CClientDC dc(this);
       CSize sz=dc.GetTextExtent(str);//取得字体宽高等值
       m_wndStatusBar.SetPaneInfo(1,IDS_TIMER,SBPS_NORMAL,sz.cx);
       m_wndStatusBar.SetPaneText(1,str);
CFrameWnd::OnTimer(nIDEvent);
}
 
要点:获得字符串显示时的宽度,需要调用GetTextMatrix()还是GetTextExtent(),
二、进度条
CProgressCtrl::Create
Creates a progress bar control and attaches it to a CProgressCtrl object.
 
virtual BOOL Create(
   DWORD dwStyle,
   const RECT& rect,
   CWnd* pParentWnd,
   UINT nID
);
 
m_progress.Create(WS_CHILD | WS_VISIBLE | PBS_VERTICAL,//垂直进度条
       CRect(100,100,120,200),this,123); //this 指定框架为进度栏的父窗口
m_progress.SetPos(50) //设置进度为50%
 
问题:为了把进度条放在状态栏指定的窗格中,应如何做?
分析:需要获得指定窗格的矩形区域,然后在创建这个进度条时,把获得的矩形区域做为参数传给进度条的创建函数
CRect rect;
m_wndStatusBar.GetItemRect(2,&rect); //获取某一窗格的矩形区域
m_progress.Create(WS_CHILD | WS_VISIBLE,// | PBS_VERTICAL,
              rect,&m_wndStatusBar,123);//做为参数传进去(粗体部分)
不足之处:
因为在OnCreate()函数中,状态栏的窗格还未完成初始化,故无法得到状态栏窗格的矩形区域,
设想,在OnCreate()函数执行完成之后,再来获取状态栏上窗格的矩形区域,
改进:采用自定义消息的方法,在OnCreate()执行完成之后,发送一条消息,在这条消息的处理函数中获取状态栏窗格的矩形区域并把它传给创建进度条的函数。
 
#define UM_MESSAGE WM_USER+1自定义消息
Afx_msg void OnUserMessage()                 声明消息处理函数
ON_MESSAGE(UM_MESSAGE OnUserMessage) 消息映射
Void CMainFrame:: OnUserMessage() 实现消息处理函数
{
       CRect rect;
       m_wndStatusBar.GetItemRect(2,&rect);
       m_progress.Create(WS_CHILD | WS_VISIBLE | PBS_SMOOTH,
              rect,&m_wndStatusBar,123);
       m_progress.SetPos(50);
}
 
在OnCreate()中发送消息 postMessage(UM_MESSAGE); //为什么不用sendMessage()?
 
不足之处:当窗口resize时,进度条不会同步改变位置,停在原来的位置。
改进:取消自定义消息的方法,改为在OnPaint()函数中获取状态栏窗格的矩形区域,调用MoveWindow(rect),把进度条移到状态栏窗格的矩形区域位置。
改进:在OnPaint()函数中,再次调用进度条的GetItemRect(2,&rect)函数,及时取得矩形区域,调用movewindow函数(或调用SetWindowPos)把进度条移动到新的窗格位置。
void CMainFrame::OnPaint()
{
       CPaintDC dc(this); // device context for painting
      
       // TODO: Add your message handler code here
       CRect rect;
       m_wndStatusBar.GetItemRect(2,&rect);
       if(!m_progress.m_hWnd) //如果进度条还未创建,则在获得的矩形区域位置处创建
              m_progress.Create(WS_CHILD | WS_VISIBLE ,//| PBS_SMOOTH,
                     rect,&m_wndStatusBar,123);
       else //如果进度条已创建,则移动它到相应的位置。
              m_progress.MoveWindow(rect);
       m_progress.SetPos(50);
       // Do not call CFrameWnd::OnPaint() for painting messages
}
 
启动进度条:让进度条以一秒钟为间隔前进
void CMainFrame::OnTimer(UINT nIDEvent)
{
      
       m_progress.StepIt();
       CFrameWnd::OnTimer(nIDEvent);
}
 
三、在状态栏中显示鼠标位置
分析:捕获OnMouseOver消息,在消息处理函数中把鼠标的位置显示在状态中,必须由View类才能捕获这条消息(为什么?)
实现:
void CStyleView::OnMouseMove(UINT nFlags, CPoint point)
{
       // TODO: Add your message handler code here and/or call default
       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);
}
 
注意有4种方法可以设置鼠标位置到状态栏:
1、调用状态栏的m_wndStatusBar.SetWindowText(str)方法
2、调用CFrameWnd::SetMessageText 直接放置内容到状态栏。
Call this function to place a string in the status-bar pane that has an ID of 0. This is typically the leftmost, and longest, pane of the status bar
void SetMessageText(LPCTSTR lpszText );
3、 取得状态栏的指针,调用SetWindowText()方法
CFrameWnd::GetMessageBar
Call this member function to get a pointer to the status bar.获得状态栏的指针。这样就不必更改m_wndStatusBar的保护属性为public了。
4、 通过ID号查找子孙窗口来取得状态栏的指针。
CWnd::GetDescendantWindow See Also
Call this member function to find the descendant window specified by the given ID.
 
CWnd* GetDescendantWindow(
   int nID,
   BOOL bOnlyPerm = FALSE
) const;
状态栏的ID号是多少呢?AFX_IDW_STATUS_BAR
不需要做强制转换,因为GetDescendantWindow本身就是CWnd类的成员函数。(GetParent()返回的正好是CWnd * 指针)
5、 调用状态栏的SetPaneText方法(课本)
((CMainFrame *)AfxGetMainWnd())->m_wndStatusBar.SetPaneText(0,str);
 
第3、4种方法,通过获得状态栏的指针来设置鼠标位置。可不必更改m_wndStatusBar的protected属性为public了
 
第四部分
增加一个启动画面
Project->add to project ->component and controls ->vc++components->splash screen –>确定

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/TSQL863/archive/2006/11/13/1381034.aspx