基于WPF技术的甘特图控件的研究与设计
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
基于WPF技术的甘特图控件的研究与设计
邹海;余籦超
【摘要】Traditional Windows Form applications have high complex degree and poor performance in rendering graphics,but Gantt chart control often needs to draw a lot of graphics.In view of this situation,we use the novel graphics rendering technology---WPF (Windows Presentation Foundation)of Microsoft,with the structured design notion we apply a series of the new features such as no appearance control, style
sheets,trigger and so on provided by WPF to create Gantt chart control,which is easier in realisation process and more excellent in per-formance and appearance.%传统的Windows窗体应用程序在图形渲染方面复杂程度高,运行性能差,而甘特图控件往往需要绘制大量的图形。
针对这种情况,运用微软全新的图形渲染技术---WPF(Windows Presentation Foundation),以结构化的设计思想,使用WPF提供的无外观控件、样式表、触发器等一系列新特性来创建甘特图控件。
实现过程更加简易,性能与外观上表现也更加出色。
【期刊名称】《计算机应用与软件》
【年(卷),期】2014(000)005
【总页数】4页(P50-52,78)
【关键词】WPF;控件;甘特图
【作者】邹海;余籦超
【作者单位】安徽大学计算机科学与技术学院安徽合肥230601;安徽大学计算机科学与技术学院安徽合肥230601
【正文语种】中文
【中图分类】TP311.52
0 引言
传统的 Windows应用程序依靠 User32库以及 GDI/ GDI+[1]来对控件、图像以及文本进行渲染。
WPF以DirectX技术为基础,为2D和3D图形提供了更好
的渲染效果[2,3]。
WPF通过统一的编辑模型,对2D、3D、控件以及视频、语音等进行整合,特别是与XAML[4,5]技术结合,运用XML的语法方式以声明式的编程方式,为软件的开发特别是UI(User Interface)部分的设计带来了极大的便利[6]。
在图形渲染方面,WPF使用矢量图形,采用与分辨率无关的单位,使得应用程序在不同的DPI下都可以获得最佳的显示效果。
此外,WPF硬件加速以及富创作等特性,使得可以在构建绚丽的UI的同时获得最佳的性能。
甘特图是各类项目管理中广为采用的一种图件。
本文以甘特图控件为例,详细阐述WPF技术在控件设计与开发中的应用方法。
1 甘特图控件的设计与实现
甘特图主要组成部分:(1)任务控件。
每个任务控件都是甘特表中的一条数据项的图
形化表示,并且可以对它进行相关操作,比如拉伸与移动;(2)甘特图容器。
在WPF 中,每个控件必须放入一个容器中才能进行绘制。
对于甘特图,每个任务控件都位于这样一个甘特图容器中,它提供一个布局算法来对其中所包含的每个任务控件进行布局;(3)时间栏。
它的作用就相当绘制函数时的坐标系,对于甘特图中每个任务,最基本的要素就是任务的开始时间与结束时间,而时间栏正相当于这样一个坐标系,
根据任务的开始时间与结束时间来决定该任务的在甘特图中的位置以及任务条的长度;(4)甘特表。
用于显示每个任务的具体信息,如:编号、任务名称、开始时间、结束时间、工期、前置任务等。
1.1 数据模型
这里采用数据库来存储该控件所需的数据。
本文的重点是用阐述使用WPF的新特性来设计甘特图,甘特图自身的相关知识在这里不详述,而且出于简单起见,只列出部分字段,如表1所示(数据库采用Access)。
表1 任务信息表用来区分不同的任务ID 数字对应在甘特图左边数据表中的“编号”EventName 文本任务的名称StartDate 日期/时间任务的开始时间EndDate 日期/时间任务的结束时间IsScope 是/否该任务是不是一个摘要任务Percentage Guid 文本全局统一标识,数字任务完成的百分比
1.2 任务控件的实现
这里将每个任务看成是一个控件,使用WPF的自定控件技术来构建无外观控件,而将外观的定义以及触发器等放在样式表中来实现,这样就实现了控件逻辑与控件外观的分离。
使用WPF的依赖属性来定义每个任务所需要的属性。
将每个任务看成一个控件的好处除了逻辑上的清晰外,还便于一些鼠标事件的实现,比如拖动、拉伸、悬停等。
1)依赖项属性
建立一个名为Event的WPF自定义控件,在生成的文件中可以看到,类Event是继承自UserControl的,所以自定义控件都继承该类。
接下来就是定义该控件的依赖项属性[7]。
这里以任务的开始时间来说明如何定义依赖项属性以及为什么要使用依赖项属性。
定义依赖项属性(注意修饰符以及关键字DependencyProperty):
public static readonly DependencyProperty StartDateProperty;
(1)注册依赖项属性必须在类的静态构造函数中定义,通过DependencyProperty.Register方法来注册依赖项属性。
这里用的一个技巧就是定义一个FramworkPropertyMetadata,并将它作为参数提供给DependencyProperty.Register方法,在定义FramworkPropertyMetadata时
我们使用FrameworkPropertyMetadataOptions枚举来添加一些附加功能。
这
里使用枚举值AffectsArrange和AffectsMeasure,其作用是当此属性发生改变
时会重新测量和布局该控件。
这样当改变一个任务的开始时间时,它就会自动进行重绘,位置和长度会自动改变。
(2)添加属性包装器依赖项属性的访问与设置需要使用GetValue方法与SetValue 方法,为了像.net中的属性那样访问依赖项属性,下面使用属性包装器来包装它。
同时使用依赖项属性便于数据绑定。
2)数据上下文及数据绑定
在该类的普通构造函数中,设置该控件的样式表以及数据上下文,这里样式表是作为一个资源访问的:
这里构造参数是一个EventInfo类型,里面定义了和该类相同的属性,不过都是
普通属性,将它作为任务控件的数据上下文的作用是便于在样式表中定义数据绑定[2],然后再将该样式应用于此控件。
3)EventInfo类
关于每个任务的相关数据(开始时间、结束时间、任务名称等等)保存在数据库中,在程序加载时将数据表中读取为List<EventInfo>,将其绑定到甘特表上,对于List中的每一个EventInfo实例,将其作为一个任务控件的数据上下文,并在控件的Style里进行双向数据绑定,这样在对控件进行操作,比如移动和拉伸时,会修
改控件的相关依赖项属性。
由于进行了数据绑定,控件对应的EventInfo实例的
数据也会进行相应修改,同时EventInfo又与甘特表进行了数据绑定,这样数据
表部分也会同时进行更新,保存数据时可以将List<EventInfo>写回数据库,整
个数据组织结构如图1所示。
图1 数据结构图
由于EventInfo是自定义的普通类,要实现它与控件的依赖项属性的双向绑定,
所以必须对EventInfo类实现INotifyPropertyChanged接口,从而使得EventInfo类的属性发生改变时通知绑定,从而进行更新。
4)拉伸与移动
一个任务控件最基本的鼠标操作就是拉伸与移动,通过在任务控件两端进行拉伸来改变一个任务的开始时间或结束时间,通过移动一个任务可保持任务的工期不变,开始时间与结束时间同时改变。
在此通过两个类来实现这个功能:MoveThumb和RemoveThumb。
这两个类都是继承自Thumb类。
这里使用该类的目的是为了
实现鼠标的拖放事件,可以分别定义Drag-Started、DragDelta、DragCompleted三个事件来分别响应拖放开始、拖放中以及拖放结束时要执行的操作。
在拖放开始时,可以通过数据上下文来获得该控件(数据上下文是在样式表
中的数据模板中实现的),然后在拖放事件时对该控件的开始时间及结束时间进行
修改并且控件会自动重绘,这样就实现了动态效果,在拖放结束时也可以进行一些操作,如验证等。
5)样式表
该控件的逻辑部分实现后,接下来就要定义控件的外观。
可以通过编写一个样式表,定义设置器、数据绑定、控件模板以及相关的触发器[7],然后将该样式表应用于控件。
关于设置器与数据绑定的部分如前所述,下面主要讨论控件模板,通过定义控件模板就可以方便地定义一个控件的外观,这里定义的控件模板主要包含一个
Grid,Grid中主要包含三个矩形,如图2所示。
图2 甘特任务结构示意图
这里以三种线型标识三个矩形,双线矩形跨三行,它是控件的大轮廓,单线矩形表示了百分比槽,虚线矩形表示了百分比的值。
在定义每个矩形时还涉及到一些数据绑定,这样矩形的长度就绑定到相应控件的数据上了。
这里可以把之前实现的MoveThumb和RemoveThumb放入模板中,并定义为透明的,需要两个RemoveThumb,分别位于控件的两端,一个MoveThumb位于控件的中间区域。
下面在Style中设置控件的外观并进行相关的数据绑定。
其语法简单,在Style中的设置器中实现,如下:
<Setter Property=″StartDate″Value=″{Binding Path=StartDate,
Mode=TwoWay}″/>
第一个StartDate是指该控件的依赖项属性,第二个Start-Date是该控件的数据
上下文中EventInfo实例的普通属性,Mode值TwoWay即定义的绑定的模式为双向绑定。
还可以定义触发器,这里我们定义一个属性触发器,根据控件的IsScope属性,
该属性决定了任务是一个普通任务还是一个摘要任务。
这个属性触发器可以在IsScope为真时定义另一个数据模板,这样控件的外观就会随着IsScope属性值
而发生变化。
2 甘特图容器的实现
WPF中的所有容器都是继承自Panel类,但鉴于该容器要对其所包含的每个任务控件的位置精确布局,所以这里采用的甘特图控件继承自Canvas,接下来要做的就是重写两个方法: MeasureOverride和ArrangeOverride,前者的作用是计算
每个任务控件的大小,后者的作用是确定每个控件在容器中的具体位置。
重写这两个方法后,当任务控件放入甘特图容器后,就会自动计算其大小并精确布局。
3 背景与时间表
背景与时间表都是通过WPF的2D引擎来绘制的,这里单独定义了一个继承自Canvas的容器,然后定义一个绘制背景和时间表的方法,最后定义一些事件,当事件触发时执行这个绘制函数。
这里要用到Visual对象,然后通过它的RenderOpeny方法来获得一个DrawingContext对象,接着调用DrawingContext对象的相关方法进行图形绘制。
当绘制完成时,将该Visual对象放入一个VisualCollection中,这里需要重写两个函数,Visual-ChildrenCount方法和GetVisualChild方法,前者返回VisualCollection中所包含的Visual对象的个数,后者返回VisualCollection中对应的Visual对象。
这样WPF就会将VisualCollection中的每个Visual对象绘制出来。
4 甘特表
甘特表的部分可以直接使用DataGrid类,然后通过样式表定义数据模板以及一些触发器,关键的数据部分可以用数据绑定来实现,具体通过将它的ItemsSource 赋一个列表对象(ObservableCollection列表)。
5 应用实例
基于上述WPF技术,系统地研制了一个外表美观、功能完善的甘特图控件,并成功应用于国内某煤矿矿井建设工程施工的项目管理,下面结合该实例来说明设计流程与核心算法。
5.1 流程图
甘特图的呈现流程如图3所示,其中Max值为当前屏幕可视区域的垂直高度,VerOffset值在初次载入时的值为0,这样会从第一条数据开始显示,当甘特图载入之后,通过调整滚动条,会改变VerOffset的值,从而决定显示的第一条数据对应为数据库中的第几条数据,这样便呈现出了页面的滚动效果。
这样的设计可以使WPF绘图引擎只绘制可视范围内的任务条,从而优化了甘特图呈现的速度及页
面滚动时的响应度。
图3 甘特图生成流程图
5.2 核心算法列举
当任务加入自定义的容器后,容器会根据算法1与算法2来进行测量与排版,最
终呈现完整的甘特图。
算法1 重写MeasuerOverride函数
①容器中的Children数等于0,返回Size(0,0),结束。
②容器中的Children数大于0,初始化Width与Height的值为0,遍历容器的Children中的对象,调用其Measure方法。
③更新Width值与Height值,其中Height值每次的增量为任务条的高度加上任务条之间的空隙,Width取当前值与当前任务条右端值中较大的值。
④遍历结束,返回Size(Width,Height)。
此为整个甘特图的大小。
算法2 重写ArrangeOverride函数
①初始化OriginPoint=new Point(0,0),此为起始点坐标,初始化x=0,y=0,此为当前待排版任务条的坐标。
②遍历Children中的对象,获取对象的宽高值 width与height。
③根据任务的开始时间、序号以及width,height值来更新x,y的当前值,从而确定任务条左上角的坐标点childPoint=new Point(x,y)。
④调用系统函数Canvas.SetTop()与Canvas.SetLeft()来设置当前对象的坐标。
⑤调用对象的Arrange()方法来对当前对象的位置进行排版。
5.3 效果截图
软件运行时的效果如图4所示。
图4 某煤矿建井工程施工计划排队图
6 结语
本文以甘特图控件的设计与实现为例,详细讨论了WPF的一些新特性,比如依赖项属性、自定义控件、自定义容器、样式表、触发器以及XAML这种声明式编程
方式。
通过上文的描述可以发现,利用WPF技术的这些新特性在实现甘特图比传统方式要便利得多,很多细节可完全交由WPF来实现,无论是在开发的复杂程度以及运行的性能优化方面都有很大的提升,并且在外观上有了更大的发挥空间,不像传统的WinForm应用程序,控件的外观无法自定义,这让你构建华丽的UI成
为可能。
参考文献
[1]Eric White.GDI+程序设计[M].清华大学出版社,2002.
[2]Charles Petzold.Application=Code+Markup:A Guide to the Microsoft Windows Presentation Foundation[M].Microsoft Press,2006.
[3]王德才,崔欣.Windows图形开发技术分析[C]//全国第19届计算机技术与应用(CACIS)学术会议,2008.
[4] Globa L S,Yermolchev F V.Tool of system prototype visualization[C]//Microwave&Telecommunication Technology,2008.
[5]Lori A,Mac Vittie.XAML in a Nutshell[M].O’Reilly,2006.
[6]Adam Nathan.Windows Presntation Foundation Unleashed
[M].Sams,2006.
[7]Matthew MacDonald.Pro WPF in C#2010[M].Apress,2010.
[8]琚彬.基于WPF平台的自定义控件开发[D].西安电子科技大学,2008.。