无需VBA:Visio ShapeData嵌套可变列表修改可行性问询
希望允许用户通过ShapeData修改嵌套可变列表(Variable List)的元素,且修改能同步至主列表,无需使用VBA,是否可行?
在形状的ShapeData中,存在一个以分号分隔、内部元素以逗号分隔的列表,列表类型为Variable List,支持用户自行添加元素。同时希望子列表也可进行此类修改,基础设置如下:
| 名称 | 类型 | 格式(公式) | 格式(输出) | 值(公式) | 值(输出) |
|---|---|---|---|---|---|
| Prop.List | 4 | "1,2,3;4,6;7,8,9" | "1,2,3;4,6;7,8,9" | =INDEX(1,Prop.List.Format* | "4,6" |
| Prop.SubList | 4 | =SUBSTITUTE(Prop.List,",",";")† | "4;6" | =INDEX(0, Prop.SubList.Format)* | "4" |
*该公式为在ShapeData窗格中选择下拉项时自动生成
†此处必须使用SUBSTITUTE()才能使下拉菜单生效,使用INDEX(0,Prop.List,",")无效
用户可直接在ShapeData字段中输入值来更新Prop.List,这正是Variable List的优势所在。例如用户输入Prop.List = "10,11,12"后,结果如下:
| 名称 | 类型 | 格式(公式) | 格式(输出) | 值(公式) | 值(输出) |
|---|---|---|---|---|---|
| Prop.List | 4 | "1,2,3;4,6;7,8,9;10,11,12" | "1,2,3;4,6;7,8,9;10,11,12" | =INDEX(3,Prop.List.Format | "10,11,12" |
| Prop.SubList | 4 | =SUBSTITUTE(Prop.List,",",";") | "10;11;12" | =INDEX(0, Prop.SubList.Format) | "10" |
此结果符合预期,是简洁优雅的解决方案。
这正是我遇到的难题。您可能注意到Prop.List中缺少数字5,用户可能希望添加该值以保证完整性!按照上述逻辑,先从Prop.List下拉菜单中选择对应的子列表,再设置Prop.SubList = "5",结果如下:
| 名称 | 类型 | 格式(公式) | 格式(输出) | 值(公式) | 值(输出) |
|---|---|---|---|---|---|
| Prop.List | 4 | "1,2,3;4,6;7,8,9;10,11,12" | "1,2,3;4,6;7,8,9;10,11,12" | =INDEX(1,Prop.List.Format | "4,6" |
| Prop.SubList | 4 | "4;6;5" | "4;6;5" | =INDEX(2, Prop.SubList.Format) | "5" |
值已添加至子列表,但Prop.List与Prop.SubList的关联被切断。主列表未包含"5",且Prop.SubList不再随Prop.List的选择变化。
我就不一一列举所有尝试的详细结果表格了。
在实际场景中,该列表无需排序,"4,6,5"是完全有效的。
1. 直通方案
我原本以为SETATREF()函数能解决问题,但使用各种形式的Prop.SubList.Format = SETATREF(Prop.List)后,"4;6;5"被追加至Prop.List.Format而非更新选中元素。这符合逻辑,因为任何不匹配列表元素的内容都会被追加。
2. 辅助单元格
更复杂的方案是使用SETF()搭配多个辅助单元格。由于用户自定义输入时Prop.SubList.Format的公式会被字符串替换,我尝试通过外部方式强制建立关联。第一个单元格确保下拉选择变化时Prop.SubList.Format始终关联Prop.List:
User.EnforceSubList = SETF(GETREF(Prop.SubList.Format), "=SUBSTITUTE(Prop.List,"", "", ";")")) + DEPENDSON(Prop.List)
第二个单元格通过替换旧子列表来更新主列表:
User.UpdateList = SETF(GETREF(Prop.List.Format),SUBSTITUTE(Prop.List.Format, Prop.List, SUBSTITUTE(Prop.SubList.Format,";","")))
注:上述公式可能无法运行,会返回#VALUE。我不清楚原因,但将替换操作放入虚拟单元格再引用应该可行!
逻辑上这是合理的,但实际运行很快失败。经过几次选择切换后,列表竟全变为7。
3. 取消嵌套
我可以完全避免嵌套列表,为每个子列表创建独立的ShapeData条目(Prop.SubList0至Prop.SubList3),然后选择性隐藏不需要的条目。
User.ListIndex = LOOKUP(Prop.List,Prop.List.Format) Prop.List.Format = SUBSTITUTE(Prop.SubList0.Format,";",",") & ";" & SUBSTITUTE(Prop.SubList1.Format...)... Prop.SubList0.Invisible = (User.ListIndex <> 0) ... Prop.SubList3.Invisible = (User.ListIndex <> 3)
此方案在硬编码示例中可行,但用户修改Prop.List后很快变得不实用。我可以创建大量子列表条目,但并不想这么做。
4. 外接Excel
我对此尝试不多,但了解到Visio 2016有多种与Excel集成的方式可实现类似功能。我猜您读完实际场景部分后,肯定会疑惑我为何不用Excel!原因很简单:我不想这么做。让用户同时操作两个程序来实现该功能并不合理,毕竟图表的其他部分无需如此。
5. VBA
用VBA实现此功能非常简单,但受公司限制无法使用。
您可能好奇我实际要做什么,为何不能手动添加5到列表中——这是一个序列化物品的库存管理系统。
Prop.Items.Format = "Hammer;Wrench;Screwdriver;..." User.ItemsIndex = LOOKUP(Prop.Items,Prop.Items.Format) User.Serials = "548935,409234,324957,...;028412,111111,76843,...;..." Prop.Serials.Format = SUBSTITUTE(INDEX(User.ItemsIndex,User.Serials),",",";")
我希望用户无需操作复杂的ShapeSheet单元格,即可快速添加新工具。
是否有更优方法获取列表项的索引,替代低效且繁琐的LOOKUP()?(LOOKUP(INDEX(0, List),List) = 0……这显然多余!)我曾尝试设置Prop.Value = INDEX(SETATREF(User.Index), Prop.Value.Format),但遗憾的是它捕获的是整个函数计算结果,而非仅索引值。
我家用电脑上没有Visio,所有公式均为凭记忆编写,可能存在错误。但方法逻辑已验证,请谅解并关注核心思路。
内容的提问来源于stack exchange,提问作者Vince

