顯示具有 Delphi - Excel 標籤的文章。 顯示所有文章
顯示具有 Delphi - Excel 標籤的文章。 顯示所有文章

2021/08/25

從Excel操作問題來看工程師的通靈技能


前陣子在 Delphi.KTOP 看到一篇「請問 Excel AddSmartArt 第一個參數該怎麼設定」。

覺得操作 SmartArt 這點很有意思,這兩天才有時間認真看了這篇文章,一開始以為使用 Excel 的「錄製巨集」就可以搞定,但直覺認為回覆此內容時會得到「我早就已經知道」的結果。

因此決定一試 Excel 巨集功能,取得的 VBA 內容是:

    Call ActiveSheet.Shapes.AddSmartArt(Application.SmartArtLayouts( _
        "urn:microsoft.com/office/officeart/2005/8/layout/cycle1")).Select

就這麼短短一行,轉到 Delphi 上會是以下內容:

procedure TForm2.Button3Click(Sender: TObject);
var
  ExcelApp,
  ActiveSheet,
  oSALayout: Variant;
begin
  try
    ExcelApp := CreateOleObject('Excel.Application');
  except
    ShowMessage('建立EXCEL錯誤');
  end;

  ExcelApp.Visible := True;
  ExcelApp.WorkBooks.Add;
  ActiveSheet := ExcelApp.ActiveSheet;
  oSALayout := ExcelApp.SmartArtLayouts('urn:microsoft.com/office/officeart/2005/8/layout/hierarchy2');
  ActiveSheet.Shapes.AddSmartArt(oSALayout, 50, 50, 200, 200);
end;

卻得到以下結果:

看來樓主的問題內容並沒有說明完整,就來看一下 Excel Developer Docs 怎麼描述 SmartArtLayouts:


文件也就這麼一點點,沒了。 再透過 Google 搜尋,沒有更多的資料,Delphi 似乎沒有人這樣做,【找不到成員】這問題難道就無解了嗎?

Google 沒有沒關係,Excel_TLB 單元來解答

利用 Excel 執行檔來製作最適合它的 Delphi 元件看來是最終解法,果不其然,答案在這裡:
ExcelApplication 確實有 SmartArtLayouts 成員,接下來就是直接操作它,程式碼直接公開:
VBA 對 OLE 成員非常自由,Item 到底是什麼東西?經查詢的結果是:

微軟連自家軟體的工程師手冊都寫得如此破碎和簡短,也難怪這方面的開發資訊幾近沒有。

原本還要多寫些關於 SmartArt 賦值的內容,無奈再往下追盡是 Access Violation,使用 Excel_TLB 時必定會發生,Stack Overflow 這篇【Delphi - How to create Excel PivotChart】也遇到一模一樣的問題,雖無法找到原因,但改以 OLE 重新刻一次後卻可以解決問題。

2020/03/12

Delphi Easy Excel 基礎類別設計


Excel是許多從事行政人員必用且愛用的工具之一,所以在程式開發上最常遇到的就是客戶拿產品來和Excel做比較。


這能比嗎!(怒吼)


忠孝不能兩全的情況下,拿Excel檔案進行匯入似乎是很常見的折衷方案。

Delphi號稱VB Killer,操作個Excel自然是基本款,來看看Delphi準備了哪些方案?

Microsoft Office 2000/XP Sample Automation Server Wrapper Components

Microsoft Office 2000/XP Sample Automation Server Wrapper Components


Delphi很貼心的提供Office元件,自Delphi 7~10.3以來,也就這麼兩套。(2020.03.19更新:XE5之後有單獨提供 2010 套件,但只要 Office 更新也是會有相容性問題。延伸閱讀:Delphi XE5 Office 2013 组件更新)

Office 至目前為止已經2019了,許多使用者都開始使用XLSX新格式,這個元件很早就出現相容性不足的問題。

另一個類似元件的做法是使用Import ActiveX Control,匯入新版Excel ActiveX控制項,實務上還蠻類似元件的操作方式,只是問題在Excel升級後仍有可能會有相容性問題產生。

3rd party

三方元件最好的地方在於使用者電腦並不需要安裝Excel即可進行XLS檔案操作,速度和品質都非常好,我用過的有:

NikaSoft NativeExcel

NativeExcel設計上非常貼近OLE的操作方式,在轉換上十分方便,操作XLS檔效率也非常棒,體驗後就離不開它。

但NativeExcel現在已經沒有維護,對XLSX相容性問題也開始浮現。

無奈之餘,還是只能忍痛放棄它。

XLSReadWriteII


這套是資深Delphier Jason Wong 愛用的Excel元件,而且仍有維護,據稱其優點和NatvieExcel相同,但設計概念非OLE操作,所以沒有深入了解。


看來看去,似乎只能回歸OLE設計方式才能達到最高相容的可能性。

畢竟穩定比效能更加重要!

使用OLE開發


使用官方元件很像是使用全域變數的概念,如果OLE也這樣設計如何?


var FExcelApp: Variant;

function ExcelApp: Variant;
  function IsExcelInstalled: Boolean;
  var
    ClassID: TCLSID;
    strOLEObject: string;
  begin
    strOLEObject := 'Excel.Application';
    Result := (CLSIDFromProgID(PWideChar(WideString(strOLEObject)), ClassID) = S_OK);
  end;
begin
  if IsExcelInstalled then
  begin
    if VarIsEmpty(FExcelApp) then
      FExcelApp := CreateOleObject('Excel.Application');
    Result := FExcelApp;
  end
  else
    WinMsgBox.WinError('Not found Excel, call EdenWu, please.');
end;

procedure CloseExcelApp;
begin
  if not VarIsEmpty(FExcelApp) then
  begin
    FExcelApp.ActiveWorkBook.Saved:= 1;
    FExcelApp.DisplayAlerts:= 0;
    FExcelApp.ActiveWorkBook.Close(SaveChanges:=0);
    FExcelApp.Quit;
    VarClear(FExcelApp);
    FExcelApp:=Unassigned;
  end;
end;

initialization
  //FExcelApp := unassinged; // this should not be necessary
finalization
  CloseExcelApp;

把Excel Application視為全域變數,隨時要使用Excel Application都沒問題,非常方便。

開始要寫一些關於Worksheet相關的處理了。咦?

好像還不錯,但不夠OOP,還要更多的OOP

寫著寫著,全域變數和全域函式跑來跑去,程式碼看起來就不夠簡潔了,如果使用OOP設計方式會不會更好呢?

自己刻好像不錯,只是有個臨摹的對象更好,來看看有沒有好範本可以參考。

Github搜尋一陣後,發現有高手寫了個SimpleExcel,仔細看了下程式碼,嗯,很OO,這我可以。

這樣的程式碼要自己來寫到底要花多少時間呢?
如果站在巨人的肩膀上會不會省下更多時間做更重要的事呢?

一邊使用SimpleExcel的同時,一邊思考著。


-全文完-

See also

2018/02/13

XLSX 檔案格式除錯 (CHT)

最近同事傳來一個轉檔為 xlsx 後出現的一個錯誤,訊息大致如下:
【Excel 發現無法讀取的內容 您是否要回復此活頁簿內容...】

修正後,會取得以下的資訊:

2014/04/16

Excel Group command with ExcelXP Component

 ExcelWorksheet1.Range[_RangeBeg+':'+_RangeEnd,EmptyParam].Rows.Group(EmptyParam,EmptyParam,EmptyParam,EmptyParam);

2014/03/20

TExcelWorksheet 取值

在開始介紹 ExcelWorksheet 如何取值前,我們要先了解 Excel 的專業術語:



上面的「A、B、C」是 Column,座標軸 X,以下用 COL 表示。
左邊的「1、2、3」是 Row,座標軸 Y,以下用 ROW 表示。

所以我們通常會使用座標方式來指定游標所在的位置,常見用語有使用名稱「C3」或座標值「3, 3」之類的表達方式。

在這邊要注意的是,在Excel中座標的表示法是「ROW, COL」。並不是常見的「COL, ROW」

所以我們得知 ExcelWorksheet 的座標取值方法是:

ExcelWorksheet.Cells.Item[ROW, COL]

要使用名稱定義也可以,以下都是正確的作法

//property Range[Cell1: OleVariant; Cell2: OleVariant]: ExcelRange read Get_Range;
ExcelWorksheet.Cells.Range['A1', EmptyParam].Value;
ExcelWorksheet.Cells.Range['A1',       'A1'].Value;
ExcelWorksheet.Cells.Range['A1', EmptyParam].Value2;
ExcelWorksheet.Cells.Range['A1',       'A1'].Value2;

請注意!參數Cell1, Cell2不能為數字,也不能為 nil,否則會報 「OLE error XXXXXXXX」 的錯誤!


追加補充,如何取得 ExcelWorksheet 的列印範圍:

ExcelWorksheet.UsedRange[_LCID].Columns.Count
ExcelWorksheet.UsedRange[_LCID].Rows.Count

到這邊為止,已經可以處理絕大部分Excel檔案匯入的工作了。

想知道 ExcelWorksheet更多的功能嗎?去找吧,所有功能屬性都寫在 ExcelWorksheet 的元件裡!

2014/03/18

ExcelXP TExcelWorksheet 連結 Worksheet

 前情提要:
1.      使用 Delphi ExcelXP 元件開啟 Excel 和活頁簿(Workbook)
2.      把開啟的 Excel 和活頁簿關閉

這一節我們要聊的是「工作表」(Worksheet) 連結 (是的,不含操作)

ExcelWorksheet 這個元件的連結方式比較特別,它必須:
1.      先指定前一節已開啟的ExcelWorkbook 指向哪一個 Worksheet
語法如下:
ExcelWorkbook.Worksheet[________]

        這邊的填空處可以放兩種類型,一種是文字,另一種就是數字:
        也就是 Worksheet 的「名字」或「排列的序號」
       
        以這個例子來說,我們要開啟「Sheet3」時,程式可以這麼寫:
        ExcelWorkbook.Worksheet[Sheet3]
       
        ExcelWorkbook.Worksheet[2]
        序號是按左至右,從 1 起始。

2.      ExcelWorksheet 元件連結或轉型後立即使用
I.            ExcelWorksheet 元件連結:(經常呼叫時使用)
ExcelWorksheet.ConnectTo(ExcelWorkbook.Worksheet[Sheet3] as _Worksheet);
II.         轉型後立即使用:(臨時呼叫時使用)
(ExcelWorkbook.Worksheet[Sheet3] as _Worksheet).ExcelWorksheet屬性

        例如:
    ExcelWorksheet.ConnectTo(ExcelWorkbook.Worksheet[Sheet3] as _Worksheet);
   _YYYMM := ExcelWorksheet.Cells.Item[y, x];
    _CONNO := ExcelWorksheet.Cells.Item[y1, x1];
   
    _CURID := (ExcelWorkbook.Worksheet[Sheet3] as _Worksheet).Cells.Item[y1, x1]


        不知道 Cells, Item 的屬性也沒有關係,下一節我們會再聊到。

2014/02/18

Excel Component 的寫作技巧

在全域變數區可設定一個 LCID 變數
var _LCID: Cardinal;

在 TForm 上放置 ExcelApplication, ExcelWorkbook, ExcelWorksheet 三個元件。

開啟 EXCEL + 開啟活頁簿 + 指定工作表:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var
  _WBK: _Workbook;
  _IndexSheet: Integer
begin
  _LCID := GetUserDefaultLCID();
  _WBK := ExcelApplication.Workbooks.Open(
          {Filename        =}aFileName,
          {UpdateLinks     =}EmptyParam,
          {ReadOnly        =}EmptyParam,
          {Format          =}EmptyParam,
          {Password        =}EmptyParam,
          {WriteResPassword=}EmptyParam,
          {IgnoreReadOnly  =}EmptyParam,
          {Origin          =}EmptyParam,
          {Delimiter       =}EmptyParam,
          {Editable        =}EmptyParam,
          {Notify          =}EmptyParam,
          {Converter       =}EmptyParam,
          {AddToMru        =}EmptyParam,
          {Local           =}EmptyParam,
          {CorruptLoad     =}EmptyParam,
          {LCID            =}_LCID);
  ExcelWorkbook.ConnectTo(_WBK);
  ExcelWorksheet.ConnectTo(ExcelWorkbook.Worksheets[_IndexSheet] as _Worksheet);
end;

關閉活頁簿 + 關閉 Excel:
1
2
3
4
5
6
7
8
9
begin
  ExcelWorkbook.Close(
    {SaveChanges  =}EmptyParam,
    {Filename     =}EmptyParam,
    {RouteWorkbook=}EmptyParam,
    {LCID         =}_LCID);
  ExcelApplication.Quit;
  ExcelApplication.Disconnect;
end;

2013/12/26

Convert Excel column to integer funtion with Delphi

See also: How to convert a column number (eg. 127) into an excel column (eg. AA)


Delphi code below:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function ColumnNumber(A_ColAddress: string): Integer;
var
  _Digits: array of Integer;
  _Pos: Integer;
  _Mul, _Res: Integer;
begin
  SetLength(_Digits, Length(A_ColAddress));
  for _Pos := 0 to Length(A_ColAddress)-1 do
  begin
    _Digits[_Pos] := Ord(A_ColAddress[_Pos+1]) - 64;
  end;
 
  _Mul := 1;
  _Res := 0;
  for _Pos := Length(A_ColAddress)-1 downto 0 do
  begin
    _Res := _Res + (_Digits[_Pos] * _Mul);
    _Mul := _Mul * 26;
  end;
 
  Result := _Res;
end;

2013/09/06

學習Excel XP Components的網站



以下這個內容好重要嘿!
==================
对Excel单元格的访问通常是使用Range属性,其调用格式如下(以下都正确):
// property Range[Cell1: OleVariant; Cell2: OleVariant]: ExcelRange read Get_Range;

ExcelApp.Cells.Range['A1:C2', EmptyParam].Value := 'ab';

ExcelApp.Cells.Range['A1', 'C2'].Value := 'ab';

ExcelApp.Cells.Range['A1:C2', EmptyParam].Value2 := 'ab';

ExcelApp.Cells.Range['A1', 'C2'].Value2 := 'ab';


但是奇怪:参数Cell1, Cell2不能为数字,也不能为Null,否则会报 “OLE error 800A03EC.” 的错误!

2012/11/26

NativeExcel - Delphi操作Excel的好朋友


官方網站:Nika Soft
Delphi內附的Excel元件真的是爆難用的,Obj的方式在Debug上也是痛苦,NikaSoft公司的NativeExcel產品就是以OleObject為基底下,再對它進行封裝。
非常好使的一套元件!

唯一可惜的是,並沒有提供C++ Builder的支援。
但是真的很好用!值得一推!

2012/05/25

使用 OLE 開啟 Excel 的小陷井

從MSDN上看到Workbook.Open函式是這樣寫的:
Workbook Open(
 [In] string Filename, 
 [In, Optional] object UpdateLinks, 
 [In, Optional] object ReadOnly, 
 [In, Optional] object Format, 
 [In, Optional] object Password, 
 [In, Optional] object WriteResPassword, 
 [In, Optional] object IgnoreReadOnlyRecommended, 
 [In, Optional] object Origin, 
 [In, Optional] object Delimiter, 
 [In, Optional] object Editable, 
 [In, Optional] object Notify, 
 [In, Optional] object Converter, 
 [In, Optional] object AddToMru, 
 [In, Optional] object Local, 
 [In, Optional] object CorruptLoad
);


一票網站在抄來抄去,除了第一個參數是Filename不變外,其它不是都沒給值,就是直接給EmptyParam

2011/07/29

令人崩潰的分頁預覽範圍…

Excel VBA中有個 RefersToRange屬性,可以正確得到可列印的最大範圍,MSDN是這樣寫的:
' RefersToRange 屬性範例
' 此範例顯示作用中工作表列印範圍中的列數和欄數。

p = Names("Print_Area").RefersToRange.Value
MsgBox "Print_Area: " & UBound(p, 1) & " rows, " & _
    UBound(p, 2) & " columns"


Delphi Excel Component要怎麼寫?

2011/07/12

TExcelWorksheet刪除列的方式

真是見鬼的做法,Google半天居然沒有網站可以解決這問題

首先,如果我要把Excel工作表的D欄刪除,在Excel VBA是這樣寫的:

Sub Macro2()
    Columns("D:D").Select
    Selection.Delete Shift:=xlToLeft
End Sub


找找找,怎麼找都是顯示ComObj的處理,沒有人用Delphi內建的元件
剛好我偏偏使用了: