ERP系統的單據具備標準的功能,這裡的單據可翻譯為Bill,Document,Entry,具備相似的工具條操作介面。通過設計可複用的基類,子類只需要繼承基類窗體即可完成單據功能的程式設計。先看標準的銷售合同單據介面:
本篇通過銷售合同單據功能,依次講解程式設計要點,供參考。\
1 新增 Insert
窗體有二種狀態,一種是編輯狀態,別一種是資料瀏覽狀態,區別在於編輯狀態的窗體資料被修改(dirty),在窗體關閉時需要儲存資料。點選工具條的新增(Insert)按鈕,窗體進入編輯狀態。新增狀態需要對窗體所編輯的單據設定預設值。一般我們在實體對映檔案中設定預設值,參考下面的例子程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public partial class SalesContractEntity { protected override void OnInitialized() { base.OnInitialized(); // Assign default value for new entity if (Fields.State == EntityState.New) { #region DefaultValue // __LLBLGENPRO_USER_CODE_REGION_START DefaultValue this.Fields[(int) SalesContractFieldIndex.Closed].CurrentValue = false; // __LLBLGENPRO_USER_CODE_REGION_END #endregion } } } |
也可以考慮在窗體中做預設值設定。當遇到這樣一種場景,兩個功能對應同一個實體型別,則需要在介面中根據需要初始化值,參考下面的程式片段。
1 2 3 4 5 6 7 8 |
protected override EntityBase2 Add() { base.Add(); this._inventoryMovement = new InventoryMovementEntity(); this._inventoryMovement.TranType = GetStringValue(InventoryTransactionType.Movement); return this._inventoryMovement; } |
2 儲存 Save
窗體基類檢測到介面中的控制元件值被修改過,窗體狀態變為編輯狀態,點選儲存按鈕執行儲存方法。儲存方法的主要內容是將資料來源控制元件(BindingSource)所繫結的控制元件值更新到它對映的實體中,再呼叫窗體儲存方法儲存實體。
1 2 3 4 5 6 |
protected override EntityBase2 Save(EntityBase2 entityToSave, EntityCollection entitiesToDelete) { SalesContractEntity SalesContractEntity = (SalesContractEntity)entityToSave; this._salesContractEntity = this._salesContractEntityManager.SaveSalesContract( SalesContractEntity, entitiesToDelete, SeriesCode); return this._salesContractEntity; } |
entityToSave是資料來源控制元件繫結的實體型別,在儲存完成後,這個值再次繫結到資料來源控制元件中。
3 刪除 Delete
窗體只有在瀏覽狀態時,才可以點選刪除按鈕,刪除按鈕的內容是獲取窗體資料來源控制元件繫結的實體,呼叫窗體刪除方法。
1 2 3 4 5 6 |
protected override void Delete(EntityBase2 entityToDelete) { base.Delete(entityToDelete); SalesContractEntity SalesContractEntity = (SalesContractEntity)entityToDelete; this._salesContractEntityManager.DeleteSalesContract(SalesContractEntity); } |
對實體的任何操作,都會跑實體驗證型別,比如在儲存時,需要驗證主鍵值是否已經儲存過,參考下面的程式碼。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public override void ValidateEntityBeforeSave(IEntityCore involvedEntity) { base.ValidateEntityBeforeSave(involvedEntity); SalesContractEntity salesContract = (SalesContractEntity)involvedEntity; if (string.IsNullOrEmpty(salesContract.ContractNo)) throw new EntityValidationException("Contract No. is required"); if (string.IsNullOrEmpty(salesContract.CustomerNo)) throw new EntityValidationException("Customer No. is required"); if (salesContract.IsNew) { ISalesContractManager salesContractManager = CreateProxyInstance<ISalesContractManager>(); if (salesContractManager.IsSalesContractExist(salesContract.ContractNo)) throw new RecordDuplicatedException(salesContract.ContractNo, "Cotract No. is already used"); } } |
4 複製 Clone
窗體支援兩種複製方法,複製當前載入的值,複製其它物件的值。物件值複製完成後,需用重置新的物件的主鍵值,讓它為空或是為預設值,供使用者修改。複製其它物件的值需要彈出物件選擇窗體。這兩種複製都需要注意複製完後,重置物件初始化預設值。因為複製時採用的是深拷貝,沒有跑物件初始化值。
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 26 27 28 29 30 31 |
protected override object Clone(Dictionary<string, string> refNo) { base.Clone(refNo); string receiptRefNo; refNo.TryGetValue("ContractNo", out receiptRefNo); if (string.IsNullOrEmpty(receiptRefNo)) { using (ILookupForm lookup = GetLookupForm("SalesContractLookup")) { if (!AllowViewAllTransaction) lookup.PredicateBucket = new RelationPredicateBucket(SalesContractFields.CreatedBy == Shared.CurrentUser.UserId); lookup.SetCurrentValue(CurrentRefNo); if (lookup.ShowDialog() != DialogResult.OK) return null; receiptRefNo = lookup.GetFirstSelectionValue(); } } if (!string.IsNullOrEmpty(receiptRefNo)) { this._salesContractEntity = this._salesContractEntityManager.CloneSalesContract(receiptRefNo); return this._salesContractEntity; } return null; } |
注意到這些方法全部是以override重寫的方式出現,被基類呼叫。每個方法都執行在後臺執行緒控制元件BackgroundWorker執行緒中,所以都不能操作介面控制元件。
5 記錄瀏覽 Record Navigator
主要用於工具條的前四個按鈕,分別對應第一筆資料,前一筆資料,下一筆資料,最後一筆資料。工具條瀏覽需要設定窗體的NavigateBindingSource屬性,傳入一個空白的BindingSource控制元件或是一個裝載頁面所有資料的BindingSource控制元件,工具條瀏覽方法重寫參考下面的程式片段。
1 2 3 4 5 6 |
protected override void InitNavigator(InitNavigatorArgs args) { base.InitNavigator(args); args.SortExpression.Add(SalesContractFields.ContractNo | SortOperator.Ascending); args.PredicateBucket.PredicateExpression.Add(SalesContractFields.Closed == false); } |
6 記錄過帳 Record Post
主要用於業務單據過帳邏輯,基類的方法為空方法,不同的業務單據有不同的邏輯定義,沒有可複用的程式碼。
1 2 3 4 5 6 |
protected override void Post(EntityBase2 entityToPost) { base.Post(entityToPost); SalesContractEntity resignEntity = entityToPost as SalesContractEntity; _salesContractEntityManager.PostSalesContract(resignEntity); } |
這裡的過帳可以理解為確認,批核,不可更改的意思。在有些系統中叫送審,稽核。
7 列印 Print
一般在設計檢視繫結當前窗體對應的水晶報表檔案以及要傳入的引數。也可以通過重寫列印方法傳入傳數值。
1 2 3 4 |
protected override void Print(ref Dictionary<string, SelectionFormula> selectionFormulas, ref List<FormulaField> formulaFields, ref List<ParameterField> parameterFields) { base.Print(ref selectionFormulas, ref formulaFields, ref parameterFields); } |
重寫方法常用於動態指定報表檔案,或是動態的引數值。不推薦在程式碼中這樣寫,這樣做導致每次都需要重新編譯和分發程式,推薦在水晶報表中做公式,在分發時只需要拷貝檔案即可。