2023年2月

我们在很多系统中,往往需要维护很多字典项目,一般的字典项目包含有一个大类名称、字典项目名称、字典值、排序、编辑者、编辑时间等字段,这些内容往往大同小异,如在车辆管理中,我们可能需要维护:车辆类型、车辆品牌、车辆用途、路途类别、用车评价、车辆状态等等信息,这些是很简单的数据,用来给业务功能使用的,是一个下拉列表形的数据,如果我们为每个这样的简单类型都创建一个表的话,那么有以下坏处:

1)  如果业务模块很多,就会产生很多这样的表,看起来不雅,维护也不方便,增加工作量。

2)  会产生很多页面代码(你需要维护界面内容展示和录入),也是看起来不雅,不方便维护,增加工作量。

3)   代码中调用处理也不好,记不住哪个是具体的字典信息了,太多。

以上总结一句话,就是做重复工作,就算有一键生成代码的工具,也是不好的。

如果采用一个公用的字典管理模块能够解决以上问题,那么整个世界就清净了,一句话,简单。

好,那我们看看如何实现以上功能的。首先看看表的设计。


字典大类DictType表(就是按业务分类的意思,如上面的车辆管理是一类,可能还有飞机管理之类的,呵呵)

字典数据项目DictData表,就是指定业务下有多少个字典项目,每个项目下面的名称和对应的值是什么,排序是什么等信息。

对这两个表生成相应的处理页面如下所示。(第一个图是大类管理、当点击字典类型名称进行指定类型的字典项目管理,也就是下面第二个图。

完成了这个字典大类和字典明细项目的基本信息管理后,今后只要有新的需要,之需要增加项目的大类和具体的业务字典项目即可。之前我的文章有介绍,整个平台的菜单是动态配置的,因此,我们在业务子菜单中增加相应的菜单管理具体的项目即可。


上面我们看到,具体字典大类下的明细字典项目,我们只需要给出相应的连接即可,连接有一个TypeID就是具体的类型ID,根据这个ID,我们就知道目前的管理类别是什么了。

这样页面的字典信息管理完成了,那么我们在具体的项目中如何使用它呢,看了下面的代码就会觉得很简单了。

其实字典项目的使用无非有两个,一个是根据大类获取对应的下拉列表项目,一个根据制定的项目获取对应的字典值。如下面两个函数就是典型的应用。


///

<summary>


///
获得选择处理人

///

</summary>


///

<returns>
ListItem[]
</returns>



public

static
ListItem[] GetListItem_MaySelProc()
{

return
SystemCommon.GetDictByDictType(
"
选择处理人
"
);
}


///

<summary>


///
转换选择处理人的名称

///

</summary>


///

<returns></returns>



public

static

string
GetMaySelProcString(
string
intSelectProc)
{

return
BLLFactory
<
DictData
>
.Instance.GetDictName(
"
选择处理人
"
, intSelectProc);
}

具体调用的业务类部分代码如下所示:


///

<summary>


///
根据字典类型名称获取所有该类型的字典列表集合(Key为名称,Value为值)

///

</summary>


///

<param name="dictType">
字典类型名称
</param>


///

<returns></returns>



public
Dictionary
<
string
,
string
>
GetDictByDictType(
string
dictTypeName)
{
IDictData dal

=
baseDal
as
IDictData;

return
dal.GetDictByDictType(dictTypeName);
}


///

<summary>


///
根据字典类型名称和字典Value值(即字典编码),解析成字典对应的名称

///

</summary>


///

<param name="dictTypeName">
字典类型名称
</param>


///

<param name="dictValue">
字典Value值,即字典编码
</param>


///

<returns>
字典对应的名称
</returns>



public

string
GetDictName(
string
dictTypeName,
string
dictValue)
{
IDictData dal

=
baseDal
as
IDictData;

return
dal.GetDictName(dictTypeName, dictValue);
}

剩下的部分就是框架代码的部分了,根据一定的条件拿到对应的列表数据操作。这里不再赘述。 如果需要了解业务层和数据访问层的具体代码,可以和我联系,或者自己实现也不是难事。

本文只是提供了一个解决的思路,相信各位大牛如要实现上面的功能也是小Case的了。

做过了很多Winform的共享软件,对界面的设计有了一定的经验和积累,准备开一个“WinForm界面开发”系列文章,介绍下相关的Winform界面设计和相关控件的使用,促进相互交流,以获得更好的发展和了解。

在很多Winform程序中 ,我们可能需要HTML内容的所见即所得的编辑控件,如Asp.net中的FreeTextBox等控件样式的。

搜索了一下,发现很少这方面的控件,找了一个,好像是收费的,而且感觉一般,跳过,再找,然后发现CodeProject 上有一个不错的东西,拿过来进行修改一下,作为控件进行使用,效果还不错,我在QQ号码采集机邮件发送系统中使用,如下图所示

控件支持基本的HTML编辑操作,如文字、图片、排列(左对齐,右对齐,居中等)、项目符号、缩进调整等操作,如下图所示

另外控件支持右键菜单操作,常用的编辑功能以及查找、修改HTML、查看内容、插入HTML元素等

内容查找界面如下所示

修改HTML窗体如下所示,可以增加修改里面的内容,确定后控件的内容将重新变化。

插入图片的对话框,提示用户输入相关的信息。

控件我们一般使用两个属性即可,如下代码所示


string
bodyHtml
=

this
.txtContent.BodyHtml;

string
bodyText
=

this
.txtContent.BodyText;

最后,呈上大家最关心的控件文件,大家可以按照使用一般的控件使用即可(控件我没有进行加密,大家需要修改源码的,反编译一下即可,呵呵):

https://files.cnblogs.com/wuhuacong/WinHtmlControl.rar

在程序中,分页总是永远的话题,因为数据总是很多很多,分页展示在程序性能和数据查看感官方面得到很好的平衡,是一种良好的编程习惯和UI设计。

Winform中的分页控件可能没有Asp.net世界中的分页控件那么丰富多彩,不过也有不少的分页控件可以采用,各个人的可能都有一些不同的东西,一些好的东西。就我而言,我希望控件能够尽可能的多一些功能,耦合性低一些,例如我不想是基于存储过程的,因为我很多程序需要使用Access作为数据库,一般来说,我还希望有导出Excel数据的功能,还有打印预览功能,由于我的数据源表头,如实体类集合、表格内容绑定的时候,表头是英文的,我需要变为中文的,其他的功能有则更好。本篇介绍我在我的共享软件中大量使用的分页控件,如送水管理系统软件、病人资料管理软件等等,希望大家有兴趣的话,可以一起讨论使用该分页控件。以便整理吸收更多好的特性,共同学习。

先给大家一个总体感觉,这是我在送水软件中的界面展示,红色圈圈部分是分页控件的展示界面。

大家可以看到,除了基本的分页外,还有其他功能,如导出当前页、导出全部页、打印列表、以及相关功能操作的菜单(只要实现了相关的接口,则呈现相同的菜单,另外还有一些小地方,也是很常用关键的地方,就是间隔行的颜色变化,表头的中文化,行提示内容等等,我认为这些分页控件应该做的,特别是表头中文化部分很重要,因为这个分页控件的数据源是基于一般的对象集合(如List<EntityInfo>())或者DataTable的,因此数据源的表头可能是英文的(一般都是^_^)。

导出Excel是基本的功能,本控件支持当前页导出,全部页导出两种模式,导出的Excel数据也还是比较好看的,不是一般的格式哦。

控件另外一项功能,也是集实用功能之所成,打印当前列表内容,如下图所示,该内容会保存用户在每个列表数据中的信息,打印不同的表头内容,如下图所示。

那么控件应该如何使用呢,下面介绍一下使用的相关代码。

1、首先在Form_Load事件中绑定相关的委托处理事件,默认有“新建”、编辑选定项、删除、刷新、打印几个按钮,您可以在此基础上增加更多的菜单。


private

void
FrmProduct_Load(
object
sender, EventArgs e)
{
BindData();


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.OnAddNew
+=

new
EventHandler(winGridViewPager1_OnAddNew);

this
.winGridViewPager1.OnDeleteSelected
+=

new
EventHandler(winGridViewPager1_OnDeleteSelected);

this
.winGridViewPager1.OnRefresh
+=

new
EventHandler(winGridViewPager1_OnRefresh);

this
.winGridViewPager1.AppendedMenu
=

this
.contextMenuStrip1;
}

2。实现表头解析和上面的委托时间的例子代码如下.


private

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


private

void
winGridViewPager1_OnDeleteSelected(
object
sender, EventArgs e)
{

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

return
;
}

DataGridView grid

=
sender
as
DataGridView;

if
(grid
!=

null
)
{

foreach
(DataGridViewRow row
in
grid.SelectedRows)
{
BLLFactory

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


private

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

=
sender
as
DataGridView;

if
(grid
!=

null
)
{

foreach
(DataGridViewRow row
in
grid.SelectedRows)
{
FrmEditProduct dlg

=

new
FrmEditProduct();
dlg.ID

=
row.Cells[
"
ID
"
].Value.ToString();

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


break
;
}
}
}


private

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

null
,
null
);
}


private

void
winGridViewPager1_OnStartExport(
object
sender, EventArgs e)
{
PagerInfo pagerInfo

=

new
PagerInfo();
pagerInfo.CurrenetPageIndex

=

1
;
pagerInfo.PageSize

=

int
.MaxValue;

this
.winGridViewPager1.AllToExport
=
BLLFactory
<
Product
>
.Instance.GetAllToDataSet(pagerInfo).Tables[
0
];
//
product.GetAllToDataSet(pagerInfo).Tables[0];


}


private

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


private

void
BindData()
{

#region
添加别名解析


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

this
.winGridViewPager1.AddColumnAlias(
"
ProductType
"
,
"
产品类型
"
);

this
.winGridViewPager1.AddColumnAlias(
"
ProductName
"
,
"
产品名称
"
);

this
.winGridViewPager1.AddColumnAlias(
"
Specification
"
,
"
产品规格
"
);

this
.winGridViewPager1.AddColumnAlias(
"
Model
"
,
"
产品型号
"
);

this
.winGridViewPager1.AddColumnAlias(
"
OfferPrice
"
,
"
进货价
"
);

this
.winGridViewPager1.AddColumnAlias(
"
AdvisePrive
"
,
"
建议价
"
);

this
.winGridViewPager1.AddColumnAlias(
"
SalePrice
"
,
"
零售价
"
);

this
.winGridViewPager1.AddColumnAlias(
"
Manufacture
"
,
"
生产厂商
"
);

this
.winGridViewPager1.AddColumnAlias(
"
Manufacture_ID
"
,
"
厂商ID
"
);

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

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

#endregion


SearchCondition condition

=

new
SearchCondition();
condition.AddCondition(

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

"
ProductType
"
,
this
.cmbProductType.Text, SqlOperator.Like)
.AddCondition(

"
Specification
"
,
this
.cmbSpecNumber.Text, SqlOperator.Like)
.AddCondition(

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

"
Manufacture
"
,
this
.cmbManufacture.Text, SqlOperator.Like);

string

where

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

List

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

this
.winGridViewPager1.DataSource
=

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

这样就实现了分页控件的内容展示以及相关功能的菜单挂接,实现后的菜单展示可能是这样子的,如下图所示,是否还可以呢,呵呵.


最后呈上控件文件一个, 大家有兴趣可以下载下载玩玩,使用有问题请在此留言.

https://files.cnblogs.com/wuhuacong/PagerWinControl.rar

我们在开发桌面应用程序的时候,由于程序启动比较慢,往往为了提高用户的体验,增加一个闪屏,也就是SplashScreen,好处有:1、让用户看到加载的过程,提高程序的交互响应;2.可以简短展示或者介绍程序的功能或者展示Logo,给客户较深的印象。

本人在开发的共享软件中,对于启动比较慢的程序,也倾向于引入这个控件来展示下,先看看软件启动的时候的效果

中间的那些文字“正在展示相关的内容”可以根据加载的进度显示不同的内容,当然最好简单扼要了,其他的内容你也可以视需要做相应变化,因为这个是一个Form,你想改变什么就改变什么的。

看看闪屏代码如何使用先,首先我们在入口的Main函数中开始,看看代码就知道


public

class
Portal
{

public

static
GlobalControl gc
=

new
GlobalControl();


///

<summary>


///
应用程序的主入口点。

///

</summary>


[STAThread]

private

static

void
Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(

false
);


//
登陆界面


Logon dlg
=

new
Logon();
dlg.StartPosition

=
FormStartPosition.CenterScreen;

if
(DialogResult.OK
==
dlg.ShowDialog())
{

if
(dlg.bLogin)
{
SplashScreen.Splasher.Show(

typeof
(SplashScreen.frmSplash));

gc.MainDialog

=

new
MainForm();
gc.MainDialog.StartPosition

=
FormStartPosition.CenterScreen;

Application.ThreadException

+=

new
System.Threading.ThreadExceptionEventHandler(Application_ThreadException);
Application.Run(gc.MainDialog);
}

}
dlg.Dispose();
}


private

static

void
Application_ThreadException(
object
sender, System.Threading.ThreadExceptionEventArgs ex)
{
LogHelper.Error(ex.Exception);


string
message
=

string
.Format(
"
{0}\r\n操作发生错误,您需要退出系统么?
"
, ex.Exception.Message);

if
(DialogResult.Yes
==
MessageUtil.ShowYesNoAndError(message))
{
Application.Exit();
}
}
}

上面中最关键的代码是:SplashScreen.Splasher.Show(typeof(SplashScreen.frmSplash));
之所以贴出全部的代码,也是供大家参考如何启动登陆窗口并运行主窗体程序的,上面的GlobalControl类是一个公共类,用来放置一些全局变量或者通用操作的函数。Application_ThreadException是用来在程序运行出错的时候,友好提示一下用户,是否退出,否则有一些莫名其妙的错误,程序没有提示就马上退出的问题,扯远了,言归正传,还是说说如何使用闪屏的功能吧。

上面开启了闪屏的功能后,那么我们可能就要在程序中,根据不同的加载进度显示不同的内容了,看看是如何做到的

记得在MainForm窗体的构造函数中添加相应的闪屏操作代码,如下所示。


public
MainForm()
{
InitializeComponent();

SplashScreen.Splasher.Status

=

"
正在展示相关的内容

"
;
System.Threading.Thread.Sleep(

100
);






..

//
此处省略部分加载耗时的代码



SplashScreen.Splasher.Status

=

"
初始化完毕

"
;
System.Threading.Thread.Sleep(

50
);

SplashScreen.Splasher.Close();
}

控件的使用代码就这么多了,其他的就是封装好的控件部分内容了,下面打包放上来,给大家参考使用,源码级的哦,下载了记得顶一下。

提示:控件的背景图片可能不能正常显示,您自己弄一个图片附上去就可以了。

https://files.cnblogs.com/wuhuacong/SplashScreen.rar

本篇介绍Winform程序开发中的布局界面的设计,介绍如何在我的共享软件中使用布局控件"WeifenLuo.WinFormsUI.Docking"。

布局控件"WeifenLuo.WinFormsUI.Docking"是一个非常棒的开源控件,用过的人都深有体会,该控件之强大、美观、不亚于商业控件。而且控件使用也是比较简单的。先看看控件使用的程序界面展示效果。

配电网络可视化管理系统的界面截图:

深田之星送水管理系统网络版的界面截图:

我在几个共享软件都使用了该布局控件,我们先以“深田之星送水管理系统网络版”这款软件为例,介绍如何完成该界面的设计及显示的。

1、首先,我们添加一个主界面窗体,命名为MainForm,该窗体IsMdiContainer设置为True,也就是设置为多文档窗体格式。拖拉布局控件"WeifenLuo.WinFormsUI.Docking.DockPanel"到主窗体MainForm中,并设置下面几个属性:

Dock为Fill、DocumentStyle为DockingMdi、RightToLeftLayout为True。

这几个属性的意思应该不难,Dock就是 覆盖整个MDI窗体的区域,DocumentStyle为多文档类型、RightToLeftLayout是指新打开的窗口都停靠在右边区域。

我们看看设计界面视图如下所示。

2、主界面其实基本上就可以了,另外我们看到“送水管理系统网络版”的界面中有一个左边的工具栏,它其实也是在一个停靠的窗体中的,我们增加一个窗体用来承载相关的工具快捷键按钮展示。命名为MainToolWindow的窗体,继承自WeifenLuo.WinFormsUI.Docking.DockContent.

其中的“HideOnClose”属性很重要,该属性一般设置为True,就是指你关闭窗口时,窗体只是隐藏而不是真的关闭。

左边的窗口MainToolWindow实现停靠的代码是在MainForm的构造函数或者Load函数中加载即可。

mainToolWin.Show(this.dockPanel, DockState.DockLeft);

3、对于工具窗口我们已经完成了,但是主业务窗口还没有做,也就是下面的部分内容。

为了方便,我们定义一个基类窗体,命名为BaseForm,继承自DockContent,如下所示

public class BaseForm : DockContent

然后每个业务窗口继承BaseForm即可。

4、剩下的内容就是如何在主窗体MainForm中展示相关的业务窗口了,展示的代码如下所示


public

partial

class
MainForm : Form
{

#region
属性字段



private
MainToolWindow mainToolWin
=

new
MainToolWindow();

private
FrmProduct frmProduct
=

new
FrmProduct();

private
FrmCustomer frmCustomer
=

new
FrmCustomer();

private
FrmOrder frmOrder
=

new
FrmOrder();

private
FrmStock frmStock
=

new
FrmStock();

private
FrmComingCall frmComingCall
=

new
FrmComingCall();

private
FrmDeliving frmDeliving
=

new
FrmDeliving();

private
FrmTicketHistory frmHistory
=

new
FrmTicketHistory();


#endregion



public
MainForm()
{
InitializeComponent();

SplashScreen.Splasher.Status

=

"
正在展示相关的内容

"
;
System.Threading.Thread.Sleep(

100
);

mainToolWin.Show(

this
.dockPanel, DockState.DockLeft);
frmComingCall.Show(

this
.dockPanel);
frmDeliving.Show(

this
.dockPanel);
frmHistory.Show(

this
.dockPanel);
frmStock.Show(

this
.dockPanel);
frmProduct.Show(

this
.dockPanel);
frmCustomer.Show(

this
.dockPanel);
frmOrder.Show(

this
.dockPanel);

SplashScreen.Splasher.Status

=

"
初始化完毕

"
;
System.Threading.Thread.Sleep(

50
);

SplashScreen.Splasher.Close();
}








5.下面贴出基本窗口的基本操作事件函数


private

void
menu_Window_CloseAll_Click(
object
sender, EventArgs e)
{
CloseAllDocuments();
}


private

void
menu_Window_CloseOther_Click(
object
sender, EventArgs e)
{

if
(dockPanel.DocumentStyle
==
DocumentStyle.SystemMdi)
{
Form activeMdi

=
ActiveMdiChild;

foreach
(Form form
in
MdiChildren)
{

if
(form
!=
activeMdi)
{
form.Close();
}
}
}

else

{

foreach
(IDockContent document
in
dockPanel.DocumentsToArray())
{

if
(
!
document.DockHandler.IsActivated)
{
document.DockHandler.Close();
}
}
}
}


private
DockContent FindDocument(
string
text)
{

if
(dockPanel.DocumentStyle
==
DocumentStyle.SystemMdi)
{

foreach
(Form form
in
MdiChildren)
{

if
(form.Text
==
text)
{

return
form
as
DockContent;
}
}


return

null
;
}

else

{

foreach
(DockContent content
in
dockPanel.Documents)
{

if
(content.DockHandler.TabText
==
text)
{

return
content;
}
}


return

null
;
}
}


public
DockContent ShowContent(
string
caption, Type formType)
{
DockContent frm

=
FindDocument(caption);

if
(frm
==

null
)
{
frm

=
ChildWinManagement.LoadMdiForm(Portal.gc.MainDialog, formType)
as
DockContent;
}

frm.Show(

this
.dockPanel);
frm.BringToFront();

return
frm;
}


public

void
CloseAllDocuments()
{

if
(dockPanel.DocumentStyle
==
DocumentStyle.SystemMdi)
{

foreach
(Form form
in
MdiChildren)
{
form.Close();
}
}

else

{
IDockContent[] documents

=
dockPanel.DocumentsToArray();

foreach
(IDockContent content
in
documents)
{
content.DockHandler.Close();
}
}
}

最后呈上该控件文件,大家可以下来玩玩。

https://files.cnblogs.com/wuhuacong/WeifenLuo.WinFormsUI.Docking.rar