跳轉到內容

Visual Basic/Jarithmetic Round Two 實現

來自華夏公益教科書,開放的書籍,開放的世界

本章介紹了迄今為止討論內容的實現。

所採用的技術是程式碼和討論交織在一起。您應該能夠透過簡單地複製整個頁面並註釋掉討論來提取程式碼。

之前的討論一直處於非常高的層次,而實現它將需要高層次和低層次的編碼,以及對我們想法的大量改進。

應用程式將由一個窗體、一些模組和一些類組成。我們將從頂部開始,建立應用程式的使用者介面,然後我們將逐個新增使它工作的程式碼。我們將發現,之前討論中的一些內容是不完整的,有些是誤導性的。這在實際開發中很常見。

使用者介面

[編輯 | 編輯原始碼]

我選擇將此程式實現為一個多文件介面應用程式。這通常被稱為MDI應用程式。這意味著,可以在程式的同一例項中同時開啟多個 Jarithmetic 文件。這是過去大多數 Microsoft Office 應用程式的工作方式。


fMainform.frm

[編輯 | 編輯原始碼]

這是一個可能的窗體。圖片顯示了比第一個版本實際實現的更多選單,請在您繼續進行時實現它們。

MDI form

以下是窗體上控制元件的宣告。您可以將其貼上到文字編輯器中並將其儲存為fMainForm.frm,以便快速開始。

主窗體是使用者想要開啟的多個文件的容器。每個文件都將是frmDocument例項,請參見下一節。

 VERSION 5.00
 Object = "{F9043C88-F6F2-101A-A3C9-08002B2F49FB}#1.2#0"; "COMDLG32.OCX"
 Begin VB.MDIForm fMainform 
    BackColor       =   &H8000000C&
    Caption         =   "Arithmetic"
    ClientHeight    =   2790
    ClientLeft      =   165
    ClientTop       =   765
    ClientWidth     =   5280
    Icon            =   "Fmainform.frx":0000
    LinkTopic       =   "MDIForm1"
    StartUpPosition =   3  'Windows Default
    Begin MSComDlg.CommonDialog CommonDialog1 
       Left            =   360
       Top             =   240
       _ExtentX        =   847
       _ExtentY        =   847
       _Version        =   393216
    End
    Begin VB.Menu mnuFile 
       Caption         =   "&File"
       Index           =   1
       Begin VB.Menu mnuFileNew 
          Caption         =   "&New"
          Shortcut        =   ^N
       End
       Begin VB.Menu mnuFileOpen 
          Caption         =   "&Open..."
          Shortcut        =   ^O
       End
       Begin VB.Menu mnuFileBar0 
          Caption         =   "-"
       End
       Begin VB.Menu mnuFileSave 
          Caption         =   "&Save"
          Shortcut        =   ^S
       End
       Begin VB.Menu mnuFileSaveAs 
          Caption         =   "Save &As..."
       End
       Begin VB.Menu mnuFileSaveAll 
          Caption         =   "Save A&ll"
       End
       Begin VB.Menu mnuFileClose 
          Caption         =   "&Close"
          Shortcut        =   ^E
       End
       Begin VB.Menu mnuFileCloseAll 
          Caption         =   "&CloseAll"
       End
       Begin VB.Menu mnuFileBar1 
          Caption         =   "-"
       End
       Begin VB.Menu mnuFilePrint 
          Caption         =   "&Print..."
          Shortcut        =   ^P
       End
       Begin VB.Menu mnuFilePrintSetup 
          Caption         =   "&PrintSetup"
       End
       Begin VB.Menu mnuFilePrintPreview 
          Caption         =   "&PrintPreview"
          Shortcut        =   ^R
       End
       Begin VB.Menu mnuFileBar3 
          Caption         =   "-"
       End
       Begin VB.Menu mnuFileSend 
          Caption         =   "&Send"
          Begin VB.Menu mnuFileSendEmail 
             Caption         =   "&Email"
          End
       End
       Begin VB.Menu mnuFileExit 
          Caption         =   "E&xit"
          Shortcut        =   {F4}
       End
    End
    Begin VB.Menu mnuEdit 
       Caption         =   "&Edit"
       Begin VB.Menu mnuEditUndo 
          Caption         =   "&Undo"
          Shortcut        =   ^Z
       End
       Begin VB.Menu mnuEditRedo 
          Caption         =   "&Redo"
       End
       Begin VB.Menu mnueditbar2 
          Caption         =   "-"
       End
       Begin VB.Menu mnuEditCut 
          Caption         =   "Cu&t"
          Shortcut        =   ^X
       End
       Begin VB.Menu mnuEditCopy 
          Caption         =   "&Copy"
          Shortcut        =   ^C
       End
       Begin VB.Menu mnuEditPaste 
          Caption         =   "&Paste"
          Shortcut        =   ^V
       End
       Begin VB.Menu mnueditbar3 
          Caption         =   "-"
       End
       Begin VB.Menu mnuEditSelectAll 
          Caption         =   "&SelectAll"
          Shortcut        =   ^A
       End
    End
    Begin VB.Menu mnuData 
       Caption         =   "&Data"
       Begin VB.Menu mnuEvaluate 
          Caption         =   "&Evaluate"
          Shortcut        =   {F9}
       End
    End
    Begin VB.Menu mnuWindow 
       Caption         =   "&Window"
       WindowList      =   -1  'True
       Begin VB.Menu mnuWindowNewWindow 
          Caption         =   "&New Window"
          Shortcut        =   {F12}
       End
       Begin VB.Menu mnuWindowBar0 
          Caption         =   "-"
       End
       Begin VB.Menu mnuWindowCascade 
          Caption         =   "&Cascade"
       End
       Begin VB.Menu mnuWindowTileHorizontal 
          Caption         =   "Tile &Horizontal"
       End
       Begin VB.Menu mnuWindowTileVertical 
          Caption         =   "Tile &Vertical"
       End
       Begin VB.Menu mnuWindowArrangeIcons 
          Caption         =   "&Arrange Icons"
       End
    End
    Begin VB.Menu mnuHelp 
       Caption         =   "&Help"
       Begin VB.Menu mnuHelpContents 
          Caption         =   "&HelpContents"
          Shortcut        =   {F1}
       End
       Begin VB.Menu mnuHelpTipoftheDay 
          Caption         =   "&TipoftheDay"
       End
       Begin VB.Menu mnuHelpAbout 
          Caption         =   "&About "
       End
       Begin VB.Menu mnuHelpSpecialThanks 
          Caption         =   "&SpecialThanks"
       End
    End
 End
 Attribute VB_Name = "fMainform"
 Attribute VB_GlobalNameSpace = False
 Attribute VB_Creatable = False
 Attribute VB_PredeclaredId = True
 Attribute VB_Exposed = False


現在是窗體的可見程式碼。它非常短,因為 MDI 窗體沒有做太多工作,它主要充當文件窗體的容器。

MDI 窗體有一個檔案選單。當沒有開啟文件窗體時,它是活動的選單。當文件窗體開啟時,顯示的檔案選單屬於文件窗體,但我們仍然會呼叫此方法。當 VB 為我們建立方法時,它將被標記為Private,我們可以將其更改為Public,如這裡所做的那樣,但將其保留為Private 並新增一個新的Friend 方法來呼叫它可能會更簡潔明瞭。

當我們開啟一個文件時,我們必須首先建立一個文件窗體來儲存它。在這個程式例項中,frmDocument 代表了文件。這裡做出的設計決策是,每次我們開啟一個文件時,它都將載入到 frmDocument 的一個新例項中。這可能是也可能不是合適的,考慮一下如果使用者兩次開啟同一個文件並編輯兩個文件會發生什麼。

我們使用 Microsoft 提供的通用對話方塊控制元件,但我們也可以使用一個包含驅動器、資料夾和檔案列表控制元件的窗體。

 Option Explicit ' always use this to ensure that you don't forget to declare variables
 
 
 Public Sub mnuFileOpen_Click()
     
   Dim oForm As frmDocument
   Set oForm = LoadNewDoc
   
   With CommonDialog1
     ' The title should probably say something meaningful about the application and the document 
     .DialogTitle = "Open" type
     .CancelError = False
     .Filter = gsFILE_FILTER
     .ShowOpen
     If Len(.FileName) = 0 Then
       Exit Sub
     End If
     If Not oForm.LoadFile(.FileName) Then
       MsgBox "Could not load file <" & .FileName & ">," & vbCrLf & "probably couldn't find the zlib.dll.", _
              vbOKOnly + vbCritical, Title
     End If
   End With
 
 End Sub
 

LoadNewDoc 函式與檔案開啟事件處理程式分離,以便其他呼叫者可以使用它。

 Public Function LoadNewDoc() As frmDocument
   
   Static lDocumentCount As Long
   lDocumentCount = lDocumentCount + 1
   Set LoadNewDoc = New frmDocument
   LoadNewDoc.Caption = "Document " & lDocumentCount
   LoadNewDoc.Show
 
 End Function
 

當我們解除安裝主窗體時,我們希望能夠確保所有文件都已正確清理,因此我們呼叫一個函式退出應用程式。我們可以使用返回值來設定Cancel 引數,然後使用者可能能夠停止關機。

 Private Sub MDIForm_Unload(Cancel As Integer)
   ExitApplication
 End Sub

frmDocument.frm

[編輯 | 編輯原始碼]

文件窗體儲存了我們的算術文件之一。它並沒有比主窗體複雜多少。但是,它確實定義了一些不同的選單。此外,由於 VB6 的工作方式,它也必須定義所有相同的選單,它不能從主窗體繼承選單。VB 顯示當前窗體定義的選單,除非只有 MDI 窗體,在這種情況下,將顯示 MDI 窗體的選單。同樣,圖形顯示了比此原型中實際實現的更多選單。

Document form

以下是選單和控制元件的定義

 VERSION 5.00
 Object = "{3B7C8863-D78F-101B-B9B5-04021C009402}#1.2#0"; "RICHTX32.OCX"
 Begin VB.Form frmDocument 
    Caption         =   "Document"
    ClientHeight    =   3600
    ClientLeft      =   60
    ClientTop       =   60
    ClientWidth     =   6225
    Icon            =   "frmDocument.frx":0000
    KeyPreview      =   -1  'True
    LinkTopic       =   "Form1"
    MDIChild        =   -1  'True
    ScaleHeight     =   3600
    ScaleWidth      =   6225
    WindowState     =   2  'Maximized
    Begin RichTextLib.RichTextBox rtfBox 
       Height          =   3315
       Left            =   120
       TabIndex        =   0
       Top             =   240
       Width           =   6000
       _ExtentX        =   10583
       _ExtentY        =   5847
       _Version        =   393217
       Enabled         =   -1  'True
       HideSelection   =   0   'False
       ScrollBars      =   2
       TextRTF         =   $"frmDocument.frx":030A
       BeginProperty Font {0BE35203-8F91-11CE-9DE3-00AA004BB851} 
          Name            =   "Times New Roman"
          Size            =   12
          Charset         =   0
          Weight          =   400
          Underline       =   0   'False
          Italic          =   0   'False
          Strikethrough   =   0   'False
       EndProperty
    End
    Begin VB.Menu mnuFile 
       Caption         =   "&File"
       Index           =   1
       Begin VB.Menu mnuFileNew 
          Caption         =   "&New"
          Shortcut        =   ^N
       End
       Begin VB.Menu mnuFileOpen 
          Caption         =   "&Open..."
          Shortcut        =   ^O
       End
       Begin VB.Menu mnuFileBar0 
          Caption         =   "-"
       End
       Begin VB.Menu mnuFileSave 
          Caption         =   "&Save"
          Shortcut        =   ^S
       End
       Begin VB.Menu mnuFileSaveAs 
          Caption         =   "Save &As..."
       End
       Begin VB.Menu mnuSaveCompressed 
          Caption         =   "Save &Compressed"
       End
       Begin VB.Menu mnuFileSaveAll 
          Caption         =   "Save A&ll"
       End
       Begin VB.Menu mnuFileClose 
          Caption         =   "&Close"
          Shortcut        =   ^E
       End
       Begin VB.Menu mnuFileCloseAll 
          Caption         =   "&CloseAll"
       End
       Begin VB.Menu mnuFileBar1 
          Caption         =   "-"
       End
       Begin VB.Menu mnuFilePrint 
          Caption         =   "&Print..."
          Shortcut        =   ^P
       End
       Begin VB.Menu mnuFilePrintSetup 
          Caption         =   "&PrintSetup"
       End
       Begin VB.Menu mnuFilePrintPreview 
          Caption         =   "&PrintPreview"
          Shortcut        =   ^R
       End
       Begin VB.Menu mnuFileBar3 
          Caption         =   "-"
       End
       Begin VB.Menu mnuFileSend 
          Caption         =   "&Send"
          Begin VB.Menu mnuFileSendEmail 
             Caption         =   "&Email"
          End
       End
       Begin VB.Menu mnuFileExit 
          Caption         =   "E&xit"
          Shortcut        =   {F4}
       End
    End
    Begin VB.Menu mnuEdit 
       Caption         =   "&Edit"
       Begin VB.Menu mnuEditUndo 
          Caption         =   "&Undo"
          Shortcut        =   ^Z
       End
       Begin VB.Menu mnuEditRedo 
          Caption         =   "&Redo"
       End
       Begin VB.Menu mnueditbar2 
          Caption         =   "-"
       End
       Begin VB.Menu mnuEditCut 
          Caption         =   "Cu&t"
          Shortcut        =   ^X
       End
       Begin VB.Menu mnuEditCopy 
          Caption         =   "&Copy"
          Shortcut        =   ^C
       End
       Begin VB.Menu mnuEditPaste 
          Caption         =   "&Paste"
          Shortcut        =   ^V
       End
       Begin VB.Menu mnueditbar3 
          Caption         =   "-"
       End
       Begin VB.Menu mnuEditSelectAll 
          Caption         =   "&SelectAll"
          Shortcut        =   ^A
       End
       Begin VB.Menu mnuEditPloticus 
          Caption         =   "Ploticus"
       End
    End
    Begin VB.Menu mnuView 
       Caption         =   "&View"
       Begin VB.Menu mnuViewToolbar 
          Caption         =   "&Toolbar"
          Checked         =   -1  'True
       End
       Begin VB.Menu mnuViewStatusBar 
          Caption         =   "Status &Bar"
          Checked         =   -1  'True
       End
       Begin VB.Menu mnuViewRuler 
          Caption         =   "&Ruler"
          Checked         =   -1  'True
       End
    End
    Begin VB.Menu mnuFormat 
       Caption         =   "F&ormat"
       Begin VB.Menu mnuFormatFont 
          Caption         =   "&Font..."
       End
       Begin VB.Menu mnuFormatColor 
          Caption         =   "&Color..."
       End
       Begin VB.Menu mnuFormatBullet 
          Caption         =   "&Bullet"
       End
       Begin VB.Menu mnuFormatTabs 
          Caption         =   "&Tabs..."
       End
       Begin VB.Menu mnuFormatParagraph 
          Caption         =   "&Paragraph"
          Begin VB.Menu mnuParagraphLeft 
             Caption         =   "&Left Justified"
          End
          Begin VB.Menu mnuParagraphCentred 
             Caption         =   "&Centred"
          End
          Begin VB.Menu mnuParagraphRight 
             Caption         =   "&Right Justified"
          End
       End
       Begin VB.Menu mnuTypestyle 
          Caption         =   "&Typestyle"
          Begin VB.Menu mnuBold 
             Caption         =   "&Bold"
             Shortcut        =   ^B
          End
          Begin VB.Menu mnuItalic 
             Caption         =   "&Italic"
             Shortcut        =   ^I
          End
          Begin VB.Menu mnuUnderline 
             Caption         =   "&Underline"
             Shortcut        =   ^U
          End
       End
       Begin VB.Menu mnuformatfilebar1 
          Caption         =   "-"
       End
       Begin VB.Menu mnuFormatChangeCase 
          Caption         =   "&ChangeCase"
          Begin VB.Menu mnuFormatChangeCaseLowerCase 
             Caption         =   "&LowerCase"
          End
          Begin VB.Menu mnuFormatChangeCaseUpperCase 
             Caption         =   "&UpperCase"
          End
       End
       Begin VB.Menu mnuFormatFilebar2 
          Caption         =   "-"
       End
       Begin VB.Menu mnuFormatIncreaseIndent 
          Caption         =   "&IncreaseIndent"
       End
       Begin VB.Menu mnuFormatDecreaseIndent 
          Caption         =   "&DecreaseIndent"
       End
    End
    Begin VB.Menu mnuInsert 
       Caption         =   "&Insert"
       Begin VB.Menu mnuInsertObject 
          Caption         =   "&Object..."
       End
       Begin VB.Menu mnuInsertPicture 
          Caption         =   "&Picture..."
       End
       Begin VB.Menu mnuInsertbar1 
          Caption         =   "-"
          Index           =   2
       End
       Begin VB.Menu mnuPloticusPrefab 
          Caption         =   "Ploticus &Prefab"
          Begin VB.Menu mnuPloticusScatter 
             Caption         =   "&Scatter Plot"
          End
       End
       Begin VB.Menu mnuInsertbar3 
          Caption         =   "-"
       End
       Begin VB.Menu mnuInsertTextFile 
          Caption         =   "&TextFile..."
          Shortcut        =   ^T
       End
       Begin VB.Menu mnuInsertDate 
          Caption         =   "&Date"
          Shortcut        =   ^D
       End
       Begin VB.Menu mnuInsertbar2 
          Caption         =   "-"
       End
       Begin VB.Menu mnuInsertSymbols 
          Caption         =   "&Symbols"
       End
    End
    Begin VB.Menu mnuData 
       Caption         =   "&Data"
       Begin VB.Menu mnuEvaluate 
          Caption         =   "&Evaluate"
          Shortcut        =   {F9}
       End
    End
    Begin VB.Menu mnuTools 
       Caption         =   "&Tools"
    End
    Begin VB.Menu mnuWindow 
       Caption         =   "&Window"
       WindowList      =   -1  'True
       Begin VB.Menu mnuWindowNewWindow 
          Caption         =   "&New Window"
          Shortcut        =   {F12}
       End
       Begin VB.Menu mnuWindowBar0 
          Caption         =   "-"
       End
       Begin VB.Menu mnuWindowCascade 
          Caption         =   "&Cascade"
       End
       Begin VB.Menu mnuWindowTileHorizontal 
          Caption         =   "Tile &Horizontal"
       End
       Begin VB.Menu mnuWindowTileVertical 
          Caption         =   "Tile &Vertical"
       End
       Begin VB.Menu mnuWindowArrangeIcons 
          Caption         =   "&Arrange Icons"
       End
    End
    Begin VB.Menu mnuHelp 
       Caption         =   "&Help"
       Begin VB.Menu mnuHelpContents 
          Caption         =   "&HelpContents"
          Shortcut        =   {F1}
       End
       Begin VB.Menu mnuHelpTipoftheDay 
          Caption         =   "&TipoftheDay"
       End
       Begin VB.Menu mnuHelpAbout 
          Caption         =   "&About "
       End
       Begin VB.Menu mnuHelpSpecialThanks 
          Caption         =   "&SpecialThanks"
       End
    End
 End
 Attribute VB_Name = "frmDocument"
 Attribute VB_GlobalNameSpace = False
 Attribute VB_Creatable = False
 Attribute VB_PredeclaredId = True
 Attribute VB_Exposed = False
 Option Explicit

以下是程式碼。其中一些例程與我們建立即時數學文件的主要目標幾乎沒有關係。這種情況很常見;當您開發程式時,您會發現一些在您繼續進行中需要解決的小問題,以使程式正常工作或僅僅是方便使用。

在 VB 中,Tab 鍵用於將焦點從一個控制元件移動到下一個控制元件,但在編輯文字時,我們通常希望實際插入一個Tab 字元。一種實現方法是為Rich Text BoxKeyDown 事件宣告一個事件處理程式。這會檢查按鍵的ASCII 程式碼是什麼,並直接將Rich Text Box 中選定的字元覆蓋為一個Tab 字元

 Private Sub rtfbox_KeyDown(KeyCode As Integer, Shift As Integer)
   If KeyCode = 9 Then
     rtfBox.SelText = vbTab
     KeyCode = 0
   End If    
 End Sub

載入檔案很簡單。只需呼叫Rich Text BoxLoadFile 方法即可。唯一需要處理的複雜情況是,使用者可能嘗試開啟Rich Text Box 無法處理的文件。這裡我們認為這不是程式中的錯誤,因此不引發錯誤;相反,我們返回一個狀態值:如果成功,則為True,否則為false

 Public Function LoadFile(rsFile As String) As Boolean
   On Error Resume Next
   rtfBox.LoadFile rsFile
   LoadFile = Err.Number = 0    
 End Function

以下是實際執行工作的程式碼。請注意,所有複雜的操作都在另一個模組中。這是因為我們可以輕鬆地想象出我們要自動執行這些操作的情況,在這種情況下,我們可能從其他地方獲取文字

 Public Sub EvalDoc()
   goEvalDoc.EvalDoc rtfBox
 End Sub

當然,如果無法執行文件,那麼沒有必要擁有一個方法來重新計算文件,因此我們從資料評估選單項呼叫它。檢視上面的宣告,並看到一個快捷鍵附加到該選單項(F9

 Public Sub mnuEvaluate_Click()
   EvalDoc
 End Sub

請記住,當此窗體處於活動狀態時,主窗體的選單不可用,因此我們從自己的檔案開啟事件處理程式中呼叫主窗體的檔案開啟事件處理程式。這就是我們必須從Private 更改為Public 的原因(Friend 也能工作)

 Public Sub mnuFileOpen_Click()
   fMainform.mnuFileOpen_Click
 End Sub

要建立一個全新的文件,我們必須呼叫主窗體的LoadNewDoc 方法

 Public Sub mnuFileNew_Click()
   fMainform.LoadNewDoc
 End Sub

cEvalDoc.cls

[編輯 | 編輯原始碼]

這個類是完成很多艱苦工作的地方。主要方法EvalDoc 看起來非常簡單,因為它只是呼叫了另外三個函式。這些函式

  • 預處理文件,使其成為合法的 JScript,
  • 執行 JScript,
  • 使用結果更新文件。

預處理步驟將宏轉換為儲存要替換的文字位置的表的 JScript 函式,並將矩陣轉換為返回陣列的 JScript 函式呼叫。這使得將陣列的值分配給一個變數並以整體的方式處理陣列(而不是一次處理一個元素)變得實用。

以下是類的標題。在 Visual Basic IDE 中,您無法看到此文字,但您可以更改其值,因為它們在屬性視窗中顯示。

 VERSION 1.0 CLASS
 BEGIN
   MultiUse = -1  'True
   Persistable = 0  'NotPersistable
   DataBindingBehavior = 0  'vbNone
   DataSourceBehavior  = 0  'vbNone
   MTSTransactionMode  = 0  'NotAnMTSObject
 END
 Attribute VB_Name = "cEvalDoc"
 Attribute VB_GlobalNameSpace = False
 Attribute VB_Creatable = True
 Attribute VB_PredeclaredId = False
 Attribute VB_Exposed = False
 
 Option Explicit

JScript 原始碼的實際評估由MSScript 控制元件完成。不確定Attibute 是什麼。

 Public moScriptControl As MSScriptControl.ScriptControl
 Attribute moScriptControl.VB_VarHelpID = -1

Jscript 直譯器只提供 JavaScript 的基本函式,任何不常見的函式都必須由我們提供。我們透過建立一個將這些函式作為方法提供給直譯器的物件來實現這一點。

 Private moFunctions As cFunctions

文件評估器物件必須在使用之前進行初始化。這意味著建立一個指令碼控制元件的例項,告訴指令碼控制元件將使用哪種語言,並提供一個全域性物件,該物件向直譯器新增額外的函式。

 Private Sub Class_Initialize()
   Set moScriptControl = New MSScriptControl.ScriptControl
   With moScriptControl
     .AllowUI = False
     .Language = "JScript"
     .UseSafeSubset = True
   End With
   Set moFunctions = New cFunctions
   moScriptControl.AddObject "Functions", moFunctions, True
   Set moFunctions.oScriptControl = moScriptControl
 End Sub

此類的唯一公共方法是EvalDoc,它以 Rich Text 控制元件作為唯一引數,處理其中找到的文字,並將答案放回文件中。

 Public Sub EvalDoc(ByRef roDoc As RichTextBox)
   On Error GoTo ErrorHandler
   

首先,我們將文字中找到的所有宏替換為 JScript 函式呼叫,這些呼叫將在結果陣列中建立結果。

   Dim sScript As String
   sScript = xPreprocess(roDoc.Text)

結果陣列是一個動態陣列,我們允許它根據需要增長,但我們不會縮小它,因為我們知道我們會再次評估文件,所以釋放記憶體會浪費時間。因此,我們透過簡單地將結果計數設定為零來重新初始化陣列。計數器也是指向陣列中下一個空閒插槽的指標。

   glResultsCount = 0

現在一切都準備好了,我們所要做的就是使用內建的 Eval 函式執行指令碼。

   moScriptControl.Run "eval", sScript

最後,如果一切順利,我們必須將答案放回文件中。

   xUpdateDisplay roDoc
 
   Exit Sub

不幸的是,事情可能會出錯,因此我們必須採取措施防止程式崩潰。如果使用者在輸入宏時漏掉了右括號,則可能會出現問題。使用者編寫的 JScript 中可能存在普通的語法錯誤,當然,程式本身也可能存在錯誤。因此,我們必須有一個錯誤處理程式。

 ErrorHandler:
   Select Case Err.Number

如果使用者在輸入宏時漏掉了右括號,預處理器會注意到這一點。在這個原型中,我們透過選擇有問題的文字並顯示一個訊息框來提醒使用者來處理這個問題。

     Case ChunkNotTerminated
       roDoc.SelStart = xErrData(Err.Description)(0)
       roDoc.SelLength = xErrData(Err.Description)(1)
       MsgBox "Missing #> at end of macro"

如果問題是宏格式正確但無法識別,我們與語法錯誤的宏做相同的事情。

     Case UnrecognizedMacro
       roDoc.SelStart = xErrData(Err.Description)(0)
       roDoc.SelLength = xErrData(Err.Description)(1)
       MsgBox "Unrecognized macro, did you mean to display a value?"

因為我們無法預測會發生什麼錯誤,所以我們以一個捕獲所有子句結束,該子句通知使用者。注意 .ErrorContextxxx 屬性;這些屬性由預處理器編寫的 JScript 函式設定,以便使用者可以被引導到發現錯誤時正在處理的文件部分。

     Case Else
       With moFunctions
         .MakeResult Empty, .ErrorContextStart_, _
                     .ErrorContextEnd_ - .ErrorContextStart_, _
                     SourceError
       End With        
   End Select
 
 End Sub

由於 Visual basic 沒有異常,因此我們需要某種方法將資訊從引發錯誤的例程傳遞到捕獲錯誤的例程。一種簡單的方法是將資訊打包到一個字串中並使用 Err 物件的 description 屬性。然後,當錯誤被捕獲時,我們可以使用 Split 函式從 description 中獲取資料。這個小函式只是包裝了 Split 函式,部分原因是為了給它一個有意義的名稱,但也因為一開始似乎處理會更加複雜。

 Private Function xErrData(ByRef rsErrDescription As String) As Variant
   xErrData = Split(rsErrDescription, "|")
 End Function

在文件文字可以由 Script 控制元件評估之前,我們必須確保文字是合法的 JScript。我們透過在字串中複製文字,查詢指令和宏併為它們生成適當的程式碼來做到這一點。

輸出透過呼叫將變數值新增到輸出列表的函式來處理。這些函式需要三個引數:要輸出的值、文字中的起始點以及要替換的文字範圍的長度。不能期望使用者計算行並維護這些值,因此使用宏代替。輸出放在宏出現的文字中的位置。與大多數宏替換系統不同,我們不會用輸出替換整個宏,因為那樣我們就會丟失佔位符,並且無法更新它。宏包含三個部分:intro、body、outro。intro 和 outro 保留在文字中,但 body 被新的輸出替換。選擇在 JavaScript 程式中無法作為合法序列出現的字元序列,並且不太可能出現在文字字串中很重要。

我選擇的序列是 <# #>。

宏可以出現在註釋中,並且在那裡也能工作。

目前,SplitInstr 用於查詢標記,正則表示式可能更好,但我不能確定。這個函式在實踐中相當複雜,但基本思路很簡單

  • 使用 intro 作為分隔符將文字分成
  • 透過累積 的長度來記錄每個 intro 字串的字元偏移量
  • 刪除所有在註釋中的文字,
  • 將宏替換為對函式的呼叫,該函式將命名變數的值以及要插入的位置的開始和長度一起儲存,

所有這些都在文字的副本上進行,此時不會干擾富文字框。

 Private Function xPreprocess(rsText As String) As String
   
   Const sINTRO As String = "<#"
   Const sOUTRO As String = "#>"
   Dim aChunks As Variant

使用 intro 序列分割文字。這將導致一個文字塊列表(Variant 陣列),這些文字塊都以宏開頭(如果第一個宏之前有任何文字,則列表中的第一個專案除外)。

   aChunks = Split((rsText), sINTRO)
   Dim lChunk As Long

實際執行的文字不需要任何註釋,因此我們透過從第一個塊中刪除單行和多行註釋來建立一個新文字。這個塊必須特殊處理,因為它違反了所有塊都以宏開頭的規則。

   xPreprocess = xRemoveComments((aChunks(LBound(aChunks))))
   Dim lStart As Long

為了將結果放置在文字中的正確位置,我們必須跟蹤從文字開頭到宏的字元偏移量。請注意,我們必須始終顯式新增 intro 的長度,因為它不會出現在塊列表中。

   lStart = Len(aChunks(LBound(aChunks))) + Len(sINTRO)
   Dim lEnd As Long
   Dim lLenChunk As Long
   Dim lEndVar As Long

現在,我們可以處理每個塊並將處理後的文字新增到要執行的字串中。

   For lChunk = LBound(aChunks) + 1 To UBound(aChunks)

首先,我們必須檢查宏是否已終止,在塊中搜索 outro。如果缺少 outro,我們會引發錯誤,因為使用者肯定犯了一個錯誤,忘記了完成宏。有人可能會爭辯說,我們應該嘗試修補文字並繼續。參見 練習

     lEnd = InStr(aChunks(lChunk), sOUTRO)
     If lEnd Then
       Dim sChunk As String
       sChunk = Left$(aChunks(lChunk), lEnd)

現在我們擁有了一個完整的宏,我們必須檢查是否識別它。目前只有一種型別的宏,即 顯示值 宏。

       lEndVar = InStr(sChunk, "=")
       If lEndVar Then
         xPreprocess = xPreprocess & ";" & vbCrLf _
                     & "show(" & Left$(aChunks(lChunk), lEndVar - 1) _
                     & "," & (lStart + lEndVar) & "," & (lEnd - lEndVar - 1) _
                     & ")" & vbCrLf _
                     & xRemoveComments(Mid$(aChunks(lChunk), _
                                            lEnd + Len(sOUTRO)))
         lStart = lStart + Len(aChunks(lChunk)) + Len(sINTRO)
       Else

如果缺少 = 符號,則這不是 顯示值 宏。由於我們還沒有定義任何其他宏,這肯定是一個錯誤,因此我們中止該過程並報告它。

         Err.Raise UnrecognizedMacro, "xPreprocess", _
            lStart & "|" & Len(aChunks(lChunk)) & "|" & "Unrecognized macro type"
       End If
     Else

如果找不到結束塊字元,我們會引發錯誤並中止該過程。

       Err.Raise ChunkNotTerminated, "xPreprocess", _
          lStart & "|" & Len(aChunks(lChunk)) & "|" & "Unterminated chunk"
     End If
   Next lChunk
   
 End Function
 

如果我們刪除周圍的註釋,則將宏轉換為程式碼會更簡單。我們可以透過定義一個接受一個塊並返回相同塊(減去任何註釋)的函式來逐個塊地執行此操作。

 Private Function xRemoveComments(ByRef rsChunk As String) As String

首先處理最簡單的案例:單行註釋和完整的多行註釋。

   xRemoveComments = xRemoveBracketed(rsChunk, "//", vbLf)
   xRemoveComments = xRemoveBracketed(xRemoveComments, "/*", "*/")
 

現在刪除任何前導或尾隨[檢查拼寫] 多行註釋片段。這些片段的出現是因為塊分割會忽略註釋邊界。我們在塊中搜索結束和開始的多行註釋標記。

   Dim lComment As Long
   lComment = InStr(xRemoveComments, "*/")
   If lComment Then
     xRemoveComments = Mid$(xRemoveComments, lComment + Len("*/"))
   End If
   
   lComment = InStr(xRemoveComments, "/*")
   If lComment Then
     xRemoveComments = Left$(xRemoveComments, lComment - 1)
   End If
   
 End Function

此函式重複刪除位於給定開始和結束標記之間的子字串,直到開始標記不再出現在字串中。

 Private Function xRemoveBracketed(ByRef rsChunk As String, _
                                   ByRef rsStart As String, _
                                   ByRef rsfinish As String) As String
   
   xRemoveBracketed = rsChunk
   Dim lStart As Long
   Do
     lStart = InStr(xRemoveBracketed, rsStart) ' single line
     If lStart Then
       Dim lFinish As Long
       lFinish = InStr(lStart, xRemoveBracketed, rsfinish)

如果結束標記沒有出現,則將字串視為結束標記出現在字串結尾後的字元處。這允許我們刪除沒有以換行符序列結尾的單行註釋和被宏分割的多行註釋。

       If lFinish = 0 Then
         lFinish = Len(xRemoveBracketed) + 1
       End If
       xRemoveBracketed = Left$(xRemoveBracketed, lStart - 1) _
                        & Mid$(xRemoveBracketed, lFinish)
     End If
   Loop Until lStart = 0
   
 End Function


文件評估完成後,我們必須將答案放回文件中的正確位置。乍一看,這似乎是一個簡單的任務,因為我們有一系列值以及它們在文件中的位置,因此乍一看,我們似乎需要做的就是列舉結果並將指定的字元範圍替換為答案。不幸的是,這將不起作用,除非新文字與原始文字完全相同,因為插入長度不同的新文字會移動要替換的文字,因此結果記錄將指向錯誤的位置。解決方案是按相反順序列舉結果,以便將連續的結果插入到文件的更靠近開頭的位置,這意味著只有已經更新的文字才會移動。

 Private Sub xUpdateDisplay(roDoc As RichTextBox)
   On Error GoTo ErrorHandler
   
   With roDoc

另一個複雜之處是我們希望保留使用者的選擇。我們不能簡單地儲存值 .SelStart.SelLength,然後將它們複製回來,因為替換可能發生在選擇之前或選擇內部,甚至可能發生選擇邊界位於宏內部的情況。因此,每次執行替換時,我們都必須更新選擇。所以我們必須做的第一件事是複製這些屬性的值。

     Dim lSelStart As Long
     Dim lSelLen As Long
     Dim lSelNext As Long
     lSelStart = .SelStart
     lSelLen = .SelLength
     lSelNext = lSelStart + lSelLen

我們從最後一個結果記錄開始,並向後計數。結果陣列從零開始,因此結果計數器始終指向下一個可用插槽。因此,我們在迴圈開始時遞減計數器,當我們完成時,計數器將為零。

     Do While 0 < glResultsCount
       glResultsCount = glResultsCount - 1

要替換富文字框中的文字,我們必須首先設定 .SelStart.SelLength 屬性,為了保留使用者的選擇,我們必須將這些值與使用者選擇的當前值進行比較,並在必要時更新使用者的選擇。

       .SelStart = gaResults(glResultsCount).Start
       .SelLength = gaResults(glResultsCount).length
       If .SelStart + .SelLength < lSelStart Then
         lSelStart = lSelStart - .SelLength
         lSelNext = lSelStart + lSelLen
       End If

替換文字是什麼取決於結果型別以及嘗試計算結果時是否發生錯誤。如果發生錯誤,我們將有問題的文字顏色設定為紅色並保留它不變。

       Select Case gaResults(glResultsCount).ResultType
         Case SourceError
           .SelColor = vbRed
         Case Else

如果成功,我們將選擇顏色設定為黑色並將宏的可替換部分替換為結果。現在我們看到了將結果宣告為 Variant 的原因,因為最後我們看到了在這個文件中擁有圖表是可能的。實際進行替換的方式取決於結果的型別。如果結果是 標量,那麼使用 Visual Basic 的字串轉換函式很容易建立人類可讀的表示,但圖表卻完全不同。因為它們差異很大,所以我們為它們建立了單獨的函式。

           .SelColor = vbBlack
           If TypeOf gaResults(glResultsCount).Value Is cPlot Then
             xReplacePlot gaResults(glResultsCount).Value, roDoc
             If .SelStart < lSelStart Then
               lSelStart = lSelStart + 1
               lSelNext = lSelStart + lSelLen
             End If
           Else
             .SelText = xToString(gaResults(glResultsCount).Value)
             If .SelStart < lSelStart Then
               lSelStart = lSelStart _
                         + Len(xToString(gaResults(glResultsCount).Value))
               lSelNext = lSelStart + lSelLen
             End If
           End If
       End Select
     Loop

現在,我們可以再次設定 .SelStart.SelLength 屬性來恢復使用者的選擇。當然,如果它包含一個被替換的宏,那麼選擇的長度可能與最初的長度大不相同。

     .SelStart = lSelStart
     .SelLength = lSelLen
   End With
   
   Exit Sub

此函式中的錯誤處理程式不完整,也許是學生的練習。目前,它只是斷言了一個假值以停止程式並允許開發人員除錯它。在現實生活中,這將非常複雜,因為我們希望處理整個文件。

 ErrorHandler:
   Debug.Assert False
   Resume
 End Sub

由於這實際上是一個 概念證明 而不是一個完成的應用程式,因此我們可以使用一種非常簡單的方法將結果轉換為文字:只需使用隱式轉換。例外情況是 矩陣。矩陣是 JScript 陣列,因此我們必須做一些額外的工作來格式化它們。

 Private Function xToString(rvResult As Variant) As String
   
   If TypeName(rvResult) = "JScriptTypeInfo" Then
     ' assume that the object is a JavaScript array
     xToString = xJArrayToString(rvResult)
   Else
     Select Case VarType(rvResult)
       Case vbDouble, vbLong, vbInteger
         xToString = " " & rvResult
       Case vbString
         xToString = " '" & rvResult & "'"
       Case Else
         xToString = rvResult
     End Select
   End If
 End Function

如果結果是圖表,則結果實際上是圖片檔案的名稱。為了將它放在文字中,我們必須使用剪貼簿插入它,至少這是最簡單的方法。如果檔案不存在,我們將插入文字來說明這一點,而不是讓使用者想知道圖表在哪裡。請注意,當呼叫此例程時,選擇已設定為宏的可替換部分。

 Private Sub xReplacePlot(ByRef rvPlot As Variant, roDoc As RichTextBox)
   
   With roDoc
     If goFSO.FileExists(rvPlot.PicFileName) Then
       .SelText = ""     ' delete the old plot or whatever else there was.
       InsertPicture rvPlot.PicFileName, roDoc
     Else
       .SelText = "File <" & rvPlot.PicFileName & "> does not exist."
     End If
     
   End With
   
 End Sub

如果結果是陣列,則我們將使用製表符分隔列,以行和列格式化它。使用者必須為文字的該部分設定製表符。不幸的是,我們還沒有給使用者這樣做,這是學生的另一項練習。

 Private Function xJArrayToString(rvResult As Variant) As String
   
   Debug.Assert TypeName(rvResult) = "JScriptTypeInfo"
   Dim oRow As Variant
   
   Dim lRow As Long
   Dim vItem As Variant
   xJArrayToString = vbTab & "["
   For lRow = 0 To rvResult.length - 1
     If lRow <> 0 Then
       xJArrayToString = xJArrayToString & vbTab
     End If
     Set oRow = CallByName(rvResult, lRow, VbGet)
     If TypeName(oRow) = "JScriptTypeInfo" Then
       xJArrayToString = xJArrayToString & vbTab & xJRowToString(oRow)
     Else
       vItem = CallByName(rvResult, lRow, VbGet)
       xJArrayToString = xJArrayToString & vbTab & "[" & vbTab & vItem & "]"
     End If
     If lRow < rvResult.length - 1 Then
       xJArrayToString = xJArrayToString & "," & vbCrLf
     End If
   Next lRow
   xJArrayToString = xJArrayToString & "]"
   
 End Function

每一行實際上都是一個 JScript 陣列。JScript 沒有多維陣列,但由於 JScript 中的一切都是物件,我們可以透過擁有陣列的陣列來輕鬆地模擬多維陣列。

 Private Function xJRowToString(rvResult As Variant) As String
   
   Debug.Assert TypeName(rvResult) = "JScriptTypeInfo"
   Dim oRow As Variant
   
   Dim lCol As Long
   Dim vItem As Variant
   xJRowToString = "["
   For lCol = 0 To rvResult.length - 1
     vItem = CallByName(rvResult, lCol, VbGet)
     If VarType(vItem) = vbString Then
       vItem = "'" & vItem & "'"
     End If
     xJRowToString = xJRowToString & vItem
     If lCol < rvResult.length - 1 Then
       xJRowToString = xJRowToString & "," & vbTab
     End If
   Next lCol
   xJRowToString = xJRowToString & "]"
   
 End Function

cFunctions.cls

[編輯 | 編輯原始碼]

EvalDoc 的預定義函式。此類的例項被提供給 JScript 物件,以提供用於矩陣乘法等操作的全域性函式。提供多語言程式設計示例:應用程式是用 VB 編寫的,文件是用 JScript 編寫的,JScript 使用的庫是用 VB 編寫的。

 Option Explicit
   
 Public Enum enumFunctionErrors
   IncompatibleDimensions = vbObjectError + 1
   ChunkNotTerminated
   UnrecognizedMacro
 End Enum
 

ErrorContextxxxx 屬性用於使錯誤處理程式能夠確定有問題的原始碼的位置。在宏之前,程式碼中插入了語句來設定這些值。

 Public ErrorContextStart_ As Long
 Public ErrorContextEnd_    As Long
 

某些函式需要能夠動態建立變數,因此我們必須提供對正在執行指令碼的物件的引用。

 Public oScriptControl  As ScriptControl 
 

Plot 是一個函式,文件可以像呼叫內建的 JScript 函式一樣呼叫它。它生成一個物件,該物件反過來用於驅動 Ploticus 製圖程式。(非常感謝 Steve Grubb <http://ploticus.sourceforge.net>)。

第一個引數是包含要繪製資料的矩陣,第二個引數必須是一個字串,表示 Ploticus 命令列,除了它不包含 data=datafilename,因為我們使用了一個臨時檔案,所以該檔案會自動提供。

將矩陣寫入檔案,然後建立一個批處理檔案來驅動 Ploticus。執行 Ploticus 檔案以建立圖片檔案。此圖片檔案的路徑儲存在 cPlot 物件中,以便以後使用。

注意,Ploticus 對命令列中的空格非常敏感。

這將失敗:'pl -gif -prefab scat x=1 y =2 data=12 -o 11

因為 y =2 應該說 y=2,注意多餘的空格。

 Public Function Plot(ByRef rvData As Variant, ByRef rsPlotCommands As String) As cPlot
 
   Set Plot = New cPlot
   Plot.PicFileName = TF & ".gif"
   Dim sDataFileName As String
   sDataFileName = xSaveMatrix(rvData)
 
   RunCmd "pl -gif " & rsPlotCommands & " data=" & sDataFileName & " -o " & Plot.PicFileName
   ' @TODO: check output of plot command for errors
 End Function

Ploticus 從檔案讀取其資料,因此我們必須寫入一個檔案。我們不關心它叫什麼,我們可以在之後刪除它,因此我們建立一個唯一的檔名。實際上,可以建立無法正常工作的情況,但這很難。

 Public Function TF() As String
   TF = goFSO.BuildPath(gsTempFolder, Trim$(NextSerial))
 End Function
 

因為 Ploticus 是一個單獨的程式,我們必須啟動它並等待它完成。此例程是一個通用的命令列程式執行器。

對於每個需要輸出檔名作為引數的函式,請使用 $(x) 形式的宏,其中 x 是包含檔名的變數的名稱,或者是要接收自動生成的檔名的變數的名稱。該變數可以在以後的其他命令中使用。如果該變數在一系列命令中使用,使用者無需考慮其實際名稱,因為該變數將在首次使用時獲得指向臨時會話資料夾的唯一字串值。

如果您需要提供檔案作為輸入,並且資料儲存在變數中,請透過將該字串與對 SaveData 函式的呼叫連線起來來構造該字串;這會將變數寫入新的臨時檔案並返回檔名。

例如,您可以像這樣驅動 Ploticus

s=Cmd('pl -gif -prefab scat x=2 y=3 data=' + SaveData(b) + ' -o $f(plot)')

在此示例中,s 從標準輸出(如果有)接收輸出,名為 plot 的變數命名將接收 Ploticus 圖片的檔案。如果 plot 變數為空字串,則將為其建立一個唯一的臨時檔名。

如果您想重新使用檔名,請確保先清除它,除非您確實想再次使用同一個名稱。

宏是 $x(args),其中 x 是宏的名稱,args 是宏以任何形式接受的任何引數。

 Public Function Cmd(ByRef rsCommandLine As String) As cPlot
 
   RunCmd rsCommandLine
   '  ' @TODO: check output of plot command for errors
 
 End Function

Ploticus 需要以特定格式的檔案形式提供繪圖資料。啟動 Ploticus 的例程需要知道該檔案的名稱。此函式建立檔案,將資料寫入檔案並返回檔名。

 Private Function xSaveMatrix(rvData As Variant) As String
   xSaveMatrix = goFSO.BuildPath(gsTempFolder, Trim$(NextSerial)) & ".dat"
   goFSO.OpenTextFile(xSaveMatrix, ForWriting, True).Write _
        JArrayToPlotData(rvData)
 
 End Function
 

文件中的宏透過呼叫此函式在 gaResults 陣列中建立結果記錄。

 Public Sub MakeResult(ByRef rvResult As Variant, _
                       ByRef rlStart As Long, ByRef rlLength As Long, _
                       ByRef reResultType As eResultType)
 
   With gaResults(glResultsCount)

請注意,我們無法使用 TypeOf 來確定結果是否為 JScript 物件,因為根據 VB 的說法,該型別不存在。這非常奇怪,因為 LocalsWatch 視窗會正確顯示它,因此 IDE 必須知道。使用 typeName 代替,缺點是字串比較速度較慢。

     If TypeName(rvResult) = "JScriptTypeInfo" Or IsObject(rvResult) Then
       Set .Value = rvResult
     Else
       .Value = rvResult
     End If
     .Start = rlStart
     .length = rlLength
     .ResultType = reResultType
   End With

學生練習:如果 glresultsCount 大於陣列的上界,會發生什麼?這裡應該怎麼做?

   glResultsCount = glResultsCount + 1

 End Sub

Show 函式是公開的,因此 JScript 可以呼叫它。宏替換過程將這些呼叫放入原始碼中,替換宏。目前,這只是呼叫 Makeresult 例程。使用包裝器的目的是促進更復雜的錯誤處理。

 Public Function Show(o As Variant, rlStart As Long, rlLength As Long)
 
   MakeResult o, rlStart, rlLength, Result
 
 End Function

將兩個 JScript 矩陣相乘並返回另一個 JScript 矩陣。這只是底層矩陣乘法例程的包裝器。此包裝器將傳入的矩陣從 JScript 轉換為 VB,並將結果從 VB 轉換為 JScript。

 Public Function Multiply(ra1 As Variant, ra2 As Variant) As Object
   On Error GoTo ErrorHandler
 
   Set Multiply = VBMatrixToJArray(xMultiply(JArrayToMatrix(ra1), JArrayToMatrix(ra2)))
 
   Exit Function
 
 ErrorHandler:
   MakeResult Empty, ErrorContextStart_, ErrorContextEnd_, SourceError
 End Function

此函式將兩個矩陣相乘。它從公共函式 Multiply 中呼叫。

矩陣相乘的規則在 矩陣運算 中給出,在 代數 書中。此函式是一個直接的實現,基本上使用與該頁面上顯示的相同的符號。一個顯著的區別是,我們的陣列索引從零開始而不是從一開始。

 Public Function xMultiply(ByRef raA() As Double, ByRef raB() As Double) As Double()
   Dim j As Long
   Dim k As Long
   Dim m As Long
   Dim n As Long
   Dim p As Long
   Dim i As Long
   Dim aC() As Double
   Dim cij As Double

請記住,Ubound 函式接受一個可選的第二個引數,該引數指示要返回的維度;第一個維度是數字 1,第二個是數字 2,依此類推。矩陣只有兩個維度,第一個是行,第二個是列。

   n = UBound(raA, 2)
   m = UBound(raA, 1)
   p = UBound(raB, 2)
 
   ReDim aC(0 To n, 0 To p)
 
   For i = 0 To m
     For j = 0 To p
       nAcc = 0
       For k = 0 To n
         cij = cij + raA(i, k) * raB(k, j)
       Next k
       aC(i, j) = cij
     Next j
   Next i
 
   xMultiply = aC
 
   Exit Function
 
 End Function
  • 從末尾而不是從開頭讀取結果列表的策略並不保證對所有文件都適用。你能解釋一下為什麼嗎?提示,想想你可以使用哪些不同型別的 JScript 語句。

modJavaScript

[編輯 | 編輯原始碼]

此模組用於幫助將 JavaScript 文件連線到程式內部 Visual Basic 世界的函式。

此實現提供了一些簡單的矩陣操作函式。由於這些函式是用 Visual Basic 編寫的,並且矩陣必須用 JScript 編寫,因此我們需要函式來在 JScript 物件和 Visual Basic 陣列之間進行轉換。

 Option Explicit

JScript 矩陣實際上是巢狀陣列。每行都是一個一維元素陣列,矩陣也是一個一維陣列,其中每個元素都是一個數組。這意味著 JScript 矩陣可以是 不規則陣列。在 Visual Basic 中,通常將矩陣表示為矩形陣列。在此實現中,我們只假設陣列中的所有行都具有相同數量的元素,因此我們可以透過檢查外部 JScript 陣列的計數來發現行數,並透過檢查第一行的元素計數來發現列數。

此函式將 JScript 陣列轉換為 Visual Basic 陣列。

 Public Function JArrayToMatrix(rvResult As Variant) As Double()
 
   Dim oRow As Variant
   Dim lRows As Long
   Dim lCols As Long

查詢行數很容易,因為所有 JScript 物件實際上都是字典,它們都有一個 length 屬性。獲取行數稍微複雜一些。我們首先必須獲取第一個行的 JScript 物件。請記住,JScript 物件實際上是字典,因此陣列是字典,其中鍵是數字。在 Script 控制元件中,這對映到使每個專案看起來都是物件的屬性,因此我們可以使用 CallByName 來檢索值。在這種情況下,名稱只是 0(即數字零),因為 JScript 陣列從 length - 1 編號。

   lRows = rvResult.length
   Set oRow = CallByName(rvResult, 0, VbGet)    
   lCols = oRow.length

現在我們可以分配 Visual Basic 陣列。為了避免混淆,我們明確指定了下界和上界;這是一個好習慣,因為它意味著您不必擔心檔案頂部是否有一個 Option Base 語句。

   ReDim JArrayToMatrix(0 To lRows - 1, 0 To lCols - 1)

現在我們只需列舉行並將內容逐行復制到陣列中即可。

   Dim lRow As Long
   Dim vItem As Variant
   For lRow = 0 To lRows - 1
     Set oRow = CallByName(rvResult, lRow, VbGet)
     xJRowToMatrix JArrayToMatrix, lRow, oRow
   Next lRow
   
 End Function

為了提高可讀性,從行中複製資料是在單獨的例程中執行的。雖然在這種情況下,它並沒有增加太多可讀性,但它確實有助於清楚地區分行操作和列操作。請注意,這是一個接受對目標陣列的 引用行號 的子例程,因為我們無法在 Visual Basic 中分配行。

 Private Sub xJRowToMatrix(raMatrix() As Double, _
                           rlRow As Long, _
                           rvResult As Variant)
   
   Dim lCol As Long
   Dim vItem As Variant
   
   For lCol = 0 To rvResult.length - 1
     vItem = CallByName(rvResult, lCol, VbGet)
     raMatrix(rlRow, lCol) = vItem
   Next lCol
   
 End Sub

從 Visual Basic 陣列轉換為 JScript 矩陣可以透過建立一段 JScript 原始碼並對其進行評估來完成。我們所要做的就是建立一個看起來與使用者鍵入的完全相同的字串。據推測,直接操作 JScript 物件會更快,如果有人能弄清楚怎麼做的話。

注意函式結果的後期繫結。這是因為 Script 控制元件公開的物件似乎沒有實現 Visual Basic 所需的介面。這可能是因為 Script 控制元件提供了額外的間接層。

 Public Function VBMatrixToJArray(raMatrix() As Double) As Object
   
   Dim lRow As Long
   Dim lCol As Long
   Dim sArray As String
   
   sArray = "["
   For lRow = LBound(raMatrix, 1) To UBound(raMatrix, 1)
     sArray = sArray & "["
     For lCol = LBound(raMatrix, 2) To UBound(raMatrix, 2)
       sArray = sArray & raMatrix(lRow, lCol)
       If lCol < UBound(raMatrix, 2) Then
         sArray = sArray & ","
       End If
     Next lCol
     sArray = sArray & "]"
     If lRow < UBound(raMatrix, 1) Then
       sArray = sArray & ","
     End If
   Next lRow
   sArray = sArray & "]"
   Set VBMatrixToJArray = goEvalDoc.moScriptControl.Eval(sArray)
   
 End Function

cPlot.cls

[編輯 | 編輯原始碼]

表示一個繪圖。允許 tostring 函式決定做什麼來顯示繪圖。tostring 函式將使用 filename 屬性查詢 Ploticus 建立的圖片檔案,並將該圖片嵌入到富文字檔案中。

 Option Explicit
 Public PicFileName As String

modPlot.bas

[編輯 | 編輯原始碼]

此模組提供了與 Ploticus 的底層連線。

 Option Explicit

至少有兩種方法可以將圖片放入富文字框中。這裡選擇的方法需要最少的程式設計,但缺點是它使用 Windows 剪貼簿,這很不禮貌,因為使用者可能當時在剪貼簿中有一些內容。為此,我們使用 SendMessage API 呼叫,它將 Windows 訊息傳送到視窗控制代碼。

 Private Const WM_PASTE = &H302&
 
 Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" _
         (ByVal hwnd As Long, ByVal wMsg As Long, _
          ByVal wParam As Long, lParam As Any) As Long

透過將 Ploticus 建立的圖片載入到剪貼簿中,然後指示富文字框將其貼上,插入圖片。

 Public Sub InsertPicture(rsFile As String, rtfText As RichTextBox)
 
   Clipboard.Clear
   Clipboard.SetData LoadPicture(rsFile)
   
   SendMessage rtfText.hwnd, WM_PASTE, 0, 0&
 
 End Sub

Ploticus 需要一個以行和列形式存放資料的檔案。此資料在我們的文件中表示為矩陣。我們必須將該矩陣轉換為字串,然後將其儲存到檔案。此函式執行字串轉換部分。

 Public Function JArrayToPlotData(rvResult As Variant) As String
   
   Debug.Assert TypeName(rvResult) = "JScriptTypeInfo"
   Dim oRow As Variant
   
   Dim lRow As Long
   For lRow = 0 To rvResult.length - 1
     Set oRow = CallByName(rvResult, lRow, VbGet)
     If TypeName(oRow) = "JScriptTypeInfo" Then
       JArrayToPlotData = JArrayToPlotData & xJRowToPlotData(oRow)
     Else
       JArrayToPlotData = JArrayToPlotData & " " & CallByName(rvResult, lRow, VbGet)
     End If
     If lRow < rvResult.length - 1 Then
       JArrayToPlotData = JArrayToPlotData & vbCrLf
     End If
   Next lRow
   JArrayToPlotData = JArrayToPlotData
   
 End Function
 Private Function xJRowToPlotData(rvResult As Variant) As String
 
   Dim oRow As Variant    
   Dim lCol As Long
   For lCol = 0 To rvResult.length - 1
     xJRowToPlotData = xJRowToPlotData & CallByName(rvResult, lCol, VbGet)
     If lCol < rvResult.length - 1 Then
       xJRowToPlotData = xJRowToPlotData & " "
     End If
   Next lCol
   
 End Function

JArrayToPlotData 與將 JScript 矩陣轉換為 VB 陣列的函式非常相似。重寫 JArrayToPlotData,使其使用該函式,而不是單獨執行從 JScript 的轉換。這是否改進了程式?提示:要維護的程式碼量減少了,還是程式碼重複(或近似重複)消除了?

modShellWait

[編輯 | 編輯原始碼]

JArithmetic 使用外部程式來完成一些比較複雜的工作。這些程式通常是命令列程式,所以我們需要一些包裝函式來讓程式的其餘部分認為它們是內建函式。

這段程式碼的原始版本在 http://www.msfn.org/board/lofiversion/index.php/t35615.html 上找到。

 Option Explicit
 
 
 Private Const SYNCHRONIZE = &H100000
 Public Const WAIT_OBJECT_0 = &H0
 
 Private Declare Function OpenProcess Lib "Kernel32.dll" _
         (ByVal dwDA As Long, ByVal bIH As Integer, _
          ByVal dwPID As Long) As Long
 
 
 Private Declare Function WaitForSingleObject Lib "kernel32" _
         (ByVal hHandle As Long, _
          ByVal dwMilliseconds As Long) As Long
 

此函式用於執行外部程式。它最多等待十秒鐘(10000 毫秒)。不幸的是,它不知道如何處理故障。這留給學生作為練習。

 Public Sub RunCmd(CmdPath As String)
  
   On Error GoTo Err_RunCmd
   
   If ShellWait(CmdPath, 10000) Then
     Stop ' error ' @TODO: do something useful here
   End If
   
   Exit Sub
   
 Err_RunCmd:
   Stop ' do something useful here
 End Sub
 

這是執行命令列並等待呼叫中指定的時間的低階函式。請參閱 外部程序

 Function ShellWait(CommandLine As String, _
                    TimeOut As Long) As Boolean
    
   Dim ProcessID As Long
   Dim hProcess As Long

Shell 命令返回一個程序 ID,它實際上沒有用,因為幾乎所有 Windows API 函式都使用程序控制代碼,但這不是問題,因為OpenProcess API 函式可以在兩者之間進行轉換。

   ProcessID = Shell(CommandLine)

如果程序 ID 非零,則程序已建立並啟動,因此我們可以直接等待它完成。為了等待,我們使用一個名為WaitForSingleObject 的 API 函式,該函式接收程序控制代碼和以毫秒為單位的超時時間。此函式只是等待程序終止或超時,它返回一個狀態程式碼來指示哪一個。

   If ProcessID Then

非零(True),因此 Shell 成功。現在獲取 PID 的程序控制代碼(Wait 接收控制代碼)。

     hProcess = OpenProcess(SYNCHRONIZE, False, ProcessID)
     If hProcess Then
       Dim lResult As Long
       lResult = WaitForSingleObject(hProcess, TimeOut)
       If lResult = WAIT_OBJECT_0 Then
         ShellWait = True
       Else
         ShellWait = False
       End If
     Else

未能獲取程序控制代碼。如果程序非常快地終止,或者它可能根本沒有執行,即使 Windows 啟動了一個程序,也會發生這種情況。向呼叫者返回false 以表示它失敗。

       ShellWait = False
     End If
   Else

如果程序 ID 為零,則 Shell 失敗。

     ShellWait = False
   End If
 End Function

啟動和專案檔案

[編輯 | 編輯原始碼]

modMain.bas

[編輯 | 編輯原始碼]

主模組包含啟動應用程式並使其進入初始狀態的所有程式碼。在本例中,我們還使用它作為宣告各種常量和全域性函式的地方。


 Attribute VB_Name = "modMain"
   
 Option Explicit

常量gsFILE_FILTERgsSAVE_AS_FILTER 在我們顯示“通用對話方塊”以開啟和儲存檔案時使用。它們告訴對話方塊在組合框中放置哪些檔案掩碼。請注意,我們已經為rtftxt 和所有檔案提供了支援。這意味著使用者可以開啟純文字檔案並將其儲存為rtf。當然,使用者也可以嘗試開啟不是文字或富文字的檔案,我們必須應對這種情況。這些宣告是公有的,因為它們實際上是在不同的程式碼模組中使用的。

 Public Const gsFILE_FILTER As String = "Rich text Format(*.rtf)|*.rtf|Text (*.txt)|*.txt|All Files (*.*)|*.*"
 Public Const gsSAVE_AS_FILTER As String = "Rich Text Format(*.rtf)|*.rtf|Text (*.txt)"

cEvalDoc 類的例項完成了繁重的工作。我們永遠不需要超過一個此類的例項,因此我們在這裡將其宣告為全域性變數,並在main 子例程中例項化它。

 Public goEvalDoc As cEvalDoc

處理器需要跟蹤文字中進行替換的位置以及實際的替換內容。tResult 使用者定義型別 (UDT) 用於儲存必要的資訊。請注意,Value 成員被宣告為Variant,因為表示式的結果可以是任何東西,而不僅僅是數字。

 Public Type tResult
   Value As Variant
   Start As Long
   length As Long
   ResultType As eResultType
 End Type
 

結果型別是一種列舉型別。這使得程式碼更易於閱讀。請注意,沒有成員被明確分配值,這是因為我們不關心這些值是什麼,只要它們是不同的即可。

 Public Enum eResultType
   SourceError
   SourceNoError
   Result
 End Enum

我們需要一個地方來儲存我們將替換回文字中的值,因為在我們完成整個文件的處理之前,我們不能將它們放進去。這樣做的原因是結果的長度可能與它替換的文字的長度不同。我們維護結果計數,以便我們可以讓gaResults 陣列擴充套件。當我們再次評估文件時,我們首先簡單地將glresultsCount 重置為零;這樣可以節省時間,因為不需要每次都重新分配結果陣列。

 Public gaResults() As tResult
 Public glResultsCount As Long

FileSystemObject 比用於讀取檔案的舊的內建 VB 函式更易於使用。要使用它,您必須安裝指令碼執行時庫

 Public goFSO As FileSystemObject

此程式中使用的某些函式需要建立臨時檔案。為了確保避免此程式的不同例項之間以及與其他程式之間的衝突,我們在系統臨時資料夾中建立了一個新的臨時資料夾,並將它的名稱儲存在此變數中。

 Public gsTempFolder As String

main 例程初始化應用程式。它建立臨時工作區,建立文件評估器的例項,顯示主窗體並分配結果陣列。

 Sub Main()
   Set goFSO = New FileSystemObject
   
   On Error GoTo ErrorHandler
   
  
   ' Create temporary area for this instance of JArithmetic.
   gsTempFolder = Environ$("TEMP") & "\" & App.EXEName
   If Not goFSO.FolderExists(gsTempFolder) Then
     goFSO.CreateFolder gsTempFolder
   End If
   gsTempFolder = gsTempFolder & "\" & Format$(Now, "yymmddhhmmss")
   If Not goFSO.FolderExists(gsTempFolder) Then
     goFSO.CreateFolder gsTempFolder
   End If
       
   Set goEvalDoc = New cEvalDoc
   
   Load fMainform
   
   fMainform.Show
   
   ReDim gaResults(0 To 100)
   
   Exit Sub
 ErrorHandler:
   Debug.Assert False
   MsgBox Err.Number & ", " & Err.Description & ". Command line = <" & Command$ & ">", vbOKOnly, "Arithmetic"
     Resume
 End Sub

應用程式可以透過多種不同的方式關閉,但每個方式都應以呼叫此函式結束,以便所有文件都正確關閉。到目前為止,還沒有自動儲存的實現,也沒有提示使用者儲存已更改的文件。這是新增此類程式碼的地方。

 Public Sub ExitApplication()
   Dim oForm As Form
   For Each oForm In Forms
     Unload oForm
   Next oForm    
 End Sub
 
 

此例程建立一個唯一的數字。它由需要建立臨時檔案的函式使用。結果始終是一個整數,但我們使用Double 而不是Long,以便我們“永遠”不會用完數字(在這個應用程式中,這種改進幾乎沒有必要)。

 Public Function NextSerial() As Double
   Static nSerial As Double
   nSerial = nSerial + 1
   NextSerial = nSerial
 End Function

prjJarithmetic.vbp

[編輯 | 編輯原始碼]

這是用於將所有這些內容組合在一起的 Visual Basic 專案檔案 (VBP)。原則上,應該能夠自動下載和編譯它。請注意,您在此 VBP 中找到的任何絕對路徑在複製到您的計算機後可能無法指向任何內容。

 Type=Exe
 Reference=*\G{00020430-0000-0000-C000-000000000046}#2.0#0#..\..\..\..\..\..\..\WINNT\System32\stdole2.tlb#OLE Automation
 Reference=*\G{420B2830-E718-11CF-893D-00A0C9054228}#1.0#0#..\..\..\..\..\..\..\WINNT\System32\scrrun.dll#Microsoft Scripting Runtime
 Reference=*\G{0E59F1D2-1FBE-11D0-8FF2-00A0D10038BC}#1.0#0#..\..\..\..\..\..\..\ProgramFiles\Hikari\msscript.ocx#Microsoft Script Control 1.0
 Object={F9043C88-F6F2-101A-A3C9-08002B2F49FB}#1.2#0; ComDlg32.OCX
 Object={831FDD16-0C5C-11D2-A9FC-0000F8754DA1}#2.0#0; MSCOMCTL.OCX
 Object={38911DA0-E448-11D0-84A3-00DD01104159}#1.1#0; COMCT332.OCX
 Object={3B7C8863-D78F-101B-B9B5-04021C009402}#1.2#0; RICHTX32.OCX
 Class=cEvalDoc; cEvalDoc.cls
 Form=Fmainform.frm
 Form=frmDocument.frm
 Class=cFunctions; cFunctions.cls
 Module=modMain; modMain.bas
 Class=cPlot; cPlot.cls
 Module=modPlot; modPlot.bas
 Module=modShellWait; modShellWait.bas
 Module=modJavaScript; modJavaScript.bas
 RelatedDoc=..\doc\jarithmetic.htm
 RelatedDoc=..\debug\index.html
 Module=modGPL; ..\..\common\gpl\modGPL.bas
 IconForm="fMainform"
 Startup="Sub Main"
 HelpFile=""
 Title="prjArithmetic"
 ExeName32="Arithmetic.exe"
 Path32="debug"
 Name="prjJArithmetic"
 HelpContextID="0"
 Description="Arithmetic document processor"
 CompatibleMode="0"
 MajorVer=1
 MinorVer=0
 RevisionVer=2
 AutoIncrementVer=1
 ServerSupportFiles=0
 VersionCompanyName="Kevin Whitefoot"
 VersionFileDescription="Embedded JScript document processor."
 VersionLegalCopyright="Copyright Kevin Whitefoot, 2005"
 VersionProductName="JArithmetic"
 CompilationType=0
 OptimizationType=0
 FavorPentiumPro(tm)=0
 CodeViewDebugInfo=0
 NoAliasing=0
 BoundsCheck=0
 OverflowCheck=0
 FlPointCheck=0
 FDIVCheck=0
 UnroundedFP=0
 StartMode=0
 Unattended=0
 Retained=0
 ThreadPerObject=0
 MaxNumberOfThreads=1
 DebugStartupOption=0
上一篇:JArithmetic Round Two 目錄 下一篇:語言
華夏公益教科書