程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> 關於VC++ >> 編寫控制面板程序的一個例子

編寫控制面板程序的一個例子

編輯:關於VC++

本文配套源碼

最近在編寫一個Windows 98 和 Windows 2000 系統中使的MPEG播放器,我想創建一個控制面板程序,用戶可以通過這個程序改變播放器的 基本配置。大家知道,控制面板程序一般都是個DLL動態庫,然後將名字改為*.cpl。查找了許多資料後,發現所看到的一些例子都是用C寫的。能不能在MFC中用C++來編寫一個呢?答案是肯定的。本文將通過一個實際的VC++例子來示范如何編寫MFC控制面板程序。

首先要清楚雖然控制面板程序是一個DLL,但它和一般DLL還是有所差別的,因為它帶有一個特殊的輸出接口,這個接口就是CPlApplet函數,下面是它的原型:

LRESULT CPlApplet(HWND hwnd,
         UINT msg,
         LPARAM lp1,
         LPARAM lp2);  

為了使本文的例子代碼盡可能的具有可重用性,我用C++對控制面板的接口函數進行了封裝。 做了一個迷你型的控制面板應用程序開發框架,利用它開發控制面板擴展程序易如反掌。 控制面板程序除了是個特別的DLL外,還有一個特點是其擴展名必須為 *.cpl,而不是*.dll。當Windows的控制面板管理程序 (CONTROL.EXE)啟動後,它會在系統目錄(如:windows\system或者winnt\system32)中尋找名為XXX.cpl的文件,然後加載每一個DLL並以不同的消息 參數調用CPlApplet函數。例如,當控制面板第一次啟動時,它用消息msg=CPL_INIT調用 CPlApplet函數,當用戶雙擊控制面板中的應用程序圖標時,它用消息msg=CPL_DBLCLK調用CPlApplet函數,然後控制面板應用程序顯示相應的對話框,每個控制面板DLL都能支持一個以上的圖標或應用。通過對消息CPL_GETCOUNT的響應,可以讓控制面板知道DLL中有多少個應用,通過發送CPL_INQUIRE 或 CPL_NEWINQUIRE消息,控制面板可以請求 與每一個應用有關的信息。 圖一是用一個跟蹤程序(TraceWin)顯示的TRACE Dump,從中可以看出控制面板對消息的處理情況。

圖一 使用TraceWin 顯示的 TRACE Dump

由於大多數控制面板和DLL之間的交互都有固定的套路,所以可以被封裝在一個框架裡。本文提供了兩個類,CControlPanelApp 和 CCPApplet,實現了上述的封裝。為了說明這兩個類的使用方法, 本文還編寫了示范的控制面板程序應用DLL:MyCtrlPanel.dll,它實現了兩個控制面板應用,圖二是本文例子程序運行後在控制面板裡創建的兩個圖標 ,這兩個圖標一個是對話框形式(如圖三)、一個是屬性頁 形式(如圖四)。

圖二 例子程序圖標

圖三

圖四

例子程序的實現代碼很象典型的MFC文檔/視圖應用,所不同的是它的APP類派生於CControlPanelApp,而不是CWinApp, 並且不用改寫InitInstance來添加文檔模板,它用一個名為OnInit函數創建控制面板應用,OnInit創建了兩個面板程序:

BOOL CMyControlPanelApp::OnInit()
{
   AddApplet(new CCPApplet(IDR_MYAPPLET1,
   RUNTIME_CLASS(CMyDialog)));
   AddApplet(new CCPApplet(IDR_MYAPPLET2,
   RUNTIME_CLASS(CMyPropSheet)));
   return CControlPanelApp::OnInit();
}

CCPApplet是個很通用的類 ,在例子程序中使用它時都不必再派生新類,其運行機制也很透明。真正需要自己編寫代碼的部分是對話框本身。MyCtrlPanel實現一個對話框CMyDialog和一個屬性頁CMyPropSheet。不管你相不相信,就這麼簡單, 創建一個對話框,並象上述那樣重載CControlPanelApp::OnInit,剩下的事情都交給迷你框架來做。

到這裡我們只完成了一部分工作,下面我們要描述由框架負責的那部分工作,比如:在哪裡獲取圖標以及描述性信息、CPlApplet函數 的實現在哪裡?CPL消息的處理例程等等。所有這些工作都由CControlPanelApp 和 CCPApplet來完成。CPanel.cpp中有一個CPlApplet函數負責將CPL消息轉換成虛擬函數調用。當控制面板以消息CPL_INIT 調用 CPlApplet時,CPlApplet再調用CControlPanelApp::OnCplMsg,然後依次將控制傳到CControlPanelApp::OnInit。OnCplMsg是CWnd::WindowProc的模擬,OnInit 類似於消息處理函數,如OnCreate。有些CPL消息如CPL_INQUIRE、CPL_DBLCLK等都有面板程序號(索引),用lParam1進行傳遞,這些消息被傳到索引指示的程序。(記住:單個控制面板擴展 可以實現一個以上的圖標或應用)。此時CControlPanelApp::OnCplMsg將消息處理路由到CCPApplet類的某個虛擬函數,而非CControlPanelApp。

以上我們介紹了一大堆的類代碼運行邏輯,將底層的DLL調用和消息代碼映射到較高級C++雷和虛擬函數。可光有邏輯是不行的,要實現這個邏輯才有價值。CControlPanelApp 和 CCPApplet 便是最終的結果。它們根據給定的靜態信息實現了需要的處理。當你創建一個新的控制面板應用時,只要給構造函數一個資源ID和一個MFC運行時類:

AddApplet(new CCPApplet(IDR_MYAPPLET2,
RUNTIME_CLASS(CMyPropSheet)));

這就是框架實現控制面板應用時需要的全部信息。AddApplet將應用添加到m_lsApplets列表。默認的CPL_GETCOUNT消息處理函數可以返回列表中 應用的個數。當控制面板發送CPL_INQUIRE 或 CPL_NEWINQUIRE消息時,CCPApplet使用資源ID來獲得應用的圖標、名字和描述。名字和描述被解析為主資源串中的子串。

STRINGTABLE PRELOAD DISCARDABLE?
BEGIN
IDR_MYAPPLET3 "Intergalactic\n
Intergalactic settings for space cadets\n\n"
END

這類似於MFC使用IDR_MAINFRAME處理串資源情況,如應用程序名、文當類型、COM ProgID等。只要按規范定義圖標和資源串,就不必再實現OnInqure 或者 OnNewInqure,調用默認的實現即可。另外,這裡要對CPL_INQUIRE 和 CPL_NEWINQUIRE消息的處理要做一點說明,CPL_NEWINQUIRE是新增的消息。一般說來,一個應用只要實現OnInqure就可以了,但如果 面板應用程序的信息從一個SESSION到另一個SESSION的過程中是可變的(似乎有點不可思議),那麼就只需實現OnNewInquire,如果是這樣,應將CCPApplet::m_bDynamic賦值為TRUE; 以便告訴框架旁路掉對CPL_INQUIRE消息的處理,也就是讓它返回FALIED,從而讓控制面板程序去處理CPL_NEWINQUIRE消息。 是不是有點神奇啊!就是為什麼你能忽略所有的那些細節,僅僅使用資源串就能搞掂的緣故。

當用戶雙擊控制面板中的應用圖標時,Windows發送CPL_DBLCK消息。 這個消息被映射到CCPApplet::OnLaunch,此函數用對話框或者屬性頁的運行時類來創建一個實例,並調用DoModal:

LRESULT CCPApplet::OnLaunch(CWnd* pWndCpl, LPCSTR lpCmdLine)
{
  CWnd* pw = (CWnd*)m_pDialogClass->CreateObject();
  if (pw)
  {
    if (pw->IsKindOf(RUNTIME_CLASS(CPropertySheet)))
    {
      CPropertySheet* ps = (CPropertySheet*)pw;
      ps->SetActivePage(lpCmdLine ? atoi(lpCmdLine) : 0);
      ps->DoModal();
    }
    else
    {
      if (pw->IsKindOf(RUNTIME_CLASS(CDialog)))
      {
        CDialog* pd = (CDialog*)pw;
        pd->DoModal();
      }
    }
    return pw==NULL;
   }
}

一定要用DECLARE_DYNCREATE來聲明對話框類和屬性頁類。如果不這樣做Create會調用失敗,而且可能還會有MFC的TRACE診斷錯。除此之外,還要記住重 寫對話框和屬性頁的OnPostNcDestroy函數,加入代碼“delete this”。這是因為,通常創建對話框是在棧(stack)上進行的,代碼如下:

CMyDialog dlg;
dlg.DoModal();

這種情況不用關心delete。而CPApplet是在堆(heap)上創建的對話框和屬性頁,在對話框和屬性頁被destroy掉以後,必須要進行delete操作,否則造成內存溢出。

綜上所述,我們可以看到,使用CControlPanelApp 和 CCPApplet編寫控制面擴展易如反掌。為了添加普通對話框或者基於屬性頁的面板應用,只需要重寫CControlPanelApp::OnInit即可,然後實現相應的對話框代碼和屬性頁代碼。 表一概括地總結了框架處理各種CPL消息的方法和注釋。

表一:控制面板消息和控制面板應用框架

CPlApplet 消息 框架 class::function 是否需要改寫? CPL_INIT CControlPanelApp::OnInit 是,每個控制面板程序都調用 AddApplet 添加 CPL_GETCOUNT 沒有 否,CControlPanelApp 決定控制面板程序數量 CPL_INQUIRE CCPApplet:: OnInquire 很少用 CPL_NEWINQUIRE CCPApplet::OnNewInquire 很少用 CPL_DBLCLK CCPApplet::OnLaunch 很少用,僅用於沒有對話框和屬性頁界面的情況 CPL_SELECT (已廢掉) CCPApplet::OnSelect 否 CPL_STOP CCPApplet::OnStop 很少用,除非控制面板程序都進行垃圾收集,但這種情況最好在程序的析構函數中進行 CPL_EXIT CControlPanelApp::OnExit 很少用,用 ExitInstance 代替 CPL_STARTWPARAMS
(Windows 98 或者Windows NT 4.0) CCPApplet::OnLaunch 很少用,僅用於沒有對話框和屬性頁界面的情況

下面是編寫控制面板程序需要注意的幾件事情:首先是生成控制面板擴展後,它是一個*.dll文件,不要忘了把它的擴展名改為*.cpl。其次,運行*.cpl之前,必須將它拷貝到Windows的系統目錄中,如C:\\Winnt\\System32,或者C:\\Windows\\System,根據你的操作系統安裝目錄決定。你可以通過“Project”=>“Setting”菜單將這一步添加到Post-Build步驟中。如果你想在自己的目錄中保存一份DLL文件,那麼可以通過安裝程序在Windows的CONTROL.INI文件的[MMCPL]

段添加一行內容:

[MMCPL]

MyCtrlPanel=c:\utils\MyCtrlPanel\MyCtrlPanel.cpl

第三要注意的事情是當你生成控制面板程序時,可能會遇到下面的情況:如增加另一個面板應用、改變名字或者圖標——而這些更改在控制面板中反映不出來。這是因為控制面板讀取(CPL_INQUIRE)信息時,需要第一次與你的DLL見面後, 它才能將信息緩沖到磁盤。讓控制面板重讀新信息的一種比較保險的方法是改DLL名字。你也可以試一下在控制面板中按F5(Refresh),但這個方法不一定每次都奏效。在開發期間,你可以將CCPApplet::m_bDynamic 設置為 TRUE,讓它告訴框架使用CPL_NEWINQUIRE(不緩沖信息)代替 CPL_INQUIRE (緩沖信息)。當你完成調試准備發行正式版時,再將CCPApplet::m_bDynamic置回(缺省值)FALSE。

說到調試,下面介紹兩種調試控制面板應用程序的方法:一種是在調試器中啟動控制面板程序;如果你覺得那太麻煩,那麼還有另外一種方法是使用rundll32:

rundll32 shell32.dll,Control_RunDLL MyCtrlPanel.cpl

你可以在命令行上面的命令,也可以在VC++的Debug Settings菜單中進行設置。

相信很多人都沒像這樣用過 rundll32.exe 程序,它可以從命令行調用某個DLL中的某個函數。Control_RunDLL是shell32.dll中的一個特別的函數, 顧名思義,它運行一個控制面板程序;為了運行DLL中一個特定的控制面板程序,敲入:

rundll32 shell32.dll,Control_RunDLL MyCtrlPanel.cpl,@n

這裡 n 是基於零的應用程序索引。如果你在末尾加一個串,它被傳遞到CPL_STARTWPARAMS,就象一個常規的Windows應用程序命令行一樣。通常,這個串被用於啟動一個基於屬性頁的控制面板應用, 並且定位到特定的屬性頁上。例如:為了調出顯示器的“設置”屬性頁標簽,敲入:

rundll32 shell32.dll,Control_RunDLL desk.cpl,,3

如果你以前不知道有些應用程序是怎麼啟動特定的控制面板應用的,現在你應該知道了。如果你使用本文提供的迷你框架,自己就不必寫任何代碼來解析這個參數;對於一個基於屬性頁的面板,CCPApplet自動將相關參數解釋為一個屬性頁號。

LRESULT CCPApplet::OnLaunch(CWnd* pWndCpl, LPCSTR lpCmdLine)
{
  CWnd* pw = (CWnd*)m_pDialogClass->CreateObject();
  if (pw) {
   if (pw->IsKindOf(RUNTIME_CLASS(CPropertySheet))) {
     CPropertySheet* ps = (CPropertySheet*)pw;
     ps->SetActivePage(lpCmdLine ? atoi(lpCmdLine) : 0);
     ps->DoModal();
   } else if (pw->IsKindOf(RUNTIME_CLASS(CDialog))) {
        CDialog* pd = (CDialog*)pw;
        pd->DoModal();
      }
  }
  return pw==NULL;
}

最後,如果你不知道如何卸載控制面板程序,這裡告訴你一個笨方法:重啟機器,只要你不運行控制面板,Windows就不會加載那些.cpl文件,所以你可以直接到Windows系統目錄 (比如c:\windows\sysytem或者c:\winnt\system32)下刪除相應的.cpl文件。注意這樣做的風險是——萬一你刪錯了文件, 那麼對應的控制面板程序圖標將從控制面板中消失。所以刪除文件的時候一定要小心。如果哪位有更好的辦法,請告訴我。

希望本文的介紹和例子對大家開發控制面板程序有所幫助

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved