Windows Forms程序實現界面與邏輯分離的關鍵是數據綁定技術(Data Binding),這與微軟推出的ASP.NET MVC的原理相同,分離業務代碼與界面層,提高系統的可維護性。
數據綁定 Data Binding
數據綁定技術的主要內容:數據源(Data Source),控件(Control),綁定方式(Binding)。通過綁定控件,將數據源中的屬性綁定到界面控件中,並可以實現雙向的數據綁定。當界面控件中的值發生改變時,可以通過數據綁定控件,更新控件綁定的對象,當通過代碼直接改變對象值后,數據綁定控件可以將值更新到它所綁定的界面控件中。
ERP系統選擇LLBL Gen ORM框架作為數據訪問技術基礎,數據源為實體類型。主要綁定以下幾種類型:
IEntity 數據庫表的實體映射,每個屬性。綁定時,控件的類型與實體的屬性類型嚴格匹配與驗證。
/// <summary> The TotLdiscAmt property of the Entity PurchaseOrder<br/> /// Mapped on table field: "PUORDH"."TOT_LDISC_AMT"<br/> /// Table field type characteristics (type, precision, scale, length): Decimal, 16, 2, 0<br/> /// Table field behavior characteristics (is nullable, is PK, is identity): true, false, false<br/><br/> /// ReadOnly: <br/></summary> /// <remarks>Mapped on table field: "PUORDH"."TOT_LDISC_AMT"<br/> /// Table field type characteristics (type, precision, scale, length): Decimal, 16, 2, 0<br/> /// Table field behavior characteristics (is nullable, is PK, is identity): true, false, false</remarks> public virtual System.Decimal SalesAmount { get { return (System.Decimal)GetValue((int)PurchaseOrderFieldIndex.TotLdiscAmt, true); } set { SetValue((int)PurchaseOrderFieldIndex.TotLdiscAmt, value); } } /// <summary> The TotAtaxAmt property of the Entity PurchaseOrder<br/> /// Mapped on table field: "PUORDH"."TOT_ATAX_AMT"<br/> /// Table field type characteristics (type, precision, scale, length): Decimal, 16, 2, 0<br/> /// Table field behavior characteristics (is nullable, is PK, is identity): true, false, false<br/><br/> /// ReadOnly: <br/></summary> /// <remarks>Mapped on table field: "PUORDH"."TOT_ATAX_AMT"<br/> /// Table field type characteristics (type, precision, scale, length): Decimal, 16, 2, 0<br/> /// Table field behavior characteristics (is nullable, is PK, is identity): true, false, false</remarks> public virtual System.String CustomerNo { get { return (System.Decimal)GetValue((int)PurchaseOrderFieldIndex.TotAtaxAmt, true); } set { SetValue((int)PurchaseOrderFieldIndex.TotAtaxAmt, value); } }
上面的代碼例子定義了二個屬性:數值類型的銷售金額,字符串類型的客戶編號。在做數據綁定設計時,需要分別選擇NumbericEditor和TextEditor。
控件Control 簡單的數據綁定,比如TextEditor,NumbericEditor,CheckedEdidtor只綁定一個屬性,復雜的數據定比如Grid綁定一個對象的多個屬性,根據實體的屬性自動設定綁定控件的屬性。
數據源控件BindingSource 負責將控件的屬性和對象的屬性關聯起來。對象的屬性值會被自動傳遞個控件的屬性,而控件的屬性值更改后也會直接傳回對象的屬性(雙向綁定)。
簡單綁定綁定 下面的代碼的含義是將客戶編號綁定到txtCustomerNo控件的Text屬性。
CustomerEntity customer=...... txtCustomerNo.DataBindings.Add("Text", customer, "CustomerNo");
復雜數據綁定 下面的代碼將客戶集合(EntityCollection)綁定到網格控件。
EntityCollection<CustomerEntity> customers=...... customerBindingSource.DataSource = customers; gridCustomer.SetDataBinding(customerBindingSource, "Customer", true, true);
下拉選擇框(Combox)的數據綁定,下面是綁定到枚舉類型的例子:
comboDepartment.InitializeValueFromEnum<CustomerType>();
代碼中將枚舉的值反射出來,創建為ValueList綁定到下拉框中。
業務邏輯 Business Logic
從ERP的技術層面考慮,ERP包含以下四種邏輯:
1 自動賦值邏輯。在采購訂單輸入窗體中,輸入供應商編號,自動帶入供應商名稱,供應商付款貨幣和付款條款的值到當前采購訂單單據中。
//PurchaseOrderEntity.cs private void OnChangeVendorNo(string originalValue) { if (string.CompareOrdinal(this.VendorNo, originalValue) == 0) return; IVendorManager vendorMan = ClientProxyFactory.CreateProxyInstance<IVendorManager>(); VendorEntity vendor = vendorMan.GetValidVendor(Shared.CurrentUserSessionId, this.VendorNo); this.VendorName = vendor.VendorName.; this.PayTerms = vendor.PayTerms; this.PayCurrency = vendor.PayCurrency; }
2 計算邏輯。輸入單價和數量后,自動計算出物料金額金額,再輸入折扣率后,計算出折扣金額和應付款金額。
//PurchaseOrderEntity.cs private void OnChangeQty(decimal? originalValue) { if (!originalValue.HasValue || this.Qty != originalValue.Value) { this.Amount=this.Qty * this.UnitPrice; } }
3 數據驗證。出倉時,庫存余額不能是負數。付款金額必須大於等於訂單金額時,訂單才能完成付款。
//Shipment.cs public bool ValidateQtyShiped(ShipmentEntity shipment, decimal value) { IInventoryBalanceManager balanceManager = CreateProxyInstance<IInventoryBalanceManager>(); decimal qtyRemaining = balanceManager.GetItemBalance(shipment.ItemNo); if (qtyRemaining < 0| qtyRemaining<value ) throw new FieldValidationException("Negative item balance detected"); return true; }
4 業務關聯。送貨單要依據銷售訂單的訂單數量為依據來送貨,采購驗貨的結果中,合格和不合格數量要等於采購訂單要驗貨的數量。
//PurchaseInspectionEntity.cs private void OnChangeGrnNo(string originalValue) { if (Shared.StringCompare(this.GrnNo, originalValue) == 0)
return; IPrefetchPath2 prefetchPath = new PrefetchPath2(EntityType.GrnEntity); prefetchPath.Add(GrnEntity.PrefetchPathGrnOrderDetails); IGrnManager grnManager =CreateProxyInstance<IGrnManager>(); GrnEntity grn = grnManager.GetGrn(Shared.CurrentUserSessionId, this.GrnNo, prefetchPath); //待檢驗數量= 訂單數量 - 已檢驗數量 this.Qty=grn.QtyOrder - grn.QtyInspected; }
界面設計 Interface Design
幾乎所有的界面綁定業務實體到控件中的方法都一致,參考下面的方法:
protected override void BindControls(EntityBase2 entity) { base.BindControls(entity); this.purchaseOrderBindingSource.DataSource = entity; }
界面中讀取數據的方法LoadData,讀取數據后綁定到數據源控件BindingSource控件中。
//PurchaseOrderEntry.cs protected override EntityBase2 LoadData(Dictionary<string, string> refNo) { string orderNo; if (refNo.TryGetValue("OrderNo", out orderNo)) { PurchaseOrderEntity result = _purchaseOrderManager.GetPurchaseOrder(orderNo); this._purchaseOrder=result; } return _purchaseOrder;
}
以上是界面層中數據綁定的兩個核心方法,讀取數據並將數據綁定到界面控件中。注意到方法是通過參數實體EntityBase2操作的,所以它是通用的方法實現,被框架調用實現界面與邏輯分離。
