wenmo8 发布的文章

在我们开发工作流的时候,往往需要设计到具体业务表单信息的编辑,有些是采用动态编辑的,有些则是在开发过程中处理的,各有各的优点,动态编辑的则方便维护各种各样的表单,但是数据的绑定及处理则比较麻烦,而自定义开发的,则数据弹性很大,方便修改调整。本篇随笔基于表单的开发设计过程,介绍在工作流中如何新增一个业务表单,以便快速的实现审批业务的上线处理。

1、业务表单的基类继承

首先我们来了解一下业务表单的对应关系,一般创建一个业务流程处理,都需要有一个具体的创建业务表单的界面,以及一个查看处理表单的界面。

为了方便,我们尽可能减少代码编写,我们需要把大多数的逻辑处理放在基类实现,这样我们在新增一个业务表单的时候就可以减少很多代码编写及维护了。

如对于FrmAddApply类定义如下,我们定义一些抽象接口用于下面的业务表单实现

    /// <summary>
    ///创建申请单的窗体基类/// </summary>
    public partial classFrmAddApply : BaseForm
{
/// <summary> ///表单ID/// </summary> public string FormID { get; set; }/// <summary> ///申请单ID/// </summary> public string ApplyId { get; set; }publicFrmAddApply()
{
InitializeComponent();
}
/// <summary> ///显示数据的函数(子类必须实现)/// </summary> public virtual voidDisplayData() { }/// <summary> ///实现控件输入检查的函数(子类必须实现)/// </summary> /// <returns></returns> public virtual bool CheckInput() { return true; }/// <summary> ///编辑状态下的数据保存(子类必须实现)/// </summary> /// <returns></returns> public virtual bool SaveUpdated() { return true; }/// <summary> ///新增状态下的数据保存(子类必须实现)/// </summary> /// <returns></returns> public virtual bool SaveAddNew() { return true; }

...................

这样我们创建一个新的业务表单,只需要利用代码生成工具,生成所需要的各层框架代码,然后再生成Winform窗体代码,复制部分界面处理代码过来这个业务表单的子类即可。

下面是一个请假申请的业务表单设计,如下所示。

我们看到这个表单可以使用代码生成工具Database2Sharp快速生成后进行一定调整的,而这个编辑表单的界面,我们只需要使用自动生成的部分代码即可。

相关代码只需要复制上面的新增、更新、显示数据的代码即可。

对于查看申请单的基类FrmViewApply类,我们更加简单,我们需要把它的自定义界面控件加载出来即可。

下面是查看申请单的基类,封装了相关的处理逻辑。

    /// <summary>
    ///本窗体是通用的查看申请单界面基类。///为减少开发相关页面的工作量,只需要创建一个新窗体,并继承本窗体,然后在子窗体Form_Load函数里面,初始化对应的申请单显示控件即可。/// </summary>
    public partial classFrmViewApply : BaseDock
{
/// <summary> ///申请单ID/// </summary> public string ApplyId { get; set; }/// <summary> ///申请单自定义控件/// </summary> public BaseUserControl ApplyControl { get; set; }/// <summary> ///默认构造函数/// </summary> publicFrmViewApply()
{
InitializeComponent();
}
private void FrmViewApply_Load(objectsender, EventArgs e)
{
if (!this.DesignMode)
{
InitToolBar();
}
}
/// <summary> ///初始化申请单控件/// </summary> protected virtual voidInitApplyControl(BaseUserControl control)
{
if (control != null)
{
this.ApplyControl =control;this.ApplyControl.Dock =DockStyle.Fill;this.Controls.Add(control);
}
}
/// <summary> ///打印申请单控件内容(默认调用窗体打印)/// </summary> protected virtual voidPrintApplyControl()
{
if(this.ApplyControl != null)
{
PrintFormHelper.Print(
this.ApplyControl, false);
}
}
/// <summary> ///表单另存为/// </summary> protected virtual voidApplySaveAs()
{
}
/// <summary> ///初始化工具栏的按钮和状态/// </summary> protected virtual voidInitToolBar()
{
................
//基类实现,控制什么时候该做什么审批处理,以及一些常见按钮 }

.................

查看请假申请单的窗口就是继承这个FrmViewApply即可,如下所示。

    /// <summary>
    ///查看请假申请单的窗体/// </summary>
    public partial classFrmViewLeave : FrmViewApply
{
private LeaveControl control = null;publicFrmViewLeave()
{
InitializeComponent();
}
private void FrmViewLeave_Load(objectsender, EventArgs e)
{
//初始化控件并展示在基类窗体里面 control = newLeaveControl();
control.ApplyId
= this.ApplyId;
control.DisplayData();
base.InitApplyControl(control);
}
}

这个就是全部的窗体源码了,主要的内容我们看到是在LeaveControl这个用户控件类里面的了,

而这个控件主要就是上面编辑请假申请单的界面设计,并复制相关的显示数据代码即可。

相关界面代码如下所示。

    /// <summary>
    ///查看请假申请单的内容显示控件/// </summary>
    public partial classLeaveControl : BaseUserControl
{
/// <summary> ///申请单ID/// </summary> public string ApplyId { get; set; }publicLeaveControl()
{
InitializeComponent();

SetReadOnly();
}
/// <summary> ///设置整个窗体布局为只读并设置只读的背景颜色/// </summary> private voidSetReadOnly()
{
this.layoutControl1.OptionsView.IsReadOnly =DevExpress.Utils.DefaultBoolean.True;this.layoutControl1.Appearance.ControlReadOnly.BackColor =Color.SeaShell;
}
private void LeaveControl_Load(objectsender, EventArgs e)
{
this.applyInfoControl1.ApplyId = this.ApplyId;this.applyInfoControl1.BindData();
}
/// <summary> ///初始化数据字典/// </summary> private voidInitDictItem()
{
//初始化代码 }/// <summary> ///数据显示的函数/// </summary> public voidDisplayData()
{
InitDictItem();
//数据字典加载(公用)//由于申请单一般是用申请表单入口,而非业务数据表,因此只能传入ApplyId获取信息 if (!string.IsNullOrEmpty(ApplyId))
{
....................
}

通过上面定义的对应表单的窗体基类,可以减少我们重复编码的需要,我们只需要利用最有效率的生成界面,然后复制代码后调整即可快速生成我们所需要的不同表单界面。

每个表单我们放在一个目录上,这样我们就可以很好管理它们了。

2、业务表单的动态展示处理

上面介绍了业务表单的填写、查看两个不同的窗口,我们在申请单的审批界面里面,统一显示不同的表单,以及创建不同的业务表单界面,这种动态的处理可以实现不同业务表单的创建及显示界面。

如我的审批工作中,表单的显示界面如下所示,查看具体表单后,可以动态展示不同的业务窗口界面。

另外我们在创建业务表单的时候,根据数据库的配置信息,动态展示所有可以展示的创建入口,单击相关的按钮,可以动态调用创建对应的表单界面。

创建流程业务表单的入口如下所示。

在我的审批工作界面,动态创建对应的查看表单窗体代码如下所示。

        /// <summary>
        ///分页控件编辑项操作/// </summary>
        private void winGridViewPager1_OnEditSelected(objectsender, EventArgs e)
{
//获取记录ID和表单ID string ID = this.winGridViewPager1.gridView1.GetFocusedRowCellDisplayText("ID");string FormId = string.Concat(this.winGridViewPager1.gridView1.GetFocusedRowCellValue("FormId"));if (!string.IsNullOrEmpty(ID) && !string.IsNullOrEmpty(FormId))
{
var formInfo = BLLFactory<BLL.Form>.Instance.FindByID(FormId);if (formInfo != null && !string.IsNullOrEmpty(formInfo.ApplyWin2))
{
try{//根据配置的查看窗体,动态构建查看申请单对象 FrmViewApply dlg = Assembly.GetExecutingAssembly().CreateInstance(formInfo.ApplyWin2) asFrmViewApply;if (dlg != null)
{
dlg.ApplyId
=ID;
dlg.OnDataSaved
+= newEventHandler(dlg_OnDataSaved);if (DialogResult.OK ==dlg.ShowDialog())
{
BindData();
}
}
}
catch(Exception ex)
{
LogHelper.Error(ex);
MessageDxUtil.ShowError(ex.Message);
}
}
}
}

这个代码替代了需要手动创建不同对象的处理

    var dlg = newFrmViewAssignWork();
dlg.ApplyId
=ID;
dlg.OnDataSaved
+= newEventHandler(dlg_OnDataSaved);if (DialogResult.OK ==dlg.ShowDialog())
{
BindData();
}

同理,对于创建编辑界面,我们也可以同样的方法动态创建相关的编辑表单界面,如下代码所示。

WInform开发框架之工作流系列文章:

Winform开发框架之简易工作流设计


Winform开发框架中工作流模块的表设计分析


Winform开发框架中工作流模块的业务表单开发

Winform开发框架中工作流模块之审批会签操作

Winform开发框架中工作流模块之审批会签操作(2)

在前面介绍了框架中工作流的几个开发过程,本篇随笔重点介绍一下日常审批环节中的具体处理过程,从开始创建表单,以及各个审批、会签过程的流转过程,希望大家对其中流程的处理有一个大概的印象。

1、请假申请表单

这里介绍的具体功能,是在业务表单已经完成好,具体的使用过程,我们以请假申请单为例,在我们创建对应的流程步骤如下所示后,

我们就可以开始创建具体的业务审批流程了,在业务受理列表里面选择对应的请假申请,弹出相应的创建业务流程表单窗体。

单击【发送】按钮,会要求选择下一步的审批人,如下所示。

这样对应的审批人员进去后,在我的待办业务列表里面,就可以看到刚才的表单了,双击可以进行查看,以及相关的审批处理工作。

单击表单左上角的工具栏按钮【审批】,可以进行流程的审批处理操作了。

在处理完成相关的审批操作后,流程就会自动流转到了表单定义的下一流程人了,这里为了方便选择了管理员的角色来处理而已。

流程人登陆后查看具体业务申请单后,可以看到前面的审批意见等信息,如下所示。

继续【审批】处理环节,这样两个环节的流程就处理完成了。

最后我们查看完成的业务申请单,可以看到相关的审批信息了。

当然也可以查看具体的业务流程日志。

整个流程步骤全部完成了。

2、报销申请单

为了增加复杂一点的表单我们引入一个含有主从表的业务表单,明细表包括报销的具体列表内容。

具体的业务表单设计如下所示。

首先我们定义好业务流程,如下所示。

创建一个业务表单,如下所示。

其中几个流程的审批和上面的处理操作差不多,最后完成表单后,那么这个总共参与的步骤是三个步骤,包括了总经理审批环节了,和下面的表单过程类似,界面效果如下所示。

如果是报销费用少于条件值(3000)的,那么审批的环节就不包括总经理审批环节了,如下表单所示。

以上就是两类表单,一个是普通的单表表单、一个是包含明细表的主从表表单的流程处理,以及步骤中包含相关的过滤条件,满足条件的则包含该条件的审批处理,否则系统自动跳过这个步骤,直接跳转到下一步或者完成整个申请单的处理。

有时候为了方便查看对应的业务表单,一般提供一个所有表单的入口给管理查看 ,方便进行维护管理,如下所示。

管理员可以在这里对业务表单进行删除、撤回等特殊处理,另外,我们也可以提供一个业务数据的入口方便查询统计,如下所示的界面。

WInform开发框架之工作流系列文章:

Winform开发框架之简易工作流设计


Winform开发框架中工作流模块的表设计分析


Winform开发框架中工作流模块的业务表单开发

Winform开发框架中工作流模块之审批会签操作

Winform开发框架中工作流模块之审批会签操作(2)

前面随笔介绍了请假申请单和报销申请单两个不同的业务表单的流程处理,一个是单表信息,一个包含明细的主从表信息,后者包含了条件流程的处理,在流程审批中,一般还有一种流程处理就是会签的操作,会签处理是几个审批步骤中审批人同时处理是否通过的,一般同时通过即为通过。本篇随笔介绍工作流中的会签处理过程。

1、会签流程定义

会签是指创建一个或多个子流程供相关人员进行审批,等待全部人员完成处理后再次回到主流程上,然后决定是否继续流转到下一个流程步骤上去,一般的申请单的主流程如下所示。

这里设置的会签处理就是其中一个步骤,一旦会签处理步骤发起会签,就会构建多个可供审批的子流程了,如下所示。

在会签发起的步骤,指定参与具体流程会签审批的人员,然后流程则会流转到不同人员进行相关的处理【待办事项】。

我在工作流中定义会签完成后,由会签发起人审核(会签结果审核),决定是否进入下一步流程,在审核过程中决定如何处理这个申请单。

在流程定义里面,我们创建一个会签的流程步骤,我们以请假单为例,加入我们要求请假需要由各组长会签通过,然后在继续下面的部门审批、总经理审批步骤,如下所示。

增加会签后的流程步骤如下所示。

完成后可以在流程步骤列表中看到会签的步骤了,如下所示。

2、会签流程处理

了解了会签的处理过程,并完成了上面的会签定义后,我们创建一个请假申请单,用来发起会签处理,介绍会签的步骤说明。

完成请假单后提交给相关处理人,处理人员在待办事项中查看申请单,如下界面所示。

那么他会发起【发起会签】的处理操作,把相关的投票权发送给各个组长进行会签处理。

会签发起后,各个参与会签的人员在【待办事项】里面处理会签意见,如下所示。

各个待审批的人员进行处理后,最后返回给会签发起人决定是否进入下一步流程,如下所示。

我们查看相关的流程日志,看到会签的流程步骤已经完成了,其他步骤还需要进行处理。

这样这个会签流程就算整体完成了,剩下的就是其他步骤的处理,按正常的审批处理即可。

WInform开发框架之工作流系列文章:

Winform开发框架之简易工作流设计


Winform开发框架中工作流模块的表设计分析


Winform开发框架中工作流模块的业务表单开发

Winform开发框架中工作流模块之审批会签操作

Winform开发框架中工作流模块之审批会签操作(2)

目前微信的企业号已经切换到企业微信里面,这个是一个APP程序,提供了很丰富的企业应用,其中包括了业务审批处理,审批业务包括请假、报销、费用、出差等很多个审批场景,在Winform开发框架中工作流模块这些模块也是可以很好的实现,本篇随笔介绍如何参照企业微信审批业务,结合框架中的工作流模块,实现多种表单的审批处理。

1、企业微信审批

在开始介绍框架中工作流模块之前,我们先来了解下企业微信中的审批业务,如下界面所示,这些审批表单包括有:请假、报销、费用、出差、采购、加班、外出、用车、用章、合同审批、物品领用、付款、物品维修、会议室预定等这些常见的流程业务。

我们打开其中一个业务,看看其中的业务表单信息。

其中包括一些请假业务的相关信息,包括内容输入、附件管理等,这样就可以提交及抄送给对应给的人员了。

提交后,对业务表单的数据就可以查看及进行流程处理了,如下是业务表单的查看界面。

2、开发框架中工作流模块的业务审批

首先我们需要根据业务表单的数据,填写相关的信息,从而可以在业务的动态展示列表中展现可以新建的表单,如下界面所示。

至于每个业务表单的相关开发,我在随笔《
Winform开发框架中工作流模块的业务表单开发
》中介绍比较详细,每个流程业务表单主要涉及到几个窗体的处理,一个是新增编辑业务申请单、一个是查看业务申请和展示申请单的用户控件,以及一个常规的业务查询需要的界面,其中一些窗体我们已经进行了基类封装,简化了子类窗体的代码,如下界面所示。

例如对于请假业务申请单,我们的界面效果如下所示。

它主要是继承FrmAddApply ,并实现业务申请单数据校验、表单流程发送及数据显示(重新编辑的时候显示)等处理即可。界面代码相对比较简单了。

对于查看具体业务表单的显示界面,继承查看申请单的基类FrmViewApply类基类,那么这个子类只需要负责数据的展示就是了。

数据展示部分分为固定通用的申请单信息,以及业务表单信息两部分组成,其中附件处理则采用通用附件管理模块按钮即可,如下所示。

这些是在用户控件上进行展示的,设计状态下的用户控件效果如下所示。

上面的介绍的请假申请单是单表的情况,有时候,我们申请单是主从表的情况,如报销申请单,需要汇总总的报销信息、包括总金额,然后把各种明细记录提交,这种表单界面效果如下所示。

这种明细表单可以直接在表格控件Griview上进行新增、编辑处理。

查看详细的表单界面效果如下所示。

这种业务申请单比较单表而已,相对复杂一些,需要处理GridView的数据录入和保存处理,以及数据的显示处理,不过也是比较标准的处理,我们也是同样继承相同的基类,在子类实现不同的业务处理代码即可。

后面我们为了减轻开发工作流,把这些业务表单的界面使用代码生成工具一键生成,直接可用,这样就省事多了。

3、工作流业务界面的代码生成

从上面我们可以看到,其中对于工作流业务表单的窗体界面都可以实现标准的处理了,继承自某个基类,然后整合相关的数据处理规则即可。

那么我们提炼业务信息后,可以使用代码生成工具快速生成,这样可以极大提高我们的开发效率。

针对上面的构想,我们花费了好几天的时间,创建了工作流界面的自动生成规则和反复校验,最终整合到代码生成工具中方便开发。

对于主从表表的界面,我们依旧也可以使用代码生成工具进行快速的工作流界面生成。

至于如何使用这个功能,后面在开一篇随笔详细进行介绍过程。

WInform开发框架之工作流系列文章:

Winform开发框架之简易工作流设计


Winform开发框架中工作流模块的表设计分析


Winform开发框架中工作流模块的业务表单开发

Winform开发框架中工作流模块之审批会签操作

Winform开发框架中工作流模块之审批会签操作(2)


参照企业微信审批业务,在Winform开发框架中工作流模块的实现业务审批

在我们开发工作流模块的时候,有时候填写申请单过程中,暂时不想提交审批,那么可以暂存为草稿,以供下次继续填写或者提交处理,那么这个草稿的功能是比较实用的,否则对于一些填写内容比较多的申请单,每次要重填写很多数据,那会被用户骂的,从用户的角度上来讲,提供草稿保存的功能是比较友好的。本篇随笔介绍在工作流模块中使用一种通用的存储方式来存储及显示申请单草稿的信息。

1、申请单填写

在我们提交申请前,我们一般是需要填写一些相关的资料,如下界面所示。

这个表单记录的信息不多,不过提供存为草稿的功能也是要的,我们所有申请单都提供这个标准功能。

或者复杂一点的申请单

以往做过草稿保存,把记录复制在正式的申请单里面,设置它为草稿状态即可,这种方式可以实现,不过不好统一处理,本篇随笔介绍的是所有草稿存储在一个表里面,我们定义一些字段用来存储对应信息的JSON数据,然后需要的时候,把它们逐一解析为对应的对象即可,这种我们可以在基类窗体里面封装它的【存为草稿】的逻辑处理了。

2、草稿功能实现

首先我们定义一个存储草稿信息,可以对单表,也可以对主从表的数据,我们把它存储为对应的JSON字段即可,设计草稿的数据表如下所示。

在申请单填写的基类窗体里面,我们定义界面如下所示。

然后我们在基类提供一个通用的业务草稿保存处理函数,供子类进行调用即可。

        /// <summary>
        ///保存草稿/// </summary>
        /// <param name="title">草稿标题</param>
        /// <param name="mainJson">主业务表单数据</param>
        /// <param name="detailJson">从表业务表单数据(如无则为null)</param>
        /// <param name="detailJson2">从表业务表单数据(如无则为null)</param>
        /// <param name="detailJson3">从表业务表单数据(如无则为null)</param>
        /// <returns></returns>
        protected virtual void SaveDraft(string title, string mainJson, string detailJson = null,string detailJson2 = null, string detailJson3 = null)
{
var formInfo = BLLFactory<BLL.Form>.Instance.FindByID(this.FormID);
ArgumentCheck.Begin().NotNull(formInfo,
"表单对象");var infoDraft = newApplyDraftInfo();if(!string.IsNullOrEmpty(this.DraftId))
{
infoDraft.ID
= this.DraftId;//如果已有的则更新 }
infoDraft.BizDraftJson
=mainJson;
infoDraft.BizDraftJson2
=detailJson;
infoDraft.BizDraftJson3
=detailJson2;
infoDraft.BizDraftJson4
=detailJson3;
infoDraft.Form_ID
= this.FormID;
infoDraft.FormName
=formInfo.FormName;
infoDraft.Category
=formInfo.Category;
infoDraft.Title
=title;
infoDraft.Creator
=LoginUserInfo.ID;
infoDraft.CreateTime
=DateTime.Now;var flag = BLLFactory<ApplyDraft>.Instance.InsertUpdate(infoDraft, infoDraft.ID);

MessageDxUtil.ShowTips(
"保存草稿-" + (flag ? "成功" : "失败"));if(flag)
{
this.DialogResult =System.Windows.Forms.DialogResult.OK;
}
}

我们可以看到,草稿可以新增或者更新,如果对于已经存在的草稿,我们再次编辑的时候,不会新增另外一条记录,而是修改原来的记录。

对于普通单表的申请单处理,如下界面所示。

那么它的保存草稿的功能代码是如何实现的?

        /// <summary>
        ///保存草稿处理/// </summary>
        private void btnSaveDraft_Click(objectsender, EventArgs e)
{
string title = string.Format("{0}的付款申请单【{1}】(草稿)", LoginUserInfo.FullName, DateTime.Now.ToShortDateString());var info = tempInfo;//必须使用存在的局部变量,因为部分信息可能被附件使用 SetInfo(info);
info.Creator
= base.LoginUserInfo.ID;
info.CreateTime
=DateTime.Now;//保存草稿:对象信息转换为JSON进行保存 SaveDraft(title, info.ToJson());
}

这里保存实际上就是获取对应的表单信息转换为JSON存储即可。

例如对于费用及费用明细的报销处理界面,如下所示。

那么我们的草稿处理有什么不同呢?

在填写申请单的子类我们实现按钮【存为草稿】的单击事件处理,代码如下所示。

        /// <summary>
        ///保存申请单草稿的处理/// </summary>
        private void btnSaveDraft_Click(objectsender, EventArgs e)
{
string title = string.Format("{0}的{1}报销申请单【{2}】(草稿)", LoginUserInfo.FullName, this.txtCategory.Text, DateTime.Now.ToShortDateString());var info = tempInfo;//必须使用存在的局部变量,因为部分信息可能被附件使用 SetInfo(info);
info.Creator
= base.LoginUserInfo.ID;
info.CreateTime
=DateTime.Now;//获取费用明细 var list =GetDetailList();//保存草稿处理:如果有多个明细,可以增加在后面 SaveDraft(title, info.ToJson(), list.ToJson());
}

我们这里需要把费用信息、明细信息的对象转换为JSON对象,然后统一调用基类的保存草稿函数即可。

而对于草稿信息加载,还原为实际表单的信息显示,我们处理代码就是先解析JSON对象,转换为实际表单对象,然后进行界面赋值展示即可,如下代码所示。

完成这些,我们就可以在实际申请单业务中进行草稿的存储和显示了。

3、界面代码生成

以上代码相对都比较简单,不过我们为了开发工作流模块更加高效,统一使用代码生成工具Database2Sharp进行界面的代码生成即可,同时保存草稿、附件处理等代码都一并生成,直接使用即可。

对于主从表表的界面,我们依旧也可以使用代码生成工具进行快速的工作流界面生成。

至于如何使用这个功能,后面在开一篇随笔详细进行介绍过程。

WInform开发框架之工作流系列文章:

Winform开发框架之简易工作流设计


Winform开发框架中工作流模块的表设计分析


Winform开发框架中工作流模块的业务表单开发

Winform开发框架中工作流模块之审批会签操作

Winform开发框架中工作流模块之审批会签操作(2)


参照企业微信审批业务,在Winform开发框架中工作流模块的实现业务审批


Winform开发框架中工作流模块之申请单草稿处理