概述
在上一篇博客【工业软件开发学习——AnyCAD篇】模式(Schema)、组件(Component)与参数化对象(Part I)中我们介绍了AnyCAD的模式(Schema)与组件(Component)机制,并通过一个自定义参数化圆柱体的示例展示了如何使用它们来扩展实体对象(EntityElement)的数据和行为,但我们并未具体探讨这几个对象间的关系,本篇文章我们尝试通过修改属性->触发关联更新来探索更多细节。
属性修改
先通过一个简单的测试来看看如何更新实体对象的属性,我们新增一个菜单项,点击后执行下面方法:
// MainViewModelImpl.cs
// ..
[RelayCommand]
void OnModifySize()
{
var id = GetSelectedId();
if (id.IsValid())
{
// 根据ObjectId从文档中查找实体对象
var entity = EntityElement.Cast(Document.FindElement(id));
if (entity == null)
return;
// 从实体对象中获取组件SchemaComponent
var component = SchemaComponent.Cast(entity.GetComponent(nameof(SchemaComponent)));
if (component == null)
return;
// 借助MyCylinderModel加载保存在component中的属性,并修改高度和半径
MyCylinderModel model = new();
model.Load(component);
model.Height = 25.0;
model.Radius = 5.0;
// 将修改保存回component中,并在事务中提交
UndoTransaction undo = new(Document);
undo.Start("update.model");
model.Save(component);
undo.Commit();
}
}
和查询一样,我们依然先通过选择集找到圆柱体对应的EntityElement,然后获取到关联的SchemaComponent,并创建一个MyCylinderModel的实例用于加载component中存放的属性,这里可以看到MyCylinderModel的作用类似于ORM(Object Relational Mapping,对象关系映射),属性数据以key/value的形式存放在component中(可以理解为数据库),MyCylinderModel只是让我们可以通过面向对象的方式来便捷地访问和修改数据库。也正是因此,对数据的真正修改需要在事务中进行,以保证一致性等原则。
既然component起到了类似数据库的作用,我们也可以直接根据key查询和修改其中的数据,这里我们新增一个属性视图PropertiesView和相对应的PropertiesViewModel,利用DataGrid控件来便捷地查看和修改属性:
// ProeprtiesViewModel.cs
// ..
/// <summary>
/// 初始化属性表
/// </summary>
private void InitializeData()
{
var entity = GetEntity();
if (entity == null)
return;
// 查询实体对象所关联的属性参数
ParameterDict pd = new();
entity.ListParameters(pd);
// 通过ParameterIterator遍历每条记录
ParameterIterator pi = new(pd);
while (pi.More())
{
var pv = pi.CurrentValue();
// 每条记录构造对应的DataModel
var prop = new PropertyDataModel()
{
Name = pi.CurrentName(),
Value = pv.ToStr(),
Type = pv.GetClassId().GetName(),
};
Properties.Add(prop);
pi.Next();
}
}
/// <summary>
/// View更新时通过ViewModel更新数据
/// </summary>
/// <param name="pdm"></param>
public void UpdateModel(PropertyDataModel pdm)
{
if (pdm.Type != "ParameterDouble")
return; // 这里为保持教程的简洁度,仅处理double类型
// 获取实体和关联组件
var entity = GetEntity();
if (entity == null)
return;
var component = SchemaComponent.Cast(entity.GetComponent(nameof(SchemaComponent)));
if (component == null)
return;
// 若属性值合法,在事务中提交对数据的修改
if (double.TryParse(pdm.Value, out double val))
{
UndoTransaction undo = new(Document);
undo.Start("update");
component.SetParameter(pdm.Name, val);
undo.Commit();
}
}
动态属性扩展
如果说SchemaComponent通过预定义特定的ElementModel和ElementSchema扩展了实体对象的数据和行为,那么PropertiesComponent则提供了更为通用的方式让用户能够动态地为实体扩展属性,我们简单地增加一个菜单项执行下面方法:
// MainViewModelImpl.cs
// ..
[RelayCommand]
void OnAddPropertySet()
{
var id = GetSelectedId();
if (id.IsValid())
{
UndoTransaction undo = new(Document);
undo.Start("create.properties"); // 开启事务
// 动态地为实体对象关联属性集
var properties = PropertiesComponent.Create(Document, id); // 属性集需要关联Entity的id
properties.SetName("PropertySet1");
// 任意添加两个字段
properties.SetParameter("Field1", true);
properties.SetParameter("Field2", "field2");
undo.Commit(); // 提交事务
}
}
在查询属性时,通过类似的方式把实体所关联的ProeprtiesComponent找到:
// ProeprtiesViewModel.cs
// ..
/// <summary>
/// 初始化属性表
/// </summary>
private void InitializeData()
{
var entity = GetEntity();
if (entity == null)
return;
// 查询实体对象所关联的属性参数
ParameterDict pd = new();
entity.ListParameters(pd);
// 通过ParameterIterator遍历每条记录
ParameterIterator pi = new(pd);
BuildProeprties(pi, Properties);
// 查询动态属性集
var properties = PropertiesComponent.Cast(entity.GetComponent(nameof(PropertiesComponent)));
if (properties == null)
return;
var pd2 = properties.GetParameterDict();
pi = new(pd2);
BuildProeprties(pi, DynamicProperties);
}
private void BuildProeprties(ParameterIterator pi, ObservableCollection<PropertyDataModel> container)
{
while (pi.More())
{
var pv = pi.CurrentValue();
// 每条记录构造对应的DataModel
var prop = new PropertyDataModel()
{
Name = pi.CurrentName(),
Value = pv.ToStr(),
Type = pv.GetClassId().GetName(),
};
container.Add(prop);
pi.Next();
}
}
于是,我们实现了动态地为EntityElement扩展属性,并通过id再进行查询:
总结
通过修改属性的例子,我们了解到以下几点:
- 属性数据以key/value的形式存储在组件Component中
- ElementModel起到了ORM的作用,使我们能够以面向对象的方式读写数据
- ElementSchema把二者串联起来,是扩展实体数据与行为的关键
另外,我们还介绍了比SchemaComponent更灵活通用的属性扩展方式:不需要事先定义Schema,直接为EntityElement动态地关联PropertiesComponent。
通过Part I和Part II,我们初步掌握了AnyCAD中扩展实体的方式,同时我们发现涉及到界面与数据交互时我们会用到MVVM(Model View ViewModel)的模式来组织代码,如果要实现完整的一套流程这部分其实是不小的代码量;实际上AnyCAD不仅提供了数据相关的api,也提供了应用与界面框架AppFramework/UIFramework让用户更快地搭建起一个完整的应用,下一篇文章我们将简单介绍这一框架的使用,感兴趣的朋友可以关注订阅我的博客,谢谢!