孫鑫:第九講 界面修改,工具欄,狀態欄,啓動欄

 

1:如何修改單文檔應用程序的窗口標題,查閱MSDC文章:

Changing the styles of a window created by MFC.
  要在CMainFrame的PrecreatWindow()中加入如下代碼:
  cs.style&=~FWS_ADDTOTITLE;
  cs.lpszName="This is a test!";
  可以先不要上一句試一試!
另一種方法是 :
  cs.style=WS_OVERLAPPEDWINDOW;
再進行修改,也可以不修改,那麼是去掉默認文檔標題,而只顯示原程序標題!


  另一類方法是在窗口創建後再修改,因爲在OnCreate中,開始的這些代碼:
 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("Failed to create toolbarn");
  return -1;      // fail to create
 }

 if (!m_wndStatusBar.Create(this) ||
  ! m_wndStatusBar.SetIndicators(indicators,
    sizeof (indicators)/sizeof(UINT)))
 {
  TRACE0("Failed to create status barn");
  return -1;      // fail to create
 }
 // TODO: Delete these three lines if you don't want the toolbar to
 //  be dockable
 m_wndToolBar.EnableDocking (CBRS_ALIGN_ANY);
 EnableDocking (CBRS_ALIGN_ANY);
 DockControlBar(&m_wndToolBar);

完成了窗口創建,工具欄,狀態欄的創建等工作,可以在後面利用一個系統全局函數SetWindowLong()函數進行修改:

加入代碼爲:SetWindowLong(m_hWnd,GWL_STYLE,WS_OVERLAPPEDWINDOW);
與此相對,還有一個GetWindowLong()函數可供使用!如下面代碼去掉了窗口上的最大化按鈕:
SetWindowLong(m_hWnd,GWL_STYLE,GetWindowLong(m_hWnd,GWL_STYLE) & ~MAXIMIZEBOX);
當然SetWindowLon()還可以做別的修改.與SetWindowLong()相類似的另一個系統全局函數爲 SetClassLong();


2:如何完成一個動畫圖標
其實就是準備好幾個圖標,在定時器消息 響應中更改圖標即可完成.
第一步是準備好幾個(如三個)圖標.
第二步是在CMainFrame類中做三個圖標類的相關對象的成員變量,或者是一個大小爲3的HICON數組.
第三步是在CMainFrame類的OnCreate()函數中LoadIcon()進行對三個圖標的加載.其中用到的實例句柄的獲取有三種方法:
一:用全局函數AfxGetInstanceHandle()獲取,
二:先在CMainFrame類中用extern聲明一下全局對象theApp,然後使用theApp.hInstance;
三:使用全局函數AfxGetApp()獲取全局對象theApp對象的指針,然後用AfxGetApp()->hInstance;
第二個參數是一個字符指針,可我們只有圖標的資源ID,所以要進行必要的轉換:用MAKEINTRESOURCE宏!
第四步是設置定時器,也在OnCreate()函數中定義:SetTimer(1,1000,NULL);
第五步是在CMainFrame中添加WM_TIMER消息響應,在其中加入代碼:
 static int index=0;
 SetClassLong(m_hWnd,GCL_HICON,(LONG)m_hIcons[index]);
 index=++index%3;


3:在工具欄上新加一個按鈕,要讓它與前一個按鈕之間有一個分隔符,只需要將它輕輕向一旁拖動一點點再放開即可,而要刪除工具欄上的一個按鈕,你只是選中它再按DEL鍵是完不成的,它只是將按鈕上的圖案刪除,所以刪除一個按鈕要將它拖動到工具欄之外,再鬆手!


4:如何創建一個工具欄
在MSDN的關於CToolBar的講解頁有詳細說明!
一:插入工具欄資源,
二:在CMainFrame中加入一個CToolBar類對象的成員變量,
三:在CMainFrame的OnCreate()中加入:
 if (!m_MyToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
  | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
  ! m_MyToolBar.LoadToolBar(IDR_MYTOOLBAR))
 {
  TRACE0("Failed to create toolbarn");
  return -1;      // fail to create
 }

 m_MyToolBar.EnableDocking (CBRS_ALIGN_ANY);
 DockControlBar(&m_MyToolBar);
各個函數調用及參數傳遞查看MSDN!


5:如何讓一個工具欄隱藏或顯示:
 if(m_MyToolBar.IsWindowVisible())
 {
  m_MyToolBar.ShowWindow(SW_HIDE);
 }
 else
 {
  m_MyToolBar.ShowWindow(SW_SHOW);
 }
但這樣做的結果是工具欄雖說隱藏了, 但是工具條還在,所以還要在後面加上一句:
        ReCalcLayout();
這樣做還是有問題,如果工具欄沒有停靠在邊上而是一個單獨的小窗口,那麼只做上面的工作只使得工具欄上的按鈕不見了,而那個小窗口還在,所以,還要調用一個函數:
 DockControlBar (&m_MyToolBar);
經過上面這句,小窗口也如願消失了,但問題還有一點,就是當用戶將工具欄放置爲一個小窗口時,再點擊菜單,要讓這個工具欄顯示出來,當然我們應該將工具欄仍按用戶先前的小窗口樣式顯示出來比較好,可是這次工具欄又自動停靠在客戶區頂部了?這個功能如何實現呢?孫老師只是提示可以查MSDN中CToolBar的成員函數解決這個問題,並沒細講,所以我看了MSDN,發現有兩個函數:CToolBar::IsFloating()利用這個函數可以判斷一個工具欄是否處於浮動狀態,另一個是CFrameWnd::FloatControlBar()這個函數可以讓一個控制欄處於浮動狀態,然後我在CMainFrame中加入了一個BOOL型的成員變量,在每次判斷工具欄是否可見時用來記錄工具欄是否處於浮動狀態,然後在重新生成工具欄時根據它的置決定是否將工具欄設爲浮動狀態,但是第二個函數好像不太好使,所以我又換用了SetWindowPos()成員函數,可是也不能將它放置爲一個獨立的小窗口.
 顯示和隱藏工具欄的第二種方法:
用一個函數:CFrameWnd::ShowControlBar(),因爲這個函數的固有特性,上面是if...else...判斷就可以簡化爲一句代碼:
 ShowControlBar(&m_MyToolBar,!m_MyToolBar.IsWindowVisible(),FALSE);
並且我驚訝的發現,用這個函數時,上面提到的浮動工具欄讓它在恢復的時候仍回覆爲浮動的問題自動解決了!哈哈,好.


6:狀態欄相關編程
因爲MFC自動生成的系統已經包含了一個狀態欄,所以我們暫時僅限於已有狀態欄的修改,而不是另外生成一個狀態欄.
狀態欄最左邊的那一長條,就是經常顯示一些提示字符串的那部分叫做提示行,而右側那三個小窗口是用來指示 CapsLock,ScrollLock,NumLock開關的狀態,稱爲狀態指示器.
狀態欄跟工具欄一樣,也是在CMainFrame類中定義並在OnCreate()中創建的.
下面的代碼在狀態指示器的最左邊放置了兩個小窗口,並在第一個小窗口中放置了一個時鐘:
同樣的CMainFrame的OnCreate() 中,
 CTime tm=CTime::GetCurrentTime();
 CString strTime=tm.Format("%H:%M:%S");

 CClientDC dc(this);
 CSize sz=dc.GetTextExtent(strTime);
 m_wndStatusBar.SetPaneInfo (1,IDS_TIMER,SBPS_NORMAL,sz.cx);//調整窗口大小
 m_wndStatusBar.SetPaneText(1,strTime);
 SetTimer(2,1000,NULL);
當然要先有準備工作,在字符串資源中添加兩個字符串,ID分別爲:IDS_TIMER,IDS_PROGRESS,交將之添加到MainFrm.cpp的
static UINT indicators[] =
{
 ID_SEPARATOR,            // status line indicator
 IDS_TIMER,
 IDS_PROGRESS,
 ID_INDICATOR_CAP S,
 ID_INDICATOR_NUM,
 ID_INDICATOR_SCRL,
};
這個數組中,並且上面SetPaneInfo()中的第一個參數就是相應ID在indicators[]數組中的索引值,顯然最後三個ID是大小寫,數字鎖及ScroolLock的標識.
做完上面這些工作,這個時鐘還不能變化,只是顯示了一個定值,我們還要在定時器的響應函數中再刷新該值:
 if (2==nIDEvent)
 {
  CTime tm=CTime::GetCurrentTime();
  CString strTime=tm.Format("%H:%M:%S");
  m_wndStatusBar.SetPaneText (1,strTime);
 }
這樣就完成了一個在狀態欄中顯示的,可以動態變化的時鐘!


7:如何創建一個進度條並將之放置在狀態欄中的某個位置?
與MFC中其它標準資源一樣,進度條也有一個專門的類與之相對應:CProgressCtrl類.
先在CMainFrame中放置一個成員變量:CProgressCtrl m_ProgBar;
然後在OnCreate()中加 入:
 m_ProgBar.Create(WS_CHILD | WS_VISIBLE,CRect (300,100,500,120),this,88888);
就可以在窗口中顯示一個進度條.
至於要將之放置於狀態欄上的某個窗格之中,就要先得到窗格所在的矩形區域,然後在上面是第二個參數中進行指定,故將上面一句改爲如下:
 CRect rect;
 m_wndStatusBar.GetItemRect (2,&rect);
 m_ProgBar.Create(WS_CHILD | WS_VISIBLE,rect,this,88888);
可是這樣並未實現設置,所以我們在上面設置斷點,發現到達m_ProgBar.Create()時矩形區域的值並不正常,原來在OnCreate()未結束時,狀態欄的設置無法完成,無法獲得矩形區域位置,所以,就要想辦法在OnCreate()結束時做上面的工作,可以做一個自定義消息,在OnCreate()末尾發送一個該消息,並做一個消息響應函數,然後將上面的代碼放置在其中即可.
要自定義消息,首先要在CMainFrame的頭文件首部 做:
 #define UM_PROGRESS WM_USER+1 //注意,沒有;
WM_USER是一個系統定義宏,詳細情況查MSDN.
然後在CMainFrame的頭文件的消息響應中加入消息響應函數的聲明:
 afx_msg void OnProgress();
再加入消息映射:
 ON_MESSAGE(UM_PROGRESS,OnProgress)
再實現消息響應函數:
void CMainFrame::OnProgress()
{
 CRect rect;
 m_wndStatusBar.GetItemRect(2,&rect);
 m_ProgBar.Create(WS_CHILD | WS_VISIBLE,rect,&m_wndStatusBar,123);//注意,這裏父窗口就不能設置爲this了, 而要是狀態欄!
 m_ProgBar.SetPos(50);
}
當然,不能忘了在OnCreate()最 後發送消息:
 PostMessage(UM_PROGRESS);

這樣就可以了!
但是問題還是有的,你會發現當你拉動窗口改變其大小時,狀態欄的位置發生了變化,不再覆蓋在先關狀態欄的那個小窗口上了,怎麼辦呢?你會想到在窗口大小改變時,系統會接受到一個WM_PAINT消息,只要在那個消息的響應函數中實時獲取小窗口的矩形區域,再改變進度條的位置,不就可以了吧,沒錯,這樣的確可以,又因爲WM_PAINT消息當窗口顯示時,也就是說OnCreate()之後就會馬上收到,所以我們也不用像上面那樣麻煩的自定義什麼消息啦,直接在CMainFrame中加入WM_PAINT的消息響應函數,並在其中加入:
 CRect rect;
 m_wndStatusBar.GetItemRect(2,&rect);
 m_ProgBar.Create(WS_CHILD | WS_VISIBLE,rect,&m_wndStatusBar,123);
 m_ProgBar.SetPos(50);
即可了,但是問題又來了,當窗口大小一改變時,程序發生了一個致使錯誤,原來我們不能在每一次響應時都創建進度條,進度條對象只有一個,哪能多次使用呢?所以,改爲如下代碼:
 CRect rect;
 m_wndStatusBar.GetItemRect(2,&rect);
 if(!m_ProgBar.m_hWnd)
 {
  m_ProgBar.Create(WS_CHILD | WS_VISIBLE,rect,&m_wndStatusBar,88888);
 }
 else
 {
  m_ProgBar.MoveWindow(&rect);
 }
 m_ProgBar.SetPos(50);
這下,無論怎麼拖,也不會發生異常現象了!
那麼,如何讓進度條動起來呢?相關的成員函數爲
SetStep(),以及StepIt();


8:下面的代碼將鼠標當前的位置座標顯示在狀態欄的提示行中:

在View類中響應WM_MOUSEMOVE消息,在其中加入代碼如下:

 CString str;
 str.Format("x=%d,y=% d",point.x,point.y);
 ((CMainFrame*)GetParent())->m_wndStatusBar.SetWindowText(str);

這段代碼要正常運行的前提是首先CMainFrame在View文件中不可見,所以要先包含一個MainFrm.h頭文件,然後,因爲m_wndStatusBar在CMainFrame中是一個protected變量,所以不能在View類中訪問,所以我們要手動把它的訪問權限改爲public.然後就OK了!
其實還有一種更方便的方法:調用CFrameWnd::SetMessageText()
所以上面的代碼就 可以改爲:
  CString str;
  str.Format("x=%d,y=% d",point.x,point.y);
 ((CMainFrame*)GetParent())->SetMessageText (str);
這樣子,就不用取得狀態欄指針了,也就不用去修改其訪問權限了!
關於這個工作,還有第三種方法,也不用去修改狀態欄的訪問權限,這是應用了另一個函數:CHtmlView::GetStatusBar(),上面的代碼就可以是:
 CString str;
 str.Format("x=%d,y=%d",point.x,point.y);
 ((CMainFrame*)GetParent())->GetMessageBar()->SetWindowText(str);
下面還有第四種方式:這是應用了又一個函數:CWnd::GetDescendantWindow(),這個函數可以根據窗口ID來從調用它的窗口出發找到與該ID相同的一個子孫窗口的指針:
 CString str;
 str.Format("x=%d,y=%d",point.x,point.y);
 GetParent()->GetDescendantWindow(AFX_IDW_STATUS_BAR)->SetWindowText(str);
至於上面那個ID號,是從CStatusBar::Create()中看到它!關於GetDescendantWindow()的調用,要注意其第二個參數在MSDN中關於臨時窗口及持久窗口的講解!


9:關於程序的啓動畫面的製作
這個跟往程序中加入右鍵PopUp菜單一樣,可以使用VC的組件庫中的組件.找到一個叫SplashScreen的東西,加入它到工程中,別的什麼也別動,先編譯運行一下,你會發現程序已經有一個啓動畫面了,不過是很簡陋的.
這個組件在我們的程序中加入了一副默認位圖,並加入了一個叫CSplashWnd的類,並且在CMainFrame的OnCreate()函數中加入了一句:CSplashWnd::ShowSplashScreen(this),進行了相應的啓動工作.
在CSplashWnd的成員函數OnCreate()中有一句SetTimer(1, 750, NULL);其消息響應函數中進行了啓動畫面的隱藏,所以,修改其中的時間值可以修改啓動畫面顯示的時間長度!
另外,可以發現窗口的顯示與啓動畫面的顯示是同時進行的,如何才能讓畫面消失時窗口才顯示出來呢?

至於啓動畫面的改變,可以自己插入位圖,然後將它的ID修改爲IDB_SPLASH 然後RebuildAll,也可以在其源文件中LoadBitmap()中將ID改爲自己想要加載的位圖的ID!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章