吉日嘎啦通用权限管理系统解读及重构升华--高度封装的编辑窗体
吉日嘎拉,一个专注于通用权限管理系统的开发狂热者,在博客园是一个有争议的人物,不过从其文章数量及内容介绍,专注肯定不是浪得虚名,一个人把东西做的专注,也就意味着更多的投入及考虑,可以作为后来者更多的借鉴。
本人获其友情赠送一套源码,用作深入分析及提炼使用,初一接触,整个体系架构还是比较庞大,如下所示。
本文思路是源自对Winform窗体的分析研究,发现其中不少优点,很多闪亮的地方,结合本人在Winform方面的积累及研究,对即日嘎拉的代码进行进一步的提炼,于是撰写了本文,本文主要介绍继承窗体的封装使用。
我们先来分析下吉日嘎拉其对窗体的设计代码,一步步进行深入的研究。
首先其也是设计一个窗体基类BaseForm, 其他窗体继承自BaseForm基类,这也是一种常用的设计。
代码
///
<summary>
///
加载窗体
///
</summary>
private
void
FormOnLoad()
{
//
多语言国际化加载
this
.Localization(
this
);
//
获得权限
this
.GetPermission();
//
设置焦点
this
.ActiveControl
=
this
.txtCode;
this
.txtCode.Focus();
}
#endregion
private
void
FrmItemDetailsAdd_Load(
object
sender, EventArgs e)
{
if
(
!
this
.DesignMode)
{
//
设置鼠标繁忙状态
this
.Cursor
=
Cursors.WaitCursor;
try
{
this
.FormOnLoad();
}
catch
(Exception ex)
{
this
.ProcessException(ex);
}
finally
{
//
设置鼠标默认状态
this
.Cursor
=
Cursors.Default;
}
}
}
#region
private void SaveEntity(bool close)
///
<summary>
///
保存
///
</summary>
///
<param name="close">
关闭窗体
</param>
private
void
SaveEntity(
bool
close)
{
//
检查输入的有效性
if
(
this
.CheckInput())
{
//
设置鼠标繁忙状态
this
.Cursor
=
Cursors.WaitCursor;
try
{
if
(
this
.SaveEntity())
{
if
(close)
{
//
关闭窗口
this
.Close();
}
else
{
this
.ClearScreen();
}
}
}
catch
(Exception ex)
{
this
.ProcessException(ex);
}
finally
{
//
设置鼠标默认状态
this
.Cursor
=
Cursors.Default;
}
}
}
#endregion
private
void
btnAdd_Click(
object
sender, EventArgs e)
{
this
.SaveEntity(
false
);
}
private
void
btnSave_Click(
object
sender, EventArgs e)
{
this
.SaveEntity(
true
);
}
private
void
btnCancel_Click(
object
sender, EventArgs e)
{
this
.Close();
}
从其中的封装调用我们看到,吉日嘎拉已经把很多变化的代码抽象出来,也不失为一个较好的设计,另外对窗体的输入回车转Tab也做了一个通用的转换,如下所示:
代码
private
void
FrmItemDetailsAdd_KeyDown(
object
sender, KeyEventArgs e)
{
string
keyCode
=
e.KeyCode.ToString();
switch
(keyCode)
{
case
"
Enter
"
:
case
"
Return
"
:
if
(
this
.ActiveControl
is
TextBox)
{
if
(
!
((TextBox)
this
.ActiveControl).Multiline)
{
SendKeys.Send(
"
{TAB}
"
);
}
}
break
;
}
switch
(e.KeyValue)
{
case
116
:
//
点击了F5按钮
this
.FormOnLoad();
break
;
}
}
闪亮之处还有很多,本文暂时在此打住,介绍一下其中可以重构更好的地方。
由于吉日嘎拉的窗体设计,对于一般常用到的编辑数据、新增数据窗体,分开了两个不同的窗体进行处理,而且由于BaseForm窗体没有对通用的函数进行进一步的抽象,因此,编辑及新增窗体多了很多重复累赘的代码,其实可以把新增、编辑合并一个窗体,然后根据新增、编辑两种不同的条件进行处理即可。
由于BaseForm一般需要在大多数的窗体中,而新增编辑数据窗体一般较为特殊一点,可以再增加一个基类BaseEditForm,用来做新增编辑窗体的基类,该基类继承自BaseForm类,工程代码如下所示。
其中运行例子的效果如下所示:
编辑以及新增我们整合在一个窗体中,先看看该基类的设计视图,我们在其中添加了3个按钮(常用的添加、保存、关闭按钮)。
窗体的代码我大类采用了可重载的虚函数,留给子类窗体进行实现不同的处理操作,如窗体加载显示操作、显示数据到控件的操作、保存、新增等函数都是需要实现的,而调用逻辑以及一些通用的处理,则在基类BaseEditForm中实现,子类不用重复这些代码,按钮控件(添加、保存、关闭)的事件处理也已经进行了逻辑封装,如下所示。
代码
public
override
void
FormOnLoad()
{
base
.FormOnLoad();
if
(
!
this
.DesignMode)
{
if
(
!
string
.IsNullOrEmpty(ID))
{
this
.Text
=
"
编辑
"
+
this
.Text;
this
.btnAdd.Visible
=
false
;
//
如果是编辑,则屏蔽添加按钮
}
else
{
this
.Text
=
"
新建
"
+
this
.Text;
}
DisplayData();
}
}
///
<summary>
///
显示数据到控件上
///
</summary>
public
virtual
void
DisplayData()
{
}
///
<summary>
///
检查输入的有效性
///
</summary>
///
<returns>
有效
</returns>
public
virtual
bool
CheckInput()
{
return
true
;
}
///
<summary>
///
清除屏幕
///
</summary>
public
virtual
void
ClearScreen()
{
this
.ID
=
""
;
///
/需要设置为空,表示新增
ClearControlValue(
this
);
this
.FormOnLoad();
}
///
<summary>
///
保存数据(新增和编辑的保存)
///
</summary>
public
virtual
bool
SaveEntity()
{
bool
result
=
false
;
if
(
!
string
.IsNullOrEmpty(ID))
{
//
编辑的保存
result
=
SaveUpdated();
}
else
{
//
新增的保存
result
=
SaveAddNew();
}
return
result;
}
///
<summary>
///
更新已有的数据
///
</summary>
///
<returns></returns>
public
virtual
bool
SaveUpdated()
{
return
true
;
}
///
<summary>
///
保存新增的数据
///
</summary>
///
<returns></returns>
public
virtual
bool
SaveAddNew()
{
return
true
;
}
///
<summary>
///
保存
///
</summary>
///
<param name="close">
关闭窗体
</param>
private
void
SaveEntity(
bool
close)
{
//
检查输入的有效性
if
(
this
.CheckInput())
{
//
设置鼠标繁忙状态
this
.Cursor
=
Cursors.WaitCursor;
try
{
if
(
this
.SaveEntity())
{
MessageUtil.ShowTips(
"
保存成功
"
);
if
(close)
{
this
.DialogResult
=
DialogResult.OK;
this
.Close();
}
else
{
this
.ClearScreen();
}
}
}
catch
(Exception ex)
{
this
.ProcessException(ex);
}
finally
{
//
设置鼠标默认状态
this
.Cursor
=
Cursors.Default;
}
}
}
private
void
btnAdd_Click(
object
sender, EventArgs e)
{
this
.SaveEntity(
false
);
}
private
void
btnOK_Click(
object
sender, EventArgs e)
{
this
.SaveEntity(
true
);
}
private
void
btnCancel_Click(
object
sender, EventArgs e)
{
this
.DialogResult
=
DialogResult.Cancel;
this
.Close();
}
其中值得介绍的是,窗体的控件数据清空在基类窗体中通过遍历实现了通用的数据清空操作,该部分代码引用了“潇湘隐者的博客(
http://www.cnblogs.com/kerrycode/archive/2010/02/05/1664267.html
),对此感谢。
另外,基类窗体还实现了Tab键的转换,这个思路来源于即日嘎拉的代码,但由于是基类实现,有些不同,他的放在每个具体的子类中,因此通用性有些限制。
我们重载了ProcessCmdKey(ref Message msg, Keys keyData) 函数后,就可以实现统一的回车键转换了。
代码
protected
override
bool
ProcessCmdKey(
ref
Message msg, Keys keyData)
{
if
(keyData
==
Keys.F5)
{
this
.FormOnLoad();
}
if
((
!
(ActiveControl
is
Button))
&&
(keyData
==
Keys.Up
||
keyData
==
Keys.Down
||
keyData
==
Keys.Enter))
{
if
(keyData
==
Keys.Enter)
{
System.Windows.Forms.SendKeys.Send(
"
{TAB}
"
);
return
true
;
}
if
(keyData
==
Keys.Down)
{
System.Windows.Forms.SendKeys.Send(
"
{TAB}
"
);
}
else
{
SendKeys.Send(
"
+{Tab}
"
);
}
return
true
;
}
else
{
return
base
.ProcessCmdKey(
ref
msg, keyData);
}
}
最后,我们看看具体的子类窗体,看新增编辑界面需要实现的代码,如下所示,其中大部分是原子级别的操作,逻辑操作已经在基类中实现了哦:
代码
public
partial
class
FrmEditCustomer : BaseUI.BaseEditForm
{
public
FrmEditCustomer()
{
InitializeComponent();
}
///
<summary>
///
实现控件输入检查的函数
///
</summary>
///
<returns></returns>
public
override
bool
CheckInput()
{
bool
result
=
true
;
#region
输入验证
if
(
this
.txtName.Text.Length
==
0
)
{
MessageUtil.ShowTips(
"
宾客名称不能为空
"
);
this
.txtName.Focus();
result
=
false
;
}
else
if
(
this
.txtIDNumber.Text.Length
==
0
)
{
MessageUtil.ShowTips(
"
证件号码不能为空
"
);
this
.txtIDNumber.Focus();
result
=
false
;
}
#endregion
return
result;
}
///
<summary>
///
编辑或者保存状态下取值函数
///
</summary>
///
<param name="info"></param>
private
void
SetInfo(CustomerInfo info)
{
info.Address
=
txtAddress.Text;
info.CompanyName
=
txtCompany.Text;
info.IDCarType
=
cmbIDCarType.Text;
info.Name
=
txtName.Text;
info.Note
=
txtNote.Text;
info.IDNumber
=
txtIDNumber.Text;
info.Telephone
=
txtTelephone.Text;
info.Source
=
cmbSource.Text;
info.CustomerType
=
cmbType.Text;
info.Sex
=
cmbSex.Text;
}
///
<summary>
///
数据字典加载
///
</summary>
private
void
InitDictItem()
{
this
.cmbSource.Items.Clear();
this
.cmbSource.Items.AddRange(DictItemUtil.GetCustomerSource());
this
.cmbType.Items.Clear();
this
.cmbType.Items.AddRange(DictItemUtil.GetCustomerType());
this
.cmbIDCarType.Items.Clear();
this
.cmbIDCarType.Items.AddRange(DictItemUtil.GetIDCarType());
}
///
<summary>
///
数据显示的函数
///
</summary>
public
override
void
DisplayData()
{
//
数据字典加载(公用)
InitDictItem();
if
(
!
string
.IsNullOrEmpty(ID))
{
//
编辑状态下的数据显示
CustomerInfo info
=
BLLFactory
<
Customer
>
.Instance.FindByID(ID);
if
(info
!=
null
)
{
#region
显示客户信息
txtAddress.Text
=
info.Address;
txtCompany.Text
=
info.CompanyName;
txtName.Text
=
info.Name;
txtNote.Text
=
info.Note;
txtIDNumber.Text
=
info.IDNumber;
txtTelephone.Text
=
info.Telephone;
cmbSource.Text
=
info.Source;
cmbType.Text
=
info.CustomerType;
cmbSex.Text
=
info.Sex;
cmbIDCarType.Text
=
info.IDCarType;
lblCreateDate.Text
=
info.RegistrationDate.ToString();
#endregion
}
}
else
{
//
新增状态的数据显示
lblCreateDate.Text
=
DateTime.Now.ToString();
}
}
///
<summary>
///
新增状态下的数据保存
///
</summary>
///
<returns></returns>
public
override
bool
SaveAddNew()
{
CustomerInfo info
=
new
CustomerInfo();
SetInfo(info);
info.RegistrationDate
=
DateTime.Now;
bool
succeed
=
BLLFactory
<
Customer
>
.Instance.Insert(info);
return
succeed;
}
///
<summary>
///
编辑状态下的数据保存
///
</summary>
///
<returns></returns>
public
override
bool
SaveUpdated()
{
CustomerInfo info
=
BLLFactory
<
Customer
>
.Instance.FindByID(ID);
if
(info
!=
null
)
{
SetInfo(info);
bool
succeed
=
BLLFactory
<
Customer
>
.Instance.Update(info, info.ID.ToString());
return
succeed;
}
return
false
;
}
}