Windows Forms程序實現界面與邏輯分離的關鍵是數據綁定技術(Data Binding),這與微軟推出的ASP.NET MVC的原理相同,分離業務代碼與界面層,提升系統的可維護性。html
數據綁定技術的主要內容:數據源(Data Source),控件(Control),綁定方式(Binding)。經過綁定控件,將數據源中的屬性綁定到界面控件中,並能夠實現雙向的數據綁定。當界面控件中的值發生改變時,能夠經過數據綁定控件,更新控件綁定的對象,當經過代碼直接改變對象值後,數據綁定控件能夠將值更新到它所綁定的界面控件中。數據庫
ERP系統選擇LLBL Gen ORM框架做爲數據訪問技術基礎,數據源爲實體類型。主要綁定如下幾種類型:app
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。ide
控件Control 簡單的數據綁定,好比TextEditor,NumbericEditor,CheckedEdidtor只綁定一個屬性,複雜的數據定好比Grid綁定一個對象的多個屬性,根據實體的屬性自動設定綁定控件的屬性。fetch
數據源控件BindingSource 負責將控件的屬性和對象的屬性關聯起來。對象的屬性值會被自動傳遞個控件的屬性,而控件的屬性值更改後也會直接傳回對象的屬性(雙向綁定)。
簡單綁定綁定 下面的代碼的含義是將客戶編號綁定到txtCustomerNo控件的Text屬性。this
CustomerEntity customer=...... txtCustomerNo.DataBindings.Add("Text", customer, "CustomerNo");
複雜數據綁定 下面的代碼將客戶集合(EntityCollection)綁定到網格控件。spa
EntityCollection<CustomerEntity> customers=...... customerBindingSource.DataSource = customers; gridCustomer.SetDataBinding(customerBindingSource, "Customer", true, true);
下拉選擇框(Combox)的數據綁定,下面是綁定到枚舉類型的例子:設計
comboDepartment.InitializeValueFromEnum<CustomerType>();
代碼中將枚舉的值反射出來,建立爲ValueList綁定到下拉框中。雙向綁定
從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; }
幾乎全部的界面綁定業務實體到控件中的方法都一致,參考下面的方法:
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操做的,因此它是通用的方法實現,被框架調用實現界面與邏輯分離。