在上篇随笔《脊柱外科病人资料管理系统的界面设计分析》中介绍了一些常用的界面设计方面的内容,本篇继续上一篇,介绍脊柱外科病人管理系统的JOA评分记录模块的界面设计以及实现方面的内容。
JOA(全称 Japanese Orthopaedic Association Scores for Assessment of Cervical Myelopathy),日本骨科学会(JOA)颈椎病疗效评定标准,用于在脊柱外科的术前术后,对患者身体状况进行量化,并制定相关的护理方案提供依据。JOA评分记录模块,是软件《脊柱外科病人资料管理系统》的一个亮点,能使外科医生或者护士,对患者的信息进行全面的记录和研究参考。
JOA评分记录,有点类似于考题的方式,对各项内容进行分值的评估,每项记录的得分不同,汇总成一个总的得分,用于量化评估,它的分类大致如下所示。
从上面的文档截图可以看到,评分项目可以分为一个大类(如自觉症状),多个子项目(如下腰痛)这样的组织方式,然后我们需要记录每项的得分,以及所有项目累加的总分。
通过分拆,我们可以把一个记分的题目作为一个控件,每个题目它自己的得分明细可以动态从数据库获取即可,界面控件设计如下所示。
通过上面的分析,我们知道,一个项目大类(如自觉症状)包含了多个子项目(如下腰痛、步态等)这样的题目,也就是多个上面控件的实例,那么我们设计的项目大类控件界面如下所示。
以上的界面,其实就是在一个大的项目大类控件上组合多个子项目控件的效果。
然后,整个JOA评分的界面就是多个上面项目大类的组合了,组合成了一个JOA评分记录模块的控件效果,如下所示。
最后,我们在第一个文档截图里面看到,JOA评分分为两个方面,一个是术前的,一个是术后的,他们的评分界面完全一样,那么我们可以把它们用两个Tab界面进行分开处理,最后得到的运行界面如下所示。
为了在界面呈现各项分数项目,我们需要在数据库设计一个表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评分记录模块的实现,进一步拓展,可以把它应用到考试题目上 ,可以作为动态抽取题目,然后记录测试者的题目选择信息,最后把测试者的答案和标准答案对比得出用户的总分,这样就完成了试题的动态测试案例了。