表單開發的主題內容
1. 表單內容設定
2. CRUD與代碼產生
3. 進階選項
4. 設計規範
使用EPN架構製作員工主檔表單,如下
屬性設定
增加表單,EmployeeMaster
設定繼承的類為EntryForm
[FunctionCode("SAISEM")]
public partial class EmployeeMaster : EntryForm
{
public EmployeeMaster()
{
InitializeComponent();
}
}
並且添加功能編碼SAISEM
在EPN的分類設定選項中,設定表單的EPN屬性如下
這表示,表單支援新增,修改,刪除,拷貝,匯入/匯出,過帳,增加附件,不支援批核(工作流程)
拖動一個EntityCollection到表單中,並設定它的EntityFactoryToUse對象
依照需要展示的資料層次結構,拖動為數個BindingSource到表單中。
舉例:比如要顯示採購單頭和採購明細資訊,需要拖動2個BindingSource,分別顯示採購單頭和採購明細
對於本例,需要顯示員工表的表頭資訊和檔案(Document)資訊,則需要拖動2個BindingSource
組件視窗看起來是這樣
然後依次設定BindingSource的資料來源
設定employeeBindingSource的資料來源
設定employeeDocumentBindingSource的資料來源
依據需要顯示的欄位,放表單中拖放TextEditor,NumberiEditor,Grid,CheckBox
在Visual Studio 2010中,看起來是這樣
設定表單的資料來源屬性,MainBindingSource和NavigateBindingSource
如果當前表單要一次性將所有的Entity讀入到表單的GridView,則將Form的MainBindingSource與navigatorBindingSource都設定為employeeBindingSource。
如果表單開啟時只需顯示一條Entity,則將表單的MainBindingSource設為employeeBindingSource,再添加一個新的bindingSource組件,資料來源不要選,留空。將其設為表單的NavigatorBindingSource。
設定表單主鍵屬性
設定值如下
至此,F5,表單已經可以運行,在快速啟動欄中輸入SAISEM,即可啟動表單,如開篇所展示的效果。
代碼產生
到目前為止,表單已經可以運行,但並不可以進行資料的讀寫,我們還要給它新增加資料讀寫代碼。
再引用開發架構中關於庫存項目的一張圖
BusinessLogic 商務邏輯,業務實體,這個由代碼產生器自動產生
Interface 介面 讀寫資料和商務邏輯的介面
Manager 實現介面 實現介面
System Administration/Inventory, 介面,放入EmployeeMaster表單
為此,祭出代碼產生工具CodeSmith,幫忙我們完成枯燥容易出錯的應用程式框架的代碼。
介面檔案IEmployeeManager,將產生的檔案拷貝到Interface項目中
實現檔案EmployeeManager,將產生的檔案拷貝到Manager項目中
表單檔案產生,將產生的檔案內容拷貝到EmployeeMaster表單檔案中
代碼如下
private IEmployeeManager _EmployeeManager = null ;
private EmployeeEntity _Employee = null ;
protected override void OnShown(EventArgs e)
{
base.OnShown(e);
if (!DesignMode)
{
}
}
protected override void OnLoad(EventArgs e)
{
if(!DesignMode)
this._EmployeeManager = ClientProxyFactory.CreateProxyInstance<IEmployeeManager>();
base.OnLoad(e);
}
protected override void InitNavigator(InitNavigatorArgs args)
{
base.InitNavigator(args);
args.SortExpression.Add(EmployeeFields.EmployeeNo | SortOperator.Ascending);
}
protected override EntityBase2 LoadData(Dictionary<string, string> refNo)
{
base.LoadData(refNo);
string employeeNo = string.Empty;
if (refNo.TryGetValue("EmployeeNo", out employeeNo))
{
IPrefetchPath2 prefetchPath = new PrefetchPath2((int)EntityType.EmployeeEntity);
prefetchPath.Add(EmployeeEntity.PrefetchPathEmployeeDocument);
_employee = _employeeManager.GetEmployee(employeeNo, prefetchPath);
}
else
{
_employee = new EmployeeEntity();
}
return _employee;
}
protected override void BindControls(EntityBase2 entity)
{
base.BindControls(entity);
this.employeeBindingSource.DataSource = entity;
}
protected override EntityBase2 Add( )
{
base.Add();
this._Employee = new EmployeeEntity();
return _Employee;
}
protected override EntityBase2 Save(EntityBase2 entityToSave)
{
base.Save(entityToSave);
EmployeeEntity _Employee = (EmployeeEntity)entityToSave;
this._Employee =this._EmployeeManager.SaveEmployee(_Employee);
return _Employee;
}
protected override void Delete(EntityBase2 entityToDelete)
{
base.Delete(entityToDelete);
EmployeeEntity Employee = (EmployeeEntity)entityToDelete;
this._EmployeeManager.DeleteEmployee(Employee);
}
protected override object Clone(Dictionary<string, string> refNo)
{
base.Clone(refNo);
string employeeNo = string.Empty;
refNo.TryGetValue("EmployeeNo", out employeeNo);
if (string.IsNullOrEmpty(employeeNo))
{
using (ILookupForm lookup = EPN.Common.GetLookupForm("EmployeeLookup"))
{
lookup.SetCurrentValue(CurrentRefNo);
if (lookup.ShowDialog() != DialogResult.OK)
return null;
employeeNo = lookup.GetFirstSelectionValue();
}
}
if (!string.IsNullOrEmpty(employeeNo))
{
this._employee = this._employeeManager.CloneEmployee(employeeNo);
return this._employee;
}
return null;
}
protected override void ReleaseResources( )
{
base.ReleaseResources();
try
{
_Employee = null;
_EmployeeManager= null;
}
catch
{
}
}
到此,已經完成了表單開發的所有內容。
為加入更多的驗證邏輯,產生一些驗證,放到驗證項目中
比如,需要驗證社會安全號碼碼必須輸入,電話號碼要符合當地的編號規則,等等,這些邏輯驗證,都可以寫在這裡
進階選項
到此為止,幾乎是沒有敲任何的代碼,我們就完成了員工主檔的開發,這樣的速度,肯定讓你吃驚,也開始疑惑:
1. 商務邏輯寫在哪裡?
比如,當輸入員工的出生日期為1983年時,程式應當自動算出員工的年齡為DateTime.Now- DateTime(1983,10,20), 當輸入員工的學曆為本科時,它在公司的組織架構層級中,就自動對應22B,而不是大專畢業對應的21B。
參考一開始設計的圖,程式從一開始就是介面和邏輯分離的,邏輯直接寫到BusinessLogical項目中去,也就是代碼自動產生的EmployeeEntity檔案。
這裡有一個小技巧,代碼產生器會為我們產生2個EmployeeEntity檔案,一個是EmployeeEntity.cs, 另一個是EmployeeEntity.Logical.cs的檔案,前一個檔案,是依據資料表的欄位,自動產生的檔案,後一個檔案,是存放自訂邏輯的地方,也就是需要放入邏輯的代碼檔案。
2. 程式介面上的特殊要求,通過以上產生的程式碼肯定沒有做到。
比如,需要給員工主檔添加圖片顯示,中,顯示James Bond的圖片,這個功能需要手動寫。
其次,要給物料主檔添加條碼支援,將產生的條碼作為物料編碼的依據,這個功能也需要手寫。
再比如開篇中的那張圖片,Employee No.後面的按鈕,是用來給員工智能編碼用的。一般會依據員工學曆,工作經驗,專業的不同來產生相應的員工編碼。
3. 邏輯和介面嚴格分離帶來的一些問題。
如果要在邏輯裡面根據不同的條件,給使用者一個提示,這個會相當麻煩。因為邏輯與介面已經嚴格分離,絕不允許在邏輯中調用MessageBox.Show, 為了阻止這個行為的發生,在邏輯的References中,直接刪除了System.Windows.Forms的引用。
同理,在介面中訪問邏輯中的部分程式碼片段也相當麻煩,有時候在介面中也需要直接存取資料庫,比如,在做銷售送貨時,根據目前使用者輸入的銷售單號,判斷這個SO是否已經完成送貨,如果已經完成送貨,則不允許產生送貨單,這個邏輯只有在介面上實現,因為在BusinessLogical中Shipment對象還沒有產生,無法做判斷。
4. 效能 Performance
在把資料從資料庫讀到介面時,ORM和ADO.NET效能區別不大,但是,在做儲存和更新時,ORM會判斷當前已經做出修改的資料,僅僅產生這個被修改資料的UPDATE語句,這樣的效能提升是很佔優勢的。
ADO.NET中,更新員工的語句一般是
UPDATE Name=’James Bond’ , Country=’ England’ WHERE EmployeeNo=’007’
很少可能會這樣寫,為了更新員工的名字,寫一個SQL
UPDATE Name=’James Bond’ WHERE EmployeeNo=’007’
更新員工的國籍,於是又寫一個SQL
UPDATE Country=’ England’ WHERE EmployeeNo=’007’
ORM架構會很高興的幫忙我們判斷,究竟有哪些資料被修改(dirty),並且只產生這些資料的UPDATE.
表單規範
CodeSmith已經幫忙我們做好了大量的重複代碼,也相當規範。
如果仍然要手寫代碼,遵守共同的規範,會使代碼看起來更統一,規範
控制項命名規範
控制項類型 |
首碼 |
舉例 |
Label |
lbl |
lblMessage |
Button |
Btn |
btnSave |
GroupBox |
gbx |
gbxMain |
GridView |
grd |
grdSalesOrderDetails |
僅有這個規範還不行,畢竟遵守起來還是有點困難,EPN架構還運用分詞技術,自動為表單中的控制項重新命名。
我們的表單中的控制項代碼,看起來是這樣
private EPN.WinUI.TextEditor txtDeptName;
private EPN.WinUI.Label lblDeptName;
private EPN.WinUI.PictureBox picImage;
private EPN.WinUI.GridView grdSalesOrder;
當為控制項綁定屬性時,自動為控制項重新命名。比如,我將TextBox1綁定到資料來源EmployeeNo,於是控制項被重新命名為txtEmployeNo,看控制項名稱,就可以知道資料來源,也知道控制項類型,以達到高度的規範統一。
表單的設計標準
1 DialogBox,對話方塊,一般要能響應ESC和Enter鍵,也有OK和Cancel2個按鈕
2 表單中控制項的Tab order是否合理
3 Short cut key,快鍵鍵訪問,所有的表單也需要一致,也不能有衝突。
4 當表單中需要盛放的資料量較大時,有2種思路
以GroupBox分組,以方便查看
當資料量再增多時,以Tab分組
5 表單執行時間較長的任務,要使BackgroundWorker,以保證表單可以接受響應,任務執行完畢後,或用MessageBox.Show顯示successfully done,或與VS2010一樣,在status bar中顯示執行結果.比如在VS2010中成功check in代碼後,在status bar中會顯示成功簽入變更集54498.
同時,cursor也不能放過,像這樣寫:
this.Cursor=Cursors.WaitCursor;
.....long operation
this.Cursor=Cursors.Default;
6 表單組織方式,避免MDI forms,推薦tab式 MDI。
必要時,可以這樣,把form放到panel中,設定form的TopLevel = false
7 ListView控制項,要支援copy,而且可以多行copy
8 Enter和Tab key的作用,都會去下一個tab order的control
9 WinForms程式,最好的可以接受command line的啟動方式,這樣可以用bat命令啟動。而且可以加啟動參數。
VS2005、2008,2010都可以從command line啟動。還記得常用的deven /resetpackage嗎?
10 盡量阻止一台電腦,同時運行程式的兩個執行個體,使用Singletion模式。
11 Target盡量是AnyCPU,而不x86。因為Microsoft Jet Access 資料庫沒有64bit的版本,寧願移除對Jet Access的依賴和使用,也不願把Target改成x86. 自Windows 7以來,64bit的OS已經相當普及,要使自己的程式不會一開啟就stop working,就盡量編譯成AnyCPU吧。
12 RichTextBox,如果是程式新增內容,需要auto scroll到最行的一行資料。觀察VS2010編譯的時候,Output視窗
13 對於使用者不能修改的資料,不僅要使TextBox為readonly,最好還要使它的背景顏色變成gray,一目瞭然。
必須輸入資料的控制項,需要有個統一的提示,像這樣
當該控制項有值時,右邊的紅色圖片hidden,沒有值時,顯示圖片。這和Ajax中的watermark是相同的原理