以前收集的資料,忘了出處,感謝原作者。
在Microsoft Project中加入Microsoft VBA(Visual Basic for Applications),可使開發(fā)人員利用Microsoft Project作為整體解決方案的組成部分,迅速創(chuàng)建功能強(qiáng)大的應(yīng)用程序。在本部分中,我們將初步探討用于構(gòu)造Microsoft Project 的復(fù)雜添加型應(yīng)用程序的一些編程技巧。 我們還將對(duì)一個(gè)用Microsoft Project、Microsoft Excel電子數(shù)據(jù)表格程序以及VBA編程系統(tǒng)創(chuàng)建的應(yīng)用程序進(jìn)行檢測(cè)。該應(yīng)用程序是“真實(shí)世界”的解決方案,它的創(chuàng)建給客戶提供途徑,來(lái)分析那些僅使用Microsoft Project 無(wú)法分析的工程管理數(shù)據(jù)。其結(jié)論信息為測(cè)定工程的進(jìn)程提供了一套標(biāo)準(zhǔn)或準(zhǔn)則。在整篇論文的其他部分,我們將這個(gè)應(yīng)用程序樣例視為Project Metrics應(yīng)用程序。 Project Metrics應(yīng)用程序?qū)⒂脕?lái)演示VBA編程技巧的實(shí)際運(yùn)用。所包括的多數(shù)代碼段都取自Project Metrics應(yīng)用程序。雖然此處并未列出應(yīng)用程序的完整代碼,但包括了一個(gè)完整的Project Metrics應(yīng)用程序模塊。 本文的代碼模塊包含這樣一些過程,可用于管理Project Metrics應(yīng)用程序所需的Microsoft Excel實(shí)例。 樣例文件 該進(jìn)程包括幾個(gè)樣例文件。這些樣例文件包含了用于說明本文中所討論的編程技巧的全部代碼段。具體的樣例文件如下: PJ301A.MPP-一個(gè)Microsoft Project 4.0文件,包含本文所用的全部代碼樣例。此外,它還包含Project Metrics應(yīng)用程序使用的完整代碼模塊,這些模塊可管理應(yīng)用程序所需的Microsoft Excel實(shí)例。工程計(jì)劃是從ROLLOUT.MPT模板中復(fù)制的,該模板是Microsoft Project 中的一個(gè)樣例。 PJ301B.XLS-一個(gè)Microsoft Excel文件,包含GetXLPrefs()過程中所用的對(duì)話框和代碼。為了運(yùn)行提供的樣例代碼,你必須將PJ301B.XLS復(fù)制到安裝有Microsoft Project 的目錄下(即包含WINPROJ.EXE的目錄)。 PJDEMO.EXE-一個(gè)Microsoft Visual Basic 3.0應(yīng)用程序,可用作宏FormatGantt()的對(duì)話框。該文件也必須被復(fù)制到WINPROJ.EXE同一目錄下。 PJDEMO.FRM、PJDEMO.MAK-PJDEMO.EXE的源代碼。 STATUS.MPP-一個(gè)Microsoft Project 文件,用于性能演示中狀態(tài)消息的顯示。編程技巧 程序結(jié)構(gòu) 由于本文旨在討論高級(jí)編程技巧,對(duì)于基礎(chǔ)程序結(jié)構(gòu),我們將不在此探討。然而,有必要討論一下For Each...Next語(yǔ)句,這是由于在某些Microsoft Project 對(duì)象集合中使用該語(yǔ)句時(shí),需要考慮一些特殊問題。 For Each...Next For Each...Next語(yǔ)句用于對(duì)象集合中的迭代。For Each語(yǔ)句還可用于數(shù)組中的迭代。在Microsoft Project 集合中使用For Each時(shí),特別需要記住的是一些集合包含的成員可能為空。從下面的示例中可看到這種情況: {bmc ZCK0A.WPG} 在本示例中,任務(wù)標(biāo)識(shí)符4為空。因此,下列代碼會(huì)出現(xiàn)運(yùn)行時(shí)間錯(cuò)誤91“對(duì)象變量未設(shè)置”的問題。 Sub ForEach() Dim t As Task For Each t In ActiveProject.Tasks Debug.Print t.Name Next t End Sub 為解決這個(gè)問題,你必須在表達(dá)式中使用集合前,對(duì)集合的每個(gè)成員進(jìn)行測(cè)試,確認(rèn)其有效性。雖然可用TypeName函數(shù)來(lái)測(cè)試對(duì)象是否有效,但更為快捷的方法是測(cè)試對(duì)象是否為空。下面代碼說明了這個(gè)技巧: Sub ForEach() Dim t As Task For Each t In ActiveProject.Tasks If Not (t Is Nothing) Then Debug.Print t.Name End If Next t End Sub 模塊的申明部分應(yīng)清楚標(biāo)識(shí),將它與隨后的過程分隔開。通常,在模塊中或全局申明的變量應(yīng)具有注釋,以指明這些變量的用途。 Option Private語(yǔ)句 Microsoft Project 中包括的VBA_PJ.HLP文件指出“Option Private使用在模塊級(jí),用來(lái)聲明整個(gè)模塊都是私有的”。這種說明可能會(huì)引起誤解,因?yàn)樗凳局?,如果在模塊申明中加入Option Private語(yǔ)句,則包含在模塊中的所有數(shù)據(jù)和過程都是該模塊私有的。 Option Private語(yǔ)句意味著不能從另一工程的模塊中的過程調(diào)用私有模塊中的過程。包含在私有模塊中的過程可從該模塊所在的工程文件中的其他任何模塊調(diào)用。 如果用關(guān)鍵字Public在私有模塊申明一個(gè)變量,該變量在工程中的其他所有模塊中是可見的。而且,位于私有模塊中的Sub過程仍將出現(xiàn)在“工具”菜單“宏”選項(xiàng)的宏列表中。 Option Private語(yǔ)句可在過程間存在引用時(shí),有效防止不同工程的過程間命名的沖突。 獲取用戶的輸入 VBA沒有提供對(duì)話,因此Microsoft Project 不支持用戶定義的對(duì)話框。獲取用戶輸入的主要方法是InputBox函數(shù)。雖然InputBox可用來(lái)獲取簡(jiǎn)單的用戶輸入,但該函數(shù)不允許使用公用控件,如Optionbutton(選項(xiàng)按鈕)、Checkbox(復(fù)選框)和Listbox(列表框)。 針對(duì)該問題的解決方案是使用Microsoft Excel或Microsoft Visual Basic 3.0中創(chuàng)建的對(duì)話框,來(lái)獲取用戶對(duì)你編寫的宏的輸入。然而,這種方案會(huì)引起另一問題:怎樣將對(duì)話框中的信息傳回給Microsoft Project 宏? 使用Microsoft Excel,你可通過定義的應(yīng)用程序的宏,將自變量傳給一個(gè)宏。雖然Microsoft Project 支持宏,但它不允許你將自變量傳給宏。 有幾種方法可將用戶的輸入返回給Microsoft Project 的宏。你可使用DDE、OLE、全局存儲(chǔ)區(qū)、剪貼板或文本文件。我們將在此處檢測(cè)使用OLE的方法,雖然它并不是最快的方法,但它易于執(zhí)行并且可靠。 下列流程圖說明了這一基本概念。 {bmc ZCK1A.WPG} 在這個(gè)流程圖中,發(fā)生的行為如下(按先后順序): 1. 一個(gè)Microsoft Project 宏使用AppExecute方法,以啟動(dòng)一個(gè)Visual Basic 3.0窗體。Visual Basic窗體作為Microsoft Project宏獲取用戶輸入的對(duì)話框。下列代碼是取自本文中的示例。 Sub FormatGantt() ' 顯示VB對(duì)話框以獲取用戶的輸入。 AppExecute Command:=Application.Path & "\pjdemo.exe" _ ,Activate:=True End Sub 2. Visual Basic窗體處理獲取用戶輸入的任務(wù)。當(dāng)用戶完成輸入并選擇“確定”或“取消”鍵時(shí),Visual Basic 窗體會(huì)建立一個(gè)到Microsoft Project 的OLE連接。 Dim oProj as Object Set oProj = GetObject(, "msproject.application") 3. Visual Basic 窗體將獲得的用戶輸入值賦值給某個(gè)預(yù)先確定的工程總計(jì)任務(wù)字段。在本文(PJDEMO.EXE)提供的示例中,Visual Basic 窗體賦值給Text10字段。 If index = 0 Then '按下“確定”鍵 oProj.Activeproject.Text10 = cboColor.List(cboColor.ListIndex) Else '按下“取消”鍵 oProj.Activeproject.Text10 = "Cancel" End If 4. Visual Basic 窗體激活Microsoft Project 。然后,Visual Basic 窗體使用Macro方法,啟動(dòng)處理用戶輸入的Microsoft Project 宏。在Microsoft Project 宏啟動(dòng)后,Visual Basic 自動(dòng)從內(nèi)存中卸載。 ' 運(yùn)行Microsoft Project 宏,處理用戶的選擇 AppActivate oProj.Caption oProj.macro "DoFormatGantt" End 5. Microsoft Project宏檢測(cè)用戶的輸入,并采取相應(yīng)的動(dòng)作。 ' VB對(duì)話框調(diào)用下面的子過程,以處理用戶的選擇 Sub DoFormatGantt() Dim sMsg As String Dim nColor As Integer Const pjNoAction = -1 '檢查Text10的工程任務(wù)屬性,確定下一步應(yīng)做什么 Select Case ActiveProject.Text10 Case "cancel" sMsg = "用戶按下取消鍵" nColor = pjNoAction Case "black" sMsg = "用戶選擇黑" nColor = pjBlack Case "red" sMsg = "用戶選擇紅" nColor = pjRed Case "yellow" sMsg = "用戶選擇黃" nColor = pjYellow Case "blue" sMsg = "用戶選擇藍(lán)" nColor = pjBlue Case "green" sMsg = "用戶選擇綠" nColor = pjGreen Case Else sMsg = "從對(duì)話框返回的值無(wú)效" nColor = pjNoAction End Select If nColor <> pjNoAction Then GanttBarFormat middlecolor:=nColor MsgBox sMsg, vbInformation + vbOKOnly, "Format Gantt Bar" End Sub 這種一般方法可延伸到Visual Basic3.0以外的其他應(yīng)用程序。PJ301A.MPP中的XL代碼模塊包含調(diào)用Microsoft Excel對(duì)話框、獲取用戶輸入的過程,其過程名為GetUserPref()。 Project VBA性能問題 測(cè)定性能 為了在你的代碼中突出性能問題,你需要能夠精確測(cè)定你的程序速度。GetTickCount函數(shù)可為我們提供這種檢測(cè)能力。 GetTickCount是Windows API函數(shù),它返回從Windows當(dāng)前進(jìn)程啟動(dòng)開始到當(dāng)前時(shí)刻為止所消耗的毫秒數(shù)。該函數(shù)可在Project代碼模塊中申明: Declare Function GetTickCount Lib "USER" () As Long 計(jì)算該函數(shù)在一個(gè)程序的起始時(shí)刻與結(jié)束時(shí)刻之間的差值,可對(duì)程序的性能進(jìn)行準(zhǔn)確的測(cè)定。下面示例說明在一種簡(jiǎn)單情況下該函數(shù)的使用。 ' 該子過程顯示運(yùn)行For...Next循環(huán)所需的毫秒數(shù) Sub TimeLoop() Dim lBegin As Long ' 計(jì)時(shí)的起始時(shí)間 Dim lEnd As Long ' 計(jì)時(shí)的結(jié)束時(shí)間 Dim lIndex As Long ' 循環(huán)指數(shù) lBegin = GetTickCount() For lIndex = 1 To 60000 Next lIndex lEnd = GetTickCount() MsgBox "消耗的時(shí)間為: " & (lEnd - lBegin) & "毫秒。" End Sub 我們將在整個(gè)進(jìn)程中使用該方法,以說明在不同情況下的代碼性能。 下面三類集合可包含空成員: 工程 任務(wù) 資源正如上面所提到的,F(xiàn)or Each語(yǔ)句還可用于數(shù)組中的迭代。下面示例對(duì)此進(jìn)行了演示: Sub ForEach_Array() Dim v_array As Variant Dim v As Variant v_array = Array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) For Each v In v_array Debug.Print v Next v End Sub 注意Array函數(shù)的使用,它創(chuàng)建了一個(gè)包含Variant數(shù)據(jù)類型的數(shù)組。For Each語(yǔ)句可用于任何數(shù)組。 激活應(yīng)用程序 你經(jīng)常需要從你的VBA代碼中激活另一應(yīng)用程序。一般而言,你可使用AppActivate方法激活一應(yīng)用程序。 要使用AppActivate方法,你必須知道你希望激活的應(yīng)用程序窗口標(biāo)題的確切文本(不區(qū)分大小寫)。由于通常你不知道標(biāo)題的確切內(nèi)容,你可以利用一個(gè)應(yīng)用程序?qū)ο髞?lái)獲取窗口的標(biāo)題。下面示例演示了這項(xiàng)技巧: ' 激活Microsoft Excel (Excel 必須已經(jīng)處于運(yùn)行狀態(tài))。 Sub ActivateExcel() Dim oExcel As Object Set oExcel = GetObject(, "Excel.Application.5") AppActivate oExcel.Caption End Sub 一個(gè)更復(fù)雜的示例包含的功能是:檢查Microsoft Excel的一個(gè)實(shí)例是否已經(jīng)處于運(yùn)行狀態(tài),如果未運(yùn)行,啟動(dòng)Microsoft Excel。由于Microsoft Excel實(shí)例可能以“看不見”的方式運(yùn)行(也就是說,Microsoft Excel沒有出現(xiàn)在Microsoft Windows 任務(wù)列表上),我們需要一種可檢查看不見的Microsoft Excel實(shí)例的方法。要實(shí)現(xiàn)這點(diǎn),我們將使用FindWindow函數(shù)。 FindWindow函數(shù)是Microsoft Windows API 函數(shù),其申明如下: Declare Function FindWindow Lib "USER" (ByVal lpClassName _ As String, ByVal lpWindowName As Long) As Integer FindWindow函數(shù)將檢索那些類名和窗口名與傳給FindWindow的變量值匹配的窗口的窗口句柄。如果類名參數(shù)為空,則所有類匹配;同樣,如果窗口名參數(shù)為空,則所有窗口名匹配。我們可充分利用此特性,將空值傳給窗口名參數(shù),然后傳遞我們所要搜索的應(yīng)用程序類名。注意在上面的申明中,參數(shù)1pWindowName被申明為L(zhǎng)ong數(shù)據(jù)類型。這是因?yàn)榭罩羔樖亲鳛殚L(zhǎng)整數(shù)進(jìn)行傳遞的,其值為零(0)。 不同應(yīng)用程序的類名如下。雖然為了便于閱讀,此處列出的類名采用的是大小寫混用,但FindWindow函數(shù)是不區(qū)分大小寫的。 Microsoft Project - "JWinproj-WhimperMainClass" Microsoft Excel - "XLMain" Microsoft Word - "OpusApp" Microsoft Access - "OMain"如果FindWindow沒有找到與特定窗口類名相匹配的窗口,函數(shù)的返回值為0。下列代碼段(取自Project Metrics應(yīng)用程序的GetXLApp()函數(shù))使用FindWindow來(lái)檢查當(dāng)前的Microsoft Excel實(shí)例。 Const XL_APPCLASS = "Excel.Application.5" Const XL_WNDCLASS = "xlmain" Const API_NULL = 0& '檢查Excel是否處于運(yùn)行狀態(tài)。 hWnd_xl = FindWindow(XL_WNDCLASS, API_NULL) If hWnd_xl <> 0 Then '至少有一個(gè)可用的Excel實(shí)例;試圖獲取對(duì)象引用。 Set obj_xl_app = GetObject(, XL_APPCLASS) Else 'Excel并未運(yùn)行。啟動(dòng)Excel并獲取對(duì)象引用。 Set obj_xl_app = CreateObject(XL_APPCLASS) End If
設(shè)置鼠標(biāo)指針 與Visual Basic 3.0不同的是,對(duì)于需要指明應(yīng)用程序“忙”的情況,VBA并未提供一種設(shè)置鼠標(biāo)指針的簡(jiǎn)便方法。Microsoft Project 在冗長(zhǎng)的運(yùn)行中會(huì)出現(xiàn)“掛起”現(xiàn)象。利用Windows API的函數(shù)LoadCursor和SetCursor,我們可以把鼠標(biāo)指針改變?yōu)闃?biāo)準(zhǔn)的沙漏圖標(biāo)。 在你的VBA模塊中加入下列申明和常量: Declare Function SetCursor Lib "USER" (ByVal hCursor As _ Integer) As Integer Declare Function LoadCursor Lib "USER" (ByVal hInstance _ As Integer, ByVal lpCursorName As Any) As Integer Const INT_NULL = 0 Const IDC_WAIT = 32514& 下面示例表明了如何將光標(biāo)設(shè)置為沙漏圖標(biāo),然后再將光標(biāo)復(fù)原。 ' 將光標(biāo)設(shè)置為沙漏圖標(biāo),然后再將光標(biāo)復(fù)原 Sub SetMousePointer() Dim hPrevCursor As Integer '為原始的光標(biāo)保存句柄 '光標(biāo)源 Dim lCounter As Long Dim nRtn As Integer ' 將光標(biāo)設(shè)置為沙漏,并保存原始的光標(biāo) hPrevCursor = SetCursor(LoadCursor(INT_NULL, IDC_WAIT)) ' 暫停片刻,這樣我們可以看到沙漏 For lCounter = 1 To 1000000 Next lCounter ' 恢復(fù)為原始光標(biāo) nRtn = SetCursor(hPrevCursor) End Sub
模塊 VBA代碼儲(chǔ)存在代碼模塊中。模塊可儲(chǔ)存在單獨(dú)的工程文件(*.MPP)中或全局文件(GLOBAL.MPT)中。模塊可包含過程中的變量和常量的申明。 選擇一個(gè)有效的模塊結(jié)構(gòu) 程序設(shè)計(jì)員在組織模塊的方式上有很大的發(fā)揮余地。組織模塊的方式可以幫助或者妨礙開發(fā)的結(jié)果。 下面示例說明了一種有效的模塊組織方法。該例取自Project Metrics應(yīng)用程序。 ' 本模塊包含的過程的功能是為其他程序提供xl輔助函數(shù)。本模塊的所有過程都不控制工程數(shù)據(jù)。 ' 包含在本模塊中的過程: ' Sub GoToMetrics() ' Function GetUserPref(oXL As Object, utPref As _ ' utPrefStruct) As Boolean ' Function GetXLBook(oXLBook As Object, Status As _ ' Integer, Optional sTemplate As Variant) As Boolean ' Function GetXLApp(oXLApp As Object) As Boolean ' Function KillXL(Optional xl_obj As Variant) As Boolean ' Sub MakeXLVisible() 各模塊的前幾行是對(duì)模塊內(nèi)容所作的簡(jiǎn)要描述。在開發(fā)大型工程時(shí),這些描述是很重要的,它可幫助程序維護(hù)員進(jìn)一步修改應(yīng)用程序。 緊隨模塊描述之后的部分包括模塊中每個(gè)程序的程序標(biāo)題。程序標(biāo)題與實(shí)際程序完全匹配是非常重要的。標(biāo)題部分的作用包括: 立即指明在給定模塊中所包含的程序; 允許程序設(shè)計(jì)員用鼠標(biāo)右鍵單擊過程名并選擇“程序定義”的方法,快速轉(zhuǎn)到給定過程; 為給定過程提供了一種快速查找相應(yīng)命令和變量類型的方法。模塊申明部分應(yīng)緊放在注釋和過程列表后。 ' **** 開始模塊申明 **** Option Explicit Option Compare Text ' 文本比較不區(qū)分大小寫 Private obj_xl_app As Object ' 該對(duì)象用于存儲(chǔ)當(dāng)前Excel應(yīng)用程序的一個(gè)引用 Private obj_xl_book As Object '該對(duì)象用于存儲(chǔ)當(dāng)前Excel工作簿的一個(gè)引用 ' **** 結(jié)束模塊申明 **** 在執(zhí)行你的代碼時(shí),最小化Microsoft Project的主要缺陷是幾乎不能給用戶提供宏進(jìn)程的目視反饋。至少在任務(wù)視圖可見時(shí),屏幕閃爍可表明宏仍在運(yùn)行。對(duì)于宏的時(shí)間消耗,用戶通常愿意多占用一些時(shí)間來(lái)?yè)Q取更好的目視反饋。一種折衷的好方法是創(chuàng)造性地使用偽工程。 創(chuàng)建一個(gè)你可用來(lái)填充應(yīng)用程序窗口的偽工程,這樣做的好處有兩點(diǎn):一是消除了大量的屏幕重繪;二是提供了顯示狀態(tài)消息的良好中介。為了有效地利用這種方法,你必須先創(chuàng)建一個(gè)用來(lái)提供狀態(tài)消息的工程。參考一下提供的STATUS.MPP文件,這個(gè)工程中包含一個(gè)單任務(wù),并在自定義的名為“Macor Status(宏?duì)顟B(tài))”的PERT視圖處于活動(dòng)時(shí)得以保存。當(dāng)我們的宏運(yùn)行時(shí),我們打開這個(gè)文件,并利用顯示的單任務(wù)框來(lái)為用戶提供狀態(tài)消息。 為說明這個(gè)過程,下面的宏在活動(dòng)的工程的每個(gè)任務(wù)中運(yùn)行兩次,先將Flag1字段的值賦為True,再將Flag2字段的值也賦為True。 Sub SetFlagTest3() Dim tskIndex As Task ' Tasks集合的指數(shù) Dim tskStatus As Task ' 用來(lái)顯示進(jìn)程的任務(wù) Dim prjStatus As Project ' 用來(lái)顯示進(jìn)程的工程 Dim prjCurrent As Project ' 當(dāng)前的工程 Set prjCurrent = ActiveProject FileOpen "status.mpp" Set prjStatus = ActiveProject Set tskStatus = prjStatus.Tasks(1) ActiveWindow.Caption = "Macro Status" tskStatus.Name = "Now setting task Flag1 fields..." DoEvents ' 允許刷新視圖 For Each tskIndex In prjCurrent.Tasks tskIndex.Flag1 = True tskStatus.Name = "Now processing task " & tskIndex.ID Next tskIndex tskStatus.Name = "Now setting task Flag2 fields..." DoEvents ' 允許刷新視圖 For Each tskIndex In prjCurrent.Tasks tskIndex.Flag2 = True tskStatus.Name = "Now processing task " & tskIndex.ID Next tskIndex FileClose pjDoNotSave End Sub 注意,通過利用Microsoft Project延遲刷新顯示的任務(wù)框的方法 ,宏可以實(shí)現(xiàn)在任務(wù)框中顯示一條消息,同時(shí)在進(jìn)入欄(entry bar)中顯示另一消息。 為確定這些狀態(tài)消息對(duì)宏的性能的實(shí)際影響,我們可使用下面的代碼: Sub SetFlagtest4() Dim tskIndex As Task ' Tasks集合的指數(shù) Dim lBegin As Long ' 計(jì)時(shí)的開始時(shí)間 Dim lEnd As Long '計(jì)時(shí)的結(jié)束時(shí)間 Dim lMsgFlag1 As Long ' 在狀態(tài)消息下,賦值Flag1所用時(shí)間 Dim lMinFlag1 As Long ' 在應(yīng)用程序窗口最小化下,賦值Flag1所用時(shí)間 Dim lMsgFlag2 As Long ' 在狀態(tài)消息下,賦值Flag2所用時(shí)間 Dim lMinFlag2 As Long ' 在應(yīng)用程序窗口最小化下,賦值Flag2所用時(shí)間 Dim tskStatus As Task ' 用于顯示進(jìn)程的任務(wù) Dim prjStatus As Project ' 用于顯示進(jìn)程的工程 Dim prjCurrent As Project ' 當(dāng)前的工程 Set prjCurrent = ActiveProject FileOpen "status.mpp" Set prjStatus = ActiveProject Set tskStatus = prjStatus.Tasks(1) ActiveWindow.Caption = "Macro Status" lBegin = GetTickCount() tskStatus.Name = "Now setting task Flag1 fields..." DoEvents ' Allows view to refresh For Each tskIndex In prjCurrent.Tasks tskIndex.Flag1 = True tskStatus.Name = "Now processing task " & tskIndex.ID Next tskIndex lEnd = GetTickCount() lMsgFlag1 = lEnd - lBegin lBegin = GetTickCount() tskStatus.Name = "Now setting task Flag2 fields..." DoEvents ' Allows view to refresh For Each tskIndex In prjCurrent.Tasks tskIndex.Flag2 = True tskStatus.Name = "Now processing task " & tskIndex.ID Next tskIndex lEnd = GetTickCount() lMsgFlag2 = lEnd - lBegin FileClose pjDoNotSave Application.WindowState = pjMinimized lBegin = GetTickCount() For Each tskIndex In prjCurrent.Tasks tskIndex.Flag1 = True Next tskIndex lEnd = GetTickCount() lMinFlag1 = lEnd - lBegin lBegin = GetTickCount() For Each tskIndex In prjCurrent.Tasks tskIndex.Flag2 = True Next tskIndex lEnd = GetTickCount() lMinFlag2 = lEnd - lBegin Application.WindowState = pjNormal MsgBox "Elapsed times with messages:" & Chr(10) & _ "Flag1 - " & lMsgFlag1 / 1000 & " s." & Chr(10) & _ "Flag2 - " & lMsgFlag2 / 1000 & " s." & Chr(10) & _ Chr(10) & "While minimized:" & Chr(10) & _ "Flag1 - " & lMinFlag1 / 1000 & " s." & Chr(10) & _ "Flag2 - " & lMinFlag2 / 1000 & " s." End Sub 在一個(gè)有200個(gè)任務(wù)的工程中運(yùn)行這個(gè)宏,你可得到下列類似結(jié)果: {bmc ZCK4A.WPG} 注意,在這種情況下提供狀態(tài)消息大約要增加30%的執(zhí)行時(shí)間。雖然它會(huì)大大增加某些工程的執(zhí)行時(shí)間,但用戶通常還是愿意接受性能下降來(lái)?yè)Q取持續(xù)的目視反饋。 最小直接對(duì)象處理 改善Microsoft Project 宏的性能的另一種方法是在可能的地方,使對(duì)代碼中的對(duì)象的直接處理最小化。雖然在執(zhí)行一些動(dòng)作時(shí),通常采用整個(gè)對(duì)象集合中進(jìn)行迭代的簡(jiǎn)便方法,但如果使用Microsoft Project 的一些固有特征,將必須檢測(cè)的對(duì)象數(shù)量最小化,則比迭代法快得多。下面是這項(xiàng)技巧的示例。 在本示例中(取自Project Metrics 應(yīng)用程序),為確定是否有資源分配給任務(wù),必須檢測(cè)工程中的每個(gè)任務(wù)。下列代碼段是通過檢查分配給當(dāng)前工程的任務(wù)集合中每個(gè)任務(wù)的資源數(shù)量來(lái)實(shí)現(xiàn)的: ' 給每個(gè)尚未分配任何資源的任務(wù)分配偽資源 tassign = 0 For Each t In ActiveProject.Tasks If Not (t Is Nothing) Then If t.Resources.Count < 1 Then If tassign > 1000 Then Error err_assign_limit t.Assignments.Add ResourceID:=oNewRes.ID tassign = tassign + 1 'keeps track of the 'number of assignments made End If End If Next t 前面的代碼是有效的,但下面的示例會(huì)運(yùn)行得更快。這兩個(gè)示例的區(qū)別在于,后一個(gè)示例設(shè)置了一個(gè)任務(wù)過濾器,它可篩選出分配了資源的任務(wù)。這個(gè)篩選操作非常快,并可改善程序的性能達(dá)300%以上。 將常量cpts_nores_filter定義為任務(wù)過濾器,方法如下: Field Name Test Value(s) And/Or Resource Names Equals And Milestone Equals No And Summary Equals No tassign = 0 FilterApply cpts_nores_filter SelectAll If Not ActiveSelection.Tasks Is Nothing Then For Each t In ActiveSelection.Tasks With t If tassign > 1000 Then Error err_assign_limit .Assignments.Add ResourceID:=oNewRes.ID tassign = tassign + 1 'keeps track of the number of 'assignments made End With Next t End If 1995 Microsoft 公司。 這些材料是僅為信息用途而提供的“原樣”,微軟公司和微軟的供應(yīng)商都不對(duì)這些材料的內(nèi)容或此處包含的任何信息的正確性作出明確的或隱含的保證,包括不對(duì)某些特殊目的的商業(yè)性和適用性作出不加限制的隱含保證。上述限制不適用于某些不允許排除隱含保證的州/管轄區(qū)。 無(wú)論是微軟公司還是微軟的供應(yīng)商都不對(duì)任何后續(xù)的、偶然的、直接的、間接的、特殊的損害或利益損失負(fù)任何責(zé)任。上述限制不適用某些不允許排除后續(xù)或偶然損害的州/管轄區(qū)。在任何情況下,微軟及其供應(yīng)商對(duì)由于這些材料引起的全部責(zé)任(不管是以侵權(quán)的、合同性的還是以任何其他方式引起)不得超過這些材料的建議零售價(jià)。 屏幕更新 當(dāng)你在創(chuàng)建Microsoft Project 宏時(shí),遇到的主要性能障礙之一是,宏在執(zhí)行時(shí)不能關(guān)閉屏幕更新。Microsoft Project Application對(duì)象與Microsoft Excel不同,它不包括ScreenUpdating屬性。為此,Microsoft Project 在執(zhí)行宏時(shí),要占用大量的時(shí)間刷新其應(yīng)用程序窗口。如果顯示的是甘特進(jìn)度圖(Gantt Chart)之類的高密度圖形,更新這個(gè)恒定的屏幕可能導(dǎo)致性能大大降低。 為說明當(dāng)前視圖對(duì)宏性能的影響,試試下面的示例。該示例將同一循環(huán)運(yùn)行四次,并且每次都將工程中的任務(wù)的Flag1字段設(shè)為True。每次循環(huán)的活動(dòng)視圖不同。不同視圖之間的性能差異顯示在消息框中。注意,為簡(jiǎn)化代碼,在測(cè)試宏中不檢查任務(wù)是否為空,一個(gè)“真實(shí)”的宏通常必須對(duì)空任務(wù)進(jìn)行檢查。 ' 本子過程將四個(gè)不同視圖中所有任務(wù)的Flag1字段設(shè)為True。 Sub SetFlagTest1() Dim tskIndex As Task ' Task集合的指數(shù) Dim lBegin As Long ' 計(jì)時(shí)的開始時(shí)間 Dim lEnd As Long ' 計(jì)時(shí)的結(jié)束時(shí)間 Dim lGantt As Long ' 在Gantt視圖中執(zhí)行的時(shí)間 Dim lTSheet As Long ' 在Task Sheet視圖中執(zhí)行的時(shí)間 Dim lRSheet As Long ' 在Resource Sheet視圖中執(zhí)行的時(shí)間 Dim lEdit As Long ' 在Module Editor視圖中執(zhí)行的時(shí)間 ViewApply "Gantt Chart" lBegin = GetTickCount() For Each tskIndex In ActiveProject.Tasks tskIndex.Flag1 = True Next tskIndex lEnd = GetTickCount() lGantt = lEnd - lBegin ViewApply "Task Sheet" lBegin = GetTickCount() For Each tskIndex In ActiveProject.Tasks tskIndex.Flag1 = True Next tskIndex lEnd = GetTickCount() lTSheet = lEnd - lBegin ViewApply "Resource Sheet" lBegin = GetTickCount() For Each tskIndex In ActiveProject.Tasks tskIndex.Flag1 = True Next tskIndex lEnd = GetTickCount() lRSheet = lEnd - lBegin ViewApply "Module Editor" lBegin = GetTickCount() For Each tskIndex In ActiveProject.Tasks tskIndex.Flag1 = True Next tskIndex lEnd = GetTickCount() lEdit = lEnd - lBegin MsgBox "Elapsed times:" & Chr(10) & _ "Gantt - " & lGantt / 1000 & " s." & Chr(10) & _ "Task Sheet - " & lTSheet / 1000 & " s." & Chr(10) & _ "Resource Sheet - " & lRSheet / 1000 & " s." & _ Chr(10) & "Editor - " & lEdit / 1000 & " s." End Sub 在樣例Rollout工程(包含在Microsoft Project 產(chǎn)品中的ROLLOUT.MPT文件)中運(yùn)行這個(gè)宏,可得到下列類似結(jié)果: {bmc ZCK2A.WPG} 注意,由于只有任務(wù)數(shù)據(jù)得到更新,Microsoft Project 僅在任務(wù)視圖中重畫窗口,因此在Module Editor和Resource Sheet視圖中的執(zhí)行時(shí)間實(shí)際上是相等的。 作為應(yīng)用不同視圖的備用方法,你可以只將應(yīng)用程序窗口最小化。當(dāng)最小化窗口時(shí),Microsoft Project 就不需要重畫其窗口,這樣你的代碼執(zhí)行速度幾乎與代碼在Module Editor視圖中運(yùn)行的速度相同。 試試以下對(duì)上一示例的修改: ' 本子過程將四個(gè)不同視圖中的所有任務(wù)的Flag1字段設(shè)為True。 Sub SetFlagTest2() Dim tskIndex As Task ' Task集合的指數(shù) Dim lBegin As Long ' 計(jì)時(shí)的開始時(shí)間 Dim lEnd As Long ' 計(jì)時(shí)的結(jié)束時(shí)間 Dim lGantt As Long ' 在Gantt視圖中執(zhí)行的時(shí)間 Dim lTSheet As Long ' 在Task Sheet視圖中執(zhí)行的時(shí)間 Dim lRSheet As Long ' 在Resource Sheet視圖中執(zhí)行的時(shí)間 Dim lEdit As Long ' 在Module Editor視圖中執(zhí)行的時(shí)間 ' 最小化Project應(yīng)用程序窗口 Application.WindowState = pjMinimized ViewApply "Gantt Chart" lBegin = GetTickCount() For Each tskIndex In ActiveProject.Tasks tskIndex.Flag1 = True Next tskIndex lEnd = GetTickCount() lGantt = lEnd - lBegin ViewApply "Task Sheet" lBegin = GetTickCount() For Each tskIndex In ActiveProject.Tasks tskIndex.Flag1 = True Next tskIndex lEnd = GetTickCount() lTSheet = lEnd - lBegin ViewApply "Resource Sheet" lBegin = GetTickCount() For Each tskIndex In ActiveProject.Tasks tskIndex.Flag1 = True Next tskIndex lEnd = GetTickCount() lRSheet = lEnd - lBegin ViewApply "Module Editor" lBegin = GetTickCount() For Each tskIndex In ActiveProject.Tasks tskIndex.Flag1 = True Next tskIndex lEnd = GetTickCount() lEdit = lEnd - lBegin ' 恢復(fù)Project應(yīng)用程序窗口 Application.WindowState = pjNormal MsgBox "消耗的時(shí)間為:" & Chr(10) & _ "Gantt - " & lGantt / 1000 & " s." & Chr(10) & _ "Task Sheet - " & lTSheet / 1000 & " s." & Chr(10) & _ "Resource Sheet - " & lRSheet / 1000 & " s." & _ Chr(10) & "Editor - " & lEdit / 1000 & " s." End Sub 在同一Rollout工程中運(yùn)行該過程,得出下列結(jié)果: {bmc ZCK3A.WPG} 注意,對(duì)于這種最小化情況,所有情況的代碼實(shí)際執(zhí)行速度相同。 |
|
來(lái)自: 超酷圖書館 > 《Project VBA》