Posts Tagged With: Excel

[VBA] 轉換 Json 字串到 Excel 表格


前篇:[VBA] 抓取 Json 檔案轉入 Excel

前篇的 Json 字串解析,是從 GitHub 上抓的,別人的程式碼用一陣子總覺得全身不舒服,前陣子要處理 OCPP 的 Json 字串時,就自己寫一個。

呼叫範例:

Sub CommandOpenFile(ByVal strCommand)
   Set tSheets = ThisWorkbook.Sheets(strCommand)
   strFileName = ThisWorkbook.Path & "\ocpp.log"
   strBody = myGetStringFile(strFileName)
   arrBody = Split(strBody, vbLf)
   ubl = UBound(arrBody)
   
   iRow = 1
   For ibl = 0 To ubl
      nLoc = InStr(arrBody(ibl), StringFormat(",{0}{1}{0},{", """", strCommand))
      If nLoc > 0 Then
         sLoc = InStr(nLoc + 1, arrBody(ibl), "{")
         eLoc = Len(arrBody(ibl)) - 1
         strValue = Mid(arrBody(ibl), sLoc, eLoc - sLoc + 1)
         Set pJson = ParseJson(strValue)
         
         If iRow = 1 Then
            uCol = pJson.Count
            arrCol = pJson.Keys
            For iCol = 1 To uCol
               tSheets.Cells(iRow, 1 + iCol) = arrCol(iCol - 1)
            Next
            iRow = iRow + 1
         End If
         
         For iCol = 1 To uCol
            tSheets.Cells(iRow, 1 + iCol) = pJson(arrCol(iCol - 1))
         Next
         
         iRow = iRow + 1
         Set pJson = Nothing
      End If
   Next
End Sub

我把我的 Json 解析獨立放在一個模組內,可參考下方網址

modJsonTool.bas: https://1drv.ms/u/s!AqdV_QuSGVQUy2eZgQaLJJb2BNbz?e=7xFSKW

這段的目的是我的 OCPP.log 是一個通訊過程的紀錄檔,裡面有多段的 Json 字串,大部分的指令所形成的 Json 都比較簡單,就用一個通用的副程式解析,把相同指令的 Json 解析都扔到相同的頁籤。

OCPP 內比較複雜的類似 MetaValues 指令,因為回傳的是樹狀結構,不是列狀 (Row) ,所以要挑自己要留的欄位轉到 Excel 內,就單獨另外寫一個 Sub 處理。

Json 解析我參照前篇的架構,屬於樹狀的資料 (以 {…} 包括) 用 Dictionary 去處理,屬於陣列的資料 (以 […] 包括) ,用 Collection 處理,解析的程式碼就用遞迴處理,所以主要的只有一個函數:

Function ParseJson(ByVal strJson As String, Optional ByVal rootDelimiter As Variant)
   JsonDelimiterInit
   ubd = UBound(jsonDelimiter)
   ReDim arrIndex(ubd)
   
   ReDim colTemp(ubd) As New Collection
   fmtKeepName = "(($VbaJson${0}${1}${2}$))"
   fmtKeepLeft = 11
   
   rtnValue = Empty
   strJson = TrimEx(strJson)
   lenJson = Len(strJson)
   pbd = -1
   startLoc = 0
   
   Do
      nowIndex = lenJson + 1
      nbd = -1
      For ibd = 0 To ubd
         arrIndex(ibd) = InStr(startLoc + 1, strJson, jsonDelimiter(ibd)(0))
         If arrIndex(ibd) > 0 And nowIndex > arrIndex(ibd) Then
            nowIndex = arrIndex(ibd)
            nbd = ibd
         End If
      Next
      
      If nbd = -1 Then
         ' 沒有括號裡面開始解析內容
         Exit Do
      Else
         ' 字串的引號裡面會不會有括號?
         startLoc = arrIndex(nbd)
         endIndex = FoundBlockEndIndex(strJson, jsonDelimiter(nbd), startLoc)
         
         If endIndex > 0 Then
            pbd = nbd
            colIndex = colTemp(nbd).Count + 1
            Select Case jsonDelimiter(nbd)(0)
            Case "{"
               strObjectName = StringFormat(fmtKeepName, "Object", nbd, colIndex)
            Case "["
               strObjectName = StringFormat(fmtKeepName, "Array", nbd, colIndex)
            Case Else
               Err.Raise &H80000001, , "Json 字串解析格式錯誤"
            End Select
            
            subJson = Mid(strJson, startLoc + 1, endIndex - startLoc - 1)
            strJson = Replace(strJson, StringFormat("{1}{0}{2}", subJson, jsonDelimiter(nbd)(0), jsonDelimiter(nbd)(1)), strObjectName)
            
            colTemp(nbd).Add ParseJson(subJson, jsonDelimiter(nbd))
         Else
            Err.Raise &H80000002, , "Json 字串缺括號 " & jsonDelimiter(nbd)(1) & ",字串內容為 " & strJson
            Exit Do
         End If
      End If
   Loop
   
   If lenJson > 0 Then
      ' 回傳物件
      If IsMissing(rootDelimiter) Then
         Set rtnValue = colTemp(pbd)(colTemp(pbd).Count)
      Else
         nowDelimiter = rootDelimiter(0)
         
         Select Case nowDelimiter
         Case "{"
            ' Set rtnValue = New Dictionary ' 需在 選單 工具 設定引用項目 引用 Microsoft Scripting Runtime ,使用其中 Dictionary 類別
            Set rtnValue = CreateScriptingDictionary()
         Case "["
            Set rtnValue = New Collection
         End Select
         
         ' 解析內容
         arrItems = Split(strJson, ",")
         ubi = UBound(arrItems)
         For ibi = 0 To ubi
            arrItems(ibi) = TrimEx(arrItems(ibi))
            
            Select Case nowDelimiter
            Case "{"
               itemRow = Split(arrItems(ibi), ":")
               ubr = UBound(itemRow)
               If ubr > 1 Then
                  For ibr = 2 To ubr
                     itemRow(1) = itemRow(1) & ":" & itemRow(ibr)
                  Next
               End If
               
               For ibr = 0 To 1
                  itemRow(ibr) = TrimEx(itemRow(ibr))
               Next
               itemKey = TrimEx(itemRow(0), TES_SepcialAdd, , """'")
               strItem = itemRow(1)
            Case "["
               itemKey = CStr(ibi + 2)
               strItem = arrItems(ibi)
            End Select
         
            If InStr(strItem, Left(fmtKeepName, fmtKeepLeft)) >= 1 Then
               arrRow = Split(strItem, "$")
               Set itemValue = colTemp(CLng(arrRow(3)))(CLng(arrRow(4)))
            Else
               strValue = TrimEx(strItem, TES_SepcialAdd, , """'")
               If Len(strValue) < Len(strItem) Then
                  itemValue = strValue
               Else
                  itemValue = CVariant(strValue)
               End If
            End If
            
            Select Case nowDelimiter
            Case "{"
               rtnValue.Add itemKey, itemValue
            Case "["
               rtnValue.Add itemValue, itemKey
            End Select
         Next
      End If
   End If
   
   Set ParseJson = rtnValue
   
   Set rtnValue = Nothing
   For ibd = 0 To ubd
      Set colTemp(ibd) = Nothing
   Next
End Function

每一層物件對應到 Json 每一層。

由於用遞迴函數,如果碰上我還沒遭遇過的特殊字串,可能會卡在裡面,建議在這個函數裡面加 DoEvents ,若遭遇到無窮迴圈時,可以透過除錯模式跳出,加入特殊字串處理。

GitHub 範例要求要在 VBA 專案參照物件 (提早連結),好處是執行效能比較快,缺點是不熟的人不會處理,特別是 VBA 的安全信任一直調整,所以我改成延後連結的方式處理:

Function CreateScriptingDictionary() As Object
   Set CreateScriptingDictionary = CreateObject("Scripting.Dictionary")
End Function

目前遭遇過特殊字元已經納入處理的有這個陣列:

If Not IsArray(jsonEscape) Then jsonEscape = Array(Split("\""|\\|\/|\b|\f|\n|\r|\t", "|"), Array("""", "\", "/", Chr(8), Chr(12), Chr(13), Chr(10), Chr(9)))

我自己之後會持續使用自己程式碼,擴大支援範圍。

Categories: 工作點滴, 技術分享 | 標籤: | 發表留言

[VBA] 抓取 Json 檔案轉入 Excel


周一同事說,有個政府網頁的數據因為直接 Show 圖上,當滑鼠停在上面才會跳出數據,所以必須一筆一筆透過移動滑鼠來讀取並記錄。我回覆說,可以把網頁提供給資訊部,看看能不能協助存取。

昨天我收到網址,並由同事告訴我她要抓甚麼數據,確認後,開啟 Edge 的開發者模式,馬上就確認是透過 AJAX 讀取資料更新畫面。

用 Edge 測試了幾個網址,資料內容是 Json 格式,都可以直接撈出來,大概沒啥阻擋,打算用過去寫好 .Net framework 的 WebClient 去抓,一測試,WebClient 連線能建立成功,但是無法取得資料流,檔案大小都是 0 到逾時。


註:寫部落格時才想到,可能是 https 加密問題… 參考先前紀錄:

[CLR] .Net framework 2.0 WebClient 連接 https


剛好我以前是用 VBScript 寫過 AJAX ,有現成程式碼拷貝到 Excel 去測試,在 Excel 測 xmlhttp 抓資料就正常,所以可以推論這個網站有針對 http protocol 的 Header 做偵測與阻擋。

透過 xmlhttp 抓 AJAX 取得 Json 資料後,問題在 Json 解碼,這個在 GitHub 有現成程式碼可以引用:

GitHub – VBA-tools/VBA-JSON: JSON conversion and parsing for VBA

我直接引用他寫好的 JsonConverter ,見下圖的專案視窗。

上面網址的最下方說明,要同時引用他另一個專案:

GitHub – VBA-tools/VBA-Dictionary: Drop-in replacement for Scripting.Dictionary on Mac

我在 Excel 365 跑這段程式碼的時候,會因為他自建類別 Dictionary ,與 Scripting.Dictionary 不相容,所以我去修改他的類別,把原始 Scripting.Dictionary 透過 .This 丟出來,就可以順利的完成執行。

主要程式碼如下圖:

VBA 透過 XmlHttp 抓取 AJAX 資料並分析匯入

順利把 Json 共 24 欄 12,313 列資料匯入 Excel 。

已匯入的原始資料

Categories: 工作點滴, 技術分享 | 標籤: | 1 則迴響

[VBA] 替巨集簽署數位憑證方便存取


最近在論壇看到一堆問 Excel 巨集無法被信任問題。

對使用者自己來說,巨集可能很好用,所以希望隨心所欲的使用,對作業系統的安全性來說,巨集就可能潛藏外部危害,中毒就怪微軟。所以微軟一直在調整巨集安全性,降低中毒潛在風險,提高作業系統安全,但對使用者來說,就是越來越難用。

很多範例是走信任路徑,我個人是反對走信任路徑,比如說公司伺服器的路徑被同仁信任了,如果這個路徑被塞入惡意巨集時,因為路徑被信任,就會無法防護。

我個人是偏向開發人員應該替巨集簽署數位簽名。

這個議題在過去本網誌也多所提及,不過隨著 Office 版本更新,舊版本可能對使用者要做觀念轉換,本篇用 Office 2019 的 Excel 為範例說明。

數位憑證來源很多,可以用買的,可以用自然人憑證 (請搜尋本網誌先前說明),當然也可以用免費的,本篇是以 Office 免費的憑證為範例,所以這是 Office 預設功能,不是為了解決問題,創造更多問題。

Office 2019 x64 的免費憑證產生器在下列路徑,如果不確定 Office 版本及位元版本,建議用檔案總管搜尋「selfcert.exe」

selfcert.exe 的路徑

接下來依步驟產生,第一步就是建立憑證名稱,最好取個有識別性的名稱,範例就隨便取了。

建立憑證名稱

一個步驟就可以建立憑證。

憑證建立完成

開啟一個未被信任的巨集檔案,這是在公司內部教育訓練的一個範例檔,使用 FSO 做檔案清單,包含子目錄搜尋,方便同仁建立檔案對照表用。

未被信任的巨集檔案

從 IDE 介面選單 > 工具 > 數位簽名

選單位置

預設是沒簽名的,簽名過的檔案會顯示已簽名的內容。

選擇數位憑證進行簽署

選擇剛剛產生的數位憑證,如果憑證超過一個,點選其他選擇可展開來選其他憑證。

選擇數位憑證進行簽署

簽署完的檔案記得存檔。

顯示已簽署的數位憑證

通常開發過程已經信任了開發中的巨集檔案,所以若要測試憑證是否正確被信任,需要先清除信任,這個功能會清除所有被信任的檔案,所以建議另外準備測試電腦測試,不要在平常工作電腦測試,否則所有曾經被信任過的檔案都需要重新信任。

移除所有信任的巨集檔案

透過 [控制台] > [網際網路選項] > [內容] > [憑證] 可以管理憑證。

如果找不到控制台,先開啟檔案總管,直接在網址列輸入 [控制台] 即可。

憑證管理

Windows 11 的憑證管理畫面如下。

Windows 11 叫出網際網路選項後就能進入憑證管理。

這邊可以看到先前建立的憑證出現在這。

自建免費憑證會出現在個人區

舊版的 Office 可以在開啟帶有數位憑證的巨集將該憑證直接進行信任,新版本的 Office 移除該功能,所以必須把數位憑證匯出後,散佈給使用者匯入,使用者匯入後,凡是經由該憑證簽署的巨集檔案,就直接可以使用,不用再經過確認。

開始匯出憑證

依畫面逐步操作。

Office 的免費憑證沒有私密金鑰

依預設值匯出。

依預設值選擇匯出格式

選擇匯出路徑。

選擇路徑

完成憑證匯出。

完成憑證匯出

匯出成功。

匯出成功

憑證匯出後,就可以散佈給使用者匯入,但開發電腦已有憑證,必須先刪除才能測試使用者端的情境,所以在這邊先刪除,開發者千萬注意,開發電腦請不要刪除憑證,這個憑證匯出後再匯入,不能再被來用來程式碼簽署,所以千萬不要刪除,除非只是打算測試,刪除後必須重新產生新的憑證進行簽名,即使名稱一樣,因為實際隨機金鑰不同,仍然會被視為新憑證,需要重新散佈。

刪除憑證從上面憑證管理畫面進入進行刪除。

開發電腦千萬不能刪除憑證

刪除憑證後,數量比上面的圖片少一項。

已刪除剛建立的免費數位憑證

接下來測試使用者的匯入,一般被散佈的對象直接從這個步驟執行。

從上面憑證管理畫面,選擇受信任的發行者後選擇匯入。

選擇受信任的發行者

憑證匯入逐步執行。

匯入憑證

使用者將收到的憑證檔案存檔後,依路徑選擇進行匯入。

從路徑匯入

確認匯入的位置是 [受信任的發行者]

匯入受信任的發行者

即將完成憑證匯入。

即將完成憑證匯入

憑證匯入成功。

憑證匯入成功

可看到憑證正確匯入,其他的憑證是相關軟體的原廠憑證。

憑證匯入成功

直接開啟剛剛簽署好數位憑證的巨集檔案,可以看到直接進入,無須再信任。

已被信任的巨集檔案

檢查 Excel 信任中心,可看到數位憑證已被信任。這邊需要特別注意,因為是信任憑證,所以是對電腦所有依據憑證授權的都生效力,比如說 Word、PowerPoint 的巨集若是簽署相同的數位憑證,也會直接被信任。

已被加入受信任的發行者

重新被匯入的數位憑證不能被用來簽署程式碼,因此不用擔心散佈憑證會被其他人再利用。

先前產生的免費數位憑證刪除後再匯入就不能再被用來簽署

分別測試不同的位置,看看是否可以被信任。這是透過網路芳鄰 (SMB) 上的路徑 (UNC) 進行測試,可直接被開啟執行。

透過 Nas 存取巨集檔案

透過 OneDrive 同步路徑進行測試,可直接被開啟執行。

OneDrive 同步路徑

透過 OneDrive 網頁進行測試,可直接被開啟執行。

OneDrive 網頁

我 Google 雲端沒裝同步軟體,所以可以當作是一般網頁下載進行測試。

Google 雲端硬碟

一般網頁下載檔案仍受限 Windows 內建功能會被阻擋,可以點選啟用編輯。

預設阻擋來自網路的檔案

選擇解除封鎖後,亦可直接執行巨集檔案。

從檔案總管選擇解除封鎖

我個人比較偏好從數位憑證簽署來讓巨集檔案安全被散佈,若有簽署數位憑證的檔案,必須由開發人員才能存檔,可以確保檔案散佈後不會被修改。

Categories: Office, 技術分享 | 標籤: | 2 則迴響

[VBA] Excel 安全性調整後,跨檔案所需要的變更


我大概是 Excel 4.0 開始用巨集的,到了 Excel 5.0 (Office 4.2 / 4.3) 轉成 VBA 以後,我就比較經常用,那時 Word 6.0a 的巨集還不是 VBA ,而是 WordBasic ,到 Office 95 才統一為 VBA 。

1996/07 碩畢當兵前要交接研究資料給學弟時,寫了一篇做內部教育訓練。

MS Office 自動化巨集 http://www.hisdt.com/TLCheng/Basic/Office/officevb.htm

這篇是在 1999/02/03 網頁化上網分享,念碩士的時候還是 Excel 5.0 + Word 6.0a ,做研究計畫、論文會大量用到 Excel 整理數據,例如我的碩士論文是做人工智慧,用 VB3 寫類神經網路 (BPN) 跟自己修改的模型 (有點類似現在的 RNN) ,在下班後,丟到一堆助理的電腦上去跑,借同學的電腦夜間跑,白天一早來暫停目前類神經網路學習進度儲存後,備份計算結果各台電腦還給大家用,白天我則利用 Excel VBA 整理數據出表格、圖形自動貼到 Word 印出後,跟指導教授討論。

工作後寫 Excel VBA 的機會變少,有大量資料要分析的時候,我才會拎出來用。

Office 2007 以後,基於安全考量,有 VBA 的檔案要存成 .xlsm ,我也開始養成慣例,把 VBA 單獨寫成一個檔案,資料檔跟輸出檔分在不同的檔案,方便安全管理,大概是前三年左右吧,不知道 Excel 哪個安全性更新起,存成 .xlsm VBA 裡面的函數就不能跨黨案使用,必須存成 .xlam 做成增益集。

我以前個人基於安全考量,希望 Excel 開起來是乾乾淨淨的,所以一直沒做成增益集來用,但 Excel 內有些函數功能真的不足,還不如呼叫 VBA 內建函數,所以我就把我常用的 VB 內建函數跟自己常用的函數建成一個 VbaTools.xlam 來引用。

一個新的 .xlsm 開發完成後,可以另存新檔為 .xlam ,這時 Excel 會自動導向存放增益集的路徑,我習慣放在我個人區,移動到其他台電腦用複製貼上即可,但是預設不會啟用,要自己勾選起來。

個人區域設路徑為 %AppData%\Microsoft\AddIns

開啟 Excel 後,左上角 選單 > 檔案 > 選項 > 增益集

自訂增益集

找到自己的增益集後,下方 [管理] 選擇 [Excel 增益集] ,選擇 [執行]。

如果沒有正常出現自己的增益集,可從右側 [瀏覽] 按鈕下去尋找。

勾選自己的工具

勾選後按下確定即可。

接下來就可以在任意檔案中呼叫增益集內的函數。

呼叫增益集內的函數

比如說 Excel 內的 REPLACE 函數,一次呼叫只能替換第一格發現的字串,但是 VBA 內建函數可以一次替換所有可發現的字串,一下子就方便不少。

所以我在前年寫了個兩小時教育訓練排在去年執行,主要是讓同事知道 Excel 還能做甚麼,針對大量重複的行政作業或流程,有需要找資訊部開發。

Excel VBA 內部教育訓練簡報檔

前兩個月執行老闆叫我進行的分析,因為資料量滿大的,所以我在其他 .xlsm 的巨集打算呼叫 VbaTools.xlam 時,發現居然不能直接呼叫。

開啟舊的兩個 .xlsm 也不能互相呼叫了。

我過去的慣例是會把共用的函數 .xlsm 另外開一個檔,專用的函數 .xlsm 自己存一個檔,自先前改用 VbaTools.xlam 後,就把共用的函數轉移進去,而且我記得先前還能用。

兩年前在寫這篇時:

[Excel]白做工了,規劃求解與 VBA https://tlcheng.wordpress.com/2019/05/12/excel%e7%99%bd%e5%81%9a%e5%b7%a5%e4%ba%86%ef%bc%8c%e8%a6%8f%e5%8a%83%e6%b1%82%e8%a7%a3%e8%88%87-vba/

用 測試.xlsm 還能呼叫 規劃求解 的 VBA 函數,連程式碼截圖都在,我也不知道為啥就不行了。

經搜尋新的方案是要加入參照才能呼叫其他 .xlsm / .xlam 的函數:

參照 VbaTools.xlam

參照完舊可以在瀏覽物件瀏覽程式庫時,看到 VbaTools.xlam 函數。

程式庫瀏覽

這個月還沒想到要寫甚麼,怕忘記 Excel 的 VBA 變化,寫下來提醒自己。

Categories: Office, 工作點滴, 技術分享 | 標籤: | 1 則迴響

[Excel]白做工了,規劃求解與 VBA


很久沒有做分析了,都在 code …

上個月老闆交代我做個分析,想來想去,想要最佳化還是用數學規劃,線性規劃是個快速解決問題的方法,只要問題不複雜,直接用線性規劃求解就可以了。

問題是離開學校後,沒有好的軟體可以算線性規劃,想起來還有 Excel 的規劃求解可以用,就翻開 Excel 玩。

話說 2005 年在新加坡 MVP亞洲年會中,跟當時負責中文化的洪士吉老師與這個巨集的澳洲原作者聊過,當時聽洪士吉老師有原始碼跟密碼,我那時自己也用 VBScript 寫一個線性規劃的 ASP 線上網頁,所以主要討論的是卡曼卡法的加速解跟 Lindo,但是我論文不是搞這方向,離開學校後就甩到邊邊角角了。

我平常為了加速 Excel 開啟,所有的增益集都關閉,Office 2003 年代短暫的玩過規劃求解,但主要還是玩 Lindo ,開啟規劃求解增益集後,發覺設定方式完全不一樣了,看了一下線上手冊,上面寫 Excel 2010 後改成這樣,只好重新學習。

首先,先把自己寫的線性規劃翻出來,用相同的模型建構 Excel ,自己會比較容易上手:

線性規劃命題範例

按下 [進行最佳化分析] 後,可得到計算結果:

線性規劃計算結果

這個命題是在做教材範例,假定一個有閘門的滯洪池或水庫,在已知預報進水量下,受到滯洪池蓄洪量、放水路放水等限制,目標是穩定洩水減低洪害,各限制式的依據參閱上面網頁擷圖右側文字說明。

下面是我喜歡的規劃求解表格樣式,規劃求解仍然使用 Excel 公式,所以並沒有需要排得整整齊齊,但是為了人類容易閱讀,還是排成表格方式比較不會錯,詳細個位置說明,放到最後,先看操作步驟:

在 Sheet 上建立規劃求解命題

從 選單 點選 [資料] > [規劃求解] ,逐項設定:

規劃求解設定

規劃求解計算約 1 秒內可完成:

規劃求解完成

計算結果與設定對話盒連結註記說明:

規劃求解計算完成
  1. 圖上數字對應到 [規劃求解參數] 對話盒的數字,並不對應到這邊說明的項次。
  2. 變數名我選擇放在第一列,C1 ~ J1 ,這種架構大家都容易看得懂。
  3. 變數計算結果我選擇放在第二列,C2 ~ J2,預設可以不輸入,參見前面建立命題的圖,不輸入,就代表起始解為 {0} 集合。
  4. 目標函數的係數我放在 C3 ~ J3,也就是 MIN C。
  5. 限制式左側的係數我放在 C4 ~ J19 ,不等號我放在 M4 ~ M19,常數項我放在 N4 ~ N19。
  6. 最佳化可以在 [規劃求解參數] 輸入 Excel 公式,但這樣會造成每次設定的複雜化,比如說目標函數一變,或是限制式調整時, [規劃求解參數] 的設定就要跟著變,所以為了方便起見,建議像我這樣做,在 K3 輸入陣列公式,讓係數乘變數,就可以降低 [規劃求解參數] 的設定問題,K3 正確設定完成後,複製到 K4 ~ K19 ,讓限制式的係數一樣可以乘變數。注意,陣列公式輸入完成後,要按下 {Shift}+{Ctrl}+{Enter} 三個鍵,才能正確變成陣列公式。
  7. 最後在 [規劃求解參數] 對話盒 [載入/儲存] 按鈕,點下 [儲存] ,選擇 N2 。

這個計算結果,可以透過 K/L/M 三欄檢查限制式是否成立,或是直接觀看 N 欄,限制式成立的邏輯公式就是使用 K/L/M 三欄。

在說明項目 6 中,使用了陣列公式,實務上會增加計算量,也就是係數 0 x 變數,但卻可以讓設定參數簡易許多,就如同本例。

做到這,可以發現 L 欄用不到,因為在 [規劃求解參數] 對話盒 [新增] 限制式時,都要手動輸入,我的目的是要解決老闆交付的任務,所以針對此命題範例建立了測試的 VBA 程式碼,細節到正式命題再修改:

用 VBA 建立規劃求解參數

為了方便起見,我在 VBA 內直接建立陣列公式,也就是說上面建立規劃求解的途中,K 欄不用輸入,其他 [規劃求解參數] 對話盒的輸入,都用 VBA 自動產生。

規劃求解不支援直接輸入不等式,所以用 GetRelationIndex 函數將不等式轉成索引值輸入。

如果不想自己打太多程式碼,可以用錄製的再進行修改,不過這張圖上的程式碼已經很精簡了,錄製會很亂,不如直接打,再改欄位值即可。

規劃求解的函數使用說明參考微軟官方網址:https://docs.microsoft.com/zh-tw/office/vba/excel/concepts/functions/using-the-solver-vba-functions

好了,終於開始建立正式命題,先把正式命題縮小為一天24小時的時資料進行連續量分析,建個小小的模型,測試看看是不是能正常運作:

規劃求解超過上限

不得不爆粗口…

「規劃求解限定為 200 個變數儲存格和 100 個限制式,加上變數儲存格的限值。」

100 個限制式?這個規格限制下,根本是玩具吧… 規劃求解,我只好繼續把你放生了…

[2019/06/05]

花了點時間,把網頁版 ASP 的 VBScript 轉成 VBNET 語法,順便把命題轉成類別,測試還算成功。

VBNET WinForm 輸入命題
VBNET WinForm 輸入命題

輸入完線性規劃命題後,點選 [限制式] 頁籤 下方工具列那個圖示 (測試程式沒有管 Icon 的美觀度) ,即可得到輸出結果,輸出改用 StringBuilder 產生 HTML 語法用 WebBrowser Control 輸出,這樣之後若有需要,可以在工具列放預覽列印的按鈕,呼叫 WebBrowser Control 的預覽列印。

VBNET WinForm 輸出結果
VBNET WinForm 輸出結果

不過裡面還有一些 Bug ,大型命題解不出來,這就造成除錯困難,大概還要加一頁來輸出線性規劃各步驟階段的計算表格,才好除錯。

Categories: Office, 工作點滴, 技術分享 | 標籤: | 8 則迴響

[WU] 10 月份 Windows Update 後,Excel ODBC/Oledb 異常


目前已知 10 月份 Windows Update 更新問題將會導致使用
Microsoft Jet 4.0 的 Excel Driver 會出問題,資料庫連線字串使用:
ODBC={Microsoft Excel Driver (*.xls)}
Provider=Microsoft.Jet.OLEDB.4.0;

無法正常使用。

由於目前 Windows Update 均為彙總套件更新,暫時無法鎖定問題發生在哪個 KB 。

緊急應變需先改用:
ODBC={Microsoft Excel Driver (*.xls, *.xlsx, *.xlsm, *.xlsb)}
Provider=Microsoft.ACE.OLEDB.12.0;

以上的資料庫連線字串版本。

如需同時安裝不同位元版本 x86/x64 的 driver ,需安裝不同 Office 版本才行,例如 Office 2016 x64 + Office 2013 x86 的 driver ,請依據個人電腦內使用 Office 版本搭配:
在微軟下載中心搜尋時,使用關鍵字為 Access Runtime :
2016: https://www.microsoft.com/zh-tw/download/details.aspx?id=50040
2013: https://www.microsoft.com/zh-TW/download/details.aspx?id=39358
2010: https://www.microsoft.com/zh-tw/download/details.aspx?id=10910
2007: https://www.microsoft.com/zh-tw/download/details.aspx?id=4438
註:2007 僅有 x86 版本。

一般來說,建議安裝最新版本的 Access Runtime ,也就是 2016/2013 ,但千萬記住,安裝前先確認自己電腦 Office 版本與位元版本。

在 VS2017 透過 ODBC 開啟 Excel 檔可以列舉資料庫結構,但不能顯示資料,如下圖:

VS2017 ODBC Excel 錯誤畫面

VS2017 ODBC Excel 錯誤畫面

軟體觸發錯誤事件的錯誤訊息為:
ERROR [HY000] [Microsoft][ODBC Excel Driver] 保留錯誤 (-5016);這個錯誤並無訊息。
ERROR [01000] [Microsoft][ODBC Excel Driver]一般警告 無法開啟登錄鍵 ‘Temporary (volatile) Jet DSN for process 0xeb4 Thread 0x23ac DBC 0x16bd6cf4 Excel’。
ERROR [IM006] [Microsoft][ODBC 驅動程式管理員] 驅動程式的 SQLSetConnectAttr 失敗

事件檢視器畫面如下:

事件檢視器 ODBC Excel 錯誤畫面

事件檢視器 ODBC Excel 錯誤畫面

目前微軟論壇討論可參考這篇:
https://social.msdn.microsoft.com/Forums/en-US/2feac7ff-3fbd-4d46-afdc-65341762f753/odbc-excel-driver-stopped-working-with-unexpected-error-from-external-database-driver-1?forum=sqldataaccess

由於是底層錯誤,所以不管是 ADO / ADO.NET / MFC 等,只要使用到這個資料庫連線字串的,都會掛。

Categories: 工作點滴, 更新與回報 | 標籤: | 5 則迴響

[WU]安全性更新後 Excel 很慢、且無法開啟檔案


這個月的安全性更新中, Office 更新完後,公司部分電腦的 Excel 發生異常,直接點檔案兩次,會跳出:

傳送命令給程式時發生錯誤

Windows 找不到檔案路徑\檔案名稱,請確定您輸入正確的名稱,然後再試一次

所以依照 DDE 的標準方式處置,包含移除非微軟的增益集,其中發現了奇怪的現象,如圖,會出現:

excel_addon_01

符號表工具列 增益集

 

在 增益集設定中 ,出現硬碟找不到實體路徑的 Office 2007 (office12) 的增益集 [Chinese Translation Addin]

excel_addon_02

增益集 [Chinese Translation Addin]

 

試過修復 Office 也沒用,開新帳號測試,結果新帳號正常。

研判應該是註冊資訊檔異常。

1. 關閉 Excel ,由於 Excel 異常,可能無法正常關閉,請務必使用工作管理員確認 Excel 關閉。
2. 將下列機碼的 Excel 改成 Excel.old

Excel 2010
HKEY_CURRENT_USER\Software\Microsoft\Office\14.0\Excel
HKEY_LOCAL_MACHINE\Software\Microsoft\Office\14.0\Excel

Excel 2013
HKEY_CURRENT_USER\Software\Microsoft\Office\15.0\Excel
HKEY_LOCAL_MACHINE\Software\Microsoft\Office\15.0\Excel

註:部分電腦 HKEY_LOCAL_MACHINE 下可能沒有 Excel ,若沒有可忽略

3. 從開始工具列捷徑 開啟 Excel ,頁籤應已無 標點符號增益集
4. Excel 選項 > 增益集 > COM增益集 > 把不存在的增益集刪除
5. 關閉 Excel ,對著一般 excel 檔案點兩下,看是否正常開啟。
6. 正常開啟後,可把機碼 Excel.old 刪除,收工。

目前發現會出問題無法正常開啟的是 Office 2010/2013 x86 。
Office 2007 有碰上加入這個工具列,但可能本來就是 Office 2007 的增益集,因此不會發生衝突,不用可停用。

我懷疑是微軟封裝更新時,忘了把機碼移除,導致大家死得很難看。

參考:https://support.microsoft.com/zh-tw/help/280504/how-to-troubleshoot-startup-problems-in-excel

註:這周小紅傘更新後,預設排程每 168 小時全機掃描一次,好像變成每 168 分鐘掃描一次,也造成 Excel 很慢,暫時先停用排程,建議大家也看看小紅傘事件紀錄。

Categories: 工作點滴, 更新與回報 | 標籤: | 1 則迴響

[VBA] LongLong 超長整數


話說,公司的專案支援 VBScript 外掛,這幾天在用 Excel 2013 的 VBA 7.1 寫 VBScript 。

因為裡面要用到位元旗標,我就很順手的打了:
2^i

想說,VBA 原始碼自動感知會幫我加空白,就繼續往下寫。

寫完以後用 Excel 測試,變成 #VALUE ,就用追蹤方式追查,才知道這行有問題。

手動改成 2 ^ i 就沒問題,好奇之下另外開個小專案測試:

誤使用到超長整數符號

誤使用到超長整數符號

用除錯模式看,型別居然是 LongLong 。

按下 F2 查物件類別庫,果然有 LongLong ,用 HEX(-1) 測試,是 8 bytes 的超長整數:

LongLong 為 8 bytes 超長整數

LongLong 為 8 bytes 超長整數

用 Google 搜尋 MSDN ,看起來是 x64 版本的 VBA 就加入了,應該是 Office 2010 x64 以後開始有此功能:
64 位元 Visual Basic for Applications 概觀:https://msdn.microsoft.com/zh-tw/library/office/Gg264421.aspx

順便回頭測了 Excel 2003 的 VBA 6.5:

Excel 2003 VBA 6.5 正確處理次方運算符號

Excel 2003 VBA 6.5 正確處理次方運算符號

舊版本的 VBA 就能自動把 2^2 轉成 2 ^ 2 。

從 LongLong 超長整數的 VBA 7.1 來看當初微軟在 2005 年宣告 VB6 不再支援的理由又消滅了一個,不是不能支援 64 bits 及加超長整數,而是不願意加,是不為也,非不能也。

Categories: 工作點滴, 技術分享 | 標籤: | 2 則迴響

Excel Variant Date 在 1900/3/1 以前的基準日與 VB6 不同


這個狀況更詳細的說明可以到 http://support.microsoft.com/ 搜尋 Excel 1900 。
簡單的說,最早的試算表 Lotus 123 把 1900 視為閏年,理論上來說,逢四閏、逢百不閏、逢四百閏,所以 1900 年為非閏年,但是在 Excel 會有 1900/2/29 的存在,當時的設計是為了與 Lotus 123 相容而存在,就用到現在,所以到了 Excel 2007 還是這樣…
Excel 有兩組日期系統,以 1900/1/0 為 基準或是 1904/1/1 為基準。下面是個簡表,Variant Date 為 OleAut32.dll 裡面定義的 Variant Date ,VB6/VBA/VBScript 的內建 Date 型態,VBNET 可用 ToOADate/FromOADate 轉換,核心值域是依據 IEEE 754 的 Double 來設計,所以加上 Double 的對照:
Double Variant Date Excel Date 1904 Date
0 1899/12/30 1900/1/0 1904/1/1
1 1899/12/31 1900/1/1 1904/1/2
2 1900/1/1 1900/1/2 1904/1/3
58 1900/2/26 1900/2/27 1904/2/28
59 1900/2/27 1900/2/28 1904/2/29
60 1900/2/28 1900/2/29 1904/3/1
61 1900/3/1 1900/3/1 1904/3/2
62 1900/3/2 1900/3/2 1904/3/3
Variant Date 在 1900/3/1 以後就完全與 Excel Date 重合,基本上不建議採用 1904 為基礎的日期資料。
可以看出來,Excel Date 居然有個 1900/1/0 ,算是很有趣的東西,此外,Excel 不接受基準日以前的日期,所以不能有 1899/12/31 這類的日期出現,當然,若採用 1904 為基準,就完全不會出現 1904 年以前的日期。
因此在 1900/3/1 以前的日期若仍要使用在 Excel 內,必須改用 VBA 來處理,並運用 String <-> Date 來相互轉換。
Categories: Office | 標籤: | 發表留言

在WordPress.com寫網誌.