 |
VFP 愛用者社區 本討論區為 Visual Foxpro 愛用者經驗交流的地方, 請多多利用"搜尋"的功能, 先查看看有無前例可循, 如果還有不懂的再發問. 部份主題有附加檔案, 須先註冊成為社區居民才可以下載.
|
| 上一篇主題 :: 下一篇主題 |
| 發表人 |
內容 |
CPS0204
註冊時間: 2014-08-24 文章: 537
第 1 樓
|
發表於: 星期五 八月 21, 2026 6:15 am 文章主題: 分享:全域可見的 CREATEOBJECT("Collection") 用法 |
|
|
VFP 9 內建的 Collection(集合類別) 是極為強大卻常被低估的工具。它本質上是一個動態陣列 + 雜湊表(Hash Table/Dictionary)的綜合體,具備以下幾項在 ERP 開發中非常實用的高階技巧:
個人覺得我使用早期的public my_array[100,3] 搭配ascan 好用,
差別在:陣列會耗用 100*3 的變數個數( 變數池佔用(MVCOUNT):
o 陣列中的每一個元素都會消耗一個變數計數。)
,collection 只會占用一個變數
技巧 1:實作 Key/Value 字典(大小寫敏感度設定)
Collection 預設的 Key 是區分大小寫的(Case-Sensitive)。在 ERP 比對功能代碼(如 F_ORDER001 vs f_order001)時,容易因為大小寫不同而查不到。
解法:建立後立即將 KeySort = 2,即可關閉大小寫區分(或強制統一比對規則):
程式碼片段
LOCAL oDict
oDict = CREATEOBJECT("Collection")
oDict.KeySort = 2 && 0=不排序, 1=升冪, 2=區分大小寫由系統設定/自動不區分
* 存入資料:Add(值, 鍵值)
oDict.Add("1111", "F_ORDER001")
* 取用資料(即使小寫也能精確命中)
? oDict.Item("f_order001") && 輸出: "1111"
技巧 2:安全取值(避免觸發 Error 報錯)
如果直接使用 oCol.Item("XXX"),而該 Key 不存在時,VFP 會直接中斷並彈出 1562: Key is not found in collection 的執行時期錯誤。
技巧:使用 GetKey("Key名稱") 先行探測,存在會傳回其索引序號($> 0$),不存在則傳回 0:
程式碼片段
FUNCTION GetSafePower(toCol, tcProgKey)
LOCAL lnKeyIndex
lnKeyIndex = toCol.GetKey(tcProgKey)
IF lnKeyIndex > 0
RETURN toCol.Item(tcProgKey) && 安全取出
ELSE
RETURN "0000" && 預設完全無權限
ENDIF
ENDFUNC
技巧 3:存放自訂物件(模擬「記憶體結構 / 紀錄」)
Collection 不僅能放字串或數值,還可以直接存放 EMPTY 物件(SCATTER NAME)。這是在私有表單(DataSession = 2)之間傳遞整筆資料錄最強大的技巧,完全不依賴實體 Table 或 Cursor:
程式碼片段
* 1. 建立存放使用者資料結構的集合
PUBLIC goUserCache
goUserCache = CREATEOBJECT("Collection")
* 2. 模擬從 SQL 撈出一筆紀錄並存入 Collection
SELECT cur_user
SCAN
SCATTER NAME loRow MEMO && 將目前資料列包裝成一個物件
* 以員工編號作為 Key,物件作為 Value 存入集合
goUserCache.Add(loRow, ALLTRIM(cur_user.EmpNo))
ENDSCAN
* 3. 在任何私有表單中調用該員工的完整欄位
IF goUserCache.GetKey("EMP001") > 0
loEmp = goUserCache.Item("EMP001")
ThisForm.txt_name.Value = loEmp.EmpName
ThisForm.txt_dept.Value = loEmp.DeptName
ThisForm.txt_salary.Value = loEmp.BaseSalary
ENDIF
技巧 4:使用 FOR EACH 快速走訪所有元素
走訪 Collection 不需要寫 FOR i = 1 TO oCol.Count,直接用 FOR EACH 語法最精準且簡潔:
程式碼片段
LOCAL oItem, oCol
oCol = CREATEOBJECT("Collection")
oCol.Add("進貨單作業", "F_PUR001")
oCol.Add("銷貨單作業", "F_SALE001")
oCol.Add("庫存調整", "F_INV001")
* 走訪集合內所有值
FOR EACH lcProgName IN oCol
? "作業名稱:", lcProgName
ENDFOR
* 若需要同時取得 Key 與 Value:
FOR lnIdx = 1 TO oCol.Count
? "鍵值(Key):", oCol.GetKey(lnIdx), " => 內容(Value):", oCol.Item(lnIdx)
ENDFOR
技巧 5:更新已存在的 Key(覆寫值技巧)
Collection 不允許重複 Add 同一個已存在的 Key(會報錯 1561: Key already exists)。若要更新某個 Key 的內容,必須先 Remove 再 Add:
程式碼片段
PROCEDURE SetCollectionValue(toCol, tcKey, tuValue)
* 如果 Key 已存在,先移除舊的
IF toCol.GetKey(tcKey) > 0
toCol.Remove(tcKey)
ENDIF
* 寫入最新值
toCol.Add(tuValue, tcKey)
ENDPROC
技巧 6:封裝成全域快取管理器( Singleton 類別)
在大型 ERP 架構中,通常會以 Collection 為基礎自訂一個全域快取類別,提供更友善的介面:
程式碼片段
* 主程式初始化
PUBLIC goCache
goCache = CREATEOBJECT("AppCache")
* 存入快取(不論存在與否直接覆寫)
goCache.Set("CURRENT_EXRATE", 31.5)
goCache.Set("AUTH_F_ORDER", "1111")
* 取用快取(若不存在給予預設值,完全不報錯)
lnRate = goCache.Get("CURRENT_EXRATE", 1.0)
lcAuth = goCache.Get("AUTH_F_PUR", "0000")
* =========================================
* 類別定義
* =========================================
DEFINE CLASS AppCache AS Custom
PROTECTED oItems
PROCEDURE Init
This.oItems = CREATEOBJECT("Collection")
This.oItems.KeySort = 2
ENDPROC
PROCEDURE Set(tcKey, tuValue)
tcKey = UPPER(ALLTRIM(tcKey))
IF This.oItems.GetKey(tcKey) > 0
This.oItems.Remove(tcKey)
ENDIF
This.oItems.Add(tuValue, tcKey)
ENDPROC
PROCEDURE Get(tcKey, tuDefault)
tcKey = UPPER(ALLTRIM(tcKey))
IF This.oItems.GetKey(tcKey) > 0
RETURN This.oItems.Item(tcKey)
ELSE
RETURN tuDefault
ENDIF
ENDPROC
PROCEDURE Has(tcKey)
RETURN (This.oItems.GetKey(UPPER(ALLTRIM(tcKey))) > 0)
ENDPROC
PROCEDURE ClearAll
This.oItems.Remove(-1) && 傳入 -1 一鍵清空整個集合
ENDPROC
ENDDEFINE
總結
Collection 是 VFP 9 最理想的全域暫存與跨表單傳遞載體:
• 傳入 -1 給 Remove(-1) 可一秒清空所有內容。
• 能存字串、數值、甚至 EMPTY 物件。
• 完全不受 DataSession 隔離限制,也絕不佔用任何 Work Area(工作區)。 |
|
| 回頂端 |
|
 |
CPS0204
註冊時間: 2014-08-24 文章: 537
第 2 樓
|
發表於: 星期五 八月 21, 2026 3:02 pm 文章主題: |
|
|
CREATEOBJECT('EMPTY') 用法
在 Visual FoxPro 中,EMPTY 是一個非常特殊的底層輕量類別(Lightweight Base Class)。
它與一般的 Custom 或 Form 類別不同:EMPTY 物件「沒有任何內建的屬性、方法或事件(No PEMs)」。它純粹就是一塊乾淨的記憶體空間,專門用來當作輕量資料結構(Data Structure / Record),用來在函式、類別以及私有表單(DataSession = 2)之間傳遞資料。
一、 EMPTY 物件的核心特點
1. 極度輕量、節省記憶體:沒有繼承任何 Visual FoxPro 基礎物件的多餘負擔(如 Name、Class、Init 等都沒有)。
2. 完全動態擴充:無法直接在上面寫程式碼(Method),但可以使用 ADDPROPERTY() 隨時掛載任意自訂屬性。
3. 無縫穿透私有表單:不受 DataSession 隔離限制,可在不同表單與程序間自由傳遞。
二、 基本使用方法:手動建立與賦值
透過 ADDPROPERTY() 動態為物件新增屬性:
程式碼片段
LOCAL loUser
loUser = CREATEOBJECT("EMPTY")
* 為物件動態新增屬性:ADDPROPERTY(物件, 屬性名稱, 初始值)
= ADDPROPERTY(loUser, "UserID", "MIS001")
= ADDPROPERTY(loUser, "UserName", "張三")
= ADDPROPERTY(loUser, "Age", 56)
= ADDPROPERTY(loUser, "IsAdmin", .T.)
* 取用屬性值(直接用 . 存取)
? loUser.UserID && 輸出: MIS001
? loUser.UserName && 輸出: 張三
* 修改屬性值(直接賦值即可,不需要再呼叫 ADDPROPERTY)
loUser.UserName = "李四"
三、 與資料表連動的高階用法(ERP 開發最常用)
在 VFP 中,EMPTY 物件最經典的應用是搭配 SCATTER NAME 與 GATHER NAME,取代傳統的記憶體變數或陣列:
1. 將目前資料列瞬間轉為 EMPTY 物件
程式碼片段
USE Orders IN 0
SELECT Orders
* 自動建立一個 EMPTY 物件 loRow,並將目前資料列的所有欄位自動掛載為屬性
SCATTER NAME loRow MEMO
* 此時可直接讀取欄位名稱
? loRow.OrderNo
? loRow.OrderDate
? loRow.TotalAmt
* 修改物件內容
loRow.TotalAmt = loRow.TotalAmt * 0.9
* 將物件內容回寫進資料表
GATHER NAME loRow MEMO
四、 跨私有表單(DataSession)傳遞多筆參數
當表單啟用私有工作階段(DataSession = 2)時,若要傳遞 5~6 個參數,傳統做法寫在 DO FORM frm_detail WITH ... 容易混亂,改傳一個 EMPTY 物件最乾淨:
1. 呼叫端(主表單)
程式碼片段
LOCAL loParam
loParam = CREATEOBJECT("EMPTY")
= ADDPROPERTY(loParam, "ActionType", "EDIT")
= ADDPROPERTY(loParam, "OrderNo", "SO202608001")
= ADDPROPERTY(loParam, "ItemNo", "A001")
= ADDPROPERTY(loParam, "CanDelete", .F.)
* 將整包物件傳入子表單
DO FORM frm_OrderDetail WITH loParam
2. 接收端(子表單 Init 事件)
程式碼片段
* 子表單 Form.Init
LPARAMETERS toParam
IF VARTYPE(toParam) == "O"
ThisForm.cActionType = toParam.ActionType
ThisForm.cOrderNo = toParam.OrderNo
ThisForm.cmd_del.Enabled = toParam.CanDelete
ENDIF
五、 與 Collection(集合)搭配:打造記憶體字典快取
將多個 EMPTY 物件存入 Collection 中,能完全取代跨工作區容易被干擾的 Cursor:
程式碼片段
LOCAL oUserList, loEmp
oUserList = CREATEOBJECT("Collection")
* 建立第一筆資料物件
loEmp1 = CREATEOBJECT("EMPTY")
= ADDPROPERTY(loEmp1, "EmpID", "E001")
= ADDPROPERTY(loEmp1, "Name", "王大明")
oUserList.Add(loEmp1, loEmp1.EmpID)
* 建立第二筆資料物件
loEmp2 = CREATEOBJECT("EMPTY")
= ADDPROPERTY(loEmp2, "EmpID", "E002")
= ADDPROPERTY(loEmp2, "Name", "陳小美")
oUserList.Add(loEmp2, loEmp2.EmpID)
* 瞬間依 Key 取出物件並使用
IF oUserList.GetKey("E001") > 0
loTarget = oUserList.Item("E001")
? loTarget.Name && 輸出: 王大明
ENDIF
六、 輔助函數:檢測與移除屬性
• 檢查屬性是否存在(PEMSTATUS):
程式碼片段
IF PEMSTATUS(loUser, "UserName", 5)
? "物件擁有 UserName 屬性"
ENDIF
• 移除屬性(REMOVEPROPERTY):
程式碼片段
* 移除已掛載的屬性,釋放空間
= REMOVEPROPERTY(loUser, "IsAdmin")
|
|
| 回頂端 |
|
 |
CPS0204
註冊時間: 2014-08-24 文章: 537
第 3 樓
|
發表於: 星期五 八月 21, 2026 3:05 pm 文章主題: |
|
|
createobject('empty') 創造的是全域可見的物件嗎?
不是,CREATEOBJECT('EMPTY') 本身並不會自動創造出全域可見的物件。
物件的可見範圍(Scope)與生命週期,完全取決於存放它的「變數宣告方式」,而不是物件類別本身。
一、 變數宣告決定它的可見範圍
宣告方式 可見範圍(Scope) 生命週期
LOCAL loObj
loObj = CREATEOBJECT('EMPTY') 區域可見(局部)
只有宣告它的該段 Procedure / Method 內部看得到。 當該函式或事件執行完畢(RETURN)時,物件立即自動消滅釋放。
PRIVATE loObj
loObj = CREATEOBJECT('EMPTY') 向下層可見
當前函式以及其呼叫的所有子程序(Sub-routine)看得到。 當定義它的母程序結束時消滅。
PUBLIC goObj
goObj = CREATEOBJECT('EMPTY') 全系統全域可見(Global)
整個 ERP 系統的任何 PRG、Form、Report、Menu 皆可隨時讀寫。 直到系統關閉(QUIT)或手動執行 RELEASE goObj。
二、 為什麼常讓人誤以為它是「全域」?(跨 DataSession 特性)
大家常說 EMPTY 物件具備「穿透性」,容易讓人誤會它是全域的。實際上它指的是:物件在記憶體中的參考(Reference)可以安全地「跨私有表單(DataSession = 2)傳遞與存取」,而不會像 Table/Cursor 那樣被工作階段隔離。
1. 區域物件(LOCAL)也能傳給私有表單:
即便物件是以 LOCAL 宣告,只要透過參數傳入私有表單,子表單就能直接使用該物件實體:
程式碼片段
* 主程式 (DataSession = 1)
LOCAL loParam
loParam = CREATEOBJECT("EMPTY")
= ADDPROPERTY(loParam, "CustNo", "C001")
* 傳入私有表單 (DataSession = 2)
DO FORM frm_CustDetail WITH loParam
2. 若要做到真正「全系統到處免傳參直接存取」,必須宣告為 PUBLIC 或掛在 _SCREEN:
• 做法 A:宣告為 PUBLIC 變數
程式碼片段
* MAIN.PRG 系統啟動時
PUBLIC goSysParam
goSysParam = CREATEOBJECT("EMPTY")
= ADDPROPERTY(goSysParam, "CompanyID", "KS01")
= ADDPROPERTY(goSysParam, "TaxRate", 0.05)
* 系統內任何表單皆可隨時讀取:
? goSysParam.CompanyID
• 做法 B:掛載在 _SCREEN 或 _VFP 系統物件上(免 PUBLIC 變數)
程式碼片段
* 直接掛在主畫面上,全系統永遠可見且不會被 CLEAR ALL 誤刪
= ADDPROPERTY(_SCREEN, "oSysParam", CREATEOBJECT("EMPTY"))
= ADDPROPERTY(_SCREEN.oSysParam, "CompanyID", "KS01")
* 任何表單調用:
? _SCREEN.oSysParam.CompanyID |
|
| 回頂端 |
|
 |
CPS0204
註冊時間: 2014-08-24 文章: 537
第 4 樓
|
發表於: 星期五 八月 21, 2026 3:10 pm 文章主題: |
|
|
public my_empty_object 與 public my_array[100,3] 的差異
在 Visual FoxPro 9 中,PUBLIC my_empty_object(搭配 CREATEOBJECT('EMPTY'))與二維陣列 PUBLIC my_array[100,3] 的核心差異在於資料模型、變數池耗用、存取方式與維護成本:
有圖形,請看附件word檔 |
|
| 回頂端 |
|
 |
|
|
您 無法 在這個版面發表文章 您 無法 在這個版面回覆文章 您 無法 在這個版面編輯文章 您 無法 在這個版面刪除文章 您 無法 在這個版面進行投票 您 無法 在這個版面附加檔案 您 無法 在這個版面下載檔案
|
|