wenmo8 发布的文章

在我们开发完软件后,除了极个别案例我们把整个目录复制给客户用外,我们一般都需要做成安装包,方便整个软件的部署操作,以安装包的部署操作可能简单的是复制文件,也可能包括一些注册表、数据库等额外的操作,不过安装包部署肯定是能够给客户提供专业、统一的软件安装体验,同时封装了很多细节方面的处理。在我们日常的基于.NET的开发中,VS2010之前我们还可以使用内置的安装包制作工具,自从VS2012、VS2013以后,微软把这个去掉,集成使用了InstallShield进行安装包的制作了,我自己在这方面也写了两篇随笔《
基于DevExpress的Winform程序安装包的制作
》、《
基于InstallShield2013LimitedEdition的安装包制作
》,不过基于InstallShield的制作虽然可以完成安装包的打包操作,不过也不见得是比较方便的打包工具,本篇介绍的Setup Factory是一个非常高效,功能强大的安装包制作工具,在独立于VS之外,我们可以用它作为我们常规.NET开发的安装包制作,简单易用。

1、Setup Factory介绍

Setup Factory 是一个强大的安装程序制作工具。提供了安装制作向导界面,即使你对安装制作不了解,也可以生成专业性质的安装程序。Setup Factory 的特性包括:工程向导、自定义对话框、主题/皮肤样式、运行时间模块、最小化支持、MD5编码序列号、250个功能函数的脚本编辑器、授权支持和其他特性等。

目前使用的版本是Setup Factory 9.5,不过各个版本之间差异并不大,这个软件在网上也很容易得到(即使是特别版本的,呵呵),因此较InstallShield更为方便获取到,在简单的对这款安装包制作工具的了解后,基本上常规的功能都非常方便设置处理,确实是比较易用的。软件的界面效果如下所示。

从软件的主要功能来讲,它的安装包制作,主要就是左边的任务栏的各个处理操作了,包括内容有:文件Files、设置Settings、屏幕Screens、动作Action、资源Resources、卸载UnInstall等操作。

2、软件安装包的制作

我们创建一个安装包,输入相关的公司、软件名称等,如下所示。

一步步后,我们需要指定指定具体的位置,以便加入所需要的文件,如下所示。

然后选择安装包的界面语言包,如下所示。

进一步的操作,就可以选择环境的安装包,例如.NET4,如果选择这个依赖环境,那么在安装的过程中会自动安装,好像说需要在SetUpFactory软件所在的目录中新建一下目录:
Dependencies\dotnet4 。
并且将 dotNetFx40_Full_x86_x64.exe放在其中。

接着需要设置一些安装包的操作,如是否包含卸载操作等等,如下界面所示。

最后,程序自动加入指定目录的问价,在主体界面中显示对应的文件,如下安装包制作视图所示,就是制作一个客户关系管理软件的安装包界面视图,包含所需要安装的文件、文件文档、程序依赖文件等。

在返回到主界面的安装包视图后,我们就是按照左边的任务工具栏的顺序,一步步的对安装包的参数进行设置即可。

包含的内容有:文件Files、设置Settings、屏幕Screens、动作Action、资源Resources、卸载UnInstall等操作。我们在加入文件后,对执行文件的相关参数进行设置处理,如下所示。

对执行文件的属性进行设置,根据情况设置相关的参数信息,如下界面所示。

然后接着对设置信息进行处理,如指定安装包软件的名称、公司信息、安装版权信息、安装路径等,如下界面所示。

安装包的屏幕显示,我们可以修改其中的图片,以及相关的提示信息、按钮信息等内容,如下界面所示。

在弹出的界面里面,我们可以通过Project内容选择符合安装包的制作主题,在右下角里面,我们还可以单击编辑按钮,对样式图片进行修改,选择符合自己项目的背景图片。

其他处理如动作Action、资源Resources等,我们根据情况进行设置即可,一般如果没有特殊,则不需要进行处理。

卸载的操作,我们可以修改其中的提示名称,将其修改为中文的提示信息,如下所示。

最后可以进行编译了,指定生成的目录和文件名称,编译就可以获得一个独立的安装包的。

编译过程就是把所需的文件一步步加入到一个安装包文件里面,如下是进度提示所示。

下面介绍的界面效果就是我完成后的安装包界面效果,包括了背景图片的修改,卸载信息的设置、安装包文件的信息等等,具体效果也可以下载我的客户关系管理软件进行了解。

http://www.iqidi.com/crm.htm

安装部署后,软件的快捷键在桌面上存在,并且可以看到【打开文件位置】的操作也可以了,这个在之前VS里面的安装包是没有的,呵呵,小细节。

安装文件是一个独立的文本文件,安装Setup Factory工具后,直接使用,也可以使用其他文本编辑工具维护和查看。

2、关于DevExpress相关DLL的依赖

在我们完成添加编译完成的系统所需相关DLL外,如果我们的UI依赖DevExpress,那么也需要引入对应的DevExpress相关DLL文件,我们可以一般可以拷贝到发布目录中,然后引入响应文件即可。

另外DevExpress还需要包含相关的中文化资源,可以通过包含文件夹所有文件的方式包含进来,如下所示。

完成后编译生成安装的执行文件即可。

以上就是我对一个完整的客户关系管理软件的安装包,在Setup Factory工具上的制作过程,基本上安装包制作起来还是比较方便快捷的,而且最后也就是一个文本文件的信息,独立,比较好管理。

对比InstallShield的笨重和麻烦,这个软件独立维护安装包也是非常方便的一件事情。

在我很早的WInform随笔《
WinForm界面开发之"SplashScreen控件"
》有介绍如何使用闪屏的处理操作,不过那种是普通WInform和DevExpress都适用的方案,如果是基于DevExpress,那么可以使用更简便的方式,就是本文介绍的在DevExpress程序中使用SplashScreenManager控件实现启动闪屏和等待信息窗口,这个SplashScreenManager很好的封装了日常WInform程序中用到的启动闪屏窗口和在耗时操作中进行等待提示的信息窗口,合理使用可以增加程序界面的友好交换。

1、SplashScreenManager控件的使用

这个控件一般人很少注意到,毕竟不是很常用,我们可以通过在控件工具箱里面进行搜索,找到后拖动到WInform窗口即可,如下界面所示。

然后在SplashScreenMananger控件的右上角上单击三角符号,利用连接按钮添加两个窗口,一个是启动界面的闪屏窗口,一个是等待界面窗口。

最后得到两个新建的界面,稍作调整就可以使用在程序中了。

2、SplashScreenManager控件的调用

上面我们通过控件手工添加的方式增加了两个界面窗体,如果需要在程序中使用,那么需要进行一定的代码调用处理。

例如我们在程序启动的时候,程序入口的代码修改如下。

其中启动闪屏窗口的代码就是

 //显示闪屏窗体
SplashScreenManager.ShowForm(typeof(FrmSplashScreen));
Thread.Sleep(
3000);

在主程序窗体中,我们加载完毕界面后,我们需要手工关闭闪屏窗体的显示,代码如下所示。

而在界面中,如果我们需要显示等待窗口,如在耗时的数据获取和更新操作上,我们可以提示用户等待一下,那么这个时候就应该做等待信息窗口的显示了,如下界面效果所示。

为了实现上面的操作,我们可以对这个窗口的调用做一些简单的初始化和封装处理,如下代码所示。

privateSplashScreenManager _loadForm;/// <summary>
///等待窗体管理对象/// </summary>
protectedSplashScreenManager LoadForm
{
get{if(_loadForm == null)
{
this._loadForm = new SplashScreenManager(this, typeof(FrmWaitForm), true, true);this._loadForm.ClosingDelay = 0;
}
return_loadForm;
}
}
/// <summary> ///显示等待窗体/// </summary> public voidShowMessage()
{
bool flag = !this.LoadForm.IsSplashFormVisible;if(flag)
{
this.LoadForm.ShowWaitForm();
}
}
/// <summary> ///关闭等待窗体/// </summary> public voidHideMessage()
{
bool isSplashFormVisible = this.LoadForm.IsSplashFormVisible;if(isSplashFormVisible)
{
this.LoadForm.CloseWaitForm();
}
}

最后在数据更新的时候,我们调用上面的函数

这样就实现了两种界面效果的处理了:启动闪屏显示、数据操作的等待信息,这两个控件还可以在中间过程中调用更多的事件函数处理,如SendCommand等,从而实现更进一步的信息显示。

在很多业务系统里面,越来越多涉及到条形码、二维码的应用了,不管在Web界面还是WInform界面都需要处理很多物料相关的操作,甚至很多企业为了减少录入错误操作,为每个设备进行条形码、二维码的标签,直接在流程中进行扫描处理,仅仅在界面勾选一些处理选项即可,极大提高工作效率,降低出错的几率。本篇随笔介绍如何在基于WInform的DevExpress程序中对条形码、二维码的处理,包括界面展示,报表打印等常规的处理。

1、DevExpress的条形码操作及报表打印

在15.1以上的DevEpxress版本中,增加了BarCodeControl控件,该控件包含了条形码、二维码等的展示和处理,支持很多种编码。

在工具箱里面,我们可以找到这个控件,添加到界面后,设置属性就可以显示如下的效果。

对于条形码的控件显示,需要设置对应的编码,以及显示内容等信息。

设置正确的格式显示,才能显示出条形码的内容,否则会处理得不到显示的内容。

为了方便界面的处理,动态增加对应的条形码控件展示,我们独立出一个条形码控件,如下所示。

为了方便展示,我们在界面上添加一个FlowLayoutPanel流布局控件,用来动态添加条码内容的容器,界面代码如下所示。

运行Demo程序,可以得到下面的测试界面效果。

对于条形码的打印操作,我们可以基于XtraReport的报表模板对内容进行展示的,因此需要设计一个报表的界面,如下所示是设计好的报表界面。

这个报表界面就是添加一个空白报表,然后加入对应的标签和二维码报表控件,和操作WInform界面一样,排版后就可以了。

然后增加对应的数据源信息,以及设定绑定控件的字段名称即可。

最后绑定报表数据源,并打印报表的界面代码如下所示。

得到界面效果如下所示。

2、二维码控件的处理

上面介绍的是条形码操作,其中BarCodeControl很有迷惑性,其实它也可以作为二维码的空间处理的,这是后来才发现,哈哈。

其实Github上也有很多开源的二维码控件,可以生成图片进行展示的。

如下界面所示,我们可以看到,这个二维码也是即看即所得的效果,设置界面后,马上可以看到效果的。

使用内置控件,我们在后台代码赋值的时候,只需要设置它的内容即可,如下所示。

//使用内置控件
var url = this.txtContent.Text.Trim();this.barCodeControl1.Text = url;

如果使用第三方二维码控件,如ThoughtWorks.QRCode(https://github.com/aaronogan/QR.NET),则创建的代码如下所示。

最后得到两组界面的对比,如下所示。

在我们一般的程序中,为一般的界面元素添加一定的图标展示,有助于提升界面的整体的美观。结合排版布局,以及固定场景的图标,往往给用户非常好的直观感受:统一、美观、易理解。因此在一般的程序界面中,都尽量在略显单调的界面增加一些图标进行点缀,能够达到较好的整体效果。本文介绍在Winform程序界面中,可以使用自己的图标库,也可以引入DevExpress程序中使用内置的图标,从而实现给开发的程序润色。

1、使用自己的图标库

不管在Web还是在WInform项目里面,开发者积累一定的图标库,包括256,128,64像素等大型的图标,或者常规32,16像素规格的图标都是比较不错的,有时候可以选择符合主题和场景的图标,给开发的程序增添更美丽的元素。

不过即使自己存储不够,也不要紧,可以通过网站搜索一些符合要求的图标,网站上也有很多免费的图标可以下载,如下面几个网址:

http://www.iconfinder.com/

http://www.iconpng.com/

http://findicons.com/

http://www.iconseeker.com/

http://mricons.com/

http://www.iconarchive.com/

从中总能找到自己所需要主题的图标的。

如下面是我WInform开发框架里面的程序主界面风格,使用了很多自己的图标。

2、使用DevExpress内置的图标库

在一些常规的界面里面,如编辑界面和查询界面里面的按钮,我倾向于使用内置的DevExpress图标,这样程序看起来比较统一,例如我在界面设计的基类窗体里面,图标应用效果如下所示。

添加图标方式非常简单,在按钮的右上角小图标上单击一下进入编辑界面,如下所示。

然后选择Image按钮,进入图标选择界面,选择内置的DevExpress图标库即可,基本上,只要是DevExpress的原生控件,那么就可以通过这种内置图标的对话框进行图标选择,非常方便。

大图标选择32*32的,小图标则可以选择16*16的尺寸。

这样我们设计一些常规的列表界面操作按钮的时候,可以选择同一的图标,形成较为标准的图标。

以上就是我WInform开发框架、混合式开发框架里面,WInform界面的的图表设计,当然很多界面如树列表、右键菜单、工具条等都可以采用一定的图标,把界面装饰的更加美观大方,易于理解。

在很多情况下,我们需要通过树列表进行数据的展示,如一些有层次关系的数据,通过有层级的展示,能够使用户更加直观查看和管理相关的数据。在一般Winform开发的情况下,可以使用微软的TreeView控件,也可以使用DevExpress的TreeList控件进行数据的展示,本篇随笔主要介绍基于DevExpress的TreeList控件使用以及使用SearchControl对节点进行查询的操作。

1、 使用微软的TreeView控件的实现效果和思路

在很多情况下,我们也倾向于使用TreeView控件作为数据的展示,相对于TreeList控件,这种控件的处理,需要自己管理树节点的层次关系,不过使用也比较简单,呈现的效果两者都差别不大。

如在我开发框架中,在字典管理模块里面,就是采用这个控件进行数据的展示的,整体效果也还不错。

在树形列表里面,我们获取数据后,统一根据层级的关系构建树节点即可,如下代码所示。

/// <summary>
///初始化树信息/// </summary>
private voidInitTreeView()
{
this.treeView1.Nodes.Clear();this.treeView1.BeginUpdate();
List
<DictTypeNodeInfo> typeNodeList = BLLFactory<DictType>.Instance.GetTree();foreach (DictTypeNodeInfo info intypeNodeList)
{
AddTree(
null, info);
}
this.treeView1.EndUpdate();this.treeView1.ExpandAll();
}
/// <summary> ///根据节点数据,递归构建该层级以下的树节点/// </summary> /// <param name="pNode">父树节点</param> /// <param name="info">字典类型数据</param> private voidAddTree(TreeNode pNode, DictTypeNodeInfo info)
{
TreeNode node
= null;if (info.PID == "-1")
{
node
= new TreeNode(info.Name, 0, 0);
node.Tag
=info.ID;this.treeView1.Nodes.Add(node);
}
else{
node
= new TreeNode(info.Name, 1, 1);
node.Tag
=info.ID;
pNode.Nodes.Add(node);
}
foreach (DictTypeNodeInfo subInfo ininfo.Children)
{
AddTree(node, subInfo);
}
}

还有我们在鼠标选择某个节点的时候,触发AfterSelect事件,这样我们就可以处理鼠标节点的事件了

/// <summary>
///单击节点事件处理/// </summary>
private void treeView1_AfterSelect(objectsender, TreeViewEventArgs e)
{
if (e.Node.Tag != null)
{
this.lblDictType.Text =e.Node.Text;this.lblDictType.Tag =e.Node.Tag;

BindData();
}
}

以上就是使用TreeView控件来处理数据的展示,从上面代码可以看到,整体的内容,主要是通过递归的关系来构建TreeNode的处理,但是使用的代码也不算复杂,因此大多数可以采用这种方式来自定义树形节点的展示。

2、使用DevExpress的TreeList控件的效果和实现代码

而使用DevExpress的TeeList控件,可以通过KeyFieldName和ParentFieldName指定他们的层级关系,使用就更加简单化,提供的数据源会自动进行层次的关系处理,非常方便。

我们先来看看通过DevExpress的TreeList控件展示的字典类型和字典数据的界面效果。

这里面的效果是如何通过代码实现的呢?

首先我们使用代码获取字典类型数据并进行树列表控件的初始化操作,如下所示。

//添加显示列
this.tree.Columns.Add(new TreeListColumn{ FieldName= "Name", Caption= "字典类型名称", Width=160, VisibleIndex =0});//设置树控件的层次关系及属性
tree.KeyFieldName = "ID";
tree.ParentFieldName
= "PID";this.tree.OptionsBehavior.Editable = false;this.tree.OptionsView.EnableAppearanceOddRow = true;this.tree.OptionsView.EnableAppearanceEvenRow = true;

上面的代码我们还可以通过扩展函数对树列表的处理进行封装,已达到简化代码的目的,如下是处理后的实现代码:

//控件扩展函数封装处理
this.tree.CreateColumn("Name", "字典类型名称", 160, true);this.tree.InitTree("ID", "PID", "-1", false, false);

通过添加TreeListColumn对象给TreeList控件就可以实现字段列的显示了,同时指定数据源里面的KeyFieldName和ParentFieldName来设定层级关系即可,非常简单。

而绑定数据源,则可以通过一个函数进行处理,如下所示。

/// <summary>
///绑定树的数据源/// </summary>
private voidBindTree()
{
this.tree.DataSource = BLLFactory<DictType>.Instance.GetAll();this.tree.ExpandAll();
}

从上面代码我们可以看到,我们返回的数据源,不需要在实体类对象层级具有上下级的关系,如通过TreeView实现的时候,我们使用了DictTypeNodeInfo 对象是具有上下层级关系的。

这里只需要使用普通的DictTypeInfo 对象集合即可,通过KeyFieldName和ParentFieldName来设定层级关系即可。

为了指定树形节点的图标,我们可以通过代码进行自定义图标的处理,如下代码所示,这样每个层级的图标都不一样,自动实现获取设置的处理。

//设置树的图标集合及逐级图标
this.tree.SelectImageList = this.imageCollection1;this.tree.CustomDrawNodeImages += (object sender, CustomDrawNodeImagesEventArgs e)=>{int maxCount = this.imageCollection1.Images.Count;var index = e.Node.Level < maxCount ? e.Node.Level : 0;
e.SelectImageIndex
=index;
};

实现树节点选中的事件处理,则需要实现FocusedNodeChanged事件即可。

    //初始化树节点选择事件
    this.tree.FocusedNodeChanged += delegate(objectsender, FocusedNodeChangedEventArgs e)
{
this.FocusedNodeChanged();
};
}
private voidFocusedNodeChanged()
{
if (this.tree.FocusedNode != null)
{
var PID = string.Concat(this.tree.FocusedNode.GetValue("ID"));
treeConditionSql
= string.Format("DictType_ID = '{0}'", PID);
}
else{
treeConditionSql
= "";
}
BindData();
}

最后初始化树列表的代码如下所示。

private voidInitTree()
{
this.tree.Columns.Clear();//控件扩展函数封装处理 this.tree.CreateColumn("Name", "字典类型名称", 160, true);this.tree.InitTree("ID", "PID", "-1", false, false);//设置树的图标集合及逐级图标 this.tree.SelectImageList = this.imageCollection1;this.tree.CustomDrawNodeImages += (object sender, CustomDrawNodeImagesEventArgs e)=>{int maxCount = this.imageCollection1.Images.Count;var index = e.Node.Level < maxCount ? e.Node.Level : 0;
e.SelectImageIndex
=index;
};
}

3、基于SearchControl控件对节点进行查询的操作

上面的处理就是树列表的一般性展示,如果需要在树节点上面增加一个查询过滤的操作,那么可以使用SearchControl控件进行过滤处理,只需要设置SearchControl控件的Client属性,以及实现树控件的FilterNode事件即可。

/// <summary>
///实现树节点的过滤查询/// </summary>
private voidInitSearchControl()
{
this.searchControl1.Client = this.tree;this.tree.FilterNode += (object sender, DevExpress.XtraTreeList.FilterNodeEventArgs e) =>{if (tree.DataSource == null)return;string nodeText = e.Node.GetDisplayText("Name");//参数填写FieldName if (string.IsNullOrWhiteSpace(nodeText))return;bool isExist = nodeText.IndexOf(searchControl1.Text, StringComparison.OrdinalIgnoreCase) >= 0;if(isExist)
{
var node =e.Node.ParentNode;while (node != null)
{
if (!node.Visible)
{
node.Visible
= true;
node
=node.ParentNode;
}
else break;
}
}
e.Node.Visible
=isExist;
e.Handled
= true;
};
}

实现效果如下所示, 对于符合记录的查询,那么会有高亮的颜色进行重点标注。