孫鑫VC++講座筆記(九)

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 images.
4、Call SetButtons to set the button style and associate each button with an image 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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章