2023年2月

在前面介绍了很多篇相关的《
Bootstrap开发框架
》的系列文章,这些内容基本上覆盖到了我这个Bootstrap框架的各个主要方面的内容,总体来说基本达到了一个稳定的状态,随着时间的推移可以会引入一些更好更新的内容进行完善,本篇继续这个系列,主要介绍如何实现Web页面内容的打印预览和保存操作。

1、Web页面打印的问题

在此之前,我一般使用比较好用的LODOP来执行打印的操作,这个在我之前有很多文章都有涉及,这个控件是一个ActiveX的控件,需要下载安装后就可以在页面是进行打印的排版设计,预览,打印等操作,还是很方便的一个控件,因此都很适合普通内容的打印,证件的套打等操作。

不过随着浏览器技术的更新,这个插件在Chrome或者FireFox上好像不受支持了,基本上摒弃了这种插件的处理方式了。例如如果我在页面上需要打印对话框里面的内容,如下所示。

如果按正常使用LODOP的方式来进行处理的话,那么会得到Chrome浏览器的提示,并且这个不管你重新下载安装、更新LODOP控件,都会继续这个错误提示的。

这个在一篇《
Lodop页面总提示"未安装"或"请升级"的可能原因
》,以及《
Lodop用户应对谷歌浏览器停用Plugin技术的处理办法
》都有说明,因此我们需要另辟蹊径来处理这个页面打印等的处理了。

对于替代方式,这里就是本篇内容介绍的主题了,我一直喜欢寻找一些比较好的方式的方式来实现自己需要的功能,于是找到了PrintThis的这个插件(
https://github.com/jasonday/printThis
)以及jquery-print-preview-plugin(
https://github.com/etimbo/jquery-print-preview-plugin
),对比两者我比较喜欢第一个的简洁方便的使用。

2、PrintThis打印插件的使用

有了上面的问题,我们引入一个新的打印方式,也就是JQuery插件来实现我们所需要页面内容的打印操作。

这个插件的使用非常简洁方便,首先需要在页面里面引入对应的JS文件,如下所示。

<scriptsrc="~/Content/JQueryTools/printThis/printThis.js"></script>

我们再在页面顶部增加两个按钮,如打印和导出操作,代码如下所示

<divclass="toolbar">
 <ahref="#"onclick="javascript:Preview();"><imgalt="打印预览"src="~/Content/images/print.gif" /><br/>打印预览</a>
 <ahref="#"onclick="javascript:SaveAs();"><imgalt="另存为"src="~/Content/images/saveas.gif" /><br/>另存为</a>         
</div>

然后我们还需要声明一个DIV用来放置显示的Web页面内容,这样也方便对它调用进行打印操作。

我们打印的处理代码也很简单,就是直接对层进行打印处理就可以了,可以看到下面的使用代码非常简单。

        //打印预览
        functionPreview() {
$(
"#printContent").printThis({
debug:
false,
importCSS:
true,
importStyle:
true,
printContainer:
true,
loadCSS:
"/Content/Themes/Default/style.css",
pageTitle:
"通知公告",
removeInline:
false,
printDelay:
333,
header:
null,
formValues:
true});
};

打印执行后,IE和Chrome都会弹出一个打印预览对话框,确认是否进行打印的操作。

3、页面内容的保存操作

有时候,为了方便业务处理,我们一般也可以提供给用户一个导出打印内容的操作,如下所示代码就是把打印的内容导出到Word里面给用户加工等用途。

        functionSaveAs() {var id = $('#ID2').val();
window.open(
'/Information/ExportWordById?id=' +id );
}

上面的操作,主要就是调用了MVC的控制器方法进行处理,传入一个id就可以把内容提取出来,然后把它生成所需的Word内容即可。

这里后台我们主要利用Apose.Word控件来进行模板化的文档生成,具体可以参考一下我前面介绍过的使用方法文章《
利用Aspose.Word控件实现Word文档的操作
》、《
利用Aspose.Word控件和Aspose.Cell控件,实现Word文档和Excel文档的模板化导出
》。

其中我们可以在书签里面定义或者查看一些书签的信息,如下图所示。

这样我们在代码里面,就可以获取信息并指定这个Word模板了。

            InformationInfo info = BLLFactory<Information>.Instance.FindByID(id);if (info != null)
{
string template = "~/Content/Template/政策法规模板.doc";string templateFile =Server.MapPath(template);
Aspose.Words.Document doc
= new Aspose.Words.Document(templateFile);

WORD模板的内容,可以使用文本替换方式,如下所示。

SetBookmark(ref doc, "Content", info.Content);

也可以使用书签BookMark方式查询替换,如下代码所示。

            Aspose.Words.Bookmark bookmark =doc.Range.Bookmarks[title];if (bookmark != null)
{
bookmark.Text
=value;
}

对于主体的HTML内容,这需要特殊对待,一般需要使用插入HTML的专用方式进行写入内容,否则就显示HTML代码了,使用专用HTML方法写入的内容,和我们在网页上看到的基本没有什么差异了。如下代码所示。

                DocumentBuilder builder = newDocumentBuilder(doc);
Aspose.Words.Bookmark bookmark
= doc.Range.Bookmarks["Content"];if (bookmark != null)
{
builder.MoveToBookmark(bookmark.Name);
builder.InsertHtml(info.Content);
}

整个导入WORD文档的方法就是利用这些内容的整合,实现一个标准文档的生成,这种业务文档是固定模板的,因此很适合在实际业务中使用,比起使用其他方式自动生成的HTML文件或者文档,有更好的可塑性和美观性。

整个代码如下所示。

        public FileStreamResult ExportWordById(stringid)
{
if (string.IsNullOrEmpty(id)) return null;

InformationInfo info
= BLLFactory<Information>.Instance.FindByID(id);if (info != null)
{
string template = "~/Content/Template/政策法规模板.doc";string templateFile =Server.MapPath(template);
Aspose.Words.Document doc
= newAspose.Words.Document(templateFile);#region 使用文本方式替换 //Dictionary<string, string> dictSource = new Dictionary<string, string>();//dictSource.Add("Title", info.Title);//dictSource.Add("Content", info.Content);//dictSource.Add("Editor", info.Editor);//dictSource.Add("EditTime", info.EditTime.ToString());//dictSource.Add("SubType", info.SubType);//foreach (string name in dictSource.Keys)//{//doc.Range.Replace(name, dictSource[name], true, true);//} #endregion //使用书签方式替换 SetBookmark(ref doc, "Title", info.Title);
SetBookmark(
ref doc, "Editor", info.Editor);
SetBookmark(
ref doc, "EditTime", info.EditTime.ToString());
SetBookmark(
ref doc, "SubType", info.SubType);//SetBookmark(ref doc, "Content", info.Content);//对于HTML内容,需要通过InsertHtml方式进行写入 DocumentBuilder builder = newDocumentBuilder(doc);
Aspose.Words.Bookmark bookmark
= doc.Range.Bookmarks["Content"];if (bookmark != null)
{
builder.MoveToBookmark(bookmark.Name);
builder.InsertHtml(info.Content);
}

doc.Save(System.Web.HttpContext.Current.Response, info.Title, Aspose.Words.ContentDisposition.Attachment,
Aspose.Words.Saving.SaveOptions.CreateSaveOptions(Aspose.Words.SaveFormat.Doc));

HttpResponseBase response
=ControllerContext.HttpContext.Response;
response.Flush();
response.End();
return new FileStreamResult(Response.OutputStream, "application/ms-word");
}
return null;
}
private void SetBookmark(ref Aspose.Words.Document doc, string title, stringvalue)
{
Aspose.Words.Bookmark bookmark
=doc.Range.Bookmarks[title];if (bookmark != null)
{
bookmark.Text
=value;
}
}

最后导出的WORD文档就是模板化的具体文档内容了,WORD预览界面如下所示。

如果有兴趣,可以继续参考系列文章:

基于Metronic的Bootstrap开发框架经验总结(1)-框架总览及菜单模块的处理

基于Metronic的Bootstrap开发框架经验总结(2)--列表分页处理和插件JSTree的使用


基于Metronic的Bootstrap开发框架经验总结(3)--下拉列表Select2插件的使用

基于Metronic的Bootstrap开发框架经验总结(4)--Bootstrap图标的提取和利用

基于Metronic的Bootstrap开发框架经验总结(5)--Bootstrap文件上传插件File Input的使用


基于Metronic的Bootstrap开发框架经验总结(6)--对话框及提示框的处理和优化

基于Metronic的Bootstrap开发框架经验总结(7)--数据的导入、导出及附件的查看处理


基于Metronic的Bootstrap开发框架经验总结(8)--框架功能总体界面介绍


基于Metronic的Bootstrap开发框架经验总结(9)--实现Web页面内容的打印预览和保存操作

在代码生成工具的各种功能规划中,我们一向以客户的需求作为驱动,因此也会根据需要增加一些特殊的功能或者处理。在实际的开发中,虽然我们一般以具体的表进行具体业务开发,但是有些客户提出有时候视图开发也是很常见的,为了提高代码生成和界面生成的效率,基于视图开发的过程也应该支持。还有主从表的界面生成操作,在很多实际的业务领域也是很常见的。基于上面的需求,本次代码生成工具Database2Sharp中增加视图的代码生成以及主从表界面生成功能,为客户的高效率开发快马加鞭、保驾护航。

1、基于视图的代码生成和界面生成操作

为了支持视图的相关代码生成,我们把代码生成工具底层的元数据进行了优化整合,是指在代码生成方面,具体的表和视图不再有具体的差异,基本上都是可以统一对待,实现快速的框架代码生成、Winform界面生成、Web界面生成操作的,所有的表的相关属性,视图也具有,因此在代码模板方便,不需要进行调整,兼容了代码模板的属性处理,提高了已有代码模板的安全性。

如上所示,为了区分表和视图的位置,我们把视图统一放在了表的后面,方便选择处理,在选择的时候,表和视图都是一视同仁,所以代码生成的处理适用于表的,也是适用于视图的,可以统一一并生成,极大的提高了代码生成的效率。

当然,视图的增删改操作,和表毕竟不一样,一般视图只是为了查询数据进行展示的,所以在实际开发的时候,可以适当屏蔽视图的增删改操作,或者自定义相关的接口进行处理。

在Winform界面的生成的时候,我们也一样整合了视图的列表,可以基于视图进行界面代码的生成。

不过我们注意到,一般表我们使用备注信息作为Winform界面的字段说明信息的,如果是视图,那么是无法获取到它的视图字段备注信息的,因为视图的字段备注是不存在的,为了实现和表一样具有中文备注的界面,我们增加了一个对视图字段进行备注信息维护的界面,有着字段的备注,我们生成Winform界面的时候,对应字段的标签就有中文信息了。

通过Enterprise Library架构生成的相关代码,也同时具有相关的备注信息,如下代码所示。

通过代码生成工具里面的Winform界面代码生成,当然也会具有相关的备注信息,可以在界面上显示对应的中文标签信息了。

视图的Winform界面代码生成和普通的表生成的Winform界面操作过程一样,具体界面操作如下所示。

这样生成的Winform界面操作和普通表的处理方式一致,而且对应的视图字段也有了备注信息,因此在界面上的标签说明也就和表一样,可以显示备注信息了。

2、主从表的界面生成操作

在有些情况下,有些业务表是具有主从关系的,如一个是汇总信息,一个是明细信息,如仓库的入库、出库操作,会员的消费操作,都是典型的主从表应用场景,可以把它们作为一个界面生成的案例进行处理。

标准的主从表界面如下界面所示。

或者会员消费信息的横向界面展示如下所示。

在我之前的代码生成界面里面,主要是生成标准的分页列表展示,以及编辑明细两个标准界面,为了更加丰富开发工具的界面生成,根据上面两种界面的综合情况,增加一个主从表的界面生成操作,这样可以更加适应实际的业务开发工作,高效进行界面的快速生成。

如在代码生成工具里面,设置主从表的界面生成如下所示。

生成Winform界面代码后,在VS编辑器里面,可以看到如下所示界面。

最后在实际程序上运行生成的界面,就可以看到预览的界面效果了,界面效果如下所示。

为了方便,上面界面增加了一个复选框,用来切换横向或者纵向展示明细列表的,勾选后进行横向显示,如下所示。

上面主从表的展示,还包括了明细表信息的汇总功能,这样基本上满足了主从表的信息展示了,如果需要,还可以在这基础上进行更方便的改进了。

Winform混合式开发框架,是一种支持分布式部署的应用模式,支持直接连接数据库,访问远程WCF服务,访问远程Web API服务等服务的综合性框架,根据不同的需求采用不同的数据接口,是一个适应性很广的应用框架。  混合式开发框架它本身是一个完整的业务系统外,它外围的所有辅助性模块(如通用权限、通用字典、通用附件管理、通用人员管理、通讯录管理......)均都实现了这种混合型的框架,因此使用非常方便。

1、多种数据接入方式

《混合式开发框架》混合了传统《Winform开发框架》、《WCF开发框架》和Web API接口框架的特点,可以在直接访问数据库、利用WCF服务获取数据、利用Web API服务获取数据三者之间自由切换,统一了系统界面层对业务服务的调用模式,所有组件模块均实现三种方式的调用,是一种弹性化非常好的框架应用,既可用于单机版软件或者基于局域网内的应用软件,也可以用于分布式技术的互联网环境应用,是一种成熟稳定、安全高效的技术框架。

由于混合型框架,既可以用于传统Winform系统开发,也可以用于WCF分布式系统开发,还可以用于轻型高效的Web API的分布式系统开发,因此环境适应性强;而且由于模块具有这些特点,可重用性更高,特别对于通用性的模块,更是具有无可替代的优越性。

2、
独立配置,更少的代码修改

混合框架所有通用模块,如果是通过WCF或者Web API服务接入的,那么客户端模块都需要配置好对应的访问地址。

WCF服务连接,通过独立配置文件进行配置WCF的连接,减少主配置文件的复杂性;

Web API每个服务的连接地址也是可以通过配置文件进行指定,根据需要采用HTTP或者HTTPS协议进行数据传输。

这些接入的配置,都是独立放在在不同的文件里面,这样可以方便集中修改,也方便通过程序界面进行配置调整。如在混合框架里面,提供了一个管理界面,用来管理这些接口的切换和配置参数的。

其中相关的接口地址参数,可以在管理界面里面统一进行维护修改。

3、提供多种主体界面布局方式

混合框架提供多种模块调用方式,可以通过预先配置好的菜单,以插件方式动态构建对应的菜单按钮,并触发调用对应的插件模块进行展示;也可以按常规的添加菜单按钮方式进行功能按钮布局。

混合框架主体界面同时也提供多种界面布局方式,可以按标准的顶栏菜单模式展示,如下所示。

也可以在左边放置类似OutLookBar的功能条,在放置对应的菜单功能树,这样可以展示更多的功能模块,适用于系统功能较为复杂的情况使用。

4、代码生成工具的集成

整个框架的应用开发,代码生成工具Database2Sharp是灵魂,它围绕着不同的框架,根据设计好的数据库信息,生成主体框架信息,把不同类型的类文件放在不同的项目中,实现快速的框架增量开发;另外整个框架的Winform界面和Web界面,都可以快速生成,稍微调整下即可实现专业性界面的设计开发工作,并能够迅速编译运行起来,从而实现快速、高效、统一的框架应用开发。

1)主体框架代码的生成

EnterpriseLibrary代码生成时一个整体性项目代码的生成操作,它能根据设计好数据库信息以及模板文件,生成一个完整性非常高的项目。一般结合我们的
Winform开发框架

WCF开发框架、混合型开发框架
或者Web开发框架,进行增量式的项目开发,效率更高,而且可以可以利用更多已经开发好的、现成的组件模块的集成,完美的整合,以及模块化的封装,能带给你无穷的开发乐趣同时,使得项目无论从代码风格、用户界面、设计理念,都能保持很好的统一,快速优雅的完成碰到的项目。

代码生成工具,生成整体性的混合型框架项目如下所示,只是没有下图的界面部分,这部分在实际开发过程中,结合我的混合型框架案例进行整合即可,另外也可以界使用Database2Sharp进行Winform界面的开发,这样整体性就非常方便操作了:

2)界面代码的生成

界面开发,无论对于Web开发,还是Winform开发,都需要耗费一定的时间,特别对于一个数据库字段比较多的界面,一般就需要在编辑界面上摆的更多的控件来做数据显示,每次碰到这个,都有点头痛,反复的机械操作让人挺累,也很烦,但是又必须这样做。

由于数据库字段和界面的排版都有一定的关联关系,因此可以通过代码生成工具Database2Sharp的数据库元数据,包含表名称、备注信息、字段列表,以及每个字段的名称、备注、类型等信息,构造一个基础的界面,把重复机械的部分给快速完成,这就是我所说的界面快速生成。当然,对于精致的界面,机械的生成肯定不能满足我们的需要,因此真正的界面需要在这个基础上修改完善一下,但是由于重复劳动部分,已经给工具处理掉了,因此,界面开发效率会大大提高。

Winform界面可以生成标准的列表展示界面,也可以生成主从表界面,基本上适应了大多数的情况。

在我前面很多关于Visio的开发过程中,介绍了各种Visio的C#开发应用场景,包括对Visio的文档、模具文档、形状、属性数据、各种事件等相关的基础处理,以及Visio本身的整体项目应用,虽然时间过去很久,不过这些技术依旧还在使用中,最近应客户培训的需要,我对所有的内容进行了重新整理,把一些没有介绍的很详细或者很少的内容进行了丰富,因此本文介绍的主题-Visio二次开发之文件导出及另存Web页面,介绍一下Visio文件另存为其他几种格式的处理,以及另存为Web文件等相关操作。

1、Visio导出为PDF格式

在一般情况下,PDF格式是较为常用的内容格式,因此Visio文档(Vsd格式)导出为PDF也是很常见的一件事情,Office文档本身很好支持PDF格式的输出,因此对于Visio来说,也不是什么难事,基本上利用它现有的API就可以导出为PDF格式了。

在Visio的Document文档对象中,就有ExportAsFixedFormat这个方法,可以导出为PDF或者XPS的格式的,这个格式有很多参数,用来确定导出那页,以及格式等设置。

expression
.ExportAsFixedFormat(
FixedFormat
,
OutputFileName
,
Intent
,
PrintRange
,
FromPage
,
ToPage
,
ColorAsBlack
,
IncludeBackground
,
IncludeDocumentProperties
,
IncludeStructureTags
,
UseISO19005_1
,
FixedFormatExtClass
)

同时,这些参数的相关说明如下所示。

Name Required/Optional Data Type Description
FixedFormat Required VisFixedFormatTypes The format type in which to export the document. See Remarks for possible values.
OutputFileName Optional String The name and path of the file to which to output, enclosed in quotation marks.
Intent Required VisDocExIntent The output quality. See Remarks for possible values.
PrintRange Required VisPrintOutRange The range of document pages to be exported. See Remarks for possible values.
FromPage Optional Long If
PrintRange
is
visPrintFromTo
, the first page in the range to be exported. The default is 1, which indicates the first page of the drawing.
ToPage Optional Long If
PrintRange
is
visPrintFromTo
, the last page in the range to be exported. The default is -1, which indicates the last page of the drawing.
ColorAsBlack Optional Boolean True
to render all colors as black to ensure that all shapes are visible in the exported drawing.
False
to render colors normally. The default is
False
.
IncludeBackground Optional Boolean Whether to include background pages in the exported file. The default is
True
.
IncludeDocumentProperties Optional Boolean Whether to include document properties in the exported file. The default is
True
.
IncludeStructureTags Optional Boolean Whether to include document structure tags to improve document accessibility. The default is
True
.
UseISO19005_1 Optional Boolean Whether the resulting document is compliant with ISO 19005-1 (PDF/A). The default is
False
.
FixedFormatExtClass Optional [UNKNOWN] A pointer to a class that implements the
IMsoDocExporter
interface for purposes of creating custom fixed output. The default is a null pointer.

我们在代码里面导出PDF如下所示。

            SaveFileDialog dlg = newSaveFileDialog();
dlg.FileName
= "";
dlg.Filter
= "Pdf文件 (*.pdf)|*.pdf|AutoCAD 绘图 (*.dwg)|*.dwg|所有文件(*.*)|*.*";
dlg.FilterIndex
= 1;if (dlg.ShowDialog() ==DialogResult.OK)
{
if (dlg.FileName.Trim() != string.Empty)
{
VisDocument.ExportAsFixedFormat(Visio.VisFixedFormatTypes.visFixedFormatPDF,
dlg.FileName,
Visio.VisDocExIntent.visDocExIntentScreen,
Visio.VisPrintOutRange.visPrintAll,
1, VisDocument.Pages.Count, false, true, true, true, true,
System.Reflection.Missing.Value);
}
}

这样,我们通过指定PDF格式,以及导出文件名,以及起止页码等信息后,就可以顺利导出对应的Visio文档了,这种方式导出的Visio文档,效果非常好,可以放大到最大清晰都很好的。

2、Visio另存为CAD格式

Visio和CAD之间是比较好的兼容模式的,Visio和CAD本身都是基于矢量图形的绘制,因此转换为CAD在继续进行编辑也是很常见的事情,因此在较早时期,Visio本身就对CAD格式(dwg格式)就提供了很好的支持,它可以通过下面代码进行CAD格式的导出。

            SaveFileDialog dlg = newSaveFileDialog();
dlg.FileName
= "";
dlg.Filter
= "AutoCAD 绘图 (*.dwg)|*.dwg|所有文件(*.*)|*.*";
dlg.FilterIndex
= 1;if (dlg.ShowDialog() ==DialogResult.OK)
{
if (dlg.FileName.Trim() != string.Empty)
{
VisApplication.ActivePage.Export(dlg.FileName);
}
}

如果CAD文件顺利导出,那么会有一个日志文件提示用户操作的结果的,如下所示。

Visio还可以导出为JPG格式,这个和CAD操作类似,都是通过Page对象的Export方法进行导出,操作代码如下所示。

            SaveFileDialog dlg = newSaveFileDialog();
dlg.FileName
= "";
dlg.Filter
= "JPEG文件 (*.jpg)|*.jpg|所有文件(*.*)|*.*";
dlg.FilterIndex
= 1;if (dlg.ShowDialog() ==DialogResult.OK)
{
if (dlg.FileName.Trim() != string.Empty)
{
VisApplication.ActivePage.Export(dlg.FileName);
}
}

虽然这个导出的JPG格式,也是比较不错的,不过相对PDF的矢量效果来说,JPG放大的话,一般来说没有PDF格式那么清晰,但总体效果也还是可以。

3、Visio文档另存Web页面

对于Visio文档的另存为Web页面的操作,就没有上述几个方法那么简单了,一般需要更加复杂一点的处理方式。

虽然对于Visio文档来说,在IE上可以通过ActiveX的Visio Viewer来进行查看,不过其他浏览器都不支持,因此对于另存为Web页面的文件,这种方式显得比较通用一些,可以在各个浏览器上查看HTML页面,里面就包含了对Visio文件的显示了。

Visio的文档另存为Web页面的操作,主要思路是利用Application对象的SaveAsWebObject属性,并通过VisWebPageSettings对象进行一些导出属性的设置,如页面范围,文档分辨率等属性设置,以及是否在完成后使用浏览器打开文件等设置。

如获得对象的操作如下所示。

                //获取文档的Application对象
                targetApplication =targetDocument.Application;//获取并转换SaveAsWebObject对象
                saveAsWebAddon =(VisSaveAsWeb)targetApplication.SaveAsWebObject;//获取保存Web页面的参数设置对象
                saveAsWebSetting = (VisWebPageSettings)saveAsWebAddon.WebPageSettings;

通过获得页面参数对象,我们可以设定导出的起始页面,如下所示。

                    saveAsWebSetting.StartPage =startPage;
saveAsWebSetting.EndPage
= endPage;

然后在绑定到具体导出的文档里面就确定对应
导出

文档了。

                //使用AttachToVisioDoc指定那个文档作为保存页面的对象
                saveAsWebAddon.AttachToVisioDoc(targetDocument);    

为了提高导出Web页面的Visio清晰度,我们需要设置文档的显示比例,如下所示为使用源格式大小。

                //设置其中的相关参数
                saveAsWebSetting.DispScreenRes = VISWEB_DISP_RES.resSource;//显示比例

这个VISWEB_DISP_RES里面有很多参数可以设置的。

Constant Value Description

resSource

0

Use resolution of the source image for output.

res180x260

1

180 x 260 pixels

res544x376

2

544 x 376 pixels

res640x480

3

640 x 480 pixels

res720x512

4

720 x 512 pixels

res768x1024

5

768 x 1024 pixels

res800x600

6

800 x 600 pixels

res1024x768

7

1024 x 768 pixels

res1152x882

8

1152 x 882 pixels

res1152x900

9

1152 x 900 pixels

res1280x1024

10

1280 x 1024 pixels

res1600x1200

11

1600 x 1200 pixels

res1800x1440

12

1800 x 1440 pixels

res1920x1200

13

1920 x 1200 pixels

resINVALID

14

Reserved.

另外还有一个参数确定是批处理方式(静默方式)还是完成后通过浏览器打开文件的方式,如下所示。

                //判断是否为批处理模式
                if ((flags & RunInBatchMode) != 0)
{
//如果为批处理模式,那么浏览器窗口不会自动打开 saveAsWebSetting.OpenBrowser = 0;
saveAsWebSetting.SilentMode
= 1;
}
else{//否则保存完毕后打开对应给的浏览器显示文件 saveAsWebSetting.OpenBrowser = 1;
saveAsWebSetting.QuietMode
= 1;
}

如果一切顺利,那么通过方法直接创建页面就可以了,如下所示。

saveAsWebAddon.CreatePages();//创建页面

以上的方法处理,我们一般封装在一个类里面,方便调用处理,那么在界面上,我们处理的方法就可以简单化一些。

            var fileName = System.IO.Path.Combine(System.Environment.CurrentDirectory, "test.html");var success = SaveAsWebApi.SaveDocAsWebPage(this.axDrawingControl1.Document, -1, -1, fileName,
SaveAsWebApi.ShowPropertiesWindow
| SaveAsWebApi.ShowNavigationBar |SaveAsWebApi.ShowSearchTool|SaveAsWebApi.ShowPanAndZoom);

MessageBox.Show(success
? "成功生成Web文件" : "生成Web文件操作失败");

最后,我们就可以在各个浏览器里面查看相关的Visio文件了,这种方式比Visio Viewer的处理更通用,效果也很不错哦。

虽然在APP应用、Web应用、Winform应用等大趋势下,越来越多的企业趋向于这些应用系统开发,但是Socket的应用在某些场合是很必要的,如一些停车场终端设备的接入,农业或者水利、压力监测方面的设备数据采集等,以及常见的IM(即时通讯,如腾讯QQ、阿里旺旺等)的客户端,都可以采用Socket框架进行相关的数据采集和信息通讯用途的,Socket应用可以做为APP应用、Web应用和Winform应用的补充。

1、Socket应用场景

一般情况下,客户端和服务端进行Socket连接,需要进行数据的交换,也就是后台提供数据查询或者写入的相关操作,它们的应用场景也是在后台有一个应用数据库支持的,如下所示。

Socket服务器和客户端的通讯原理如下所示,客户端通过服务器地址和端口发起Socket连接,服务器在接收到Socket客户端的请求后,开辟一个新的Socket连接进行通讯管理,两方基于Socket协议进行数据的交互处理。

2、Socket框架设计思路

Socket开发是属于通信底层的开发,.NET本身也提供了非常丰富的类来实现Socket的开发工作,Socket框架应针对这些基础功能进行了很好的封装处理,已达到统一、高效的使用。

要掌握或者了解Socket开发,必须了解下面所述的场景及知识。

  • TCP客户端,连接服务器端,进行数据通信
  • TCP服务器端,负责侦听客户端连接
  • 连接客户端的管理,如登陆,注销等,使用独立线程处理
  • 数据接收管理,负责数据的接受,并处理队列的分发,使用独立线程处理,简单处理后叫给
    “数据处理线程
  • 数据处理线程,对特定的数据,采用独立的线程进行数据处理
  • 数据的封包和解包,按照一定的协议进行数据的封装和解包

针对以上内容,可以封装以下功能的操作类作为共用基类:

  • BaseSocketClient,客户端基类,负责客户端的链接、断开、发送、接收等操作。
  • BaseSocketServer,
    TCP服务器管理基类,负责在独立的线程中侦听指定的端口,如果有客户端连接进来,则进行相应的处理。
  • BaseClientManager,连接客户端管理类,该类主要负责客户端登录超时处理,连接上来的客户端维护,经过登陆验证的客户端维护,客户端登陆验证接口,客户端发送数据处理等功能。
  • BaseReceiver,数据接收处理类,该基类是所有接受数据的处理类,负责维护数据的队列关系,并进一步进行处理。
  • ThreadHandler,数据独立线程处理类,对每个不同类型的数据(不同的协议类型),可以用独立的线程进行处理,这里封装了一个基类,用于进行数据独立线程的处理。

1)Socket客户端基类

我们知道Socket通讯,分为了客户端和服务端,它们各自处理的事情是有所不同的,因此为了实现更好的代码重用,我们在这个基础上进行了不同的封装。针对Socket客户端类,我们主要需要提供基础的Socket连接及断开、接收及发送、封包拆包等常规操作过程,因此我们封装了一个客户端基类 BaseSocketClient。

但是为了基于不同的应用客户端,实现不同的业务沟通,我们可以在服务端接收处理不同的客户端,因此也就是需要对Socket客户端进行派生扩展,例如本框架增加了一个中心的Socket客户端、分店的Socket客户端、还有一个桥接的连接客户端(可实现转发数据功能)。

2)Socket服务端基类

相对于Socket客户端基类,同样我们也创建一个Socket服务端基类,通过继承的方式,我们可以用于简化代码的重复性。该服务端基类称为TCP服务器管理基类 BaseSocketServer,负责在独立的线程中侦听指定的端口,如果有客户端连接进来,则进行相应的处理。

同样我们也派生了两个服务端的基类,方便对不同的Socket客户端进行差异性处理,如对应上面的中心客户端类ClientOfCall,我们增加一个对应的服务端类ServerForCall,其他的也类似,它们的继承关系如下所示。

另外,由于我们允许不同的Socket客户端类(如ClientOfCall、ClientOfShop)的接入,那么在服务器端也会有对应Socket服务端类(ServerForCall、ServerForShop)进行不同端口的侦听,一旦在自己所属端口有Socket接入,那么服务端类会分派给不同Socket客户端管理类来处理他们的关系和数据,这样也就进一步引入一个客户端管理类的概念,它对应不同的Socket客户端。

这里也根据需要定义了一个Socket客户端管理基类BaseClientManager<T>,这个T代表对应不同的客户端,这样我们就可以派生出CallClientManager和ShopClientManager两个不同的客户端管理类了,它们的继承关系如下所示。

3)数据接收处理基类

在不同的Socket客户端连接到服务端后,服务端开辟一个新的线程进行对应的Socket数据通讯,那么数据通讯这里面的管理,我们可以为不同的Socket客户端订做一个对应的数据接收处理类,专门针对特定的Socket客户端连接的数据进行处理。

这里也根据需要定义了一个数据接收的基类BaseReceiver,同样我们派生对应不同客户端的数据接收类ReceivedForCall、ReceivedForShop和ReceivedForBridge等几个具体的数据处理类,它们的继承关系如下所示。

3、框架界面设计

1)参数配置

Socket服务器需要一些参数来确定侦听的IP地址、端口,以及数据库的连接信息,各种数据的处理时间间隔等参数,因此需要提供一个较好的管理界面来进行管理,本框架使用基于本地配置文件的参数管理方式进行管理,参数界面如下所示。

客户端也同样需要配置一些参数,用来确定连接的服务器IP及端口信息,如下配置界面所示。

Socket服务器监控界面,需要显示一些基础的状态和Socket连接等基础信息,作为我们对整体状态的了解,同时这些信息可以记录到日志里面供我们进行查阅和分析。

除了上面总体的设计外,其中还有一个地方需要细致的展开来介绍,就是对Socket传输消息的封装和拆包,一般的Socket应用,多数采用基于顺序位置和字节长度的方式来确定相关的内容,这些处理对我们分析复杂的协议内容,简直是一场灾难,协议位置一旦变化或者需要特殊的处理,就是很容易出错的,而且大多数代码充斥着很多位置的数值变量,分析和理解都是非常不便的。

如果对于整体的内容,使用一种比较灵活的消息格式,如JSON格式,那么我们可以很好的把消息封装和消息拆包解析两个部分,交给第三方的JSON解析器来进行,我们只需要关注具体的消息处理逻辑就可以了,而且对于协议的扩展,就如JSON一样,可以自由灵活,这样瞬间,整个世界都会很清静了。由于篇幅的原因,我将在下一个随笔在进行介绍JSON格式的消息处理过程。

除了上面的场景外,我们还需要考虑用户消息的加密和校验等内容处理,这样才能达到安全、完整的消息处理,我们可以采用 RSA公钥密码系统。平台通过发送平台RSA公钥消息向终端告知自己的RSA公钥,终端回复终端RSA公钥消息,反之亦然。这样平台和终端的消息,就可以通过自身的私钥加密,让对方公钥解密就可以了。