◆OwnerDocument 動作對象所操作的文檔對象
各種實際的動作對象都是從EditorAction派生的,若對象有熱鍵則在初始化時設置HotKey字段,首先重載ActionName給定一個名稱,然後重載Execute來實現各自的動作處理過程,還可根據需要重載isEnable或TestHotKey。
在TextDocument中有個屬性Actions,該只讀屬性為包含各種動作對象的列表,當TextDocument初始化時就初始化該動作對象列表,當文本編輯器獲得輸入焦點時按下鍵盤按鍵則程序會遍歷Actions中所有的動作,進行熱鍵判斷,若命中熱鍵則執行該動作,其他應用程序也可根據各個動作的isEnable屬性來設置文本編輯功能按鈕和相應菜單的可用性。
比如定義復制動作對象EditorCopyAction,該類型從EditorAction派生的,重載ActionName使其返回"copy";重載isEnable,當文檔有被選中的部分則返回True否則返回False,重載Execute來調用TextDocument中實現復制功能的函數,該對象初始化的時候設置HotKey為 System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.C,這樣定義了該動作的熱鍵為Ctl+C。
這種動作處理的模式還便於程序進行擴展,其他應用程序也可往動作列表中添加自定義的動作對象,這樣文本編輯器就能自動應用該動作。應用程序還可修改各種動作的熱鍵設置來實現用戶操作的個性化。
其實這種動作處理的模式我是看了SharpDevelop的文本編輯器部分的源代碼而領悟的,拿過來用用,實踐證明還是很不錯的。
我既然做的是文本編輯器當然支持復制粘貼功能了,首先將將復制操作。程序可以同時向Windows剪貼板發送多種格式的數據,這些數據可以是純文本的,也可以是圖象或者自定義格式,其他程序在進行粘貼操作是可以選擇其中所需格式的數據。例如大家在VS.NET的代碼窗體中復制某段代碼,粘貼到Word和記事本中的結果是不一致的,雖然文本內容是一樣的,但粘貼到Word中連代碼文本的顏色也顯示出來的,而記事本則是純文本數據。大家可以用剪貼板查看器clipbrd.exe來實時查看Windows剪貼板中的內容。在.Net中向剪貼板發送數據還是比較方便的,首先實例化一個System.Windows.Forms.DataObject對象,調用它的SetData方法,該方法第一個參數為格式的名稱,第二個參數為數據,可以多次調用該方法來保存不同格式的數據,然後調用靜態庫函數System.Windows.Forms.Clipboard.SetDataObject 方法即可。在這個文本編輯器中復制數據時同時向系統剪切板保存兩種數據,首先保存文檔中被選中部分的純文本數據,然後將被選中的部分轉換為一個XML字符串,然後使用自定義的格式名稱保存進去。這樣其他程序就能使用其中的純文本數據了。程序在進行粘貼操作時首先調用靜態庫函數System.Windows.Forms.Clipboard.GetDataObject方法,獲得一個 實現了System.Windows.Forms.IDataObject接口的對象,然後調用它的GetDataPresent方法,若發現其中有我自定義的數據則讀取該數據,然後將其中的數據當作字符串取出來,這是一個XML字符串,解析該XML字符串,並生成一系列的文檔元素對象插入到文檔當前位置,這種粘貼操作能將所有的文檔元素及其格式給粘貼過來。若沒有自定義數據但是有純文本數據,則讀取純文本數據,並根據文本生成一系列文本元素對象,然後插入到文檔當前位置。
VBA
文檔對象還支持VBA,.NET框架支持VB.NET腳本語言,.NET類庫中的類 Microsoft.VisualBasic.Vsa.VsaEngine及接口 Microsoft.Vsa.IVsaSite 就支持腳本語言。我參照Html文檔對象模型,在VB.NET的基礎上設計一種處理文檔的腳本語言,該語言中直接使用腳本全局對象document就訪問了文檔對象TextDocument,而使用document.all就能訪問文檔中的某些做了標記的文檔元素對象,使用 dbconnection 就能使用文本編輯器後台使用的數據庫連接對象,使用eventobj訪問文檔編輯器觸發的事件的信息,使用vbsystem來調用某些例程。首先定義一些類型,用於實現腳本全局對象dbconnection,eventobj,vbsystem的功能,而全局對象document的類型就是TextDocument,已經實現,但document.all還未實現,為此在TextDocument中新增只讀屬性all,該屬性返回一個System.Object類型的對象,由於document.all的類型中定義的字段根據文檔的內容而動態改變,因此需要使用.Net的反射機制動態的創建對象類型並實例化對象,其創建過程為:
◆新增一個System.Reflection.AssemblyName對象,設置其Name屬性為“RunTimeTextDocumentLib”
◆使用AppDomain.CurrentDomain.DefineDynamicAssembly來創建一個程序集生成器System.Reflection.Emit.AssemblyBuilder
◆使用程序集生成器的DefineDynamicModule來創建一個模塊生成器
◆使用模塊生成器的DefineType來創建一個類型生成器,類型名稱為AllElements
◆遍歷文檔內容,根據名稱和特定文檔對象的對應關系生成一個按名稱訪問的哈希列表
◆遍歷哈希列表中的名稱,使用類型生成器的DefineFIEld方法創建一個公開字段,字段類型為object類型
◆使用類型生成器生成一個新的類型System.Type,然後動態創建一個該類型的實例,這樣動態生成了AllElements對象
◆遍歷文檔元素對象哈希列表,使用System.Type.InvokeMember向該AllElements對象設置字段值
這樣應用程序動態的創建了AllElements類型並實例化了一個對象引用,這時VB.Net腳本程序就可以直接使用 document.all.文檔元素對象名稱 來直接訪問文檔中特定內容了。注意當文檔內容發生改變時需要重新生成AllElements的類型並實例化。
以上的程序模塊建好後就可以搭建VB.Net腳本語言運行環境了,首先定義類型TextDocumentVsaSite來實現IVsaSite接口,實現其中的GetGlobalInstance函數,該函數參數為字符串,返回一個對象,該函數實際上判斷若參數"document"則返回文檔對象TextDocument ,若參數為"eventobj"則返回剛剛定義了事件對象,若為"dbconnection"則返回數據庫連接對象。該對象還實現了IVsaSite.OnCompilerError來處理腳本編譯錯誤。
程序還從Microsoft.VisualBasic.Vsa.VsaEngine派生了腳本引擎VBScriptEngine。該模塊使用VsaEngine的Items.CreateItem來向引擎添加document,eventobj,dbconnection等全局變量,還添加一些所需的.Net引用,此外還實現了對腳本代碼文本的一些處理,比如加密,自動添加某些必須的代碼等。
腳本環境還模擬實現了文檔事件的處理,比如文檔中某些元素對象支持onchange事件,這些元素是有名稱的,當用戶修改這些元素的內容時,程序會查詢腳本引擎來看是否存在名為對象名稱_OnChange的過程存在,若存在則執行它,這樣就模擬實現了事件處理。
在VB.Net腳本環境中,全局對象的成員函數可以直接調用,因此在vbsystem中定義一些例程就可以直接調用,可以在vbsystem中定義諸如Alert,ConFirm,Prompt,DebugPrint等成員函數,腳本中就能直接使用這些函數了。