最近接到物流管理人員的需求,希望能同時查看跨倉別及跨品牌的單據,
經了解之後發現公司的物流管理系統,天生就無法輸出這種需求的報表,
各品牌負責人在操作系統後,將單據匯出Excel檔案並且交付倉管部門,
物流人員收到各方報表後,再依不同的下一關卡分批進行人工彙整作業,
不論是系統當初未客製的操作需求,或者是同仁們必須的人工彙整作業,
其實只要公司再花一筆錢,請系統廠商再開發新增需要的功能就解決了。
在經過多方討論後發現,其實大夥兒並不想改變現有慣性的工作模式,
所以物流管理人員提出,如果能將來自各方的Excel檔內容合併在一起,
可以在同一個檔案內處理單據資料,這樣就能大幅度的縮減作業時間了,
求助Google大神,找到了一大堆關於轉換檔案格式的工具軟體,
不論是單一或是整批轉換,Excel的格式可以作為轉換的來源或是目的,
但是就是沒有從整批合併成一個檔案的工具...
想到了之前研究過的筆記[C# 寫資料到Excel的方式]
https://jerry5217.pixnet.net/blog/post/312240331
用這個方法可以從"檔案"、"工作表"操作到"儲存格"及其屬性設定...
不過這次的需求很單純,
使用者在公司系統的報表功能選取不同的單據類別進行輸出Excel,
在相同的報表功能進行輸出,就代表會有相同的排版格式內容,
所以我只需要能夠讀取到儲存格的文字內容(步驟1),
然後再用寫入文字檔的方式逐列編排成CSV格式(步驟2),
就完成合併的目的了~\(^o^)/
這次的筆記是要提供使用者執行各自檔案處理的工具程式,
如下圖使用[Visual Studio Community 2017]免費的開發工具,
新增一個最簡單的Windows Form應用程式,
然後要利用OleDb的介面來完成上述的步驟1,
再用基本的文字檔寫入技巧即可完成步驟2,
如下圖先加入步驟1、2所需要的NameSpace,
處理來源資料的二個重點,"建立連線"以及"設定處理方式",
回顧以下之前連線SQL資料庫的方式,
https://jerry5217.pixnet.net/blog/post/225960490
NameSpace使用[System.Data.SqlClient]
建立連線使用[SqlConnection]
資料處理使用[SqlDataReader]執行[SqlCommand]的內容,
使用包含字樣"Reader"的指令,顧名思義就是要讀取資料而已,
這次我們要用OleDb讀取Excel的資料,
NameSpace使用[System.Data.OleDb]
建立連線使用[OleDbConnection]
資料處理使用[OleDbDataReader]執行[OleDbDataAdapter]的內容,
OleDb建立連線的屬性設定,用字串變數ConStr打包起來:
OleDb的資料讀取,如下圖:
變數cn依據變數ConStr的內容建立連線,
變數DR1執行DA1的指令即可開始逐列讀取資料,
讀取Excel與SQL資料表的技巧頗為類似,
但是讀取Excel時無法像讀取SQL那樣指定欄位(Column)名稱,
而且如下圖到了第10列(Row)才出現了第E欄(Column)的資料,
由於我們無法預知準備要讀取的工作表中資料排列的樣子,
所以我們必須要先知道,有資料的最大欄位是哪一個?
總不能所有欄位全部讀一遍吧!
(.xls格式欄位代號從A到IV、.xlsx格式欄位代號從A到XFD)
在連線開啟工作表後用while迴圈讀取每一列資料前,
先用個變數抓取這張工作表有資料的最大欄位數字,
如下圖用數值變數[ColMax]來決定每一列要讀幾個欄位的資料,
對應上述的Excel表格截圖,ColMax變數值會得到5,
利用while迴圈指向每一列,然後用for迴圈讀取5個欄位的資料,
每讀取一個欄位就加上逗號","收集在字串變數RowStr中,
收集完一列資料就由MessageBox.Show函數指令輸出如下...
A1,B1,C1,,
A2,B2,C2,,
A3,B3,C3,,
A4,B4,C4,,
A5,,C5,,
A6,B6,C6,,
A7,B7,C7,,
A8,B8,C8,,
A9,B9,C9,,
A10,B10,C10,,E10
上述字串變數RowStr輸出的內容不就是.csv檔的格式嗎?
所以把每一列組合的內容寫入文字檔,附檔名設定為.csv就可以了,
如下圖在while迴圈開始前先建立一個文字檔的寫入物件fileW,
將收集在變數RowStr的內容用fileW.WriteLine逐行寫入檔案,
關於上述程式碼中建立文字檔的寫入物件fileW,
參數設定可參考MSDN的說明截圖如下,
我們將第二個參數append設定為true,
就可以依序讀取不同來源的Excel檔然後累積寫入同一個文字檔中,
如上圖程式碼內容是指定寫入累積在"Merged.csv"這個檔案,
第三個參數encoding可不指定,但是這裡使用big5字元編碼寫入,
否則程式會依據使用者電腦系統的預設編碼寫入文字檔,
當使用Excel開啟.csv檔時就會容易出現無法辨識的亂碼文字,
如下圖,出現亂碼的.csv檔必須用[記事本]開啟,
然後指定ANSI編碼執行存檔後,使用Excel開啟才不會出現亂碼,
到這裡已經完成了上述步驟1及步驟2的技巧了,
先將這二個步驟包裝成一個可回傳bool值的函式,
必須要提供處理檔案的路徑、檔名、工作表名稱及對第一行的處理,
最後回傳true或false的bool值以判斷是否成功...
接下來設計使用者的操作介面表單,
作業計畫是由使用者選擇來源資料夾,
然後程式在選擇的資料夾中尋找Excel檔,
程式依序讀取找到的Excel檔執行上述步驟1及2的流程,
最後"Merged.csv"這個檔案內容就會是結果了,
簡單的利用幾個物件說明如下:
使用者指定工作表名稱(textBox_SheetName)
以及選擇第一行資料是否讀取(comboBox_Ignore1stLine)
按下button物件"button_MergeExcelFile"選擇資料夾,
選取後先將完整路徑顯示在物件"textBox_FolderPath"
建立陣列函式"GetFileList"收集指定路徑下的所有檔案,
將上述函式收集來的內容放入陣列變數"file"中,
再篩選副檔名為.xls的檔名存放至陣列變數"FileList",
用for迴圈逐一讀取陣列變數"FileList"中的檔名,
加上已顯示在物件"textBox_FolderPath"的路徑,
以及表單中指定的工作表名稱及是否處理第一行的選項,
呼叫已包裝好步驟1及2的函式"Excel_OLDBE_Load",
完成後會在相同路經中產生的檔案"Merged.csv"...
操作範例,
舉例在D:\Excel_Temp路徑中有三個Excel檔案,
Test_1.xlsx的內容:
Test_2.xls的內容:
Test_3.xlsx的內容:
在執行檔的表單中輸入指定讀取的工作表名稱為"工作表1",
然後按下按鈕"Find path and merge excel...",
選擇D:\Excel_Temp的路徑後便直接開始執行動作,
最後在相同路徑下產生檔案"Merged.csv"並以小視窗告知已完成,
如下圖"Merged.csv"檔案以記事本(左邊)及Excel(右邊)開啟,
上圖結果有沒有發現哪裡不一樣!?
合併後的第1~10列是完整對應Test_1.xlsx的來源,
但是第11~14列在來源的Test_2.xls是第2~5列,
Test_2.xls原本沒資料的第1列被忽略掉了!
如下圖只會讀取的紅框部分,
還有合併後的第A欄15列資料"C36",
在來源的Test_3.xlsx當中是存在第C欄6列,
如下圖只會讀取的紅框部分,
看樣子這隻程式面對來源檔案似乎只會讀取有資料的範圍而已!
不過筆記一開始就說明了,
操作相同系統的報表功能輸出,其排版格式內容都會相同,
所以有時間再來慢慢研究吧!
完整的程式碼及範例已壓縮放置雲端空間:
https://1drv.ms/u/s!AO1CPC3fzWzdgrEq
(已編譯好的執行檔放在預設的"bin\Release\"路徑下)
