2023年2月

CodeDom是.NET框架中比较强大也是比较难懂的对象模型,通过它可以实现.NET支持各种语言代码的动态生成及动态编译。我们先来看看CodeDom的定义:.NET Framework 中包含一个名为“代码文档对象模型”(CodeDOM) 的机制,该机制使编写源代码的程序的开发人员可以在运行时,根据表示所呈现代码的单一模型,用多种编程语言生成源代码。

Refly则是国外一个开发者对CodeDom进行封装,目的是使得Codedom的实现更加方便易懂,和CodeDom的使用对比,代码更加简洁优雅,不过要了解整体的东西,也需要对CodeDOM进行详细的了解才能熟练应用。

本人在研究学习Refly当中(详细可以参考
http://www.codeproject.com/Articles/6283/Refly-makes-the-CodeDom-er-life-easier
),对其中简单的应用有一些体会,做了一个使用Refly生成代码的例子进行测试,并使用CodeDOM进行动态编译。例子应该还算简单,用来说明Refly的工作机制应该还是足够的,同时也希望与大家探讨一下进一步的应用。

生成类代码的代码如下所示:

            #region 生成代码

//创建命名空间
NamespaceDeclaration ns = new NamespaceDeclaration("Demo");
ns.Imports.Add("System.Xml");
ns.Imports.Add("System.IO");
ns.Imports.Add("System.ComponentModel");
ns.Imports.Add("System.Xml.Serialization");

//创建类定义
ClassDeclaration user = ns.AddClass("User");
//添加类说明
user.Doc.Summary.AddText("测试用户类描述");
user.Doc.Remarks.Add("para");
user.Doc.Remarks.Into();
user.Doc.Remarks.AddText("该类是使用Refly进行生成");
user.Doc.Remarks.OutOf();

//添加字段
FieldDeclaration name = user.AddField(typeof(string), "name");
FieldDeclaration age = user.AddField(typeof(int), "age");

//添加构造函数(默认)
user.AddConstructor();
ConstructorDeclaration cstr = user.AddConstructor();
//添加构造函数(参数)
ParameterDeclaration cstr_name = cstr.Signature.Parameters.Add(typeof(string), "name", true);

cstr.Body.AddAssign(Expr.This.Field(name), Expr.Arg(cstr_name));

//添加属性Name
PropertyDeclaration proName = user.AddProperty(name, true, true, false);
proName.Doc.Summary.AddText("用户名称");
//添加属性的Attribute
AttributeDeclaration attr = proName.CustomAttributes.Add(typeof(XmlElementAttribute));
attr.Arguments.Add("ElementName", Expr.Prim(proName.Name));

//添加属性Age
PropertyDeclaration proAge = user.AddProperty(age, true, true, false);
proName.Doc.Summary.AddText("用户年龄");

//添加方法
MethodDeclaration add = user.AddMethod("Add");
add.Doc.Summary.AddText("添加用户内容");
ParameterDeclaration pName = add.Signature.Parameters.Add(typeof(string), "name", true);
add.Doc.AddParam(pName);
ParameterDeclaration pAge = add.Signature.Parameters.Add(typeof(int), "age", true);
add.Body.Add(Stm.Assign(Expr.This.Prop("Name"), Expr.Arg(pName)));
add.Body.Add(Stm.Assign(Expr.This.Prop("Age"), Expr.Arg(pAge)));

//添加方法2
MethodDeclaration show = user.AddMethod("Show");
show.Doc.Summary.AddText("输出用户名称");
show.Body.Add(Expr.Snippet("Console").Method("WriteLine").Invoke(Expr.This.Prop("Name")));

//输出结果
Refly.CodeDom.CodeGenerator gen = new Refly.CodeDom.CodeGenerator();
gen.Provider = Refly.CodeDom.CodeGenerator.CsProvider;
gen.GenerateCode(Application.StartupPath + "/CS", ns);

#endregion

编译代码好像Refly没有找到,所以用原始的CodeDOM的对象操作进行代码的动态编译,编译代码如下所示:

           #region 动态编译代码

string file = string.Format("{0}\\CS\\Demo\\User.cs", Application.StartupPath);
string code = FileUtil.FileToString(file);
string output = string.Format("{0}\\CS\\Demo\\User.dll", Application.StartupPath);

CSharpCodeProvider codeProvider = new CSharpCodeProvider();
CompilerParameters parameters = new CompilerParameters();
parameters.ReferencedAssemblies.Add("System.dll");
parameters.ReferencedAssemblies.Add("System.Data.dll");
parameters.ReferencedAssemblies.Add("System.Xml.dll");
parameters.GenerateInMemory = false;
parameters.TreatWarningsAsErrors = false;
parameters.OutputAssembly = output; //设定输出文件名称路径

//判断编译结果
//CompilerResults results = codeProvider.CompileAssemblyFromSource(parameters, code);
CompilerResults results = codeProvider.CompileAssemblyFromFile(parameters, file);
if (results.Errors.HasErrors)
{
string errorMessage = "";
errorMessage = results.Errors.Count.ToString() + "Errors:";
for (int x = 0; x < results.Errors.Count; x++)
{
errorMessage = errorMessage + "\r\nLine:" +
results.Errors[x].Line.ToString() + "-" + results.Errors[x].ErrorText;
}
MessageUtil.ShowError(errorMessage);
}
//string path = results.PathToAssembly;

return results.Errors.Count == 0;

#endregion

代码编译了,我们使用的编译好的类也就可以了,使用操作代码如下所示,例子我使用反射,把生成的对象加载,并绑定到PropertyGrid控件中。

        private void btnTest_Click(object sender, EventArgs e)
{
GenerateCompile();

string assemblyFile = string.Format("{0}\\CS\\Demo\\User.dll", Application.StartupPath);
if (File.Exists(assemblyFile))
{
Assembly assObj = Assembly.LoadFile(assemblyFile);
if (assObj != null)
{
object obj = assObj.CreateInstance("Demo.User");
this.propertyGrid1.SelectedObject = obj;
}
}
}

最终例子运行的效果如下所示。

其实最终生成的User类代码如下所示。

//Generated by Refly
namespace Demo
{
using System;
using System.Xml;
using System.IO;
using System.ComponentModel;
using System.Xml.Serialization;

/// <summary>测试用户类描述</summary>
/// <remarks>
///<para>该类是使用Refly进行生成</para>
/// </remarks>
public class User
{

private int _age;

private string _name;

public User()
{
}

public User(string name)
{
this._name = name;
}

/// <summary>用户名称用户年龄</summary>
[XmlElementAttribute(ElementName="Name")]
public virtual string Name
{
get
{
return this._name;
}
set
{
this._name = value;
}
}

public virtual int Age
{
get
{
return this._age;
}
set
{
this._age = value;
}
}

/// <summary>添加用户内容</summary>
/// <param name="name" />
public virtual void Add(string name, int age)
{
this.Name = name;
this.Age = age;
}

/// <summary>输出用户名称</summary>
public virtual void Show()
{
Console.WriteLine(this.Name);
}
}
}



在我的早期的一篇文章中《
Web打印的解决方案之证件套打
》,介绍过基于Web的证件套打方式;另外在我的代码生成工具中,一直介绍使用基于NVelocity模板引擎生成的代码,能够高效、快速、拥抱变化式的生成想要的代码。那么结合两者的特点,我们可以Winfrom界面中,利用NVelocity动态生成页面代码,然后利用WebBrowser控件,实现在Winfrom中的证件套打。在我的Winform框架集成了一些证件的套打功能,这样更能丰富日常开发中碰到的各种打印问题,提供更加广阔的应用前景。

首先我们来了解一下实现的效果,然后再来分析其中的实现思路和代码片段。

上面的Winform套打模块中,包含了三个不同证件,6个套打打印的效果。下面我们一个运输许可证的套打为例进行介绍讲解。

首先就是通过上面填写的数据,利用模板引擎自动生成页面代码,把相关的数据写到网页里面去。

        private void btnLicensePrint_Click(objectsender, EventArgs e)
{
string tempalte = "Template/LicensePrintTemplate.htm";//相对目录 HelperClassAdapter adapter = newHelperClassAdapter(tempalte);
adapter.DirectoryOfOutput
= "Template";
adapter.AddKeyValue(
"CompanyName", this.txtCompanyName.Text)
.AddKeyValue(
"Scope", this.txtScope.Text)
.AddKeyValue(
"License", this.txtLicense.Text)
.AddKeyValue(
"ParkAddr", this.txtParkAddr.Text)
.AddKeyValue(
"IssuingDept", this.txtIssuingDept.Text)
.AddKeyValue(
"LicenseYear", this.txtLicenseDate.Value.Year)
.AddKeyValue(
"LicenseMonth", this.txtLicenseDate.Value.Month)
.AddKeyValue(
"LicenseDay", this.txtLicenseDate.Value.Day)
.AddKeyValue(
"ValidateYear", this.txtLicenseValidity.Value.Year)
.AddKeyValue(
"ValidateMonth", txtLicenseValidity.Value.Month)
.AddKeyValue(
"ValidateDay", this.txtLicenseValidity.Value.Day)
.AddKeyValue(
"Year", DateTime.Now.Year)
.AddKeyValue(
"Month", DateTime.Now.Month)
.AddKeyValue(
"Day", DateTime.Now.Day)
.AddKeyValue(
"EconomicType", this.txtEconomicType.Text);string filePath =adapter.ExecuteSingle();
filePath
=Path.Combine(Application.StartupPath, filePath);

FrmWebBrowser frmView
= newFrmWebBrowser();
frmView.webBrowser1.Navigate(filePath);
frmView.Show();
}

通过以上的代码,会根据模板生成相应的页面文件,然后界面就会看到如下所示的效果,虽然看不到具体的套打数据,但是已经写到网页里面去了。

里面的页面代码会生成相应的证件数据,如下所示。

当我们选择“打印预览”的时候,就会调用打印控件进行套打显示了,里面显示的信息就是具体的证件信息了。当然,套打的背景是不会输出到打印机上的。

当我们选择“打印维护”的时候,就可以根据测试打印效果,对内容在纸张中的位置进行调整,指导满意位置,可以把调整的位置信息保存。

它是保存到本地的文件系统里面去的,所以只需要一次设置保存即可,今后就可以利用调好的位置进行套打了。

这样我们就可以实现基于Winform的证件套打,客户可以定制修改位置,使用起来非常友好和人性化。

由于Winform里面已经集成了打印控件的注册认证,因此打印的时候,不会出现版权文字信息了,真的是省心,省钱,呵呵。

我们知道,在做Web开发的时候,很多控件可以用来做HTML内容编辑的,Winform开发就相对少很多,不过很多时候,我们在做Winform开发的时候,也是需要处理这样的场景的。例如发送HTML邮件内容的时候,在给客户编写内容丰富的反馈意见或者邮件的时候等等,因此有必要做一个支持好HTML格式,支持图片插入等丰富内容的控件。

本文就是介绍这么一个控件,这个控件增加了一些常见HTML格式的工具栏操作,并且改良支持Win7系统下的内容编辑(一般WebBrowser包装类,在Win7下内容会变得不可编辑),本控件不会有这个问题。另外,一般WebBrowser需要客户端有Microsoft.MSHTML.dll,这个文件7M多,一般开发的机器上会有,但普通用户的电脑就不一定有这个文件,而且这个文件很大,非常不适合软件的部署。本控件使用WindowsLive.Writer.Interop.Mshtml和WindowsLive.Writer.Mshtml两个小文件来替代Microsoft.MSHTML.dll这个庞然大物的dll。

下面是一个在Winform开发框架中用于意见反馈的场景,里面可以使用丰富的HTML格式,可以插入图片等操作,如下所示。

打开问题反馈出现下面的HTML内容编辑界面

由于我是把反馈信息作为邮件发送的,因此,插入的本地图片,也会作为嵌入内容写入到邮件当中,在邮件内容中插入嵌入图片是一个有点技术含量的工作,不同于一般的邮件附件,他是把图片字节写在邮件的同时,也同时修改邮件内容的图片应用为cid:***这样的路径格式。收到的邮件如下所示。

由于发送邮件是常见的操作,因此我把它集成在我的公用类库(详细参考《
公用类库系列文章
》)里面去了,因此发送邮件(可带附件、HTML内容带嵌入图片)是一件很轻松的事情,发送邮件的详细介绍在我前面一篇随笔《
厚积薄发,丰富的公用类库积累,助你高效进行系统开发(12)--- 网络相关操作辅助类
》有具体的介绍,下面摘录下来强调一下。

1、发送邮件的辅助类 EmailHelper。

实现效果

1)  本辅助类主要是用来方便实现发送邮件操作,可以发送附件、嵌入图片、HTML等内容邮件。使用底层SMTP协议指令进行发送,成功率较高。

2)邮件发送可以用在客户反馈,信息推广,客户沟通,员工交流等方面业务需要。

实现代码

1)辅助类提供的方法源码如下所示: ..............省略..............

2)辅助类EmailHelper的使用例子代码如下所示

EmailHelper email = new EmailHelper("smtp.163.com", "wuhuacong2013@163.com", "password");    
email.Subject
= "伍华聪的普通测试邮件";
email.Body
= string.Format("测试邮件正文内容");
email.IsHtml
= true;
email.From
= "wuhuacong2013@163.com";
email.FromName
= "wuhuacong2013";
email.AddRecipient(
"6966254@qq.com");try{bool success =email.SendEmail();
MessageUtil.ShowTips(success
? "发送成功" : "发送失败");
}
catch(Exception ex)
{
MessageUtil.ShowError(ex.Message);
}

3)如果使用发送附件、发送嵌入图片(正文中有图片显示内容的)方式,则例子代码如下所示。

EmailHelper email = new EmailHelper("smtp.163.com", "wuhuacong2013@163.com", "password");    
email.Subject
= "伍华聪的图片附件测试邮件";string embedFile = Path.Combine(Application.StartupPath, "cityroad.jpg");
email.Body
= string.Format("测试邮件正文内容<img src=\"{0}\" title='测试图片' />", embedFile);
email.IsHtml
= true;
email.From
= "wuhuacong2013@163.com";
email.FromName
= "wuhuacong2013";
email.AddRecipient(
"6966254@qq.com");
email.AddAttachment(Path.Combine(Application.StartupPath,
"ringin.wav"));//.AddAttachment("C:\\test.txt"); try{bool success =email.SendEmail();
MessageUtil.ShowTips(success
? "发送成功" : "发送失败");
}
catch(Exception ex)
{
MessageUtil.ShowError(ex.Message);
}

其实该控件还可以改造成HTML内容显示控件,不过由于涉及要将服务器的图片文件下载并保存到一个目录,并修改HTML内容中图片的路径以便进行显示,修改内容的时候,还得记住那些图片是原有的,新增的图片才提交到服务器即可,不过这样就会搞得比较复杂一点。大家如果有好的建议可以讨论下。

在我的《
Winform开发框架
》系列文章中,除了介绍Winform开发框架外,还详细介绍了基于Winform开发框架的WCF开发框架,WCF开发框架除了具有Winform程序的丰富体验,绚丽的界面外,还具备Web应用程序的分布式应用特点,数据库服务端配置而不用客户端操心等特点,而随着.NET4.0的出现,只有40多M的安装包,也确实为WCF应用的推广使用更加方便。由于WCF应用的分布式这个重要的特点,客户数据全部保存在服务端,因此也提高了数据的安全性,因此很多传统应用的场合都可以使用WCF技术的。本文主要介绍我近年来做WCF开发的案例应用进行总结,对一些问题进行共同探讨,共同进步。

1、内部跨部门的应用案例

刚开始由于工作需要,需要开发一个给各部门协作,录入数据、查询资料的小型MIS系统,由于有些人可能需要在外出的时候获取相关的数据,另外一方面,也需要利用Winform界面的良好体验和操作性能,因此选择WCF技术进行开发桌面应用程序,刚刚开始的摸索过程是比较痛苦的,一方面要考虑程序的顺利调用,一方面也要考虑对证书加密技术的应用提高数据的安全性,另外由于是分布式数据应用,因此,也需要考虑程序能够自动升级功能,以便我不断的更新部署。综上所述,解决上面的问题,基本能够建立一个较为完善的WCF应用了。当在其中还可能会碰到自动切换内外网功能、数据量大的配置修改,调用时间等方面的设置因素,这些可能最终都导致调用WCF没那么顺利。

下面是我开始的第一个内部跨部门的WCF应用案例。

2、提炼为WCF开发框架。把仓库管理系统提炼为完善的WCF开发框架。

由于我之前几年一直有开发一些各类型的Winform共享软件,经过多年的积累,逐渐形成我的比较成熟稳定的Winform开发框架,结合新探索研究的WCF技术应用,综合两者的特点,进一步提炼为我的WCF开发框架,这一步提炼虽然看似简单,实则也是整合了我的通用权限管理系统、通用字典管理模块、分页控件、公用类库等组件,把框架的数据基类进一步提炼,扩展并支持Oracle数据库、SqlServer数据库、MS Access数据库、Sqlite数据库等,这些基本上是日常开发,常碰到的数据库了,其他数据库扩展也很方便,集成一个总的数据库基类,然后重载几个特殊的函数即可。另外一方面,也把WCF开发框架应用分为了三个界面样式,传统界面样式、基于DotNetBar的界面样式、基于DevExpress界面控件样式,尤其是DevExpress样式,非常美观。

1) 基于传统界面样式

2)基于DevExpress界面样式


整个DevExpress界面样式的程序,在布局及控件使用上,都尽量采用常见美观的界面控件以及美观的图标,使得看起来更加美观大方。

3、基于互联网的房产中介项目开发

一个朋友让我开发一个基于互联网的WCF应用,主要就是采集发布房产信息的,可供很多用户在各个地方进行使用,他们把数据集中放到一台租借的虚拟服务器上,有了之前提炼完善的WCF开发框架作为借鉴参考,这一切做起来还是很轻松,真是应了那句话,机会总是青睐有准备的人,呵呵。在这个项目开发总,很多技术问题,不用在反复尝试去解决,只需要集中精力,把他们重要的业务串联起来即可,顺便可以改善一些界面效果,也获得更多的积累,反哺我的WCF开发框架,使之更加精纯。

在这个软件的开发过程中,发现有些因素会导致接口调用出现错误的,如上传的图片字节比较大、返回的列表集合比较大等情况,会出现调用错误,其实这些都是因为配置参数的问题,修改即可避免,但是如果开始就知道避免,会减少反复调试的时间,提高开发效率。

4、基于互联网的病人资料管理系统

过了不多久,由于共享软件中《
病人资料管理系统
》的使用老客户提出,需要改造目前基于Access单机版的病人资料管理系统为网络版,要求在各处办公的相关人员能够登录管理他们的病人治疗,从而实现多个部门之间的协作,因此我建议他们使用WCF技术部署客户端的应用,这样他们操作起来更加方便灵活,这样顺便可以有效利用我的WCF开发框架成功,他们要求各个不同级别的用户能够操作不同的模块,这些使用权限控制模块就搞定了,几乎可以不费吹灰之力,只需要在权限管理系统界面中定义好相关的功能、角色控制,并在软件中集成控制逻辑即可,这些都可以利用成熟的模式。俗话说,铁打的营盘,流水的兵,在纷繁变化的业务中,不可能要求一个框架包罗万象,不过基础框架搭建的好,底座较好,就像一个稳固的营盘,事半功倍。

在这两次项目演练过程当中,由于需要远程创建、维护数据库表,填入必要的基础资料等,而且还需要涉及提交图片资料作为数据库字段内容,以及提交图片附件保存到网站的相对目录中,因此也进一步丰富了相关的应用开发过程,罗马不是一天建成的,同样,很多东西,也需要逐步的积累完善,才更加精纯和稳靠。

在这次开发中,寻找发现了一个很好的图片查看控件,支持图片的反转、滚动放大缩小等操作,而且图片不用下载到本地,非常不错。

有好的WCF开发框架作为参考,项目做起来肯定会事半功倍,留一些精力做一些拓展性的研究及业务性开发,整体项目做起来就轻松稳妥很多。

在进行Winform开发或者WCF桌面应用的开发过程中,总体来讲,利用DevExpress可以做出更加丰富、绚丽的界面,虽然开始接触DevExpress控件有些不太习惯,不过用起来还是非常不错,因此做企业应用的时候,我们说服企业购买DevExpress的版权也是会得到

不少的支持的,比较大家都会比较喜欢好看一些的界面效果。

从事Winform开发很多年了,由于项目的需要,设计过各种各样的界面效果。一般来说,运用传统的界面控件元素,合理设计布局,能够设计出比较中规中矩的标准界面;利用一些换肤的控件或者部分界面组件,能够设计出相对好看一些的界面效果,如以前很盛行的ActiveSkin、
IrisSkin和DotNetSkin等,这些能够对传统的界面元素进行换肤,确实比标准灰色的界面控件好看了很多。不过随着界面控件的组件发展,目前一般倾向于是用较为大型的控件组,他们除了提供设计得体的界面控件外,还提供了非常多种绚丽多彩的界面样式供选择,如DotNetBar、netadvantage、DevExpress等大型界面控件组。

无论界面设计如何变化,一般基本原则都是为客户提供直观、易用、体验效果较好的界面效果哦,从Office的发展历程我们也可以看到整体的界面效果趋向,从开始的标准控件到目前的Ribbon窗体,从单色调变化到绚丽多彩的界面样式,都给我们提供很好的界面设计参考,大型的界面组件也是模仿这一趋势。言归正传,我们来谈谈详细一点的东西,目前我趋向于采用一种基于Ribbon样式的界面,以及一种基于传统界面结合OutLook样式的界面设计。

一、基于Ribbon样式的界面


以上的界面样式,是一种比较大气、符合Office界面效果的界面布局,通过把不同的功能块集中在不同的面板上显示,确实简洁、美观很多,本文不重复介绍该界面效果的优劣,我们主要来集中看看下面的另外一种界面效果。

二、基于OutLook样式的界面设计

其实OutLook样式很早就有,也可以在很多公开的控件组中看到,如果仅仅是追求部分的OutLook界面效果,而不是整体性的方案,那么CodeProject上的这款开源Outlook组件,估计是其中的佼佼者(
http://www.codeproject.com/Articles/43181/A-Serious-Outlook-Style-Navigation-Pane-Control
)。不过由于需求的是整体性效果,而且还要考虑更多控件界面样式的一致性及美观性,因此我们还是基于DevExpress界面组来设计这样的OutLook界面效果,会显得更加美观大方一点。界面效果如下所示。

以上通过把一个系统很多相关的功能模块放到一颗树上进行分类展示,对于一个比较复杂的人力资源管理系统或者其他复杂功能的系统,也是一个比较合理的布局方式,另外OutLook工具条还是可以隐藏起来,节省右边多文档界面的数据展示空间,这样整体还是比较合理及美观的。其中右边的布局,还可以通过SplitContainer方式把它分成多个模块,然后客户想哪个数据显示面板大一点,拖动一下就可以了,这样不至于数据比较多的时候,导致显示布局不好的情况。下面我们来介绍下如何实现以上的界面布局效果。

1、创建一个基于DevExpress.XtraEditors.XtraForm基类的窗体。如下代码所示

public partial classMainForm : DevExpress.XtraEditors.XtraForm
{
publicMainForm()
{
InitializeComponent();
}
}

2、在界面设计中,在DevExpress工具箱Navigation & Layout里面拖动添加一个BarManager控件到窗体中,并删除默认的工具栏Tools,并添加一些菜单项和状态条数据。如下所示。

3、添加PanelControl和NavBarControl,添加一些测试功能按钮。

由于我们需要使用MDI多文档界面效果,因此先设置Mainform的IsMdiContainer属性为True。

然后添加一个PanelControl,设置其Dock为Top布局,为该控件ContentImage设置一个背景图片(事先用PS设计好,保存为png格式即可),添加几个小Lable,设置其的图片和文字。

最后拖入一个NavBarControl控件到界面中,设置其Dock为Left布局,通过控件的右键菜单上的“Run Designer"进入设计界面,先随便添加一些NavBarGroup和NavBarItem项目,粗略设置得到界面效果如下所示。

注意,在NavBarcontrol里面,默认是没有一个Panel可以添加一些特殊的控件,如树、按钮等,默认只有BarItem对象可以添加进入。为了在一个NavBarGroup里面添加这样的控件,需要修改NavBarGroup的属性才行,如下所示。

4、添加DockManager和XtraTabbedMdiManager 控件实现多文档布局,其最终将以Tab方式进行展现。

继续在上面的窗体中添加DockManager控件和XTraTabbedMdiManager控件,这两个控件可以实现在右边以Tab方式展现多文档布局,这样对用户操作来说,可以一次性打开多个窗体进行操作,方便很多,也必将美观,是一种常见的布局展现。为了在窗体激活的时候,在顶部显示关闭按钮,其他的不显示,那么需要设置XTraTabbedMdiManager控件的ClosePageButtonShowMode=InActiveTabPageHeader即可实现了。最终Tab效果如下所示。

以上就是我设计的一个系统界面的具体操作流程,其实很多时候,介绍总是很快,摸索总是很慢,这个就是知识积累的效率提升。当然,要设计好一个系统界面,除了考虑界面的布局美观性、还要考虑图标的协调性、还有就是整体的框架,要可以比较好的适应这些布局控件的操作,不能太过臃肿或者难以阅读。