虚拟实验室引擎的开发和实现(三、Virtual Object)
在前一篇日志里,我用两个UML图描述了引擎中所使用的数据对象和界面渲染对象。本文将创建VObject类。VObject是引擎中所有虚拟对象的父类,包括一些公共的属性和方法。
![]()
VObject内部,主要通过以下顺序来创建数据。
属性
让我们看看VObject有哪些属性:
| 属性名 | 类型 | 说明 |
| Name | String | 名称 |
| ID | Guid | 唯一的编号值 |
| Width | Int32 | 宽 |
| Height | Int32 | 高 |
| CenterX | Int32 | 中点横坐标(相对于VObject) |
| CenterY | Int32 | 中点纵坐标(相对于VObject) |
| TopX | Int32 | 顶点横坐标(相对于所在的Place) |
| TopY | Int32 | 顶点纵坐标(相对于所在的Place) |
| X | Int32 | 中心横坐标(相对于所在的Place) |
| Y | Int32 | 中心纵坐标(相对于所在的Place) |
| ConfigFile | String | 配置文件 |
| UI | UIObject | UI |
CenterX、TopX、X及CenterY、TopY、Y的关系如下:
INotifyPropertyChanged接口
Silverlight提供了数据绑定功能。数据绑定为基于 Silverlight 的应用程序提供了一种显示数据并与数据进行交互的简便方法。数据的显示方式独立于数据的管理。UI 和数据对象之间的连接或绑定使数据得以在这二者之间流动。绑定建立后,如果数据更改,则绑定到该数据的 UI 元素可以自动反映更改。同样,用户对 UI 元素所做的更改也可以在数据对象中反映出来。
为了使源对象的更改能够传播到目标,必须实现 INotifyPropertyChanged 接口。INotifyPropertyChanged 具有 PropertyChanged 事件,该事件通知绑定引擎源已更改,以便绑定引擎可以更新目标值。
VObject实现了INodifyPropertyChanged接口,并利用OnPropertyChanged函数来发送目标更改通知:
public abstract class VObject:INotifyPropertyChanged { //………… protected void OnPropertyChanged(string property) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(property)); } } }
例如使用以下形式的属性设置函数来设定VObject的X坐标:
public int X { get { return m_X; } set { if (m_X != value) { m_X = value; OnPropertyChanged("X"); OnPropertyChanged("TopX"); } } } public int TopX { get { return m_X - m_CenterX; } set { m_X = value + m_CenterX; } }
从XML中读取属性值
在上一篇日志里,我提到使用一个XML文件传递数据,对于VObject对象,该XML文件的形式可能为:
<object name='周慊' width='147' height='144' centerX='73' centerY='144' x='1100' y='780' file='person.xml' id='80d76e6b-08d6-4225-bdcc-bf68ca19ca58' />
我利用函数Parse来转换数据,Parse是一个重载的函数,参数分别为String和XElement:
public virtual void Parse(string data) { XElement xElement = XElement.Parse(data); Parse(xElement); } protected virtual void Parse(XElement xElement) { if (xElement.Attribute("X") != null) { if (!String.IsNullOrEmpty(xElement.Attribute("X").Value)) { X = int.Parse(xElement.Attribute("X").Value); } else { X = 0; } } //………… }
扩展XElement类
注意上面的第二个函数,要解析XElement的属性,至少需要两个if判断:判断该XML中存在属性,判断该属性有值。如果属性很多,为每个属性添加那么多if判断是一件很麻烦的事情。我使用一个扩展方法来解决这个它。扩展方法是我喜欢的一个C# 3.0特性,允许为类添加额外的方法,我的代码如下:
public static class XElementExtensions { public static String Attribute(this XElement xelement, String name, String defaultvalue) { if (xelement.Attribute(name) == null) { return defaultvalue; } else if (String.IsNullOrEmpty(xelement.Attribute(name).Value)) { return defaultvalue; } else { return xelement.Attribute(name).Value; } } public static T Attribute<T>(this XElement xelement, String name, T defaultvalue, Func<String,T> func) { if (xelement.Attribute(name) == null) { return defaultvalue; } else if (String.IsNullOrEmpty(xelement.Attribute(name).Value)) { return defaultvalue; } else { return func(xelement.Attribute(name).Value); } } public static Func<String, Guid> GuidParser = x => new Guid(x); public static Func<String, int> Int32Parser = x => Int32.Parse(x); }
于是上面的函数可以写成:
protected virtual void Parse(XElement xElement) { Name = xElement.Attribute("name", String.Empty); ID = xElement.Attribute<Guid>("id", Guid.NewGuid(), XElementExtensions.GuidParser); ConfigFile = xElement.Attribute("file", String.Empty); Width = xElement.Attribute<Int32>("width", 0, XElementExtensions.Int32Parser); Height = xElement.Attribute<Int32>("height", 0, XElementExtensions.Int32Parser); X = xElement.Attribute<Int32>("x", 0, XElementExtensions.Int32Parser); Y = xElement.Attribute<Int32>("y", 0, XElementExtensions.Int32Parser); CenterX = xElement.Attribute<Int32>("centerX", 0, XElementExtensions.Int32Parser); CenterY = xElement.Attribute<Int32>("centerY", 0, XElementExtensions.Int32Parser); }
创建UI及初始化操作
在VObject里,使用CreateUI函数来创建UI对象,使用Initialize函数来初始话一些对象(例如VPlace类可以用该函数来初始化VItem容器),这两个函数都是抽象的,需要子类实现:
protected abstract void CreateUI(); protected abstract void Initialize();
最后,VObject的构造函数为:
public VObject(String data) { Initialize(); Parse(data); CreateUI(); }
浙公网安备 33010602011771号