[转]Windows Shell 编程 第十四章【来源:http://blog.csdn.net/wangqiulin123456/article/details/7988010】...
第十四章?設計Shell集成應用
有一些工具可以使應用程序更緊密地與Shell和底層系統進行集成。也就是說,用戶可以象處理系統文檔和程序那樣處理你的文檔和程序。例如,右擊文件來顯示可用功能列表等。Windows為每一個文件提供默認的功能集,如‘打開…’,‘屬性’,‘拷貝’等。是否能為特定的文檔增加特殊功能。為此,我們必須客戶化這個文檔類的關聯菜單。
另一個應該與Shell集成的例子是:假設你的程序有建立空文檔的能力,用戶使用系統的‘新建’菜單項在任一文件夾上飛快地建立新文檔,要想如此,就必須在系統注冊表中記入一些信息。
當然,這是特殊情形,作為開發人員和應用設計人員還應該重視許多其它有用的特征。在這一章中我們將討論Shell集成的各方面技術,以幫助開發者將應用與系統Shell無縫集成,使你的產品更專業。這些技術包括:
?????????怎樣客戶化關聯菜單
?????????怎樣注冊新文件類型
?????????怎樣設計和編程處理命令行
?????????怎樣編程定制‘打開’對話框
我們將設計一個基于文檔的全屬性應用,這個應用顯示和打印所有Windows支持的元文件,從傳統的(*.wmf)到增強的(*.emf)。我們將應用所有我們前面討論過的理論技術,以及Shell對元文件的豐富支持。
?
Shell集成應用
?????????頭一個問題顯然是Shell集成應用的組成結構是怎樣的。就Win32的面向文檔程序概念,有幾個關系到系統Shell的特征需要給出。簡單地說,有三組定義Shell集成應用的特征:
?????????為程序處理的任何文檔注冊圖標和類型名
?????????為程序處理的文檔客戶化關聯菜單
?????????在系統的‘新建’菜單中可能有一個或多個客戶化條目
?????????單一程序實例
?????????對每一個打開過的文檔在‘最近文檔’文件夾中有一個新條目
?????????完全支持長文件名,尤其是對于用戶文檔。
除了這些基本特征外,我們還需增加一些不常使用的特征:
?????????在系統的‘發送到’菜單中客戶化一個或多個條目
?????????在‘開始’和‘程序’菜單中客戶化一個或多個條目
?????????在‘Favorites’文件夾中客戶化一個或多個項
?????????在桌面上生成一個或多個新快捷方式
?????????有一個應用桌面工具條來集中程序的所有主要功能
?????????客戶化某些系統公共對話框
?????????在用戶下一次登錄時注冊應用自動啟動
第三組特征主要受限于特定安裝器,如InstallShield?和WISE,它們是:
?????????拷貝共享文件到系統公共路徑
?????????在‘程序文件’文件夾下安裝應用
?????????提供卸載程序
?????????探測Shell的應用路徑名來定義查找文件的路徑
這些要求來自于Windows標記規范,然而對于Windows環境下更高層次的抽象概念而言它們是最基礎的:為了打開和使用文檔,用戶不必知道實際裝入和顯示文檔的是什么程序,只需找到和雙擊文檔的圖標和這個文檔的名字即可(實際在桌面設置中也可以設置單機打開方式)。
?
文檔和Shell
?????????在Windows95發布以后,文檔成為系統Shell更中心的角色,文檔已經成為了主角,而實際處理它的程序則縮減成配角。甚至它們在硬盤驅動器上的位置都標明其狀態的低下:程序被分組在‘程序文件’文件夾下,每一個都有自己的子文件夾,以及一個存儲DLL和其它幫助文件的子目錄。這樣的許多文件夾都是隱藏的——這進一步確認程序相對于文檔是次要的。
?????
?
查看上面的截圖,我們可以看到文檔有它們自己的圖標和描述,更進一步,每一個文檔都有專門的關聯菜單,從菜單中可以執行一些Shell功能。有些功能可以應用于各種文檔,因而出現在所有文檔的關聯菜單中,有些則是個別文檔類型所具有的。
?
基本的文檔功能
?????????Windows Shell提供一些菜單動詞,可以自由使用,它們是:
???????????????????拷貝、剪切、粘貼
???????????????????刪除
???????????????????重命名
???????????????????建立快捷方式
???????????????????屬性
此外,還有兩個菜單命令,‘打開’或‘打開方式…’,但是這兩個是相互排斥的——后者僅僅在沒有注冊程序打開指定文檔時出現,并且導出下面的對話框:
??????????????????????
?
?
相反‘打開’命令則依賴于存儲在注冊表中的條目內容,這就是我們將要討論的。
?
‘發送到’命令
?????????另一個我們總能看到的命令是‘發送到’,它有一個子菜單。這個子菜單列出了選中文檔可能的目的地?!康牡亍且粋€在命令行上接收給定文件名的程序。下圖說明‘發送到’菜單是怎樣把文件設置成新郵件的附件:
???????????????????
?
?
通過我們在這一節中列出的命令,Shell保證了對PC上各種文檔的最低層支持。對于有經驗的用戶和軟件工程師,這就等于提供了使用更多的文檔特定特征擴展這些基本行為的機會。
?
注冊文檔類型
?????????關于Shell構造的所有信息都存儲在系統注冊表中,所以修改Shell表現或行為的任何方法都必須通過注冊表。
?????????為了使Shell識別和適當處理一定種類的文檔,它就必須是一個注冊類型。一個文檔的類型由它的文件名的擴展所標識,而且所有注冊的文檔類型都存儲在HKEY_CLASSES_ROOT注冊表節點下:
?????????????
?
文件擴展(.ext)的條目指向同一個節點下的另一個鍵,其名字存儲在.ext的默認值中,在上圖中,對EML文件(微軟郵件消息,Outlook Express格式文件),其值為:
Microsoft Internet Mail Message
如果需要獲取這個文檔類型的注冊信息,你就必須探索:
HKEY_CLASSES_ROOT
/Microsoft Internet Mail Message
?
?????????
?
在這個鍵下,存儲了應用于三個方面的信息:
???????????????????用戶接口
???????????????????關聯菜單
???????????????????Shell擴展
?
文檔的Shell用戶接口
?????????從這一節的標題可以看出,我們主要是想說明設置文檔的圖像屬性——即圖標和類型名。‘DefaultIcon’鍵使你可以指派圖標到特定類型的所有文件。這個鍵的Default值包含一個如下格式的字符串:
C:/PROGRAMS/THEPROG.EXE,0
再次注意,這個信息不是存儲在.ext鍵下的,而是在.ext指向的鍵下。
????這個串標識默認圖標由全路徑名、逗號和索引號構成。要顯示的圖標是給定文件中指定索引號的圖標——記住圖標索引總是從0開始的。如果索引是負數,則表示是一個資源ID,比如對于EML,DefaultIcon串為:
C:/PROGRAM FILES/OUTLOOK EXPRESS/MSIMN.EXE,-4
正象上面顯示的,主鍵(上面示例中是Microsoft Internet Mail Message)的默認值包含了用于這個文檔類型名的串。要修改這個設置并不需要專門的Windows程序員進行,任何老練的Windows用戶都可以改變EML文件的描述,或表示的圖標。然而要編程地插入鍵來注冊文檔完全不同于手動修改注冊表操作。我們需要集中討論軟件自動集成其文檔與Shell的操作關系。
?
關聯菜單的特定文檔命令
?????????命名為Shell的鍵可以包含一定數量的子鍵,每一個子鍵都對應一個特殊的命令,這是將要顯示在文檔關聯菜單上的。在Shell鍵下的這些鍵稱為動詞(此時的動詞是‘打開’),這些鍵的默認值包含了將要顯示在關聯菜單上的串。如果沒有設置,則菜單顯示鍵本身的名字。因此動詞打開可以在菜單中顯示一個相當不同的名字。在我們討論的郵件消息這個例子中,‘Read’命令在菜單中替代了‘打開’。如果你改變了默認值的內容并且調出關聯菜單,你將看到這個結果:
?????????????
?
?
關聯菜單顯示的是‘讀’,而實際行為一點也沒改變,因為這是在命令子鍵的默認值中確立的。每一個動詞必須有一個命令子鍵包含可執行文件路徑和命令行,以及任何其它必要的設置。指定命令行以及適當的開關,用%1表示要操作的文件也是十分重要的:
?
C:/PROGRAM FILES/OUTLOOK EXPRESS/MSIMN.EXE" /eml:%1
?
上面這行顯示EML文件的命令行——在你的機器上是否有相同的設置依賴于Outlook Express的安裝設置。
?
Shell對文檔的擴展
?????????通過修改注冊表你可以添加靜態動詞到文檔的關聯菜單。你所定義的任何靜態動詞總是被顯示,并且總是執行同樣的命令行。
?????????更靈活的動態的行為可以通過Shell擴展獲得,我們將在下一章中進行討論?,F在,我們可以說Shell擴展是運行在探測器地址空間中的一段代碼,每次探測器需要做某個客戶化行為時都調用這段代碼,如繪制圖標、顯示關聯菜單等。你的代碼段給出一個動態確定菜單項添加的機會和響應用戶的點擊的操作。
?????????對于給定文檔類的所有Shell擴展,都列出在shellex鍵下,它與Shell鍵同層。
?
怎樣影響程序
?????????我們現在接觸到了一定數量的影響文檔的屬性,并且在這一章的開始我們就說明了文檔是Windows Shell的重點。然而,我們并沒有脫離文檔仍然通過程序顯示這樣一個事實——問題是,通過我們致力的Shell集成怎樣和什么程度上影響到程序。
?????????有兩個重點,首先是,用戶可以點擊重復打開不同的文檔,甚或是同一個文檔的多個拷貝。當這種情況發生時,程序被重復調用,所以,為了避免窗口增殖,你可能希望只允許運行一個程序實例。其次是,程序命令行的重要性,因為靜態動詞通常是通過命令行的開關實現的。你應該以非常標準的方法輸出最重要的功能。
?????????在第11章中我們討論了RunDLL32模塊,它給出了通過命令行調用具有固定原型的動態庫函數的好方法。在這兩種情形下程序的功能都必須明顯地隔離開,而且可容易地從外部模塊調用。
?????????當某人在Windows Shell下點擊文檔時,程序被調用,程序每次啟動,都檢查是否有運行中的副本,如果有,則傳遞控制和命令行,而當前的實例則退出,我們將在后面進一步討論這個問題。
?
MDI?與?SDI
?????????MDI和SDI是Windows基于文件應用的兩個典型設計。MDI表示多文檔界面,說明程序可以同時打開幾個文檔,每一個都有自己的窗口。相反,SDI是單文檔界面的首字母縮寫——SDI程序每次僅能打開一個文檔。傳統上主要的Windows應用都是MDI形式的——Office套件是一個典型的MDI例子,而記事本和圖畫程序則是SDI的例子。
?????????從Shell的觀點上看,選擇MDI或SDI并不是實際的結果。然而在進一步的探索后,你可能會認識到對MDI和SDI之間差異的討論實際是要在一個更寬泛的對比上打開窗口:應用為中心對比文檔為中心的環境。
?????????MDI方案由應用來支配,是應用打開和管理各個子文檔。反之SDI界面是更加文檔為中心的:你查看由可用工具環繞的單一文檔,可以使用這些工具來修改它。
?????????自從Windows95發布以后微軟就開始推薦盡可能使用SDI開發應用,但是有許多人都對這個建議不感冒。
?
建立新文檔
?????????在任何時候你在探測器顯示文件夾內容的窗口上右擊,都會有下圖樣式的菜單出現:
???????????????????
?
?
‘New’命令列出了所有文檔類型,這些是可以經由Shell建立的文檔類型。當你選擇了一個列出的文檔類型后,Shell調用注冊的應用,并請求它來建立一個新的文檔,其名字是一個來自文檔類型名(與它在菜單中的相同),前綴有一個‘New’字。例如,要建立一個新的bitmap圖像文檔,文件名默認為:
???????????????????New Bitmap Image.bmp
?
New菜單
出現在‘New’菜單中的每一項(除了文件夾和快捷方式)都有相關的文件類對應的ShellNew鍵存在,它在下面的注冊表路徑上:
HKEY_CLASSES_ROOT
/.ext
/ShellNew
ShellNew鍵的內容確定了New菜單上顯示的內容,以及當點擊時所需要做的動作。實際上有四種通過Shell建立新文檔的方法,你可以建立:
???????????????????空,零長度文檔
???????????????????從默認文檔拷貝的文檔
???????????????????來自注冊表存儲的二進制數據的文檔
???????????????????由特殊外部程序建立的文檔,例如建立大師軟件
很自然這些選擇要求不同的注冊表設置:
?
| 值 | 內容 |
| NullFile | 空字符串 |
| FileName | 作為模板使用的文件名。這是假設這種文件是駐留在Windows/ShellNew目錄下的。 |
| Data | 一塊從注冊表讀出的二進制數據 |
| Command | 建立文檔所需要的命令行 |
?
下面圖像顯示對機器上的BMP文件的設置:
?????????????????????
?
?
一般使用NULLFile設置,令應用處理空或零長度文件。FileName設置與Word和Excel密切相關,如果使用復雜的、混合文件作為文件所需要的最小結構,即使是空文件,這個設置是有用的。此時,你可以準備一個標準文件(空的或不空的),把它保存到FileNew值中,每次建立這種類型的新文件時,就建立這個文件的一個拷貝。Data是可以包含二進制數據的值,它們可以填充到新建立的文件中。這與FileNew的情況稍有不同,在FileNew中,模板是單獨文件,而Data,是一塊存儲在注冊表中的數據。
?????????在第11章討論置換快建立捷方式標準處理器時我們遇到過Command值。如果給出這個值,將限制Shell運行指定的命令行,并斷定它將建立指定類型的新文檔。這個選項由逐步建立文檔的大師程序所特殊設定。
?????????下面我們將考察一個例子,其作用是添加一個命令到Shell的‘New’菜單,建立一個新的具有最小內容的HTML文件。
?
建立HTML新文件
?????????我們假設在PC上注冊了一個處理HTML文件的程序。當需要從腹稿建立新的HTML文檔時,通常是:
???????????????????運行可視HTML編輯器(例如微軟的FrontPage)
???????????????????運行記事本或其它普通的文字編輯器
像絕大多數其它Windows文檔一樣,在你需要編寫HTML頁面時,需要借助‘記事本’。然而,HTML文件不是ASCII文件,它需要有標記來描述使它成為有效的瀏覽器可處理的文檔。最小HTML文件可以有如下形式:
<html>
<body>
</body>
</html>
保存這段代碼到一個命名為html4.htm的文件,并把它放置在Windows/ShellNew(或Winnt/ShellNew)目錄中。然后打開注冊表編輯器,添加ShellNew鍵到:
HKEY_CLASSES_ROOT
/.htm
下。這個新建立的鍵也必須給定FileName串值:
?
?
保存了這些設置后,你就能右擊桌面,激活菜單:
???????????????
?
?
這個圖說明在PC上改變了htmlfile注冊表鍵的初始描述為‘Web Page’之后任何從這個菜單項建立的新文件都將調用‘New Web Page.htm’。
????注意:只有在文件類型被正確地注冊之后,你才可以添加項目到‘New’菜單項。
?
其它特征
?????????要設計和編寫良好的Shell集成應用,還有兩個特征需要注意。它們是:
???????????????????存儲目錄列表到輔助模塊如DLL可以找到的位置
???????????????????安排在下一次登錄時自動運行
第一個特征可能與設置程序有更大的關聯,但是并不是所有安裝器都確實完成你所要求的任務,因此需要你自己編寫功能擴展,以及深入專研注冊表路徑。第二個特征典型是探測器和其他幾個應用的特征。在關閉系統時,如果應用仍然在運行,Shell將在下一次登錄時自動重新啟動它?,F在就讓我們看一下怎樣編碼這個行為。
?
應用路徑
?????????幾乎所有的Windows應用都由一個或多個文件組成。典型地是有一個EXE文件和多個DLL(不是提及的系統Dlls,如kernel32.dll和user32.dll)。輔助的DLL必須由安裝器拷貝到某個地方。它們可以在‘程序’文件夾或其它地方,但是,微軟反對將DLL拷貝到系統主文件夾下,如Windows或Windows/System等。如果你確定不將DLL放到與EXE文件相同的目錄下,很快你就會獲得系統通知的錯誤信息,說明系統不能定位指定的DLL。
?????????決定不把DLL放置到與EXE相同的文件夾下是為什么。因為你的應用可以是一套共享輔助DLL的很多程序的一部分,如果每一部分都備份這些DLL,顯然是一種浪費,所以,你可以建立一個公共文件夾,放置可共享的每一件東西到這個文件夾?,F在的問題是怎樣使Shell知道它的存在——當你導出需要確定的庫的應用時,你必須保證這個庫的路徑是全程可視的。
?????????MS-DOS基礎程序(Windows程序也一樣)依賴于PATH環境變量。類似地,對于Shell把這個置換成應用路徑。要使用這個概念,在安裝了應用之后,需要添加下面的注冊表鍵(比如對于Program.exe):
HKEY_LOCAL_MACHINE
/SOFTWARE
/Microsoft
/Windows
/CurrentVersion
/App Paths
/Program.exe
這個鍵的默認值包含可執行文件的全路徑名,如果給出,則路徑值列出所有可以查找其它文件的路徑:
?
?
應用的自動啟動
?????????當一個特殊用戶登錄時,Windows將努力讀出下面的鍵:
HKEY_CURRENT_USER
/Software
/Microsoft
/Windows
/CurrentVersion
/RunOnce
如果這個鍵存在,名字被存儲在鍵值中的任何程序都將被執行。在檢查過之后所有條目都將被刪除。所以,它們僅被執行一次。據此,我們可以編碼使應用能夠在下一次特殊用戶登錄時執行。注意,這僅僅是在下一次登陸時,不是每一次持續登陸時。要每一次特殊用戶登錄都運行應用有一個注冊表條目在上面位置的Run鍵下。自動執行不完全是系統特征,程序必須協同操作。特別是,程序添加其本身(或任何其它應用)到RunOne鍵下,正確地作這個操作就是響應WM_ENDSESSION消息。當然,在任何時候都可以做,然而因為我們的目標是獲得持續的交互會話,所以,只有在關閉當前會話后我們的程序仍然在運行時才建立這個條目。這也WM_ENDSESSION消息到達時。關于應用運行的信息在注冊表中有如下格式:
ID = program name
你需要建立一個值,其內容是可執行程序的路徑名。ID必須是唯一的,但是與你指定什么名字并不重要。下圖顯示一個例子:
???????????
?
?
這些條目以它們鍵入的順序依次取出——這個順序不一定符合注冊表編輯器的輸出順序,在編輯器中條目是按字母順序顯示的。程序是一個接一個異步導出的,如果你有上面圖中顯示的注冊表條目設置,你就會發現,當你登錄時記事本程序在桌面上打開。
?
另一個RunOnce鍵
?????????還有另一個比RunOnce更強的RunOnce鍵在下面的路徑下:
HKEY_LOCAL_MACHINE
/SOFTWARE
/Microsoft
/Windows
/CurrentVersion
/RunOnce
使用這個鍵的語法實際與前面介紹的相同,但是,其工作方法有三方面不同,它們是:
?
???????????????????任何用戶登錄,這個鍵的內容都被檢查
???????????????????各個注冊程序同步執行——僅在前一程序完成之后,下一條目開始運行。
???????????????????在這個鍵下注冊的程序運行在HKEY_CURRENT_USER節點相同子鍵注冊的程序之前。
?
如果你在HKEY_LOCAL_MACHINE/.../RunOnce鍵下注冊了記事本程序,下一次登錄或重啟動時記事本將在任務條和桌面圖標之前出現在桌面上。更要緊的是你不能看到桌面和圖標,直道關閉這個窗口終止過程之后才可以。
?
運行鍵
?????????Run鍵,我們在上面討論中臨時提及的鍵,也是在HKEY_LOCAL_MACHINE和HKEY_CURRENT_USER
下都存在的鍵。Run和RunOnce除了在最后刪除所讀出的注冊條目外,它們有一致的邏輯。在Run下的項目每次用戶登錄時都執行。
?
RunServices鍵
????在Windows95和Windows98下有兩個鍵允許你模仿NT服務——即,在用戶登錄之前運行模塊。它們是:
HKEY_LOCAL_MACHINE
/SOFTWARE
/Microsoft
/Windows
/CurrentVersion
/RunServices
/RunServicesOnce
它們的語法與我們前面討論過的其它鍵的語法相同。RunService在每次登錄之前運行應用,而RunServiceOnce則與下一次登陸作相同的操作。執行程序是異步的,在用戶實際登錄之后可以終止。在任何場合,比照RunOnce和Run鍵,所有服務都必須在系統開始之前執行,
?????????下面的表給出啟動期間,Windows操作注冊表鍵的實際順序:
?
| 步 | 鍵 |
| 1 | HKLM/.../RunServicesOnce?( NT下不支持) |
| 2 | HKLM/.../RunServices?(NT下不支持) |
| 3 | 用戶登錄。用戶可以在所有服務開始之前登錄 |
| 4 | 所有服務開始和用戶登錄 |
| 5 | HKLM/.../RunOnce |
| 6 | 所有注冊程序完成 |
| 7 | HKLM/.../Run |
| 8 | HKCU/.../Run |
| 9 | 當前用戶包含在Startup文件夾下的程序 |
| 10 | HKCU/.../RunOnce |
?
Winlogon鍵
?????????如果在任何用戶登錄之前,僅需要簡單地顯示信息,你可以探索下面鍵的注冊條目:
HKEY_LOCAL_MACHINE
/SOFTWARE
/Microsoft
/Windows
/CurrentVersion
/Winlogon
LegalNoticeCaption和LegalNoticeText值可以定義系統消息框的標題和文字,這個消息框將在任何用戶登錄之前出現。
?
Windows 9x的服務
?????????在WindowsNT下你可以編寫服務來實現要求系統特權的特定任務。NT服務是具有特殊結構和行為的Win32應用。除了特殊的實現細節外,服務的主要特征可以概括為以下幾點:
?????????服務在任何用戶登錄之前運行
?????????服務持續運行,即使用戶注銷也不終止
?????????服務沒有用戶界面,不是交互的
?????????服務從操作系統取得特殊待遇——如,它可以在包括系統帳號的任何用戶帳號下自動啟動和運行
?????????服務運行在單獨的虛擬桌面上,它不同于應用使用的桌面
?????????服務可以停止或暫停
WindowsNT有一個特殊的控件稱為服務控制管理器(SCM),它管理運行中的服務。由于這個接口非常強,因此在NT下不需要RunServices和RunServicesOnce注冊表鍵幫忙。
????我們的目的是通過探索RunServices鍵,你可以仿真NT服務,以獲得大致相同的行為。Windows9x的服務正常是Win32應用(無論是否為GUI或控制臺應用),僅是簡單地注冊在RunServices下在登陸之前運行的程序。
?????????通過調用RegisterServiceProcess() API函數,你可以注冊當前進程(或任何其它運行中的進程)為服務。以使它持續工作即使用戶注銷也不停止。這個函數并不在任何內部庫中輸出,因此,需要動態經由GetProcAddress()加載,它包含在kernel32.dll中。
?????????下表列出WindowsNT與Windows9x服務的區別:
| WindowsNT服務 | Windows9x服務 |
| 輸出ServiceMain()函數的Win32應用 | 傳統的Win32應用 |
| 在登陸之前運行 | 如果注冊在RunServices之下,在登陸之前運行 |
| 在注銷后繼續運行 | 如果使用RegisterServiceProcess()注冊為服務,在注銷后繼續運行 |
| 一個沒有用戶界面的GUI或控制臺應用 | 一個沒有用戶界面的GUI或控制臺應用 |
| 可在系統帳號下運行 | 不支持 |
| 運行在單獨桌面上 | 不支持 |
| 可以停止或暫停 | 僅可以通過調用TerminateProcess()停止 |
服務必須沒有用戶界面并不是系統要求的,但確實合理并強烈推薦。
?
設計Shell集成的應用
?????????到目前為止我們已經調查了應用程序與Shell集成的主要技術。現在我們給出一個具體的例子來說明實際程序的設計原理和規則。
?????????對于面向Shell的應用程序,第一個要求是程序應該是基于文件的。這就是說,應用必須是圍繞一定種類文檔操作而工作的。其菜單應該誠實地呈現出對處理文檔的操作活動,所以在設計Shell集成應用中你應該清楚地知道哪些功能是要通過Shell展示的。
?????????其次,這些功能必須盡可能模塊化編碼,并且通過命令行、RunDLL32或Shell擴展的方法可以訪問它們,下面我們就研究怎樣來吸納這些建議。
?
元文件觀察器
?????????我們將要開發的這個應用是元文件觀察器。選擇這個例子有兩個原因:
???????????????????它是一個有意義的基于文件的應用
???????????????????在Windows中沒有系統實用程序來查看WMF和WMF文件
要說明第二點,當前唯一觀察元文件的方法是打開文件夾的‘觀察?|?作為Web頁面’選項,并且有賴于一個嵌入的小控件(當然,不難獲得這個共享件使用程序,但是這仍然可以說明這樣的實用程序提供了足夠的Shell層支持)。
?????????我們的例子是一個簡單的基于對話框的應用,它允許你選擇,顯示,打印和轉換任何Windows元文件。下面的截圖顯示這個樣例程序的外觀,使用AppWizard建立一個稱之為WMFView的基于對話框的應用:
?????????????
?
?
我們首先調查怎樣使這個程序能實際顯示元文件,而后怎樣增強它的代碼求得關聯菜單客戶化的幫助。
?
Windows元文件和增強元文件
?????????元文件是一些稱作記錄的圖形指令集,它們以產生圖像的順序一個接一個地執行。在Win32以前有兩種元文件:
???????????????????Windows元文件
???????????????????可定位元文件
例如,Office的剪裁文件就全部是可定位元文件,這兩種類型的元文件通常都有.wmf擴展名。
?????????關于元文件的詳細說明超出了本書的范圍,你可以參考MSDN庫的幫助。
隨著Win32平臺的出現,Windows元文件的格式也發生了變化。Win32提出了更新的.emf格式(增強元文件),但是仍然提供對老的WMF型元文件的支持。
?????????顯然,API函數更多地關注增強元文件,值得注意的是,在Win32公共控件中是‘圖像’控件才有能力顯示增強元文件,因而它打開和顯示EMF也是直觀的,然而不幸的是,對于老的WMF文件就不是那么容易的了。很幸運,我們發現了一個工具在微軟的Web站點上:
http://support.microsoft.com/download/support/mslfiles/enmeta.exe
因此我們能夠使用這個例子作為參考建立我們自己的源文件觀察器。
?
顯示元文件
?????????wmfview.exe程序識別三種類型的元文件:
????????Windows元文件
????????可定位元文件
????????增強元文件
?
頭兩個有.wmf擴展名,最后一個有.emf擴展名。無論當前打開文件的初始格式是什么,程序總是內部使用增強元文件格式。下面代碼顯示怎樣打開和顯示元文件,無論其初始格式如何:
//
//?需要處理16位可定位元文件
#pragma pack(push)
#pragma pack(2)
typedef struct{
DWORD dwKey;
WORD hmf;
SMALL_RECT bbox;
WORD wInch;
DWORD dwReserved;
WORD wCheckSum;
} APMHEADER, *LPAPMHEADER;
#pragma pack(pop)
//
//?獲取Handle和顯示指定的元文件
void DisplayMetaFile(HWND hwndMeta, LPTSTR szFile)
{
//?取得元文件的Handle
HENHMETAFILE hemf = GetMetaFileHandle(szFile);
if(hemf == NULL)
{
MessageBox(NULL, __TEXT("不能處理這個文件."),
szFile, MB_OK | MB_ICONSTOP);
return;
}
//?釋放老文件并顯示新文件
HENHMETAFILE hemfOld = reinterpret_cast<HENHMETAFILE>(
SendMessage(hwndMeta, STM_GETIMAGE, IMAGE_ENHMETAFILE, 0));
if(hemfOld)
DeleteEnhMetaFile(hemfOld);
// hwndMeta?是圖像控件
SendMessage(hwndMeta, STM_SETIMAGE, IMAGE_ENHMETAFILE,
Reinterpret_cast<LPARAM>(hemf));
lstrcpy(g_szCurFile, szFile);
}
DisplayMetaFile()函數調用GetMetaFileHandle()輔助函數來獲取傳遞來的元文件Handle,刪除任何當前顯示,然后發送消息到控件使它顯示新的元文件。
//?對指定文件恢復它的?HENHMETAFILE Handle
HENHMETAFILE GetMetaFileHandle(LPTSTR szFile)
{
DWORD dwSize = 0;
LPBYTE pb = NULL;
//?試著作為EMF讀取文件
HENHMETAFILE hEMF = GetEnhMetaFile(szFile);
if(hEMF)
return hEMF;
//?試著作為WMF讀取文件
HMETAFILE hWMF = GetMetaFile(szFile);
if(hWMF)
{
dwSize = GetMetaFileBitsEx(hWMF, 0, NULL);
if(dwSize == 0)
{
DeleteMetaFile(hWMF);
return NULL;
}
//?分配足夠的內存
pb = new BYTE[dwSize];
if(pb == NULL)
{
DeleteMetaFile(hWMF);
return NULL;
}
//?取得元文件的位
dwSize = GetMetaFileBitsEx(hWMF, dwSize, pb);
if(dwSize == 0)
{
delete [] pb;
DeleteMetaFile(hWMF);
return NULL;
}
//?轉換成?EMF
hEMF = SetWinMetaFileBits(dwSize, pb, NULL, NULL);
//?清理
DeleteMetaFile(hWMF);
delete [] pb;
return hEMF;
}
//?試著處理輸入為可定位元文件
HANDLE hFile = CreateFile(szFile, GENERIC_READ, 0, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if(hFile == INVALID_HANDLE_VALUE)
return NULL;
//?讀文件到緩沖
dwSize = GetFileSize(hFile, NULL);
pb = new BYTE[dwSize];
ReadFile(hFile, pb, dwSize, &dwSize, NULL);
CloseHandle(hFile);
//?檢查看是否為可定位元文件
if((reinterpret_cast<LPAPMHEADER>(pb))->dwKey != 0x9ac6cdd7l)
{
//?這個文件不可知怎樣處理
delete [] pb;
return NULL;
}
//?從位信息建立增強元文件
hEMF = SetWinMetaFileBits(dwSize, &(pb[sizeof(APMHEADER)]), NULL, NULL);
delete [] pb;
return hEMF;
}
GetMetaFileHandle()的操作隱藏了它所涉及到的一般元文件和增強元文件的不同,而且上述大多數代碼都是關于格式轉換的。最后總是返回增強型元文件的Handle到調用它的函數。
?
打印和轉換元文件
?????????這個程序還可以打印元文件,或轉換元文件從WMF到EMF,或從EMF到WFM。打印就是簡單地取得關聯的打印設備,然后在打印機上顯示元文件。
void PrintMetaFile(LPTSTR szFile)
{
//?取得?EMF handle
HENHMETAFILE hEMF = GetMetaFileHandle(szFile);
if(hEMF == NULL)
return;
//?取得打印機的?DC
PRINTDLG pdlg;
ZeroMemory(&pdlg, sizeof(PRINTDLG));
pdlg.lStructSize = sizeof(PRINTDLG);
pdlg.Flags = PD_RETURNDC;
HDC hDC = NULL;
if(PrintDlg(&pdlg))
hDC = pdlg.hDC;
else
return;
//?準備打印文檔
DOCINFO di;
ZeroMemory(&di, sizeof(DOCINFO));
di.cbSize = sizeof(DOCINFO);
di.lpszDocName = "Printing EMF";
//?啟動打印
StartDoc(hDC, &di);
StartPage(hDC);
//?標定符合整個打印頁
RECT rc;
SetRect(&rc, 0, 0, GetDeviceCaps(hDC, HORZRES), GetDeviceCaps(hDC, VERTRES));
PlayEnhMetaFile(hDC, hEMF, &rc);
//?清理
EndPage(hDC);
EndDoc(hDC);
DeleteDC(hDC);
DeleteEnhMetaFile(hEMF);
}
轉換元文件也并不復雜,就像下面程序清單中說明的。三個函數里的頭一個SaveMetaFile(),處理EMF存儲成可定位的WMF文件(或相反),具有相同的名字,和不同的擴展名。實際上,每一個元文件都首先轉換成EMF(由GetMetaFileHandle()函數),然后再作為EMF或WMF存儲到磁盤。
void SaveMetaFile(LPTSTR szFile)
{
TCHAR szOutputFile[MAX_PATH] = {0};
HENHMETAFILE hEMF = GetMetaFileHandle(szFile);
if(hEMF == NULL)
return;
//?確定輸出格式
lstrcpy(szOutputFile, szFile);
strlwr(szFile);
if(strstr(szFile, ".emf"))
{
PathRenameExtension(szOutputFile, ".wmf");
SaveToWMF(hEMF, szOutputFile);
}
else if(strstr(szFile, ".wmf"))
{
PathRenameExtension(szOutputFile, ".emf");
SaveToEMF(hEMF, szOutputFile);
}
DeleteEnhMetaFile(hEMF);
}
兩個輔助函數SaveToEMF()和SaveToWMF()是非常簡單的:
void SaveToEMF(HENHMETAFILE hEMF, LPTSTR szFile)
{
//?取得存儲EMF位的內存
DWORD dwSize = GetEnhMetaFileBits(hEMF, 0, NULL);
LPBYTE pb = new BYTE[dwSize];
//?取得EMF位信息
GetEnhMetaFileBits(hEMF, dwSize, pb);
//?存儲到文件
HANDLE hFile = CreateFile(szFile, GENERIC_WRITE,
0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
if(hFile == INVALID_HANDLE_VALUE)
{
UINT rc = MessageBox(GetFocus(), "File exists. Overwrite?",
szFile, MB_ICONQUESTION | MB_YESNO);
if(rc == IDYES)
hFile = CreateFile(szFile, GENERIC_WRITE,
0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
else
{
delete [] pb;
return;
}
}
DWORD dwBytes;
WriteFile(hFile, pb, dwSize, &dwBytes, NULL);
CloseHandle(hFile);
delete [] pb;
}
void SaveToWMF(HENHMETAFILE hEMF, LPTSTR szFile)
{
//?取得存儲WMF位信息的內存
HDC hDC = GetDC(NULL);
DWORD dwSize = GetWinMetaFileBits(hEMF, 0, NULL, MM_ANISOTROPIC, hDC);
LPBYTE pb = new BYTE[dwSize];
//?從EMFHandle中取出WMF位信息
GetWinMetaFileBits(hEMF, dwSize, pb, MM_ANISOTROPIC, hDC);
ReleaseDC(NULL, hDC);
//?存儲到文件
HANDLE hFile = CreateFile(szFile, GENERIC_WRITE,
0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
if(hFile == INVALID_HANDLE_VALUE)
{
UINT rc = MessageBox(GetFocus(), "File exists. Overwrite?",
szFile, MB_ICONQUESTION|MB_YESNO);
if(rc == IDYES)
hFile = CreateFile(szFile, GENERIC_WRITE,
0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
else
{
delete [] pb;
return;
}
}
DWORD dwBytes;
WriteFile(hFile, pb, dwSize, &dwBytes, NULL);
CloseHandle(hFile);
delete [] pb;
}
它們看來對于我們的討論似乎是多余的,然而轉換函數不僅是用于顯示的。盡管Win32開始就支持增強元文件,但你總能接觸到WMF文件——大部分是可定位元文件。因此對于這類應用一種容易的格式轉換方法是無價的。
?
組建觀察器
?????????要把前面給出的離散函數組裝到一起組建成一個應用,我們需要在某個地方調用它們。在開發的這個一階段我們選擇對話框的相關菜單來完成這個工作(你可以使用VC++的資源編輯器的‘屬性’關聯菜單生成)。添加本節開始的截圖上顯示的菜單項,然后修改APP_DlgProc()中的WM_COMMAND處理器,如下:
case WM_COMMAND:
switch(wParam)
{
case ID_FILE_OPEN:
OnOpen(hDlg);
return FALSE;
case ID_FILE_PRINT:
OnPrint(hDlg);
return FALSE;
case ID_FILE_SAVEAS:
OnSave(hDlg);
return FALSE;
case ID_FILE_EXIT:
case IDCANCEL:
EndDialog(hDlg, FALSE);
return FALSE;
}
break;
最后,問題歸結到實現三個相對容易的函數或例程上,我們在下面給出它們的定義:
void OnOpen(HWND hDlg)
{
TCHAR szFile[MAX_PATH] = {0};
OPENFILENAME ofn;
ZeroMemory(&ofn, sizeof(OPENFILENAME));
ofn.lStructSize = sizeof(OPENFILENAME);
ofn.lpstrFilter =
"Metafiles/0*.?mf/0WMF/0*.wmf/0Enhanced/0*.emf/0All Files/0*.*/0";
ofn.nMaxFile = MAX_PATH;
ofn.lpstrFile = szFile;
if(!GetOpenFileName(&ofn))
return;
else
{
HWND hwndMeta = GetDlgItem(hDlg, IDC_METAFILE);
DisplayMetaFile(hwndMeta, ofn.lpstrFile);
RefreshUI(hDlg, ofn.lpstrFile);
}
}
void OnPrint(HWND hDlg)
{
if(lstrlen(g_szCurFile))
PrintMetaFile(g_szCurFile);
else
Msg("當前沒有打開的元文件.");
}
void OnSave(HWND hDlg)
{
TCHAR s[1024] = {0};
TCHAR szOutputFile[MAX_PATH] = {0};
if(!lstrlen(g_szCurFile))
{
Msg("當前沒有打開的元文件.");
return;
}
//?請求用戶確認
lstrcpy(szOutputFile, g_szCurFile);
if(strstr(g_szCurFile, ".emf"))
PathRenameExtension(szOutputFile, ".wmf");
else if(strstr(g_szCurFile, ".wmf"))
PathRenameExtension(szOutputFile, ".emf");
wsprintf(s, "You're about to convert %s to %s./nAre you really sure?",
g_szCurFile, szOutputFile);
UINT rc = MessageBox(hDlg, s, APPTITLE, MB_ICONQUESTION | MB_YESNO);
//?處理...
if(rc == IDYES)
SaveMetaFile(g_szCurFile);
}
上面代碼顯示這些函數完成了應用的操作,除了下述的三件事。第一,APPTITLE是一個全局串常量,它是對話框的標題,因而應該等于“元文件觀察器”。第二,g_szCurfile是一個全局字符數組,用于存儲打開的元文件名,并且在WinMain()中應該設置成空串。第三,RefreshUI()是一個輔助函數,它用于把打開的源文件名字附加到對話框標題上:
void RefreshUI(HWND hWnd, LPTSTR szFile)
{
TCHAR szCaption[MAX_PATH] = {0};
//?刷新標題條
wsprintf(szCaption, "%s - %s", APPTITLE, szFile);
SetWindowText(hWnd, szCaption);
}
使用這最后一個函數和通用對話框及Shell輕量級API的頭文件和庫文件,你現在就可以自豪的擁有這個有用的應用程序來顯示,打印和轉換元文件了。
?
改編這個應用
?????????通過文檔的關聯菜單打印和轉換元文件是一種更好的方法。在上面的函數中,我們已經用模塊的方法實現了三個函數:
???????????????????打開
???????????????????打印
???????????????????轉換到
也就是說,有三個靜態動詞可以加到WMF和EMF文檔上,然而,在我們能夠斷言可以成功地客戶化關聯菜單之前,在應用中有幾個問題需要解決。首先,我們需要加入對命令行的支持,其次,我們需要注冊EMF和WMF系統文件類——默認情況下它們是不可知文件類。此后,第三個問題是每次點擊元文件都導出新的wmfview實例運行。這比只有一個實例運行要好,因為可以打開,打印或轉換任何新文檔。下面就讓我們來著手解決這些問題。
?
命令行的重要性
這個應用應該支持下面的命令行:
wmfview.exe filename
wmfview.exe /p filename
wmfview.exe /s filename
頭一行打開指定的文件,其他兩行是打印和轉換文件。有命令行的支持可以使我們容易地添加新動詞到EMF和WMF文檔,看一下這段代碼:
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevious,LPTSTR lpsz, int iCmd)
{
//?這段代碼沒有改變,因而省略
//?運行主對話框
BOOL b = DialogBoxParam(hInstance, "DLG_MAIN", NULL, APP_DlgProc,
reinterpret_cast<LPARAM>(lpsz));
//?退出
DestroyIcon(g_hIconLarge);
DestroyIcon(g_hIconSmall);
return b;
}
WinMain()函數傳遞對話框過程通過調用DialogBoxParam() API函數而接收的命令行串。任何命令行變量則通過WM_INITDIALOG消息的響應來處理,如下代碼顯示:
void OnInitDialog(HWND hDlg, LPARAM lParam)
{
//?設置圖標(T/F?作為大/小圖標)
SendMessage(hDlg, WM_SETICON, FALSE, reinterpret_cast<LPARAM>(g_hIconSmall));
SendMessage(hDlg, WM_SETICON, TRUE, reinterpret_cast<LPARAM>(g_hIconLarge));
if(lstrlen(reinterpret_cast<LPTSTR>(lParam)))
ParseCommandLine(hDlg, reinterpret_cast<LPTSTR>(lParam));
}
void ParseCommandLine(HWND hwnd, LPTSTR pszCmdLine)
{
if(!lstrlen(pszCmdLine))
return;
//?取得命令行的頭兩個字符(+ 1?它是開關)
TCHAR pszSwitch[2] = {0};
lstrcpyn(pszSwitch, pszCmdLine, 3);
LPTSTR psz = pszCmdLine + lstrlen(pszSwitch) + 1;
//?解析條件并發送客戶消息
if(!lstrcmpi(pszSwitch, "/p"))
SendMessage(hwnd, WM_EX_PRINTMETA, 0, reinterpret_cast<LPARAM>(psz));
else if(!lstrcmpi(pszSwitch, "/s"))
SendMessage(hwnd, WM_EX_SAVEMETA, 0, reinterpret_cast<LPARAM>(psz));
else
SendMessage(hwnd, WM_EX_DISPLAYMETA,
?0,reinterpret_cast<LPARAM>(pszCmdLine));
}
如上所示,ParseCommandLine()函數檢查命令行,決定要做什么,然后發送客戶消息到應用窗口過程??蛻粝⑷缦露x:
const int WM_EX_DISPLAYMETA = WM_APP + 1;
const int WM_EX_PRINTMETA = WM_APP + 2;
const int WM_EX_SAVEMETA = WM_APP + 3;
APP_DlgProc()得到幾個調用我們已經定義過的函數處理器,如下:
BOOL CALLBACK APP_DlgProc(HWND hDlg, UINT uiMsg, WPARAM wParam, LPARAM lParam)
{
switch(uiMsg)
{
case WM_INITDIALOG:
OnInitDialog(hDlg, lParam);
break;
case WM_EX_DISPLAYMETA:
DisplayMetaFile(GetDlgItem(hDlg, IDC_METAFILE),
reinterpret_cast<LPTSTR>(lParam));
RefreshUI(hDlg, reinterpret_cast<LPTSTR>(lParam));
break;
case WM_EX_PRINTMETA:
PrintMetaFile(reinterpret_cast<LPTSTR>(lParam));
break;
case WM_EX_SAVEMETA:
SaveMetaFile(reinterpret_cast<LPTSTR>(lParam));
break;
case WM_COMMAND:
現在你可以看出,用命令行運行應用和顯示、打印以及保存元文件就象使用對話框的菜單一樣。
?
為什么應用要單實例化
?????????上面代碼顯示每次都運行一個新實例。一旦我們把它添加到Shell使之支持元文件,就可以在任何WMF或EMF文件上點擊來導出wmfview,顯示指定的元文件。問題是新的程序副本被導出不僅是在打開文件時,在打印或轉換文件時也產生。為了避免太多的wmfview窗口,我們需要使之成為單實例應用。
?????????回顧Windows3.x中WinMain()的hPrevious變量,它用于表示應用的前一個實例是否存在。在Win32平臺下,這個變量僅僅是為了維護兼容性而存在。并且總是NULL。因此很難知道是否有同一個進程的副本當前正在運行,但是仍然有幾種可用的技術:
| 技術 | 描述 |
| FindWindow() | 這個API函數返回屬于指定類并具有給定標題的頭一個窗口的Handle。因而它可以通過類名或標題鑒別窗口。 |
| EnumWindows() | 這個API函數枚舉所有存在的非子窗口的窗口。這對于調查是否有相同類或標題的多重窗口是有用的。 |
| 進程名 | 這項技術要求枚舉所有活動進程并檢查程序名。 |
| 互斥量和信號燈 | 如果你想限制實例的數量,也可以使用同步結構,如互斥量和信號燈?;コ饬繉τ趩螌嵗龖酶靡恍?#xff0c;而信號燈則允許有固定數量的副本。 |
| ? | ? |
?
基于對話框的單實例的應用
?????????在wmfview情況下,要求我們采用EnumWindows()方法。首先,互斥量和進程名的方法對于我們不是太好,因為我們需要恢復前面窗口的Handle以便再重新使用它。簡單地知道它存在是沒有幫助的。
?????????再有,盡管FindWindow()更簡單,但是我們的程序是基于對話框的,所以沒有一個可以容易辨別的類名。相反,我們程序的主窗口類名是#32770,這實際與任何對話框或基于對話框應用同名。而且FindWindow()在頭一個匹配后就停止了。我們可以使用標題來減少匹配的機會,但是我們在標題條上附加了打開的文件名,因此標題是經常變化的。
?????????剩余的只有基于EnumWindows()這一種方法。我們將枚舉窗口,并每次檢查類名和標題。對于對話框窗口,我們驗證標題具有我們期望的前綴——即,“源文件觀察器”。為了絕對保證我們獲得正確的窗口,我們還檢查建立它的可執行文件。
?????????取得可執行文件名比想象的要困難得多,因為在Windows9x和WindowsNT中沒有共同的方法。我們需要在Windows9x下使用ToolHelp?API函數,而在NT下使用PSAPI函數。關于這兩種API的資料在MSDN庫中可以找到。
?????????只要獲得了前一個實例的HWND Handle,我們就可以把它推到前臺并調用ParseCommandLine(),傳遞我們接收的命令行。下面是對WinMain()代碼的修改:
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevious,LPTSTR lpsz, int iCmd)
{
//?是否有運行中的實例
HWND hwnd = AnotherInstanceRunning();
if(IsWindow(hwnd))
{
//?推送前一個窗口到頂部
if(IsIconic(hwnd))
ShowWindow(hwnd, SW_RESTORE);
SetForegroundWindow(hwnd);
//?解析‘這個’命令行而不是發送消息到前一個窗口
ParseCommandLine(hwnd, lpsz);//這里使用的是獲得的?hWnd,在分析中發送消息。
//?現在可以退出了
return 1;
}
//?其余函數不變
}
這里的AnotherInstanceRunning()就是實際用于測試的函數,它調用EnumWindows()函數,而回調函數僅在找到同類窗口時停止。
HWND AnotherInstanceRunning()
{
HWND hwndFound = NULL;
EnumWindows(CheckRunningApps, reinterpret_cast<LPARAM>(&hwndFound));
// hwndFound?將取匹配窗口的Handle
return hwndFound;
}
BOOL CALLBACK CheckRunningApps(HWND hwnd, LPARAM lParam)
{
TCHAR szClass[MAX_PATH] = {0};
GetClassName(hwnd, szClass, MAX_PATH);
if(!lstrcmpi(szClass, "#32770"))
{
TCHAR s[MAX_PATH] = {0};
TCHAR szTitle[MAX_PATH] = {0};
GetWindowText(hwnd, szTitle, MAX_PATH);
lstrcpyn(s, szTitle, 1 + lstrlen(APPTITLE));
if(!lstrcmpi(s, APPTITLE))
{
//?使用緩沖指針lparam返回HWND
HWND* lphwnd = reinterpret_cast<HWND*>(lParam);
*lphwnd = hwnd;
return FALSE;
}
}
return TRUE;
}
現在我們最終有了一個單實例應用,可用它來觀察,打印和轉換元文件,因此可以考慮怎樣添加某些Shell的支持了。
?
添加Shell支持
?????????典型地,Shell支持意思是:
???????????????????注冊應用處理的每一種文件類型
???????????????????注冊文件類型的默認圖標
???????????????????添加關聯菜單動詞
???????????????????添加打開的文檔到最近文檔列表
我們已經看到了怎樣注冊新的文件類型及其圖標,并且在Wmfview.exe的資源中我們也安排了包含元文件和增強元文件的圖標:
???????????????????????????????????????
?
?
添加到系統注冊表中的條目包含在下面的腳本代碼中:
REGEDIT4
; //
; //?注冊?WMF?和?EMF?文件類型
[HKEY_CLASSES_ROOT/.wmf]
@= "WinMetafile"
[HKEY_CLASSES_ROOT/WinMetafile]
@= "Windows Metafile"
[HKEY_CLASSES_ROOT/.emf]
@= "EnhMetafile"
[HKEY_CLASSES_ROOT/EnhMetafile]
@= "Enhanced Metafile"
; //
; //?注冊WMF?和?EMF的圖標
[HKEY_CLASSES_ROOT/WinMetafile/DefaultIcon]
@= "C://WmfView//WmfView.exe,2"
[HKEY_CLASSES_ROOT/EnhMetafile/DefaultIcon]
@= "C://WmfView//WmfView.exe,1"
; //
; //?添加?WMF?打開,打印和保存動詞
;?打開
[HKEY_CLASSES_ROOT/WinMetafile/Shell/Open/Command]
@= "C://WmfView//WmfView.exe %1"
;?打印
[HKEY_CLASSES_ROOT/WinMetafile/Shell/Print/Command]
@= "C://WmfView//WmfView.exe /p %1"
;?保存到?EMF
[HKEY_CLASSES_ROOT/WinMetafile/Shell/Save]
@= "&Convert to EMF"
[HKEY_CLASSES_ROOT/WinMetafile/Shell/Save/Command]
@= "C://WmfView//WmfView.exe /s %1"
; //
; //?添加打開,打印和保存動詞到?EMF
;?打開
[HKEY_CLASSES_ROOT/EnhMetafile/Shell/Open/Command]
@= "C://WmfView//WmfView.exe %1"
;?打印
[HKEY_CLASSES_ROOT/EnhMetafile/Shell/Print/Command]
@= "C://WmfView//WmfView.exe /p %1"
;?保存到?WMF
[HKEY_CLASSES_ROOT/EnhMetafile/Shell/Save]
@= "&Convert to WMF"
[HKEY_CLASSES_ROOT/EnhMetafile/Shell/Save/Command]
@= "C://WmfView//WmfView.exe /s %1"
除了假定已經安裝了WmfView.exe到c:/wmfview文件夾下,這個清單還展示了在編寫自己的注冊腳本時所需要了解的一些事情。特別是:
?
???????????????????在REG腳本中?@符號表示默認值
???????????????????非常重要的是在路徑名中使用兩個反斜杠,而在注冊條目中使用一個反斜杠
???????????????????在編寫REG腳本時,記住不要割裂包含在括號中的注冊表路徑到兩個行或多個行上
?
默認情況下動詞名(此時為Shell鍵下的任何子鍵)就是實際出現在關聯菜單上的項。這對于‘打開’和‘打印’是適合的,但是并不適合‘保存’表示的從EMF轉換到WMF或相反的命令。這就是為什么我們設置‘保存’動詞的默認值為客戶想要出現在關聯菜單項上的串的理由。你可以通過使用注冊表編輯器的‘注冊表?|?輸入注冊文件’菜單項,通過雙擊這個REG文件,或編程通過ShellExecute()函數執行這個REG文件運行這個腳本。此后,重新啟動探測器,其效果如下圖中顯示的一樣:
??????????
?
?
改變默認菜單項
?????????如圖表示的,‘打開’動詞總是設置為默認的關聯菜單項。默認項是以粗體顯示的,并且雙擊左鍵自動選擇(或單擊,依賴于桌面設置)。默認項總是顯示在關聯菜單中的第一項。然而,通過設置下面鍵的默認值,我們可以重排增強元文件關聯菜單的項,例如:
HKEY_CLASSES_ROOT
/EnhMetafile
/Shell
正常情況下這個值包含空字符串,但是如果你設置它為用逗號分隔的串,其標記是各個動詞的名,則他們將以你指定的順序顯示,而第一項是默認項,這并不要求你指定所有列表中的動詞,所以,你可以限制有多少要重排的項。
?????????記住這個技術僅對定義在Shell鍵下的靜態動詞有效
?
添加所有文件的關聯菜單項
?????????在上面的截圖中你可能已經注意到有兩個關系到WinZip的項。這兩個項并不是特別針對元文件的,而是針對所有文件的(但不對文件夾)。因此,它們沒有在WinMetaFile或EnhMetaFile的Shell鍵下列出。要為所有文件類添加關聯菜單項,簡單地在下面鍵中添加動詞即可:
HKEY_CLASSES_ROOT
/*
/Shell
?
??????????
?
有時添加新項將從‘打開方式’項中刪除默認風格。還要注意,所有在這里添加的項總是在特殊文件類型的Shell項之前顯示。然而這顯然不是WinZip的場合。那么WinZip是怎么做的呢。如果你想要添加新命令在HKEY_CLASSES_ROOT/*/Shell鍵下而又不刪除當前默認項,一個辦法就是定義Shell擴展。就象上圖
中的WinZip和‘公文包’那樣。Shell擴展在shellex鍵下,并且,如果應用于關聯菜單,還需要有進一步的ContextMenuHandlers子鍵。我們將在下一章中討論這個問題。
如果想要添加客戶菜單項到任何文件夾或驅動器,則注冊鍵應該分別在:
HKEY_CLASSES_ROOT/Folder
和
HKEY_CLASSES_ROOT/Drive
下。
?
給文件夾指定客戶圖標
?????????假設你編寫了一套應用,并安裝在公共路徑下。如果這個文件夾有一個客戶化圖標是否會更好一點??匆幌孪旅娴膱D:
?????????
?
?
這個Wrox Applications應用的文件夾比正常的要好一些,而且它的圖標也是不同的。要獲得這個效果,只需完成下面幾步:
?
在想要客戶化的文件夾中建立一個名為desktop.ini的ASCII文件??梢允蛊潆[藏,但并不是必要的。過一會我們將討論這個文件的內容。
使這個目錄為只讀。這可以編程實現或通過屬性對話框實現?。
?
desktop.ini文件是一個特殊意義的探測器——它表示在Shell與一個客戶化文件夾外觀和行為的代碼段之間的連接點。在第16章中我們將揭示怎樣通過命名空間擴展改變文件夾的行為,而不要求包含多余信息的desktop.ini文件。為了改變文件夾的圖標,我們需要把下面行添加到這個文件中:
[.ShellClassInfo]
IconIndex=0
IconFile=C:/WMFVIEW.EXE
這些條目的作用是相當清楚的:IconIndex表示圖標在IconFile文件中的索引。因此這兩個項組合定義了文件夾顯示的圖標。
????注意,當你改變文件夾的設置時——特別是在Web觀察下——這個文件的內容被自動更新。
?
自由添加最近文檔
?????????Win32資料說如果想要添加文檔到系統文件夾作為最近使用的文檔,你需要產生對SHAddToRecentDocs()函數的調用。要完成這項工作,我們發現有時并不需要走這么遠。當處理元文件已經被注冊了之后,我們發現wmfview自動保存打開的文檔到這個文件夾,甚至我們并沒有顯式地調用SHAddToRecentDocs()。這顯然是Shell施加于注冊了文檔的應用的一個特征。對于非注冊文件類型,你仍然需要這個API函數。
????事實上,你沒必要必須打開文檔,當你打印或確切地執行任何動詞的時候,都可以。然而,當你使用‘文檔’菜單時,總是執行默認動詞。
?
支持拖拽
????基于文件的應用有對拖拽的支持是一個極強的功能,并且如果你仔細地設計軟件并很好地與Shell集成,添加這個能力就象給你的主窗口賦一個WS_EX_ACCEPTFILES風格的值一樣容易,這就使它能夠感覺到后面的WM_DROPFILES消息。
????重要地是需要有一個可以作為模塊化過程運行的函數來處理這個事件。如果使用命令行來解釋,則添加Shell拖拽要求附加下面的代碼,它將被喚醒響應WM_DROPFILES消息:
case WM_DROPFILES:
HandleFileDrop(hDlg, reinterpret_cast<HDROP>(wParam));
break;
?
void HandleFileDrop(HWND hDlg, HDROP hDrop)
{
TCHAR szFileName[MAX_PATH] = {0};
//?由于是SDI應用,它不能感覺接收超過一個文件,因此僅是第一個文件被接收
DragQueryFile(hDrop, 0, szFileName, MAX_PATH);
SendMessage(hDlg, WM_EX_DISPLAYMETA, 0, reinterpret_cast<LPARAM>(szFileName));
DragFinish(hDrop);
}
?
客戶化打開對話框
?????????面向Shell應用可以使用的另一個值得注意的屬性是定制系統公共對話框。Win32 API描述了我們需要客戶化公共對話框的技術,比如顏色,字體或打印,你應該參考微軟的資料來獲得更多細節信息。
?????????就我們的目的而言,最值得關注的對話框是‘打開’/‘保存’對話框。讓我們從一個客戶化打開對話框的例子開始,在對話框上添加幾個新按鈕作為特殊路徑的標簽。下圖顯示了這個客戶化的對話框。要特別注意‘Open As’的標號和組合框。
??????????????????
?
?
?????????下面我們將說明怎樣建立類似于Office2000產生的打開對話框。在此之前我們要向你解釋一下對話框的客戶化是怎樣發生的。
?
定義新的模版
?????????這個打開對話框允許你定義非標準模板,但是你沒有象Windows3.1中那樣可用的自由度。如果你使用類探測器的外觀,則不允許隱藏不需要的控件。這是由設計規定的,我們現在還沒有看到可靠的工作方法,除非編寫一個新的初級對話框。
?????????要調用打開對話框,你需要GetOpenFileName()函數,顯示如下:
?
OPENFILENAME ofn;
TCHAR szFile[MAX_PATH] = {0};
ZeroMemory(&ofn, sizeof(OPENFILENAME));
ofn.lStructSize = sizeof(OPENFILENAME);
ofn.lpstrFile = szFile;
ofn.nMaxFile = sizeof(szFile);
ofn.lpstrFilter = __TEXT("All files/0*.*/0");
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
GetOpenFileName(&ofn);
為了允許客戶化,需要在調用之前添加下面的代碼行:
ofn.lpTemplateName = MAKEINTRESOURCE(IDD_DIALOG);
ofn.lpfnHook = OpenDlgExProc;
ofn.Flags |= OFN_EXPLORER | OFN_ENABLEHOOK | OFN_ENABLETEMPLATE;
ofn.lpTemplateName指向一個對話框模板,ofn.lpfnHook指向一個窗口過程,這個過程將處理所有客戶控件的消息。
????形成初始對話框的控件必須作為一個整體控件加以考慮,所以,你所設計的任何模板都必須包含整個初始對話框作為構件塊。實際上,這就是說要使用你所附加控件的相對位置,看下面截圖,其中對話框模版有一個‘Child’風格,但是沒有邊緣:
????????????
?
?
高亮部分是靜態部件,其ID為stc32,這是定義在dlg.h(VC++的一個頭文件)中的特殊常量。它表示為一個標準的‘打開’對話框。然后你可以在這個部件周圍放置你的控件,并且不必考慮stc32控件的尺寸,最后的窗口是適當可調整的。就如同圖中顯示的。下面截圖顯示這個打開對話框在運行時使用上面的模版形成的顯示界面,這是一個由AppWizard生成的應用。
??????????????
?
?
標準對話框被擴展成有三個豎排按鈕在左側的客戶化對話框,看上去不過如此,它的行為又如何呢?
?
新對話框屬性
?????????除了新的外表,這個‘打開’對話框應該有什么樣的客戶化行為呢?
??????????????????
快捷方式的常用路徑是一個好想法,如果可以由用戶定義就更好了
???????????????????新控件上的工具標簽是另一個特征,這改進了對話框的外觀和感覺
???????????????????這個對話框應該能夠防止刪除和重命名文件夾中的項
?
常用路徑的標簽
?????????返回前面的討論,我們選擇添加了幾個按鈕來存儲指向‘Favorites’,‘最近文檔’和‘Windows’目錄的標簽。代碼的最有技巧部分是編程獲得特定的路徑,方案與手動所作的一樣。換言之,只需設置‘文件名’編輯框到路徑名,并點擊‘打開’即可。
?????????所有在公共對話框中使用的控件,其ID都定義在dlgs.h頭文件中,但不幸的是這個文件并沒有給這些ID分配助記符。通過使用Spy++和dlg.h,可以對應給出它們的標記。文件名編輯框的ID是0x0480,其相關助記常量是edt1。從窗口過程調用函數處理按鈕點擊,我們需要作下面的操作:
#include <dlgs.h>
#include <shlobj.h>
void Goto(HWND hDlg, WORD wID)
{
TCHAR szDir[MAX_PATH] = {0};
LPITEMIDLIST pidl;
//?恢復轉跳路徑
if(wID == IDC_WINDOWS)
GetWindowsDirectory(szDir, MAX_PATH);
else
{
if(wID == IDC_FAVORITES)
SHGetSpecialFolderLocation(hDlg, CSIDL_FAVORITES, &pidl);
else
if(wID == IDC_RECENT)
SHGetSpecialFolderLocation(hDlg, CSIDL_RECENT, &pidl);
SHGetPathFromIDList(pidl, szDir);
}
//?在文件名編輯框中設置新路徑
HWND hdlgParent = GetParent(hDlg);
SetDlgItemText(hdlgParent, edt1, szDir);
SendMessage(hdlgParent, WM_COMMAND, IDOK, 0);
SetDlgItemText(hdlgParent, edt1, "");
}
注意這段代碼,鉤子過程所接收的窗口Handle不是實際對話框窗口的Handle。這個操作僅僅適用于‘打開’對話框,要獲得實際窗口,你必須取得傳輸窗口的父窗口Handle:
HWND hdlgParent = GetParent(hDlg);
獲得父窗口后,你才能安全地獲得‘打開’對話框的每一個子控件。
?
按鈕的圖標和提示
?????????每一個新按鈕都有一個圖標和提示。下面是怎樣設置按鈕的圖標和提示:
?
void InitNewButtons(HWND hDlg)
{
SHFILEINFO sfi;
LPITEMIDLIST pidl = NULL;
//?指派圖標到‘Favorites’按鈕
SHGetSpecialFolderLocation(hDlg, CSIDL_FAVORITES, &pidl);
SHGetFileInfo(reinterpret_cast<LPTSTR>(pidl),
0, &sfi, sizeof(SHFILEINFO), SHGFI_PIDL | SHGFI_ICON);
SendDlgItemMessage(hDlg, IDC_FAVORITES, BM_SETIMAGE, IMAGE_ICON,
reinterpret_cast<LPARAM>(sfi.hIcon));
//?指派圖標到‘最近文檔’按鈕
SHGetSpecialFolderLocation(hDlg, CSIDL_PERSONAL, &pidl);
SHGetFileInfo(reinterpret_cast<LPTSTR>(pidl),
0, &sfi, sizeof(SHFILEINFO), SHGFI_PIDL | SHGFI_ICON);
SendDlgItemMessage(hDlg, IDC_RECENT, BM_SETIMAGE, IMAGE_ICON,
reinterpret_cast<LPARAM>(sfi.hIcon));
//?指派圖標到‘Windows’按鈕
SendDlgItemMessage(hDlg, IDC_WINDOWS, BM_SETIMAGE, IMAGE_ICON,
reinterpret_cast<LPARAM>(LoadIcon(NULL, IDI_WINLOGO)));
//?設置每個按鈕的提示
SetTooltips(hDlg);
}
圖標根據特定的圖標需求從不同的地方恢復,對于特殊文件夾,如‘Favorites’或‘最近文檔’,使用SHGetFileInfo(),而LoadIcon()則幫助獲得Windows的標記。每一個按鈕也都有自己的提示:
void SetTooltips(HWND hDlg)
{
//?建立提示控件
HWND hwndTT = CreateWindow(TOOLTIPS_CLASS, NULL, TTS_ALWAYSTIP,CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, hDlg,
NULL, GetModuleHandle(NULL), NULL);
//?為每一個按鈕定義要求的工具
//?查看‘Favorites’
TOOLINFO ti;
ZeroMemory(&ti, sizeof(TOOLINFO));
ti.cbSize = sizeof(TOOLINFO);
ti.uFlags = TTF_IDISHWND | TTF_SUBCLASS;
ti.hwnd = hDlg;
ti.uId = reinterpret_cast<UINT>(GetDlgItem(hDlg, IDC_FAVORITES));
ti.lpszText = __TEXT("查看Favorites");
SendMessage(hwndTT, TTM_ADDTOOL, 0, reinterpret_cast<LPARAM>(&ti));
//?查看最近文檔
ZeroMemory(&ti, sizeof(TOOLINFO));
ti.cbSize = sizeof(TOOLINFO);
ti.uFlags = TTF_IDISHWND | TTF_SUBCLASS;
ti.hwnd = hDlg;
ti.uId = reinterpret_cast<UINT>(GetDlgItem(hDlg, IDC_RECENT));
ti.lpszText = __TEXT("查看最近文檔");
SendMessage(hwndTT, TTM_ADDTOOL, 0, reinterpret_cast<LPARAM>(&ti));
//?查看Windows
ZeroMemory(&ti, sizeof(TOOLINFO));
ti.cbSize = sizeof(TOOLINFO);
ti.uFlags = TTF_IDISHWND | TTF_SUBCLASS;
ti.hwnd = hDlg;
ti.uId = reinterpret_cast<UINT>(GetDlgItem(hDlg, IDC_WINDOWS));
ti.lpszText = __TEXT("Look in Windows");
SendMessage(hwndTT, TTM_ADDTOOL, 0, reinterpret_cast<LPARAM>(&ti));
}
我們建立一個新的工具窗口,并定義了五個工具,每一個都有自己的顯示文字。在每一個定義新工具的塊中,uFlags字段指定uId字段表示窗口Handle(TTF_IDISHWND)。意思是這個窗口,即按鈕之一是一個顯示分配給lpszText文字的區域。
?????????對于要顯示的標簽,本質上是一個窗口區域,它具有發送適當的鼠標通知的到提示窗口的能力。TTF_SUBCLASS標志使提示窗口自動子類為按鈕以便發送消息。
?
組裝代碼
????為了盡可能地重用代碼,下面的函數是圍繞標準GetOpenFileName()API函數構件的一個封裝,它接收指向OPENFILENAME結構的指針作為輸入。如果這個結構中包含了非空的模版字段,則調用標準函數,換句話說,如果用戶請求了定制的文件夾,則這個函數只是調用原始例程,否則,它用我們開發的對話框模版置換標準的:
#include <commdlg.h>
BOOL GetOpenFileNameEx(LPOPENFILENAME lpofn)
{
//?如果模板是定制的,回復到標準對話框
if(lpofn->lpTemplateName)
return GetOpenFileName(lpofn);
//?調整?OPENFILENAME?結構
lpofn->hInstance = GetModuleHandle(NULL);
lpofn->lpTemplateName = MAKEINTRESOURCE(IDD_DIALOG);
lpofn->lpfnHook = OpenDlgExProc;
lpofn->Flags |= OFN_EXPLORER | OFN_ENABLEHOOK | OFN_ENABLETEMPLATE;
BOOL b = GetOpenFileName(lpofn);
return b;
}
由上函數使用的回調需要處理兩個消息。由于對話框構造而接收到WM_NOTIFY消息時,必須調用InitNewButtons()函數來設置按鈕的圖標和提示。如果接收到WM_COMMAND消息,我們就檢查按下的是哪一個按鈕,并作出適當的響應:
UINT CALLBACK OpenDlgExProc(HWND hDlg, UINT uiMsg, WPARAM wParam, LPARAM lParam)
{
LPOFNOTIFY pN = NULL;
switch(uiMsg)
{
case WM_NOTIFY:
pN = reinterpret_cast<LPOFNOTIFY>(lParam);
if(pN->hdr.code == CDN_INITDONE)
InitNewButtons(hDlg);
break;
case WM_COMMAND:
switch(LOWORD(wParam))
{
case IDC_FAVORITES:
case IDC_RECENT:
case IDC_WINDOWS:
Goto(hDlg, LOWORD(wParam));
break;
}
break;
}
return 0;
}
現在所有剩下的是使用來自主應用對話框OnOK()函數的適當變量調用GetOpenFileNameEx()函數。下面是它的代碼:
void OnOK(HWND hDlg)
{
//?局部數據
OPENFILENAME ofn;
TCHAR szFile[MAX_PATH] = {0};
ZeroMemory(&ofn, sizeof(OPENFILENAME));
ofn.lStructSize = sizeof(OPENFILENAME);
ofn.hwndOwner = hDlg;
ofn.lpstrFile = szFile;
ofn.nMaxFile = sizeof(szFile);
ofn.lpstrFilter = "All files/0*.*/0";
ofn.nFilterIndex = 1;
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
if(GetOpenFileNameEx(&ofn))
MessageBox(hDlg, ofn.lpstrFile, "Open", MB_OK | MB_ICONINFORMATION);
}
?
防止重命名項
????默認情況下‘打開’對話框允許刪除和重命名它顯示的文件。這個特征是由系統提供的,而且沒有標志來抑制它。然而,在很多情況下,并不需要這個功能,有時它會變得非常討厭。現在讓我們來看一下怎樣關閉這個功能。
????允許列出文件的控件是列表觀察控件,它具有LBS_EDITLABELS風格。為了得到我們想要的行為,只需要把這個位關閉掉即可。最困難的是怎樣取得這個列表觀察控件的Handle。
void ModifyStyle(HWND hDlg)
{
//?取得文件的列表觀察Handle
HWND hwndDefView = GetDlgItem(GetParent(hDlg), lst2);
HWND hwndListView = GetDlgItem(hwndDefView, 1);
//?關閉這個位
DWORD dwStyle = GetWindowLong(hwndListView, GWL_STYLE);
dwStyle &= ~LVS_EDITLABELS;
SetWindowLong(hwndListView, GWL_STYLE, dwStyle);
}
列表觀察是窗口容器的子控件,其ID為lst2——這是另一個來自dlg.h頭文件的值。這個列表觀察的ID是1,這主要是由Spy++獲得。必須在每次用戶改變目錄后調用上面函數,因為每次目錄發生改變時都銷毀和重建這個觀察。一個好方法是響應CDN_FOLDERCHANGE通知:
case WM_NOTIFY:
pN = reinterpret_cast<LPOFNOTIFY>(lParam);
if(pN->hdr.code == CDN_INITDONE)
InitNewButtons(hDlg);
if(pN->hdr.code == CDN_FOLDERCHANGE)
ModifyStyle(hDlg);
break;
?
文件刪除提示
?????????即使你子類化了列表觀察,你也不能捕捉到對應的‘刪除’鍵。顯然探測器通過處理消息的鍵盤鉤子陷落了文件刪除的請求,然后吃掉了它。
?????????如此對于我們最好的方法也是安裝鍵盤鉤子,用以陷落‘刪除’鍵,陷落消息,然后中斷鉤子鏈,就象探測器那樣。此時探測器不能得到消息,因此將不能刪除文件。鉤子應該在調用GetOpenFileName()之前安裝,而后立即刪除。代碼有一點小小的改動:
BOOL GetOpenFileNameEx(LPOPENFILENAME lpofn)
{
//?如果模版是定制的,回復到標準對話框
if(lpofn->lpTemplateName)
return GetOpenFileName(lpofn);
//?調整?OPENFILENAME?結構
lpofn->hInstance = GetModuleHandle(NULL);
lpofn->lpTemplateName = MAKEINTRESOURCE(IDD_DIALOG);
lpofn->lpfnHook = OpenDlgExProc;
lpofn->Flags |= OFN_EXPLORER | OFN_ENABLEHOOK | OFN_ENABLETEMPLATE;
//?設置鍵盤鉤子到當前線程
g_hHook = SetWindowsHookEx(WH_KEYBOARD,HookProc,NULL,GetCurrentThreadId());
BOOL b = GetOpenFileName(lpofn);
//?刪除鉤子
UnhookWindowsHookEx(g_hHook);
return b;
}
每當在打開對話框中按鍵時鉤子過程都被調用,這個鉤子過程是非常簡單的,下面是它的代碼:
LRESULT CALLBACK HookProc(int iCode, WPARAM wParam, LPARAM lParam)
{
//?吃掉?DELETE?鍵
if(wParam == VK_DELETE)
return 1;
//?否則自由處理
return CallNextHookEx(g_hHook, iCode, wParam, lParam);
}
?
什么是Shell集成的應用
我們已經花費了一整章的內容來討論把應用與Shell集成的各個方面。我們還發現,有一定數量的特征是可以編進可執行程序的。包括解析命令行參數,確保應用單一運行實例等。還有一些其它的特征,它們是不必貼附到只應用上的,如文件類型的注冊和關聯菜單的改進等?;旧嫌袃蓚€層次的Shell集成。第一個是美化和瞄準用戶界面的:完好的文件類型名,完好的圖標和新的關聯菜單項。其次是編寫代碼,有一定的全程應用設計規則,還有其它許多小技巧,比如處理WM_ENDSESSION消息導致應用自動在下一次登錄時運行,以及添加快捷方式到‘Favorites’和‘最近文檔’文件夾等。
?
小結
?????????在這一章中,我們論證了Win32應用與系統Shell集成的各方面技術。我們調查了Shell集成的方法,要考慮的各個方面,以及怎樣設計和工程化存在的代碼以獲得Shell對應用處理文檔的支持。我們還討論了客戶化和擴展‘打開’對話框的能力。
?????????這一章所設計的主要科目是:
???????????????????文件類和存儲在注冊表中的信息
???????????????????關聯菜單的客戶化
???????????????????經由Shell建立新文檔
???????????????????應用的命令行
???????????????????怎樣編寫基于對話框的單實例應用
???????????????????客戶化打開文件公共對話框
???????????????????使應用為Shell感知的原理
然而我們說Shell擴展是擴展WindowsShell能力和行為的最靈活有力的方法。下一章我們將探討這個課題。
轉載于:https://www.cnblogs.com/songtzu/p/3239866.html
總結
以上是生活随笔為你收集整理的[转]Windows Shell 编程 第十四章【来源:http://blog.csdn.net/wangqiulin123456/article/details/7988010】...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《分拣机械臂测试》- 端拾器最大吸力测试
- 下一篇: 修改session的存储机制