在上篇隨筆《脊柱外科病人資料管理系統的界面設計分析》中介紹了一些常用的界面設計方面的內容,本篇繼續上一篇,介紹脊柱外科病人管理系統的JOA評分記錄模塊的界面設計以及實現方面的內容。
JOA(全稱 Japanese Orthopaedic Association Scores for Assessment of Cervical Myelopathy),日本骨科學會(JOA)頸椎病療效評定標准,用於在脊柱外科的術前術后,對患者身體狀況進行量化,並制定相關的護理方案提供依據。JOA評分記錄模塊,是軟件《脊柱外科病人資料管理系統》的一個亮點,能使外科醫生或者護士,對患者的信息進行全面的記錄和研究參考。
1、JOA評分記錄模塊的分析
JOA評分記錄,有點類似於考題的方式,對各項內容進行分值的評估,每項記錄的得分不同,匯總成一個總的得分,用於量化評估,它的分類大致如下所示。
從上面的文檔截圖可以看到,評分項目可以分為一個大類(如自覺症狀),多個子項目(如下腰痛)這樣的組織方式,然后我們需要記錄每項的得分,以及所有項目累加的總分。
通過分拆,我們可以把一個記分的題目作為一個控件,每個題目它自己的得分明細可以動態從數據庫獲取即可,界面控件設計如下所示。
通過上面的分析,我們知道,一個項目大類(如自覺症狀)包含了多個子項目(如下腰痛、步態等)這樣的題目,也就是多個上面控件的實例,那么我們設計的項目大類控件界面如下所示。
以上的界面,其實就是在一個大的項目大類控件上組合多個子項目控件的效果。
然后,整個JOA評分的界面就是多個上面項目大類的組合了,組合成了一個JOA評分記錄模塊的控件效果,如下所示。
最后,我們在第一個文檔截圖里面看到,JOA評分分為兩個方面,一個是術前的,一個是術后的,他們的評分界面完全一樣,那么我們可以把它們用兩個Tab界面進行分開處理,最后得到的運行界面如下所示。
2、JOA評分記錄的邏輯實現
為了在界面呈現各項分數項目,我們需要在數據庫設計一個表JOAItem,用來存儲每個評分項目的內容,然后在界面中根據分類進行動態展示。
然后定義一個評分子項目的信息對象ControlItem,作為上面控件的信息對象,方便控件和數據庫關聯起來,記分或者存儲明細到數據庫中,代碼如下所示。
/// <summary> /// JOA評分項目明細 /// </summary> [DataContract] public class ControlItem { /// <summary> /// 序號,從1開始 /// </summary> [DataMember] public virtual int Seq { get; set; } /// <summary> /// 分類名稱 /// </summary> [DataMember] public virtual string Category { get; set; } /// <summary> /// 項目明細列表(包括項目得分等信息) /// </summary> [DataMember] public virtual List<JOAItemInfo> ItemList { get; set; } /// <summary> /// 評分項目第一項得分(默認得分) /// </summary> public virtual decimal DefaultScore { get { if (ItemList == null || ItemList.Count == 0) { return 0M; } else { return ItemList[0].Score; } } } /// <summary> /// 默認構造函數 /// </summary> public ControlItem() { ItemList = new List<JOAItemInfo>(); } /// <summary> /// 參數構造函數 /// </summary> /// <param name="seq">序號</param> /// <param name="category">分類名稱</param> /// <param name="itemList">項目明細列表</param> public ControlItem(int seq, string category, List<JOAItemInfo> itemList) : this() { this.Seq = seq; this.Category = category; this.ItemList = itemList; } }
在上面的評分控件中,我們整合以上的信息對象作為一個整體,部分代碼如下所示。
public partial class ScoreItemControl : DevExpress.XtraEditors.XtraUserControl { /// <summary> /// 單項的序號,從1開始 /// </summary> public int Seq = 1; /// <summary> /// 控件綁定的信息 /// </summary> public ControlItem ControlItem { get;set; } /// <summary> /// 單項的得分 /// </summary> public decimal Score { get; set; } /// <summary> /// 處理分數變化后的事件觸發 /// </summary> public event ScoreChangedHandler OnScoreChanged; ............................
當我們在評分控件中指定了ControlItem信息后,在控件的Load事件里面,將會動態綁定評分的項目明細,代碼如下所示。
private void ScoreItemControl_Load(object sender, EventArgs e) { if (ControlItem != null && !this.DesignMode) { this.lblItemIndex.Text = ControlItem.Seq.ToString(); this.layoutItem.Text = ControlItem.Category; List<CListItem> itemList = new List<CListItem>(); foreach (JOAItemInfo info in ControlItem.ItemList) { itemList.Add(new CListItem(info.ItemDetail, info.Score.ToString())); } radItemGroup.BindDictItems(itemList); } if (radItemGroup.Properties.Items.Count > 0) { this.radItemGroup.SelectedIndex = 0; } }
除了ControlItem信息對象的綁定,我們還注意到上面的OnScoreChanged事件,它就是為了我們在整個控件中實現分數動態變化的一個事件,這樣我們變化任何一個評分項目信息,單項分數和總分都會重新計算一次的事件。
/// <summary> /// 處理分數變化后的事件觸發 /// </summary> public virtual void ProcessScoreChanged(int seq, decimal score) { if (OnScoreChanged != null) { OnScoreChanged(seq, score); } } private void radItemGroup_SelectedIndexChanged(object sender, EventArgs e) { this.lblItemScore.Text = string.Format("得分({0})分", this.radItemGroup.EditValue); decimal result = 0; if (decimal.TryParse(this.radItemGroup.EditValue.ToString(), out result)) { this.Score = result; ProcessScoreChanged(Seq, result); } }
以上代碼邏輯就是最小評分控件的一個具體的實現,完成以上這些,還需要完成一個評分項目大類的具體邏輯,它的操作方式和上面差不多,也是引入一個信息對象集合作為背后得分計算的邏輯。
定義的信息對象ProjectItem代碼如下所示。
/// <summary> /// JOA評分項目信息 /// </summary> [DataContract] public class ProjectItem { /// <summary> /// 序號,從1開始 /// </summary> [DataMember] public virtual int Seq { get; set; } /// <summary> /// 項目名稱 /// </summary> [DataMember] public virtual string Project { get; set; } /// <summary> /// 項目明細列表 /// </summary> [DataMember] public virtual List<ControlItem> ItemList { get; set; } /// <summary> /// 默認構造函數 /// </summary> public ProjectItem() { ItemList = new List<ControlItem>(); } /// <summary> /// 評分項目第一項得分總和(默認得分) /// </summary> public virtual decimal DefaultScore { get { if (ItemList == null || ItemList.Count == 0) { return 0M; } else { decimal total = 0M; foreach (ControlItem item in ItemList) { total += item.DefaultScore; } return total; } } } /// <summary> /// 參數化構造函數 /// </summary> /// <param name="seq"></param> /// <param name="project"></param> /// <param name="itemList"></param> public ProjectItem(int seq, string project, List<ControlItem> itemList) : this() { this.Seq = seq; this.Project = project; this.ItemList = itemList; } }
然后該控件的邏輯代碼就是結合這個信息對象以及控件的一些事件進行處理了,控件的部分代碼如下所示。
public partial class ScoreProjectControl : DevExpress.XtraEditors.XtraUserControl { /// <summary> /// 項目大類的序號,從1開始 /// </summary> public int ProjectSeq = 1; /// <summary> /// 項目大類的信息 /// </summary> public ProjectItem ProjectItem { get; set; } /// <summary> /// 處理分數變化后的事件觸發 /// </summary> public event ScoreChangedHandler OnScoreChanged; private Dictionary<int, decimal> scoreList = new Dictionary<int, decimal>();//記錄單項的得分列表 ...................
由於每項評分子項目是單獨的評分控件,控件的布局采用了FlowLayout布局呈現方式,因此在控件的Load事件代碼如下所示。
private void ScoreProjectControl_Load(object sender, EventArgs e) { if (ProjectItem != null && !this.DesignMode) { this.lblProject.Text = string.Format("{0}、{1}", ProjectItem.Seq, ProjectItem.Project); this.flowLayoutPanel1.Controls.Clear(); int index = 1; foreach (ControlItem item in ProjectItem.ItemList) { ScoreItemControl control = new ScoreItemControl(); control.ControlItem = item; control.Seq = index++; control.OnScoreChanged += new ScoreChangedHandler(control_OnScoreChanged); this.flowLayoutPanel1.Controls.Add(control); } } }
同樣整個控件也有一個OnScoreChanged 的事件,我們看到,這個和上面介紹的事件操作方式類似,都是一級負責一級的分數處理,具體代碼如下所示。
void control_OnScoreChanged(int seq, decimal score) { if(!scoreList.ContainsKey(seq)) { scoreList.Add(seq, score); } else { scoreList[seq] = score; } //項目的總分變化 ProcessScoreChanged(ProjectSeq, this.TotalScores); }
以上的控件實現邏輯一步步遞推,就能很好實現評分項目的動態呈現,以及控件評分分數的動態變化和總分記錄保存,由於涉及的實現細節還比較多,一篇隨筆介紹內容太多顯得累贅,但是其他控件的實現邏輯和上面的操作方式差不多,在這里就不在一一贅述了,本文主要提供一個這種的實現思路進行JOA評分記錄模塊的實現,進一步拓展,可以把它應用到考試題目上 ,可以作為動態抽取題目,然后記錄測試者的題目選擇信息,最后把測試者的答案和標准答案對比得出用戶的總分,這樣就完成了試題的動態測試案例了。