2023年2月

在Winform开发中,一直离不开分页处理,好的分页控件封装,能为开发节省很多时间和繁琐工作,对分页控件一直的改进和完善,也是我的兴趣之一。本次主要实现对分页控件的全面重构,优化功能及界面的处理,并统一完成普通版、基于DotNetbar界面控件版本、DevExpress界面控件版本三大版本的发布,这几个界面版本,除了界面表现效果不一样外,所有的功能展示及使用代码完全一致,保证用户的知识投资及给客户良好的使用操作。本次主要增加的功能有:可通过界面设置列表显示的列,可复制选定的行信息,可设置显示的行号、可设置显示间隔行的颜色等功能。下面分别对几个例子的截图进行总体介绍,然后在详细介绍

1)普通版(即不引用任何第三方界面控件)效果图

上图中,红色的菜单是分页控件内置的菜单项,其他程序附加是扩展菜单项目,其中复制选定行信息、设置表格显示列、打印列表是对表格基本的操作功能。下面两个版本的控件右键菜单均表现一致。

2) 基于DotNetbar界面控件版本

3) 基于DevExpress界面控件版本

使用分页控件的基础代码,在所有以上版本中,代码均一致,只是一些特殊的操作,需要利用到不同的控件,有所差异而已。初始化分页控件代码如下所示。


private

void
FrmCustomer_Load(
object
sender, EventArgs e)
{

this
.winGridViewPager1.ProgressBar
=

this
.toolStripProgressBar1.ProgressBar;

this
.winGridViewPager1.OnPageChanged
+=

new
EventHandler(winGridViewPager1_OnPageChanged);

this
.winGridViewPager1.OnStartExport
+=

new
EventHandler(winGridViewPager1_OnStartExport);

this
.winGridViewPager1.OnEditSelected
+=

new
EventHandler(winGridViewPager1_OnEditSelected);

this
.winGridViewPager1.OnDeleteSelected
+=

new
EventHandler(winGridViewPager1_OnDeleteSelected);

this
.winGridViewPager1.OnRefresh
+=

new
EventHandler(winGridViewPager1_OnRefresh);

this
.winGridViewPager1.OnAddNew
+=

new
EventHandler(winGridViewPager1_OnAddNew);

this
.winGridViewPager1.AppendedMenu
=

this
.contextMenuStrip1;
//
追加额外菜单项目



this
.winGridViewPager1.ShowLineNumber
=

true
;
//
显示行号



this
.winGridViewPager1.PagerInfo.PageSize
=

30
;
//
页面大小



this
.winGridViewPager1.EventRowBackColor
=
Color.LightCyan;
//
间隔颜色



BindData();
}

以上是对分页控件的相关事件进行处理,如果实现了相关的事件,那么相应的菜单(如新增、编辑、删除等)就会在右键菜单中出现,否则不会出现,相应的事件就是具体对应操作的处理菜单操作。另外,分页控件的一些属性,如是否显示行号、页面显示记录大小、间隔颜色等属性也可以在这里设置。

这些初始化设置完成后,一般查询界面都会有一个条件的组合,然后通过组合条件查询数据库记录并显示,显示的时候,分页控件可以设置对应字段的中文显示名称,显示的字段列,显示顺序等。
显示字段的顺序就是列表的显示顺序,这个特性也是新版本增加的功能之一

private

void
winGridViewPager1_OnPageChanged(
object
sender, EventArgs e)

{
BindData();
}


///

<summary>


///
根据查询条件构造查询语句

///

</summary>


///

<returns></returns>



private

string
GetSearchSql()
{
SearchCondition condition

=

new
SearchCondition();
condition.AddCondition(

"
Number
"
,
this
.txtNumber.Text, SqlOperator.Like)
.AddCondition(

"
Name
"
,
this
.txtName.Text, SqlOperator.Like)
.AddCondition(

"
Type
"
,
this
.cmbType.Text, SqlOperator.Like)
.AddCondition(

"
Area
"
,
this
.cmbArea.Text, SqlOperator.Like)
.AddCondition(

"
Address
"
,
this
.txtAddress.Text, SqlOperator.Like)
.AddCondition(

"
Company
"
,
this
.txtCompany.Text, SqlOperator.Like)
.AddCondition(

"
Note
"
,
this
.txtNote.Text, SqlOperator.Like)
.AddCondition(

"
Telephone1
"
,
this
.txtTelephone.Text, SqlOperator.Like,
true
,
"
Telephone
"
)
.AddCondition(

"
Telephone2
"
,
this
.txtTelephone.Text, SqlOperator.Like,
true
,
"
Telephone
"
)
.AddCondition(

"
Telephone3
"
,
this
.txtTelephone.Text, SqlOperator.Like,
true
,
"
Telephone
"
)
.AddCondition(

"
Telephone4
"
,
this
.txtTelephone.Text, SqlOperator.Like,
true
,
"
Telephone
"
)
.AddCondition(

"
Telephone5
"
,
this
.txtTelephone.Text, SqlOperator.Like,
true
,
"
Telephone
"
);


if
(chkUseDate.Checked)
{
condition.AddCondition(

"
CreateDate
"
, dateTimePicker1.Value.ToString(
"
yyyy-MM-dd
"
), SqlOperator.MoreThanOrEqual,
true
)
.AddCondition(

"
CreateDate
"
, dateTimePicker2.Value.AddDays(
1
).ToString(
"
yyyy-MM-dd
"
), SqlOperator.LessThanOrEqual,
true
);

}

string

where

=
condition.BuildConditionSql().Replace(
"
Where
"
,
""
);

return

where
;
}


private

void
BindData()
{

#region
添加别名解析


//
DisplayColumns与显示的字段名或者实体属性一致,大小写不敏感,顺序代表显示顺序,用逗号或者|分开



this
.winGridViewPager1.DisplayColumns
=

"
Number,NAME,type,Area,Company,Address,Telephone1,Telephone2,Telephone3,Telephone4,Telephone5,CreateDate,Note,LastUpdated
"
;

this
.winGridViewPager1.AddColumnAlias(
"
ID
"
,
"
编号
"
);

this
.winGridViewPager1.AddColumnAlias(
"
Number
"
,
"
客户编号
"
);

this
.winGridViewPager1.AddColumnAlias(
"
Name
"
,
"
客户名称
"
);

this
.winGridViewPager1.AddColumnAlias(
"
Type
"
,
"
客户类型
"
);

this
.winGridViewPager1.AddColumnAlias(
"
Area
"
,
"
客户地区
"
);

this
.winGridViewPager1.AddColumnAlias(
"
Company
"
,
"
客户单位
"
);

this
.winGridViewPager1.AddColumnAlias(
"
Address
"
,
"
客户地址
"
);

this
.winGridViewPager1.AddColumnAlias(
"
Telephone1
"
,
"
电话1
"
);

this
.winGridViewPager1.AddColumnAlias(
"
Telephone2
"
,
"
电话2
"
);

this
.winGridViewPager1.AddColumnAlias(
"
Telephone3
"
,
"
电话3
"
);

this
.winGridViewPager1.AddColumnAlias(
"
Telephone4
"
,
"
电话4
"
);

this
.winGridViewPager1.AddColumnAlias(
"
Telephone5
"
,
"
电话5
"
);

this
.winGridViewPager1.AddColumnAlias(
"
CreateDate
"
,
"
开户日期
"
);

this
.winGridViewPager1.AddColumnAlias(
"
Shop_ID
"
,
"
分店ID
"
);

this
.winGridViewPager1.AddColumnAlias(
"
Note
"
,
"
备注
"
);

this
.winGridViewPager1.AddColumnAlias(
"
LastUpdated
"
,
"
更新日期
"
);


#endregion



string

where

=
GetSearchSql();

this
.winGridViewPager1.DataSource
=
BLLFactory
<
Customer
>
.Instance.Find(
where
,
this
.winGridViewPager1.PagerInfo);
}


private

void
btnSearch_Click(
object
sender, EventArgs e)
{
BindData();
}


很多人以前看过我介绍分页控件的用法的时候,总会觉得上面的数据绑定方式看似简单,实际操作可能绑定数据总是有问题,因为不知道Find函数里面需要实现那些操作,因为这些是基于我框架的应用逻辑。其实Find方法就是根据分页属性计算获取对应记录,然后把总记录数赋值给分页控件的属性。

下面我们来介绍一个不需要依赖我的Winform框架类,只是引用微软EnterpriseLibary数据库操作类即可完成的操作,因为最新的分页控件集成了一个基于Oracle、SqlServer、Access、Mysql数据库的分页逻辑,只需要使用PagerHelper这个类,传入相关的参数即可。

分页控件绑定数据如下代码所示。


private

void
BindData()
{

#region
添加别名解析


//
DisplayColumns与显示的字段名或者实体属性一致,大小写不敏感,顺序代表显示顺序,用逗号或者|分开



this
.winGridViewPager1.DisplayColumns
=

"
Number,NAME,type,Area,Company,Address,Telephone1,Telephone2,Telephone3,Telephone4,Telephone5,CreateDate,Note,LastUpdated
"
;

this
.winGridViewPager1.AddColumnAlias(
"
ID
"
,
"
编号
"
);

this
.winGridViewPager1.AddColumnAlias(
"
Number
"
,
"
客户编号
"
);

this
.winGridViewPager1.AddColumnAlias(
"
Name
"
,
"
客户名称
"
);

this
.winGridViewPager1.AddColumnAlias(
"
Type
"
,
"
客户类型
"
);

this
.winGridViewPager1.AddColumnAlias(
"
Area
"
,
"
客户地区
"
);

this
.winGridViewPager1.AddColumnAlias(
"
Company
"
,
"
客户单位
"
);

this
.winGridViewPager1.AddColumnAlias(
"
Address
"
,
"
客户地址
"
);

this
.winGridViewPager1.AddColumnAlias(
"
Telephone1
"
,
"
电话1
"
);

this
.winGridViewPager1.AddColumnAlias(
"
Telephone2
"
,
"
电话2
"
);

this
.winGridViewPager1.AddColumnAlias(
"
Telephone3
"
,
"
电话3
"
);

this
.winGridViewPager1.AddColumnAlias(
"
Telephone4
"
,
"
电话4
"
);

this
.winGridViewPager1.AddColumnAlias(
"
Telephone5
"
,
"
电话5
"
);

this
.winGridViewPager1.AddColumnAlias(
"
CreateDate
"
,
"
开户日期
"
);

this
.winGridViewPager1.AddColumnAlias(
"
Shop_ID
"
,
"
分店ID
"
);

this
.winGridViewPager1.AddColumnAlias(
"
Note
"
,
"
备注
"
);

this
.winGridViewPager1.AddColumnAlias(
"
LastUpdated
"
,
"
更新日期
"
);


#endregion



string

where

=
GetSearchSql();

this
.winGridViewPager1.DataSource
=
FindToDataTable(
where
,
this
.winGridViewPager1.PagerInfo);
}

FindToDataTable函数的相关实现代码如下所示:


///

<summary>


///
标准的记录查询函数

///

</summary>


///

<param name="where"></param>


///

<param name="pagerInfo"></param>


///

<returns></returns>



private
DataTable FindToDataTable(
string

where
, PagerInfo pagerInfo)
{
WHC.Pager.WinControl.PagerHelper helper

=

new
WHC.Pager.WinControl.PagerHelper(
"
All_Customer
"
,
"
*
"
,
"
LastUpdated
"
, pagerInfo.PageSize, pagerInfo.CurrenetPageIndex,
true
,
where
);

string
countSql
=
helper.GetPagingSql(WHC.Pager.WinControl.DatabaseType.Access,
true
);

string
dataSql
=
helper.GetPagingSql(WHC.Pager.WinControl.DatabaseType.Access,
false
);


string
value
=
SqlValueList(countSql);
pagerInfo.RecordCount

=
Convert.ToInt32(value);
//
为了显示具体的信息,需要设置总记录数


DataTable dt
=
SqlTable(dataSql);

return
dt;
}


///

<summary>


///
执行SQL查询语句,返回查询结果的所有记录的第一个字段,用逗号分隔。

///

</summary>


///

<param name="sql">
SQL语句
</param>


///

<returns>


///
返回查询结果的所有记录的第一个字段,用逗号分隔。

///

</returns>




public

string
SqlValueList(
string
sql)
{
StringBuilder result

=

new
StringBuilder();
Database db

=
DatabaseFactory.CreateDatabase();
DbCommand command

=
db.GetSqlStringCommand(sql);


using
(IDataReader dr
=
db.ExecuteReader(command))
{

while
(dr.Read())
{
result.AppendFormat(

"
{0},
"
, dr[
0
].ToString());
}
}

string
strResult
=
result.ToString().Trim(
'
,
'
);

return
strResult;
}


///

<summary>


///
执行SQL查询语句,返回所有记录的DataTable集合。

///

</summary>


///

<param name="sql">
SQL查询语句
</param>


///

<returns></returns>




public
DataTable SqlTable(
string
sql)
{
Database db

=
DatabaseFactory.CreateDatabase();
DbCommand command

=
db.GetSqlStringCommand(sql);

return
db.ExecuteDataSet(command).Tables[
0
];
}

利用控件内置的分页辅助类

WHC.Pager.WinControl.PagerHelper,你就可以不用理会各种不同数据库的分页实现逻辑,只需要按照以上的调用方式调用即可,
该类传入的第一个参数,既可以是表名,也可以是一段查询语句,非常方便各种复杂条件的查询分页

最新分页控件的相关界面截图如下所示。

1)设置表格列的可见性


2)表格数据打印预览

3)行数据基本信息提示

4)复制选定行后在文本中查看

为了使得大家对我的Winform分页控件在不同情况下的深入了解和使用,我特意编写了几种情况下的使用标准例子,供大家下载学习研究,例子包括利用基于泛型模式的Winform开发框架结构的分页控件使用例子,基于传统简单数据绑定的分页控件使用例子, 基于DotNetBar界面控件及简单数据绑定的分页控件使用例子,基于DevExpress界面控件及简单数据绑定的分页控件使用例子,如下图所示。

各种例子的源码的下载地址如下:
http://www.iqidi.com/download/TestWinPager_Demo.rar

DevExpress是一个比较有名的界面控件套件,提供了一系列的界面控件套件的DotNet界面控件。本文主要介绍我在使用DevExpress控件过程中,遇到或者发现的一些问题解决方案,或者也可以所示一些小的经验总结。总体来讲,使用DevExpress控件,可以获得更高效的界面设计以及更美观的效果。本文主要通过给出相应的例子以及相关界面效果来说明问题,希望大家能够从中获得好的知识和思路。

1、 应用Office2007和Office2010的界面主题

开始使用DevExpress的时候,发现程序界面效果好像没有出现Office的样式,只是有几种可怜的内置效果。经过查找发现需要在入口函数里面添加几行代码,如下所示。

DevExpress.UserSkins.OfficeSkins.Register();
DevExpress.UserSkins.BonusSkins.Register();
DevExpress.Skins.SkinManager.EnableFormSkins();

指定界面主题效果,可以通过代码设置,指定主题的名称即可。

UserLookAndFeel.Default.SetSkinStyle(
"
Office 2010 Blue
"
);

或者在界面中添加一个控件 DefaultLookAndFeel,设置其对应的界面效果即可实现整个设计时刻和运行时刻的界面效果,如下图所示。

2、Properties属性

DevExpress很多控件的一般属性,会放置在该属性下面,如ComboBox下拉列表的属性操作如下

private
void
InitDictItem()
{
this
.txtManufacture.Properties.Items.Clear();
this
.txtManufacture.Properties.Items.AddRange(DictItemUtil.GetDictByDictType(
"
供货商
"
));
}

这是一个很丰富属性的归类,很多常用的属性基本上都囊括在这里了,如果你是从传统界面转换过来DevExperss开发,找不着相关的属性,尽管来这里看看。例如设置显示的可见性、可用性、

自定义显示格式等等。

3、GridView的相关操作

获取选定行制定列的内容,用于删除记录的操作代码如下

DevExpess的GridView控件,和传统WinFrom的GridView有很大的不同,如他没有GetSelectedRow的行集合操作,但可以通过
GetSelectedRows
获取制定的行序号,通过行序号来进行操作,如要获得指定行,制定列的内容,可以通过
GetRowCellDisplayText
获取文本或者通过GetRowCellValue获取对应的值。

如果需要获取当前选定行(焦点所在行)的制定列的数据,那么可以通过函数GetFocusedRowCellDisplayText("ID")来获取。

private
void
winGridViewPager1_OnDeleteSelected(
object
sender, EventArgs e)
{
if
(MessageUtil.ShowYesNoAndTips(
"
您确定删除选定的记录么?
"
) == DialogResult.No)
{
return
;
}

int
[] rowSelected =
this
.winGridViewPager1.GridView1.GetSelectedRows();
foreach
(
int
iRow
in
rowSelected)
{
string
ID =
this
.winGridViewPager1.GridView1.GetRowCellDisplayText(iRow,
"
ID
"
);
BLLFactory<ItemDetail>.Instance.Delete(ID);
}
BindData();
}


为GridView行提示信息以及显示行号,有时候为了方便数据的显示,需要在GridView的第一列显示该列的行信息以及行号,那么需要为GridView控件添加一个ToolTipController控件,然后实现该控件的GetActiveObjectInfo事件,在事件里面添加下面代码以及实现GridView控件的
CustomDrawRowIndicator事件
即可,如下代码所示。

private
void
toolTipController1_GetActiveObjectInfo(
object
sender, DevExpress.Utils.ToolTipControllerGetActiveObjectInfoEventArgs e)
{
if
(e.SelectedControl != gridControl1)
return
;

ToolTipControlInfo info =
null
;
//
Get the view at the current mouse position


GridView view = gridControl1.GetViewAt(e.ControlMousePosition)
as
GridView;
if
(view ==
null
)
return
;

//
Get the view's element information that resides at the current position


GridHitInfo hi = view.CalcHitInfo(e.ControlMousePosition);
//
Display a hint for row indicator cells


if
(hi.HitTest == GridHitTest.RowIndicator)
{
//
An object that uniquely identifies a row indicator cell


object
o = hi.HitTest.ToString() + hi.RowHandle.ToString();
StringBuilder sb =
new
StringBuilder();
sb.AppendLine(
"
行数据基本信息:
"
);
foreach
(GridColumn gridCol
in
view.Columns)
{
if
(gridCol.Visible)
{
sb.AppendFormat(
"
{0}:{1}\r\n
"
, gridCol.Caption, view.GetRowCellDisplayText(hi.RowHandle, gridCol.FieldName));
}
}
info =
new
ToolTipControlInfo(o, sb.ToString());
}

//
Supply tooltip information if applicable, otherwise preserve default tooltip (if any)


if
(info !=
null
)
{
e.Info = info;
}
}



private
void
gridView1_CustomDrawRowIndicator(
object
sender, RowIndicatorCustomDrawEventArgs e)
{
if
(ShowLineNumber)
{
e.Appearance.TextOptions.HAlignment = DevExpress.Utils.HorzAlignment.Far;
if
(e.Info.IsRowIndicator)
{
if
(e.RowHandle >=
0
)
{
e.Info.DisplayText = (e.RowHandle +
1
).ToString();
}
}
}
}

以上虽然是使用了我的DevExpress分页控件,不过分页控件的内部就是使用了DevExpress的GridControl控件的。

4、常用布局控件。

传统的数据编辑界面,我们一般需要添加一个Label,然后再加一个TextBox或者其他控件,而DevExpress在这方面确实下了不少功夫,引入了一个LayoutControl的控件,该控件可以在你添加一个TextBox等其他控件的时候,自动为你加入一个类似Label的控件,而且布局已经给你设计对齐好,添加几个可以在一行中放置多列控件,对齐效果也非常好,如果是重新设计界面,省却不少对齐以及维护Label的属性。

另外,该布局属性可以设置类似GroupBox的边界效果(默认不显示文本和边界),也可以统一设计在该布局控件内部各种控件的效果,如ReadOnly为True的显示如何,Enable为False的显示如何,只需要设置一个地方,就可以统一界面效果了。总之,用起来相当的不错。

总体来讲,第一次使用DevExpress会觉得很多地方不太适应,甚至有时候找不到对应的属性,但是当慢慢熟悉了使用该控件来开发的时候,会觉得它确实在很多地方引入了较好的理念,习惯了就觉得效率提高了不少,界面设计上也会觉得更加美观和方便了。由于我只是在我平常开发的小例子,以及我的Winform开发框架中,应用了DevExpress控件的各种常用效果,应该还有很多常用的控件以及高级控件没有用到,以后有继续使用的话,会继续记录相应的经验心得供大家探讨。

最后附上一个我应用DevExpress控件改进过Winform开发框架的界面效果图,里面应用到了Ribbon界面效果以及基于DevExpress控件的分页控件(前面有介绍过使用例子),使用了分页控件,对整个GridView的处理上,会方便很多,界面效果如下所示。

Database2Sharp是一款代码生成工具和数据库文档生成工具,该工具一直伴随着我及我的粉丝们经历过各种各样的项目开发,在实际开发中能带来效率的提高及编程的快乐。不过自上个6.0版本以来,我一直忙于各种项目及研究中,很少继续把新的思想整合进去,最近在做我的Winform开发框架的整理工作,觉得应该改进这段时间来发现的问题,并融入已经逐渐完善的Winform开发框架基础上来,于是经过几天的努力,把该代码生成工具的一些缺陷修复并增加了Winform界面代码生成的功能(以前一直只是生成底层及Web界面层),并重新命名版本号为7.0。以后再继续完善后,在此大版本上再继续升级吧。

本次主要的修改如下:

1)修改设置不重新加载数据库信息。

以前版本,为了保证对数据库基础信息的及时更新,只要对一些关键的参数设置,通常都要重新刷新数据库基础信息(如表、列等基础信息),这样生成的代码就会保证使用最新的设置信息。如上图中的过滤表前缀的参数,是为了把表名称转换为友好名称的类名而定义的,他是在数据库加载的时候,自动把表别名信息生成的。

这个问题使用上一直感觉不太好,7.0版本修复了这个问题,把相关的信息保存的同时,在内存中对数据库表、字段等基础对象进行了更新,确保使用最新配置而无需重新加载整个数据库信息。

2)Winform界面代码可配置生成

说起Winform界面的代码生成,一般来说就两种界面比较典型,一个是查询列表显示界面,一个是数据查看编辑界面。本功能也主要是提供这两类界面代码的生成,通过配置查询列表中的条件字段以及查询列表字段显示信息,就可以合理生成符合我的WInform框架要求的界面代码,查询列表显示界面类继承自BaseDock基础类。另一方面,通过配置数据查看编辑界面的编辑字段,数据检查字段,判断关键数据重复的字段等参数,可以生成较为完善的数据查看编辑界面代码,生成界面如下所示。

以上参数只要执行生成代码一次,即会自动保存起来,下次打开相同表的时候,会把之前的配置信息还原,方便用户的多次操作。由于界面在不同的界面控件(如DevExpress、传统界面等)处理上有所不同,因此生成的界面代码也会根据用户的设置信息进行选择性生成。另外界面代码是我的Winform框架体系里面一部分,所以整合了我的分页控件、基础窗体类的继承等特点,如果你看不懂,可以多看看我的Winform框架文章或者分页控件文章介绍。

Winform界面代码生成后,会直接在代码编辑窗体中打开,用户可以复制或者保存起来放到VS的编辑器中进行相应的修改,后续的工作应该较为轻松了。

下面我们来分别看看生成的代码效果如何吧。

1)查询列表界面类



View Code

using
System;

using
System.Text;

using
System.Data;

using
System.Drawing;

using
System.Windows.Forms;

using
System.ComponentModel;

using
System.Collections.Generic;


using
WHC.Pager.Entity;

using
WHC.OrderWater.Commons;

using
WHC.Dictionary;


using
WHC.TestProject.BLL;

using
WHC.TestProject.Entity;


namespace
WHC.TestProject.UI
{

public

partial

class
FrmItemDetail : BaseDock
{

public
FrmItemDetail()
{
InitializeComponent();

InitDictItem();


this
.winGridViewPager1.OnPageChanged
+=

new
EventHandler(winGridViewPager1_OnPageChanged);

this
.winGridViewPager1.OnStartExport
+=

new
EventHandler(winGridViewPager1_OnStartExport);

this
.winGridViewPager1.OnEditSelected
+=

new
EventHandler(winGridViewPager1_OnEditSelected);

this
.winGridViewPager1.OnAddNew
+=

new
EventHandler(winGridViewPager1_OnAddNew);

this
.winGridViewPager1.OnDeleteSelected
+=

new
EventHandler(winGridViewPager1_OnDeleteSelected);

this
.winGridViewPager1.OnRefresh
+=

new
EventHandler(winGridViewPager1_OnRefresh);

this
.winGridViewPager1.AppendedMenu
=

this
.contextMenuStrip1;

this
.winGridViewPager1.ShowLineNumber
=

true
;

this
.winGridViewPager1.dataGridView1.DataBindingComplete
+=

new
DataGridViewBindingCompleteEventHandler(dataGridView1_DataBindingComplete);
}


///

<summary>


///
绑定数据后,分配各列的宽度

///

</summary>



void
dataGridView1_DataBindingComplete(
object
sender, DataGridViewBindingCompleteEventArgs e)
{

if
(
this
.winGridViewPager1.dataGridView1.Columns.Count
>

0
)
{

this
.winGridViewPager1.dataGridView1.Columns[
"
ID
"
].Width
=

100
;

this
.winGridViewPager1.dataGridView1.Columns[
"
ItemNo
"
].Width
=

100
;

this
.winGridViewPager1.dataGridView1.Columns[
"
ItemName
"
].Width
=

100
;

this
.winGridViewPager1.dataGridView1.Columns[
"
Manufacture
"
].Width
=

100
;

this
.winGridViewPager1.dataGridView1.Columns[
"
MapNo
"
].Width
=

100
;

this
.winGridViewPager1.dataGridView1.Columns[
"
Specification
"
].Width
=

100
;

this
.winGridViewPager1.dataGridView1.Columns[
"
Material
"
].Width
=

100
;

this
.winGridViewPager1.dataGridView1.Columns[
"
ItemBigType
"
].Width
=

100
;

this
.winGridViewPager1.dataGridView1.Columns[
"
ItemType
"
].Width
=

100
;

this
.winGridViewPager1.dataGridView1.Columns[
"
Unit
"
].Width
=

100
;

this
.winGridViewPager1.dataGridView1.Columns[
"
Price
"
].Width
=

100
;

this
.winGridViewPager1.dataGridView1.Columns[
"
Source
"
].Width
=

100
;

this
.winGridViewPager1.dataGridView1.Columns[
"
StoragePos
"
].Width
=

100
;

this
.winGridViewPager1.dataGridView1.Columns[
"
UsagePos
"
].Width
=

100
;

this
.winGridViewPager1.dataGridView1.Columns[
"
Note
"
].Width
=

100
;

this
.winGridViewPager1.dataGridView1.Columns[
"
WareHouse
"
].Width
=

100
;

this
.winGridViewPager1.dataGridView1.Columns[
"
Dept
"
].Width
=

100
;
}
}


///

<summary>


///
编写初始化窗体的实现,可以用于刷新

///

</summary>



public

override

void
FormOnLoad()
{
BindData();
}


///

<summary>


///
初始化字典列表内容

///

</summary>



private

void
InitDictItem()
{

//
初始化代码


}


///

<summary>


///
分页控件刷新操作

///

</summary>



private

void
winGridViewPager1_OnRefresh(
object
sender, EventArgs e)
{
BindData();
}


///

<summary>


///
分页控件删除操作

///

</summary>



private

void
winGridViewPager1_OnDeleteSelected(
object
sender, EventArgs e)
{

if
(MessageUtil.ShowYesNoAndTips(
"
您确定删除选定的记录么?
"
)
==
DialogResult.No)
{

return
;
}

DataGridView grid

=

this
.winGridViewPager1.dataGridView1;

if
(grid
!=

null
)
{

foreach
(DataGridViewRow row
in
grid.SelectedRows)
{
BLLFactory

<
ItemDetail
>
.Instance.Delete(row.Cells[
"
ID
"
].Value.ToString());
}
}

BindData();
}


///

<summary>


///
分页控件编辑项操作

///

</summary>



private

void
winGridViewPager1_OnEditSelected(
object
sender, EventArgs e)
{
DataGridView grid

=

this
.winGridViewPager1.dataGridView1;

if
(grid.SelectedRows.Count
==

0
)
return
;


string
ID
=
grid.SelectedRows[
0
].Cells[
"
ID
"
].Value.ToString();
List

<
string
>
IDList
=

new
List
<
string
>
();

if
(grid
!=

null
)
{

foreach
(DataGridViewRow row
in
grid.Rows)
{
IDList.Add(row.Cells[

"
ID
"
].Value.ToString());
}
}


if
(
!
string
.IsNullOrEmpty(ID))
{
FrmEditItemDetail dlg

=

new
FrmEditItemDetail();
dlg.ID

=
ID;
dlg.IDList

=
IDList;

if
(DialogResult.OK
==
dlg.ShowDialog())
{
BindData();
}
}
}


///

<summary>


///
分页控件新增操作

///

</summary>




private

void
winGridViewPager1_OnAddNew(
object
sender, EventArgs e)
{
btnAddNew_Click(

null
,
null
);
}


///

<summary>


///
分页控件全部导出操作前的操作

///

</summary>




private

void
winGridViewPager1_OnStartExport(
object
sender, EventArgs e)
{

string

where

=
GetConditionSql();

this
.winGridViewPager1.AllToExport
=
BLLFactory
<
ItemDetail
>
.Instance.FindToDataTable(
where
);
}


///

<summary>


///
分页控件翻页的操作

///

</summary>




private

void
winGridViewPager1_OnPageChanged(
object
sender, EventArgs e)
{
BindData();
}


///

<summary>


///
根据查询条件构造查询语句

///

</summary>




private

string
GetConditionSql()
{
SearchCondition condition

=

new
SearchCondition();
condition.AddCondition(

"
ItemNo
"
,
this
.txtItemNo.Text, SqlOperator.Like);
condition.AddCondition(

"
ItemName
"
,
this
.txtItemName.Text, SqlOperator.Like);
condition.AddCondition(

"
Manufacture
"
,
this
.txtManufacture.Text, SqlOperator.Like);
condition.AddCondition(

"
MapNo
"
,
this
.txtMapNo.Text, SqlOperator.Like);
condition.AddCondition(

"
Specification
"
,
this
.txtSpecification.Text, SqlOperator.Like);
condition.AddCondition(

"
Material
"
,
this
.txtMaterial.Text, SqlOperator.Like);
condition.AddCondition(

"
ItemBigType
"
,
this
.txtItemBigType.Text, SqlOperator.Like);
condition.AddCondition(

"
ItemType
"
,
this
.txtItemType.Text, SqlOperator.Like);


string

where

=
condition.BuildConditionSql(DatabaseType.SqlServer).Replace(
"
Where
"
,
""
);


return

where
;
}


///

<summary>


///
绑定列表数据

///

</summary>



private

void
BindData()
{

this
.winGridViewPager1.DisplayColumns
=

"
ID,ItemNo,ItemName,Manufacture,MapNo,Specification,Material,ItemBigType,ItemType,Unit,Price,Source,StoragePos,UsagePos,Note,WareHouse,Dept
"
;

#region
添加别名解析



this
.winGridViewPager1.AddColumnAlias(
"
ID
"
,
"
ID
"
);

this
.winGridViewPager1.AddColumnAlias(
"
ItemNo
"
,
"
备件编号
"
);

this
.winGridViewPager1.AddColumnAlias(
"
ItemName
"
,
"
备件名称
"
);

this
.winGridViewPager1.AddColumnAlias(
"
Manufacture
"
,
"
供货商
"
);

this
.winGridViewPager1.AddColumnAlias(
"
MapNo
"
,
"
图号
"
);

this
.winGridViewPager1.AddColumnAlias(
"
Specification
"
,
"
规格型号
"
);

this
.winGridViewPager1.AddColumnAlias(
"
Material
"
,
"
材质
"
);

this
.winGridViewPager1.AddColumnAlias(
"
ItemBigType
"
,
"
备件属类
"
);

this
.winGridViewPager1.AddColumnAlias(
"
ItemType
"
,
"
备件类别
"
);

this
.winGridViewPager1.AddColumnAlias(
"
Unit
"
,
"
单位
"
);

this
.winGridViewPager1.AddColumnAlias(
"
Price
"
,
"
单价
"
);

this
.winGridViewPager1.AddColumnAlias(
"
Source
"
,
"
来源
"
);

this
.winGridViewPager1.AddColumnAlias(
"
StoragePos
"
,
"
库位
"
);

this
.winGridViewPager1.AddColumnAlias(
"
UsagePos
"
,
"
使用位置
"
);

this
.winGridViewPager1.AddColumnAlias(
"
Note
"
,
"
备注
"
);

this
.winGridViewPager1.AddColumnAlias(
"
WareHouse
"
,
"
所属库房
"
);

this
.winGridViewPager1.AddColumnAlias(
"
Dept
"
,
"
所属部门
"
);


#endregion



string

where

=
GetConditionSql();
List

<
ItemDetailInfo
>
list
=
BLLFactory
<
ItemDetail
>
.Instance.Find(
where
,
this
.winGridViewPager1.PagerInfo);

this
.winGridViewPager1.DataSource
=

new
WHC.Pager.WinControl.SortableBindingList
<
ItemDetailInfo
>
(list);

this
.winGridViewPager1.PrintTitle
=
Portal.gc.gAppUnit
+

"
--
"

+

"
信息报表
"
;
}


///

<summary>


///
查询数据操作

///

</summary>



private

void
btnSearch_Click(
object
sender, EventArgs e)
{
BindData();
}


///

<summary>


///
新增数据操作

///

</summary>



private

void
btnAddNew_Click(
object
sender, EventArgs e)
{
FrmEditItemDetail dlg

=

new
FrmEditItemDetail();

if
(DialogResult.OK
==
dlg.ShowDialog())
{
BindData();
}
}


///

<summary>


///
提供给控件回车执行查询的操作

///

</summary>



private

void
SearchControl_KeyUp(
object
sender, KeyEventArgs e)
{

if
(e.KeyCode
==
Keys.Enter)
{
btnSearch_Click(

null
,
null
);
}
}
}
}

DevExpress界面效果图

2)数据编辑界面类



View Code

using
System;

using
System.Text;

using
System.Data;

using
System.Drawing;

using
System.Windows.Forms;

using
System.ComponentModel;

using
System.Collections.Generic;


using
WHC.Dictionary;

using
WHC.OrderWater.Commons;


using
WHC.TestProject.BLL;

using
WHC.TestProject.Entity;


namespace
WHC.TestProject.UI
{

public

partial

class
FrmEditItemDetail : BaseEditForm
{

public
FrmEditItemDetail()
{
InitializeComponent();
}


///

<summary>


///
实现控件输入检查的函数

///

</summary>


///

<returns></returns>



public

override

bool
CheckInput()
{

bool
result
=

true
;
//
默认是可以通过




#region
MyRegion



if
(
this
.txtItemNo.Text.Trim().Length
==

0
)
{
MessageUtil.ShowTips(

"
请输入备件编号
"
);

this
.txtItemNo.Focus();
result

=

false
;
}

else

if
(
this
.txtItemName.Text.Trim().Length
==

0
)
{
MessageUtil.ShowTips(

"
请输入备件名称
"
);

this
.txtItemName.Focus();
result

=

false
;
}

else

if
(
this
.txtManufacture.Text.Trim().Length
==

0
)
{
MessageUtil.ShowTips(

"
请输入供货商
"
);

this
.txtManufacture.Focus();
result

=

false
;
}

#endregion



return
result;
}


///

<summary>


///
初始化数据字典

///

</summary>



private

void
InitDictItem()
{

//
初始化代码


}


///

<summary>


///
数据显示的函数

///

</summary>



public

override

void
DisplayData()
{
InitDictItem();

//
数据字典加载(公用)




if
(
!
string
.IsNullOrEmpty(ID))
{

#region
显示客户信息

ItemDetailInfo info

=
BLLFactory
<
ItemDetail
>
.Instance.FindByID(ID);

if
(info
!=

null
)
{
txtItemNo.Text

=
info.ItemNo;
txtItemName.Text

=
info.ItemName;
txtManufacture.Text

=
info.Manufacture;
txtMapNo.Text

=
info.MapNo;
txtSpecification.Text

=
info.Specification;
txtMaterial.Text

=
info.Material;
txtItemBigType.Text

=
info.ItemBigType;
txtItemType.Text

=
info.ItemType;
txtUnit.Text

=
info.Unit;
txtPrice.Text

=
info.Price;
txtSource.Text

=
info.Source;
txtStoragePos.Text

=
info.StoragePos;
txtUsagePos.Text

=
info.UsagePos;
txtNote.Text

=
info.Note;
txtWareHouse.Text

=
info.WareHouse;
txtDept.Text

=
info.Dept;
}

#endregion


//
this.btnOK.Enabled = Portal.gc.HasFunction("ItemDetail/Edit");


}

else

{

//
this.btnOK.Enabled = Portal.gc.HasFunction("ItemDetail/Add");


}
}


///

<summary>


///
编辑或者保存状态下取值函数

///

</summary>


///

<param name="info"></param>



private

void
SetInfo(ItemDetailInfo info)
{
info.ItemNo

=
txtItemNo.Text;
info.ItemName

=
txtItemName.Text;
info.Manufacture

=
txtManufacture.Text;
info.MapNo

=
txtMapNo.Text;
info.Specification

=
txtSpecification.Text;
info.Material

=
txtMaterial.Text;
info.ItemBigType

=
txtItemBigType.Text;
info.ItemType

=
txtItemType.Text;
info.Unit

=
txtUnit.Text;
info.Price

=
txtPrice.Text;
info.Source

=
txtSource.Text;
info.StoragePos

=
txtStoragePos.Text;
info.UsagePos

=
txtUsagePos.Text;
info.Note

=
txtNote.Text;
info.WareHouse

=
txtWareHouse.Text;
info.Dept

=
txtDept.Text;
}


///

<summary>


///
新增状态下的数据保存

///

</summary>


///

<returns></returns>



public

override

bool
SaveAddNew()
{
ItemDetailInfo info

=

new
ItemDetailInfo();
SetInfo(info);


try

{

#region
新增数据


//
检查是否还有其他相同关键字的记录



bool
exist
=
BLLFactory
<
ItemDetail
>
.Instance.IsExistKey(
"
ItemNo
"
, info.ItemNo);

if
(exist)
{
MessageUtil.ShowTips(

"
指定的【备件编号】已经存在,不能重复添加,请修改
"
);

return

false
;
}

bool
succeed
=
BLLFactory
<
ItemDetail
>
.Instance.Insert(info);

if
(succeed)
{

//
可添加其他关联操作




return

true
;
}

#endregion

}

catch
(Exception ex)
{
LogHelper.Error(ex);
MessageUtil.ShowError(ex.Message);
}

return

false
;
}


///

<summary>


///
编辑状态下的数据保存

///

</summary>


///

<returns></returns>



public

override

bool
SaveUpdated()
{

//
检查不同ID是否还有其他相同关键字的记录



bool
exist
=
BLLFactory
<
ItemDetail
>
.Instance.CheckExist(
this
.txtItemNo.Text, ID);

if
(exist)
{
MessageUtil.ShowTips(

"
指定的【备件编号】已经存在,不能重复添加,请修改
"
);

return

false
;
}
ItemDetailInfo info

=
BLLFactory
<
ItemDetail
>
.Instance.FindByID(ID);

if
(info
!=

null
)
{
SetInfo(info);


try

{

#region
更新数据


bool
succeed
=
BLLFactory
<
ItemDetail
>
.Instance.Update(info, info.ID.ToString());

if
(succeed)
{

//
可添加其他关联操作




return

true
;
}

#endregion

}

catch
(Exception ex)
{
LogHelper.Error(ex);
MessageUtil.ShowError(ex.Message);
}
}

return

false
;
}
}
}

DevExpress界面效果图

Database2Sharp代码生成工具已经上传到服务器,需要该代码生成工具的,请到下载地址(
http://www.iqidi.com/download/Database2SharpSetup.rar
)下载。

一直以来,多数时间在开发一些Winform共享软件,经过多年的积累,逐渐形成比较成熟稳定的Winform开发框架,并结合Web项目开发经验、代码生成工具、相关的控件开发及项目开发经验,逐渐形成一个相对比较完善的.NET开发体系。不过由于种种原因,甚少涉足WCF的相关应用,只是在09年初的时候,开发一个送水软件网络版的时候,玩过WCF,那时候主要是把WCF作为一个送水各种业务数据的同步服务实现。由于研究兴趣及工作便利等原因,最近学习研究,把WCF服务搭建在我传统的Winform开发框架基础上,完成一个分布式的WCF开发框架,本文主要介绍相关的框架实现过程及总结碰到的问题,逐步深入研究,力求把其设计为我的Winform开发框架外的一个补充,可以作为分布式应用开发框架。

首先总结一下WCF的一些特点,WCF主要是基于客户端-服务端通讯模式来实现分布式应用,并通过服务公布的节点进行访问,实现数据的交换等服务。下面是其中应用的几个示意图。

我们先来看看我的传统Winform开发框架的设计图,如下所示。其中界面层UI直接访问BLL层,不需要通过网络,其中公用辅助类库Common层、实体类层可以在各个层中访问,并把常用的权限管理、字典管理封装为组件模块,直接调用,底层则使用工厂方式,来支持各种不同的数据库,其中UI层、BLL层、DAL层、实体层均使用继承类方式实现最良好的封装、最优的代码设计。

关于我的Winform开发框架相关内容,可以参考下面几篇文章:

(1)
Winform开发框架之字典数据管理

(2)
Winform开发框架之权限管理系统

(3)
Winform开发框架之终极应用

(4)
Winform开发框架之Office Ribbon界面

另外还有未发布的基于DevExpress样式的Winform开发框架,整个多样式界面的Winform框架工程如下图所示。

由于工程管理的需求,我倾向于尽可能减少工程的项目,因此框架业务层把BLL层、DAL层、IDAL层、Entity层均融合放到一个工程项目中,通过不同的目录进行区分即可,这样既方便应用,也方便部署管理。

下面开始介绍基于以上Winform框架的WCF框架扩展,首先我们在界面层和BLL层插入一层WCF服务层,界面层UI不再业务层BLL打交道,而是代之以WCF服务层的客户端代理类打交道,而WCF服务层则是BLL层更进一步的包装,设计图如下所示。

项目工程截图如下所示

客户端调用WCF服务非常方便简单,构造一个WCF服务客户端代理类实例即可应用,如下所示,如果有多个服务,创建多个实例服务即可。

private
ManufacturerServiceClient client =
new
ManufacturerServiceClient();
private
CompanyParkServiceClient companyParkClient =
new
CompanyParkServiceClient();

由于WCF服务是直接对BLL层的封装,因此也需要提供BLL层基础的增删改查等操作,我们把它定义基类接口IBaseService,并实现该基类接口即可。如下代码所示。

[ServiceContract]
public
interface
IBaseService<T>
where
T : BaseEntity
{
[OperationContract]
bool
Delete(
string
key);

[OperationContract]
bool
DeleteByCondition(
string
condition);

[OperationContract]
List<T> FindWithPager(
string
condition, PagerInfo info);

[OperationContract]
List<T> Find(
string
condition);

[OperationContract]
T FindByID(
string
key);

[OperationContract]
List<T> FindByIDs(
string
idString);

[OperationContract]
T FindFirst();

[OperationContract]
T FindLast();

[OperationContract]
T FindSingle(
string
condition);

[OperationContract]
DataTable FindToDataTable(
string
condition);

[OperationContract]
List<T> GetAllWithPager(PagerInfo info);

[OperationContract]
List<T> GetAll();

[OperationContract]
DataSet GetAllToDataSet(PagerInfo info);

[OperationContract]
DataTable GetAllToDataTable();

[OperationContract]
int
GetRecordCount2(
string
condition);

[OperationContract]
int
GetRecordCount();

[OperationContract]
bool
Insert(T obj);

[OperationContract]
int
Insert2(T obj);

[OperationContract]
bool
IsExistKey(
string
fieldName,
object
key);

[OperationContract]
bool
IsExistRecord(
string
condition);

[OperationContract]
bool
Update(T obj,
string
primaryKeyValue);
}

基类的实现如下所示,其中看到,基本是封装了BLL业务层的封装,和传统Winform框架界面层的调用很类似。另外该类通过泛型来识别对应的实体类,并通过实体类的类型名称来构造对应的业务层实例,这个和DAL、IDAL的继承关系很接近,因此不难理解。

public
class
BaseService<T> : IBaseService<T>
where
T : BaseEntity,
new
()

{
BaseBLL<T> bll =
null
;

public
BaseService()
{
bll = (BaseBLL<T>)Assembly.Load(
"
ParkDeviceUserBiz
"
).
CreateInstance(
typeof
(T).FullName.Replace(
"
Entity
"
,
"
BLL
"
).Replace(
"
Info
"
,
""
));
}

#region
对象添加、修改、查询接口

///

<summary>


///
插入指定对象到数据库中

///

</summary>


///

<param name="obj">
指定的对象
</param>


///

<returns>
执行成功返回新增记录的自增长ID。
</returns>


public
virtual
bool
Insert(T obj)
{
return
bll.Insert(obj);
}

///

<summary>


///
插入指定对象到数据库中

///

</summary>


///

<param name="obj">
指定的对象
</param>


///

<returns>
执行成功返回新增记录的自增长ID。
</returns>


public
virtual
int
Insert2(T obj)
{
return
bll.Insert2(obj);
}

///

<summary>


///
更新对象属性到数据库中

///

</summary>


///

<param name="obj">
指定的对象
</param>


///

<returns>
执行成功返回
<c>
true
</c>
,否则为
<c>
false
</c>

</returns>


public
virtual
bool
Update(T obj,
string
primaryKeyValue)
{
return
bll.Update(obj, primaryKeyValue);
}

///

<summary>


///
查询数据库,检查是否存在指定ID的对象(用于字符型主键)

///

</summary>


///

<param name="key">
对象的ID值
</param>


///

<returns>
存在则返回指定的对象,否则返回Null
</returns>


public
virtual
T FindByID(
string
key)
{
return
bll.FindByID(key);
}

///

<summary>


///
根据条件查询数据库,如果存在返回第一个对象

///

</summary>


///

<param name="condition">
查询的条件
</param>


///

<returns>
指定的对象
</returns>


public
virtual
T FindSingle(
string
condition)
{
return
bll.FindSingle(condition);
}

///

<summary>


///
查找记录表中最旧的一条记录

///

</summary>


///

<returns></returns>


public
T FindFirst()
{
return
bll.FindFirst();
}

///

<summary>


///
查找记录表中最新的一条记录

///

</summary>


///

<returns></returns>


public
T FindLast()
{
return
bll.FindLast();
}

///

<summary>


///
获取表的所有记录数量

///

</summary>


///

<returns></returns>


public
int
GetRecordCount2(
string
condition)
{
return
bll.GetRecordCount(condition);
}

///

<summary>


///
获取表的所有记录数量

///

</summary>


///

<returns></returns>


public
int
GetRecordCount()
{
return
bll.GetRecordCount();
}

///

<summary>


///
根据condition条件,判断是否存在记录

///

</summary>


///

<param name="condition">
查询的条件
</param>


///

<returns>
如果存在返回True,否则False
</returns>


public
bool
IsExistRecord(
string
condition)
{
return
bll.IsExistRecord(condition);
}

///

<summary>


///
查询数据库,检查是否存在指定键值的对象

///

</summary>


///

<param name="fieldName">
指定的属性名
</param>


///

<param name="key">
指定的值
</param>


///

<returns>
存在则返回
<c>
true
</c>
,否则为
<c>
false
</c>

</returns>


public
virtual
bool
IsExistKey(
string
fieldName,
object
key)
{
return
bll.IsExistKey(fieldName, key);
}

///

<summary>


///
根据指定对象的ID,从数据库中删除指定对象(用于整型主键)

///

</summary>


///

<param name="key">
指定对象的ID
</param>


///

<returns>
执行成功返回
<c>
true
</c>
,否则为
<c>
false
</c>

</returns>


public
virtual
bool
Delete(
string
key)
{
return
bll.Delete(key);
}

///

<summary>


///
根据指定条件,从数据库中删除指定对象

///

</summary>


///

<param name="condition">
删除记录的条件语句
</param>


///

<returns>
执行成功返回
<c>
true
</c>
,否则为
<c>
false
</c>

</returns>


public
virtual
bool
DeleteByCondition(
string
condition)
{
return
bll.DeleteByCondition(condition);
}

#endregion

#region
返回集合的接口

///

<summary>


///
根据ID字符串(逗号分隔)获取对象列表

///

</summary>


///

<param name="idString">
ID字符串(逗号分隔)
</param>


///

<returns>
符合条件的对象列表
</returns>


public
virtual
List<T> FindByIDs(
string
idString)
{
return
bll.FindByIDs(idString);
}

///

<summary>


///
根据条件查询数据库,并返回对象集合

///

</summary>


///

<param name="condition">
查询的条件
</param>


///

<returns>
指定对象的集合
</returns>


public
virtual
List<T> Find(
string
condition)
{
return
bll.Find(condition);
}

///

<summary>


///
根据条件查询数据库,并返回对象集合(用于分页数据显示)

///

</summary>


///

<param name="condition">
查询的条件
</param>


///

<param name="info">
分页实体
</param>


///

<returns>
指定对象的集合
</returns>


public
virtual
List<T> FindWithPager(
string
condition, PagerInfo info)
{
return
bll.Find(condition, info);
}

///

<summary>


///
返回数据库所有的对象集合

///

</summary>


///

<returns>
指定对象的集合
</returns>


public
virtual
List<T> GetAll()
{
return
bll.GetAll();
}

///

<summary>


///
返回数据库所有的对象集合(用于分页数据显示)

///

</summary>


///

<param name="info">
分页实体信息
</param>


///

<returns>
指定对象的集合
</returns>


public
virtual
List<T> GetAllWithPager(PagerInfo info)
{
return
bll.GetAll(info);
}

public
virtual
DataSet GetAllToDataSet(PagerInfo info)
{
return
bll.GetAllToDataSet(info);
}

public
DataTable GetAllToDataTable()
{
return
bll.GetAllToDataTable();
}

public
DataTable FindToDataTable(
string
condition)
{
return
bll.FindToDataTable(condition);
}

#endregion
}

创建服务的时候,我们在工程上创建一个WCF服务类接口,类的名称和BLL层的类名称一样,不过后面增加一个Service后缀即可,如下所示。

接下来要修改相关的实现代码,主要是继承关系,如下代码所示。

public
class
ManufacturerService : BaseService<ManufacturerInfo>, IManufacturerService
{
///

<summary>


///
检查是否存在重复的单位名称(排除ID本身的)

///

</summary>


///

<param name="companyName">
单位名称
</param>


///

<param name="ID"></param>


///

<returns></returns>


public
bool
CheckExist(
string
companyName,
string
ID)
{
return
BLLFactory<Manufacturer>.Instance.CheckExist(companyName, ID);
}
}

编译服务后,在WinformUI层添加WCF服务应用即可,如下所示 。

引用、创建WCF服务客户端代理类,并调用,即可调用IBaseService里面定义的接口和扩展接口IManufacturerService里面的接口实现。WCF接口测试可以使用WCFStorm Lite 来进行接口查看及调试,如下所示。

整个项目可以在局域网或者广域网中部署,实现更大范围的分布式应用,最后呈上该项目的运行界面,供参考验证。

以上界面层在传统的Winform框架界面中和WCF+winform框架界面中表现一致,在局域网中部署测试,客户端 + WCF服务器 + Oracle数据库服务器这种部署模式,非常流畅,由于是基于WCF框架结构,可以应用在广域网中,不过可能服务相应及Winform的体验要依赖于带宽的大小吧。

后续的文章继续就改WCF框架进行改进,并总结开发过程中遇到的问题及解决思路。

基于WCF技术,搭建基于分布式的网络应用,目前已经是很常见的应用,在上篇随笔《
基于我的Winform开发框架扩展而成的WCF开发框架
》中,介绍了我自己的Winform开发框架的一些基础知识及扩展而成的WCF开发框架,上篇随笔只是基于快速应用搭建而成的WCF框架,并没有考虑到数据安全性的问题,在互联网的环境中,数据的安全性是必须考虑的一道门槛,否则明文数据满天飞,犹如人在大街上裸奔一样令人不安,除非是你认为你的应用数据无关紧要。本文主要通过技术对比,介绍如何创建一个Winform客户端测试WCF服务,并说明为什么需要在WCF服务中对数据进行加密。

为了对WCF项目进行介绍讲解,我们创建一个测试的WCF服务项目,并创建一个Winform的客户端进行测试,如下图所示。

创建项目后,会自动添加一个服务的测试例子,我们就以该服务的测试例子进行介绍。创建服务后,我们在本地把服务连接打开,进行浏览,看到下面的界面,表示服务运行正常。

同时需要添加一个Winform的客户程序,并添加WCF服务的应用,如何操作这些步骤,请参考上篇随笔《
基于我的Winform开发框架扩展而成的WCF开发框架
》 即可,把窗体绘制一下,并增加一个按钮操作,测试WCF服务接口。

按钮操作实现代码如下:

private
void
button1_Click(
object
sender, EventArgs e)

{
try
{
Service1Client client =
new
Service1Client();
string
result = client.GetData(
6666661
);
this
.txtResult.Text = result;
}
catch
(Exception ex)
{
this
.txtResult.Text = ex.ToString();
}
}

运行客户端程序,测试功能,并得到返回结果,如下所示。

一切似乎都很顺利,很完美,不是吗,创建服务,创建客户端调用,都非常方便快捷,而且是基于互联网的引用哦。但是,我们先留意一下,通过该操作实现的服务以及客户端调用,数据传输情况是如何的。我这里通过HTTP网络包分析工具Http Analyzer来对数据进行监控看看,如下所示。

可以看到,输入及输出的结果全部是明文,可以完全看到,没任何秘密可言,当然,你也可以对这些数据自己加密后发送出去,但是每个接口,大量信息也要这样自己手工加密吗,难道微软的WCF那么弱智?当然不是,这些操作交给WCF处理会便捷很多,传说中有一个X509的证书加密,不过如果是第一次搞这个,要完全调通要走不少弯路,我就是参考了很多文章(很多文章都介绍了一部分内容,很少能系统性的给你提出完全的解决方法),遇鬼杀鬼,遇佛拜佛,这样慢慢才有一个清晰的思路,逐步解决问题,最终才有心思来写这篇文章,后面的随笔我会介绍完整的解决思路来处理X509加密的操作。

通过X509加密处理后,我们看到下面的效果。

里面的内容,包括输入及获取到的输出内容,均被加密的乱七八糟,一塌糊涂,即使是在公网中,看到也是白搭,不知是美女还是野兽。

实现以上的加密操作,在调用上,完全不会受影响,还是和原来的调用一样,不过需要在WCF的服务以及调用的客户端进行一些文件配置等操作即可(当然还包含创建相关证书、授权IIS访问),如下所示配置WCF服务的Web.Config和客户程序App.Config。

Web.Config 部分内容:

客户程序App.Config部分内容: