VSTO线程模型

在VSTO开发领域,每个开发者都曾经历过这样的挫败:精心设计的插件界面在数据加载时陷入卡顿,复杂的计算过程使整个Office程序失去响应。这不是代码质量问题,而是VSTO特有的线程模型带来的挑战。本文将围绕如何利用独立线程和异步调度解决该问题进行剖析。 VSTO线程模型的本质矛盾 Office的COM对象是基于单线程单元(STA, Single Threaded Apartment)模型的。这个模型的好处是保障了线程安全,但也造成了性能瓶颈。具体表现: UI线程和Office线程绑在一起,做耗时数据读取或计算时,界面会卡死甚至完全无响应。 想用多线程访问COM对象? 不行,需要走代理(Proxy)封送(marshalling)调用,开销大且复杂。 这种设计就像双刃剑,让我们陷入两难:一边要保证线程安全,一边又想流畅不卡顿。 突破瓶颈的思路:线程分离 + 异步计算 要打破这个瓶颈,必须实现UI线程与Office线程的分离,并确保所有COM对象的访问都发生在Office线程上。同时,为避免业务逻辑阻塞UI或Office线程,计算应异步在线程池(TaskPool)中进行。 这带来关键问题:如何在不同线程间高效且安全地传递数据? 如何在后台任务中发起Office数据读取,并调度到VSTO主线程执行? 数据处理完成后,如何将结果发回UI线程,满足UI线程禁止跨线程更新界面的要求? 那具体怎么调度线程?如何跨线程安全调用Office对象并更新UI?这里,SynchronizationContext和调度器(Scheduler)派上用场了。 下面用时序图简单描述整体流程: sequenceDiagram UI线程->>+线程池: 发起计算请求 线程池->>+VSTO主线程: 通过Office的SyncContext或Scheduler获取数据 VSTO主线程-->>-线程池: 返回封送后的数据 线程池->>线程池: 执行计算 线程池->>+UI线程: 通过UI的SyncContext或Dispatcher更新界面 SynchronizationContext 的选型与实践 为 VSTO 主线程设置上下文 SynchronizationContext是一种线程抽象机制,提供“将任务调度到指定线程”的能力。VSTO项目启动时默认无上下文,无法通过SynchronizationContext.Current直接获取VSTO主线程上下文。 一种解决方案是自定义上下文,但更简便的做法是利用Office主线程已有的Win32消息泵,使用WindowsFormsSynchronizationContext。尽管名字带“Windows Forms”,它不仅限于WinForms应用,只要线程是STA且运行Win32消息循环,它都能正常工作。 若需更细粒度的任务优先级控制,可考虑DispatcherSynchronizationContext,但它实现复杂且需手动启动消息泵。因职责已隔离,通常用WindowsFormsSynchronizationContext即可。 示例: public partial class ThisAddIn { private static SynchronizationContext _officeSyncContext; private void ThisAddIn_Startup(object sender, System.EventArgs e) { // 为 VSTO 主线程设置上下文,供后续调度使用 _officeSyncContext = new WindowsFormsSynchronizationContext(); SynchronizationContext.SetSynchronizationContext(_officeSyncContext); // 其他初始化代码 } } 在自建 UI 线程中设置上下文 WPF和WinForms要求UI线程必须是STA,然而默认线程池线程为MTA,不适合做UI线程。为避免阻塞Office线程(VSTO_Main,唯一STA线程),只能新建一个专用STA线程运行UI。 Avalonia允许非STA线程,但考虑到VSTO依赖Windows平台和消息泵机制,建议UI线程依然使用STA模式。 WPF/WinForms依赖Win32消息泵,同样可用WindowsFormsSynchronizationContext。 示例: public partial class ThisAddIn { private static SynchronizationContext _uiSyncContext; private void ThisAddIn_Startup(object sender, System.EventArgs e) { // 其它代码 // 创建专用 UI 线程 _uiThread = new Thread(() => { _uiSyncContext = new WindowsFormsSynchronizationContext(); SynchronizationContext.SetSynchronizationContext(_uiSyncContext); }) { Name = "UI Thread", IsBackground = true }; _uiThread.SetApartmentState(ApartmentState.STA); _uiThread.Start(); } } 利用SynchronizationContext.Post可在线程池、VSTO主线程与UI线程间安全传递数据。 ...

June 5, 2024 · 1 min · 190 words · Snailya

AE PID快速入门

本文以中涂洁净间循环风机为例介绍AE PID绘制设备原理图的基本流程。 详细步骤 在Visio中使用空白模板创建新的绘图,单位选择“公制单位”; 在功能区处切换至AE PID选项卡; 点击“编辑”-“初始化”按钮对当前绘图进行初始化。初始化操作将在当前页面中插入一个A0图框,并设置文档页面的网格宽度为2.5 mm。 此外,为文档增加2个AE指定样式。其中,“AE Normal”样式是设备单元的默认样式,“AE Pipeline”是管线的默认样式。 AE样式将使用“思源黑体”作为指定字体,在AE PID插件的安装过程中会自动为系统安装思源黑体。若未能正确显示思源黑体,请尝试手动安装。 若要修改图框大小,用鼠标右键点击图框,并在子类中选择合适的尺寸; 在选项卡中点击“编辑”-“库”加载模具库。 从“AE逻辑”库中拖拽“功能单元”至绘图区; 当您不确定所需的设备对象属于哪一个类型时,可以借助Visio模具的搜索栏进行搜索。这将帮助您快速找到所需的设备对象,无需事先知道其具体类型。 如果您正在使用Windows11系统,搜索功能可能未能如期使用,请按照修复了Visio桌面应用中的形状Windows 11的方法修补。 用鼠标右键点击该“功能单元”,打开“形状数据”面板; 在“形状数据”面板中输入“功能组”值:“GF612”,“功能组名称”值:“中途洁净间循环风机”; 此时,功能单元显示如下: 使用相同的方法从“AE基础”库中拖拽“鼓风机”至功能组内,此时可以看到功能单元边框被高亮为绿色,表示该风机已被加入功能单元。 保持风机的选中状态,在“形状数据”面板中键入“功能元件”值:11,按下回车后该值将显示为“GQ11”; 拖动GQ11上的黄色控制点,可以移动功能元件标签的位置; 为了表示鼓风机配备的电机,从“AE逻辑”库中拖拽“代理功能元件”至功能组内; 选中该代理功能元件,将黄色控制点拖拽至鼓风机上,使代理功能元件与鼓风机相关联; 被关联后,代理功能元件的形状数据处可以看见被关联设备的位号; 当被关联设备发生移动时,关联元件会跟随移动; 保持代理功能元件的选中状态,补充“元件位号”值:“MA01”,描述:“电机”,并将代理功能元件拖拽至鼓风机附近。 继续从“AE基础”库中拖拽“仪表”至功能单元中,并在右键菜单中选择“子类”-“本地面板监视仪表”; 补充形状数据并将仪表移动至合适的位置; 将仪表正中的控制点拖拽至鼓风机上; 使用同样的方法绘制下方的“中控监视操作仪表”; 从“AE管线”库中拖拽“管路”至绘图区,并在“子类”中选择“排出空气”使其显示为柠黄色; 将管路一段连在鼓风机的连接点上,另一端连接至其他对象,并在右键菜单中点击“改变箭头方向”; 多次点击直到箭头朝向如图所示; 使用相同的方法完成另一根管路的绘制; 在选项卡中点击“导出”-“BOM”查看当前图纸的BOM结构; 在选项卡中点击“编辑”-“图例”将在图签上方生成图例; ...

May 19, 2024 · 1 min · 41 words · Snailya

Visio模具库建库指导

分享一些Visio模具库建库过程中值得注意的事项以及原因。 模具的BaseID的唯一性。 BaseID是Master对象的属性之一,Master对象有三个ID:BaseID,UniqueID和ID。BaseID在Master被创建时生成,且不再改变(除非使用程序修改)。因此BaseID非常适合作为维护Master对象时的唯一标识。这是因为,UniqueID会随着Master内容的修改而变化,ID会随着不同的文档而变化。 通常情况下,我们不需要关注BaseID,因为当我们将形状从绘图页拖拽至模具库时,会创建唯一的BaseID。但是,很多管理员在建立新模具时,为了省去添加通用形状和属性,如Tag文本,备注属性等,会直接在模具库中选中已经存在的模具复制粘贴,然后在粘贴后的模具中进行修改。这个时候站贴后的模具将和源模具具有相同的BaseID。为了保证BaseID的唯一性,可以在程序中调用Master.NewBaseID方法获取新的BaseID,或者直接在Openxml文档中键入新的BaseID。 模具的形状界限与网格线重合。 由于Visio中的默认吸附设置包括网格线,当模具的形状界限是网格的整数倍时,模具实例的边界可以吸附在网格线上,这样用者不需要再使用对齐工具也可以快速的实现多个形状的对齐,从而使页面看起来更整洁。 这就要求管理员在设计模具的形状时,充分考虑形状的轮廓尺寸。当然,有些情况下没有办法保证这些线条刚好经过网格线,但是仍然可以通过手动设置形状的Width和Height,使其包围框刚好位于网格线上。 例如,对于图中的两个阀门,尽管电磁气动阀的轮廓远比通用阀门大,但是他们都使用同样的形状界限,即5mmx5mm。(这样做另一个隐藏的好处是:当用户使用电磁气动阀替换通用阀门时,不需要再调整对象的位置,因为他们是同轴的。) 在ShapeSheet的Geometry中使用GUARD和Rel而不是使用绝对尺寸。 当我们在ShapeSheet中修改Geometry属性时,很容易遇到插入一个新的数据行引发形状的Width和Height重新计算,导致才修改好的形状意外改变。因此,在设计初期就应该使用GUARD对形状的Width和Height以及几何数据写保护,防止插入新的几何数据时Visio重新计算引发的意外情况。 在几何数据中使用相对值而不是绝对值,则是为了解决用户手动调整实例尺寸时,代表实例的几何形状可以保持正确的比例,以防出现原来是个原型,现在是个椭圆形的情况。 在创建具有多个子类的对象时,将表示不通子类的多个几何数据创建在同一个形状对象上,而不是使用多个形状对象。 这句话描述的可能比较抽象。有的时候,为了缩减模具库中模具的数量,管理员可能会考虑将多个具有类似的对象设计成一个模具,并通过属性切换显示与隐藏。例如,图中的阀门聚合了多种形式的阀体,并根据用户的选择进行形状的切换。 在实现这个功能时,有两种做法:方法一是在模具的形状组中创建多个形状用以表示不通的阀体,例如三个形状分别表示通用发、三通阀、角式阀;方法二是在一个形状中,插入多个几何数据块。我认为方法二是更好的做法,因为如果用户在使用过程中解散了图纸中实例的组,不会在图纸中生成隐藏的形状对象。尽管这样增加了管理的复杂性,因为无法通过形状的名称判断几何数据块表示的形状。

January 29, 2024 · 1 min · 14 words · Snailya

如何更新Visio文档中的模具

在工程设计领域,流程图设计是许多企业不可或缺的一环。Visio,作为备受推崇的流程图设计工具,正在成为越来越多企业的首选,以期通过规范化的流程设计来提升整体工作效率。 尽管Visio提供了丰富的内置模具库,但在实际应用中,通用模具往往难以满足企业独特的业务需求。为了更好地适应企业的特殊流程和标准,许多企业纷纷转向定制模具库的方向,这与我们从外企的学习中所观察到的趋势是一致的。然而,由于缺乏指导性的方法,企业在推广和运用Visio时可能会面临一系列挑战。其中一个普遍存在的问题是在初版模具库建立后,如何有效地进行迭代更新。 本文将讨论Visio文档中模具更新的实现。首先介绍用户从模具库拖拽至绘图页时的背后过程,揭示为什么文档中的形状实例不会随模具库的更新而更新。随后介绍手动更新的方法。最后,提供比较两种自动化更新的方法及实现刚方法模具需满足的条件。 拖拽背后的故事 当用户首次从模具库拖拽模具到绘图页(Page)时,Visio在后台完成了多个操作。首先,Visio会在文档的文档模具(Document Stencil)中创建该模具的副本,然后再在绘图页上创建针对该模具形状的实例。由于文档模具默认是隐藏的,所以用户可能无法察觉到这一点。(要显示文档模具,首先需要在“选项”-“自定义功能区”中启用“开发者”选项卡。然后,在“开发者”选项卡-“显示/隐藏”分组中勾选“文档模具”。) 当用户再一次从模具库拖拽同一个模具时,Visio将检查文档模具中是否已存在该模具的副本。如果副本已经存在,Visio将直接创建实例。那么Visio是如何判断模具已存在的?在默认情况下Visio会比较模具的UniqueID属性。因此,即使两个模具具有相同的名称,Visio也可以通过UniqueID判断它们的对应关系。每当用户编辑并保存模具时,模具的UniqueID会发生变化。所以拖拽修改后的模具到绘图页时,可以观察到文档模具中出现了新的副本。这也就是为什么修改了模具库中的模具,绘图页中的实例没有被更新。 这显然与我们的期望不符。我们希望图纸中的实例永远与最新的模具一致。 手动更新 要将实例引用的模具修改为最新的模具,一种已知的方法是使用“主页”-“更改形状”对图纸中的实例进行更改。但是,对于已包含大量实例的文档,这个操作费时费力。尤其是当新版模具与旧模具的差异并不大时,用户很容易发生遗漏。 使用COM批量更新 借助COM组件,我们可以通过创建自动化程序的方式批量选择某一模具的实例,然后调用Shape.ReplaceShape()方法,实现批量更改这些实例的形状。 要获取文档模具中模具在文档中的所有实例,我们可以调用遍历文档中的所有形状,并筛选出Shape.Master等于文档模具中的Master情况。关键代码如下: public IEnumerable<IVShape> GetInstances(IVMaster master) { var instances = document.Pages.OfType<IVPage>() .SelectMany(x => x.Shapes.OfType<IVShape>()).Where(x => x.Master == master).ToList(); return instances; } 要找出模具库中对应的新模具,需要使用到模具的另一个ID属性————BaseID。模具的BaseID是在模具被创建的时候生成的,随后不会发生改变。因此,可以通过BaseID找到模具库中的同源模具。但是,使用这种方式时,要求模具库中的BaseID具有唯一性。一种常见的错误是管理员在创建模具时,采用的不是首先在绘图页绘制模具形状再拖拽至模具库,而是直接将模具库中的模具复制成了新的模具并编辑该模具。此时,模具库中的代表不通类型的模具具有相同的BaseID。 关键代码: public IVMaster GetLatestMaster(IVDocument document, string baseID) { var latestMaster = document.Masters.OfType<IVMaster>() .SingleOrDefault(x => x.BaseID == baseID); return latestMaster; } 然后,遍历这些事例,并将形状替换为新版本的模具。 public void Replace(IEnumerable<IVShape> instances, IVMaster latestMaster) { foreach (var instance in instances) { instance.ReplaceShape(latestMaster); } } 使用COM方式更新的好处是可以直接在原文件中进行修改。然而,由于UI的频繁更新可能导致方法执行时间较长,特别是在复杂的涂装车间原理图中,可能需要数分钟。因此,为了提升用户的使用体验,开发者可能会考虑加入进度条,以直观地显示更新进度。但是Visio使用STA模型且UI更新过程会向主线程封送消息,如果使用WPF组件,UI线程会发生阻塞,因此应使用WinForm。 另一个不利因素是,更新可能会引发连接线(Connector)的几何属性重新计算。也就是说,直角型连接线的折点位置可能会发生改变。 使用OpenXML批量更新 当不要求在原文件中完成更新时,可以考虑直接修改OpenXML文件。OpenXML是一种基于XML的文件格式,在2013年被引入Visio。OpenXML格式的Visio文档后缀为".vsdx"。要查看OpenXML格式的详细内容,可以修改文档后缀为".zip"后打开压缩包。 .\visio\masters目录下存储了文档模具的相关内容。masters.xml文件中列出了文档模具中的模具的部分属性。 其中,对我们有用的是Master节点的BaseID属性和Rel子节点的r:id属性。前者的作用已在前文中提及。r:id属性可以通过查看_rels文件夹下的masters.xml.rels确定与此Master关联的MasterContents文件。MasterContents文件定义了模具的形状。关键代码(XmlHelper部分的代码参考以编程方式处理Visio文件格式): public IEnumerable<XlElement> GetMasterElements(Package package) { // mastersPart指masters.xml var mastersPart = package.GetPart(XmlHelper.MastersPartUri); // 筛选出BaseID属性为baseID的Master节点 var masterElements = XmlHelper.GetXElementsByName(mastersPart, "Master"). return masterElements; } public PackagePart GetMasterContentsFile(Package package, string baseID){ var masterElement = GetMasterElement(package).SingleOrDefault(x=>x.Attribute("BaseID")!.Value == baseID); // 通过子节点Rel获取r:id var relElement = masterElement.Descendants(XmlHelper.MainNs + "Rel").First(); var relId = relElement.Attribute(XmlHelper.RelNs + "id")!.Value; var rel = mastersPart.GetRelationship(relId); // masterPart指master{i}.xml var masterPart = package.GetPart(PackUriHelper.ResolvePartUri(rel.SourceUri, rel.TargetUri)); return masterPart; } 当我们更新文档中的模具时,实际上只需要将Master节点和MasterContents节点替换为修改后的模具库中的对应内容。关键代码: public void Replace(Package drawingDoc, Package stencilDoc, string baseID){ var mastersPartDrawing = sourcePackage.GetPart(XmlHelper.MastersPartUri); foreach (var masterEleDrawing in GetMasterElements(drawingDoc)) { // 查看模具库中是否存在对应的Master var masterEleStencil = GetMasterElements(stencilDoc).SingleOrDefault(x=>x.Attribute("BaseID")!.Value == baseID); if (masterEleStencil == null) continue; // 使用模具库中的Master节点替换文档中的Master节点。但是由于模具库中的Rel关系和可能与文档中的不一致,所以为了不去修改masters.xml.rel文件,仍使用原文档中的Rel节点 var relEleDrawing = masterEleDrawing.Descendants(XmlHelper.MainNs + "Rel").First(); masterEleStencil.Descendants(XmlHelper.MainNs + "Rel").First().ReplaceWith(relEleDrawing); masterEleDrawing.ReplaceWith(masterEleStencil); // 替换MasterContents var contentsPartDrawing = GetMasterContentsFile(drawingDoc, baseID); var contentsPartStencil = GetMasterContentsFile(stencilDoc, baseID); XmlHelper.SaveXDocumentToPart(contentsPartDrawing, XmlHelper.GetXmlFromPart(contentsPartStencil)); } XmlHelper.RecalculateDocument(drawingDoc); XmlHelper.SaveXDocumentToPart(mastersPartDrawing, XmlHelper.GetXmlFromPart(mastersPartDrawing)); } 查看完整代码 ...

December 25, 2023 · 1 min · 201 words · Snailya

解决Visio使用Shape Replace方法显示OBJ的BUG 2

很不幸,又遇到了同样的问题。但这次问题涉及到一个容器对象,该容器内的某个形状链接了容器的某个属性值。当使用Shape.Replace()方法进行更新时,该属性又被更新成“OBJ”。 因此,按照之前提到的方法,我们首先删除了我们自定义的容器的基本属性,例如SelectMode、DisplayMode、CalWH等。然后重新执行更新程序。此时,“OBJ”被正确的属性值所取代。 通过Visio应用程序的编辑模具功能,重新定义先前的基本属性,再次执行更新程序。不幸的是OBJ又出现了。 最终我们发现,当同时满足以下两个条件时,会产生上述的BUG: IsTextEditTarget=False GlueType=8

October 11, 2023 · 1 min · 6 words · Snailya

解决Visio使用Shape Replace方法显示OBJ的BUG

引言 许多Visio管理员在项目初期都会面临模具频繁更新的问题。由于Visio的机制,每当用户首次从模具库中拖拽模具至绘图页时,实际上是从该模具库中拷贝了模具的副本至文档模具中。这种机制无疑切断了文档模具与原模具库的联系,也造成管理更新模具库后无法将更新应用到已有的绘图上。当绘图上需要更新的形状数量较少时,可以使用Visio提供的更改形状功能手动更新。但是,当绘图页中存在大量的待更新形状时,使用使用程序进行批量更新。 程序批量更新的方法是遍历文档模具,并通过文档模具中模具的BaseID从模具库中找到源模具,再比较两者的UniqueID是否一致。若UniqueID一致,说明该模具没有更新,应跳过操作;若不一致,说明源模具发生了更新。然后在绘图页中找到所有与该模具关联的形状,然后删除文档模具中的旧模具,再执行Shape.Replace()方法使用新的模具替换形状。 这种方法通常是凑效的,但是在上一周中,我遇到了如下的BUG:更新后的形状显示Obj文本。仔细查看形状shapesheet,可以发现模具中已经定义好的TextField在形状中变成了空白,造成显示错误。 不幸的是,未能在Google中搜到类似的问题反馈。 问题排查 通过查看xml文件,发现该形状的Field节被替换为了<Row IX='0' Del='1'/>。对比未出现显示错误的形状的xml文件可以发现:当TextField取值为空时,Field节应该继承自模具,因此xml文档不应出现Field节;当TextField存在取值时,Row节点内应该有Cell节点。此时,如果手动删除Field节,显示的OBJ文字消失。由此可以推出Field节数据发生了异常。 但是,仍然无法确定该异常的产生是因为形状定义错误还是其他。 回到Visio应用程序中,在源模具中的形状树中删除发生该问题的子形状,再次执行更新程序,原本没有问题的子形状的TextField出现同样的问题。重复执行删除子形状并更新的操作,问题始终出现。当所有的子形状都被删除后,更新后的形状不再显示OBJ。但是,新的问题出现了:形状显示数字3。显然我们并没有在模具的任何形状或子形状中定义过这个数值。 由此可以推出,形状定义并没有问题,而是其他数据导致了Visio更新时的异常行为。 为了确定是哪一部分数据存在问题,我们清空了User、ShapeData等全部用于定义用户数据的Section,问题依然复现。现在,只剩模具的基础数据了(诸如PinX、PinY等Shape节点下的Cell节点)。由于Visio中不能删除基础数据,所以在XML文件中用其他正常模具的基础数据替换问题模具的基础数据。此后,再使用程序对绘图页的形状进行更新,数字3不再出现,也就是这个BUG被修复了。 通过文本对比工具,我们比较了问题模具与正常模具的基础数据。 (此处,因为重启电脑图丢了。。。。。) 比较两者的基础数据并没有特别的地方,除了尺寸和定位存在数值差异外,只有Cell的顺序存在区别。最终也无法确定这个问题到底是怎么产生的。 解决方法 尽管未能找到该问题形成的原因,但通过定位问题位置,我们找到了解决该问题的方法:即使用正常模具的基础数据覆盖问题模具。

September 26, 2023 · 1 min · 18 words · Snailya

解决Visio外部数据绑定失败:自定义形状ID属性

引言 在 Visio 中,可以通过使用外部数据来创建和更新图标、图形和其它可视化元素。例如,可以通过外部数据绑定,建立形状与数据表之间的同步关系,实现当外部数据发生变化时,Visio 图形的自动更新。利用这个方法,既可以解决人工通过 Excel 批量修改形状数据的需求,又可以实现数据库自动更新数据的需求。 但是,目前针对外部数据功能的教程非常少,均是以组织架构图或时序图为例,以人工拖拽的方式实现数据链接到形状,鲜有如何自动将导入的数据连接到形状的教程。尽管 Microsoft 官方提供了自动链接向导将导入的数据连接到形状的支持文档,但是仅参照该文档并不能实现我们如下的需求: 通过数据导出功能导出形状的数据至 Excel 表格 通过数据导入及自动链接功能将修改后的 Excel 表格与 Visio 形状数据的自动绑定,实现形状数据的更新。 该需求应于我们的业务过程存在的批量修改某类设备型号、供应商的情况。面对大量的设备,如果通过 Visio 逐次去修改每个形状的数据,一是操作繁琐耗时,二是容易产生遗漏,以上两个问题均可以得到解决。 问题描述 借助数据库导出向导,我们可以将指定图层上的形状数据导出至数据库(或 Excel 文档),导出过程中将使用形状的 ID 作为数据表的 Key。因此,在将数据表导回 Visio 时,只需将 Key 值与形状的 ID 关联,理论上应该可以实现自动将数据链接至形状。 但是,在实际操作中,外部数据窗口中并没有出现链接符号,即绑定失败了。 此时,如果使用拖拽的方式将数据连接至形状,还会发现 Visio 并没有如期更新已有的属性,而是将表格中的属性作为新的属性写入形状。 也就是说,要实现我们的需求需要解决如下两个问题: 自动链接数据至形状数据 更新已有形状数据而不是新建数据 原因分析 针对问题一,使用录制宏功能,可以发现当设置形状的 ObjectID 与数据源中的 ShapeKey 匹配时,没有录制上任何有效代码,而其它选项均有代码被录制。因此推测 ID(Object Info)不是有效选项。 针对问题二,由于在导出数据时,允许重新定义属性的名称,因此推测属性名称在更新数据时起关联作用,即表格中的列名称应与属性的 Name 或 Label 一致。经过多次尝试,应设置列名称为属性的 Label。 解决方法 因此,要在 Visio 中实现外部数据绑定与自动更新,需要完成以下操作: 在 User Section 中添加新的 ShapeID 属性,并设置其公式为"=GUARD(ID())"。 在数据导出窗口中,依次修改导出属性的 Field Name 为其 Label。 在自动链接窗口中,选择 Shape Field 的值为 User.ShapeID. 此时,修改后的数据已与形状自动关联。 ...

June 25, 2023 · 1 min · 76 words · Snailya

修复Visio容器调整尺寸引发的意外的文件尾错误:正确的容器定义方法

2025.3.17更新: 将文件另存为VSD格式,然后再另存为VSDX格式即可修复。 引言 容器是 Visio 中一类可以帮助实现结构化组织和管理其它图形元素的特殊对象。通过容器,您可以将相关的子图形放置在一个框架内,以便更清晰地展示信息和关系。在管理员定义方面,与组合相比,容器可以实现更丰富的预定义内容:形状结构、外观样式、连接点定义等,以满足不同公司对图表样式的要求。在用户操作方面,用户可以通过将子图形拖拽在容器内或容器外轻松实现将子图形加入或移出容器,通过拖拽容器可以实现容器内子图形的一起移动。因此,容器尤其适用于图表、流程图、组织结构图的设计。 但是,容器作为 Visio2010 的新增特性,Visio 并没有提供关于自定义容器的相关教程及建议,这也使得当前 Visio 容器的定义存在极高的自由度。通常,若将一个对象定义为容器,只需要在该对象 Spreadsheet 中的 User Section 内增加msvStructureType属性,并设置其值为="Container"。 但是,这种简单的定义可能会在特定情况下引发意外的文件尾错误。 问题描述 星期一的时候,我的同事向我演示了他是如何引起这个错误的。首先,他将一条表示管路的线段放在了表示功能单元的容器内。由于这条管路表示外部进来的管路,所以他希望这条线段一端恰好贴在容器边框,另一端落在容器内。随后,他又删除了这条线段。在此之后,当他想要调整容器的尺寸时,出现了这个错误。 当我尝试复现这个错误时,我发现使用 Visio 预定义的容器并不会引发该现象。 原因分析 通过仔细对比我们自定义的容器和 Visio 预定义的容器,我们确定了以下两个前置条件: 线段一端必须吸附在容器的几何形状上。当线段被吸附至容器的几何形状上时,点击容器,连接处可以看到如图所示的高亮的连接点。 在默认状态下,拖拽线段至容器的边界不会引发吸附,除非文档模板中预定义或用户手动开启了该功能。该功能位于 View-Visual aids-Snap & Glue-General-Glue to节,勾选 Shape geometery 可以启用该功能。 被吸附的容器的几何形状必须定义在容器本身,而不是容器组合的子对象。也就是说,表示几何形状的 Geometry Section 位于容器的 Spreadsheet 内。 解决方法 要解决这个问题,我们需要将容器的几何形状定义在容器组合的子对象中。也就是说,在创建自定义容器时,不应在容器自身的 Spreadsheet 中定义 Geometry,而应将容器转换为组合,并将表示容器几何形状的对象作为容器组合的子对象。 以下给出了建议的自定义容器步骤: 插入表示容器造型的形状对象; 将这些形状对象使用 Ctrl+G组合成一个组合对象; 打开组合对象的 Spreadsheet,并在 User Section 内增加msvStructureType属性,并设置其值为="Container"。

June 1, 2023 · 1 min · 57 words · Snailya