为 InfoPath 2007 自动生成属性





0/5 (0投票)
为 InfoPath 2007 的 FormCode 添加自动属性的方法。
更新
2013/10/08:我对该库进行了一些改进,并增加了对重复元素(暂不支持组)的支持。现在可以通过索引设置重复元素的值!下一个更新将支持组 :p
引言
InfoPath 代码隐藏不利于简单方便地访问底层数据源中的字段。您需要使用 XPath 来获取或设置值,并使用 NodeIterators 等来遍历重复的节点。
如果您不小心,这可能导致将来的维护非常困难,因为无论您做什么,您实际上都在使用“魔术字符串”。
旧方法
所以……我以前会在解决方案中添加一个名为 FormCode.Properties.cs 的文件
其中包含一个部分类 FormCode
。
然后,我将使用 const 字符串来存储每个节点的 XPath,然后添加属性来获取/设置它们。
现在,我有了可用的属性,可以在代码隐藏中获取和设置每个字段,并在一个中心位置更新 XPath 字符串。这使得使用和维护更加容易,但仍然有点麻烦,因为每个新字段都需要添加另一个属性。
总而言之,这是一种比直接使用 MainDataSource.CreateNavigator().SelectSingleNode()
来获取或设置值更好的访问字段的方式,但仍有改进空间!
新方法
我决定我真正想要的是在表单模板构建/运行时自动生成属性。
事实证明这相当棘手。首先,对于 2007 版本,我们只能使用 VSTA 1.0,它是 .net 2.0,缺少许多您在新 IDE 和较新 C# 版本中理所当然的功能。
我的第一个想法是使用模板……不行 :(
于是我研究了一下,最终研究了 Reflection.Emit 和 CodeDom。最后,我决定在运行时生成代码没有什么用,因为您希望在开发期间能够访问它。但是,在表单运行时生成代码却很困难……
最终,我的解决方案如下:
- 我添加了一个新类
PropertyGenerator
,其中包含一个静态方法Build(XmlFormHost form)
- 从 FormCode 的
InternalStartup()
方法中调用PropertyGenerator.Build(this)
。 - 将新生成的 FormCode.Properties.cs 文件添加到解决方案中。
- 瞧,我现在可以在代码中访问属性了 :)
如果我不想每次都重新生成,我只需注释掉对 Build()
的调用。
示例演练
我将在文章后面逐步介绍代码,但我建议您下载示例并自行尝试,看看它是如何工作的。
要使用下载的文件,您可以将文件夹解压到“C:\”,然后右键单击“Properties Example.xsn”并选择“设计”。然后预览并测试结果。
模板
表单的设计很简单,包含了一系列(支持的每种 DataType 的)字段,包括组和一个重复字段。
表单为每个元素都有一个框和一个重复的表。
“测试按钮”使用代码隐藏通过生成的属性来设置所有字段。
代码后台
using Microsoft.Office.InfoPath;
using System;
using System.Windows.Forms;
using System.Xml;
using System.Xml.XPath;
using mshtml;
using InfoPathHelpers;
using System.Collections.Generic;
namespace Properties_Example
{
public partial class FormCode
{
public void InternalStartup()
{
((ButtonEvent)EventManager.ControlEvents["TestButton"]).Clicked +=
new ClickedEventHandler(TestButton_Clicked);
#if DEBUG
Dictionary<string, Type> PropertyMapping = new Dictionary<string, Type>();
PropertyMapping.Add("EnumField", typeof(Fruit));
PropertyGenerator.Build(this, PropertyMapping);
#endif
}
public void TestButton_Clicked(object sender, ClickedEventArgs e)
{
StringField = "Hello, World";
IntegerField = 25;
BooleanField = true;
DateTimeField = DateTime.Today;
GroupAttribute = "Attribute";
GroupField = "Group Element";
EnumField = Fruit.Strawberry;
for (int i = 1; i < 6; i++)
{
RepeatingNumbers[i] = i;
}
MessageBox.Show(EnumField.ToString());
}
public enum Fruit
{
Apple,
Orange,
Banana,
Strawberry
}
}
}
代码隐藏非常简单。我添加了对该库的引用。然后调用 PropertyGenerator.Build()
,传递一个字典,用于将特定字段(例如 EnumField
)映射到 Fruit
枚举。
然后运行解决方案以生成 FormCode.Properties.cs 文件。
然后可以挂接 TestButton 处理程序,并使用相应的 CLR 类型获取或设置属性。
预览
预览表单时,您应该会看到类似这样的内容:
点击测试按钮后,出现以下结果:
瞧!一种无需处理 XPath 即可在 InfoPath 中设置字段的非常简单的方法。
库
属性生成器
属性生成器没什么特别的,但它非常庞大且笨重。它只是获取一个字段列表,并为每个字段生成以下内容:
- 一个包含字段 XPath 的私有 const 字符串。
- 一个公共属性,其 getter 和 setter 都使用库中包含的方法(
TryGetValue
和SetValue
)。
正如我提到的,我使用了 CodeDom 进行代码生成,这相当直接。
最有趣的部分是如何为重复元素创建索引属性,但这并不算太难,只是比较耗时。
获取、设置、运行!
设置方法相当直接:
public static void SetValue<T>(XPathNavigator node, T value, bool isNillable)
{
var Value = (object)value;
var Type = typeof(T);
if (value == null || string.IsNullOrEmpty(value.ToString()))
{
node.SetValue(string.Empty);
if (isNillable)
{
SetNilAttribute(node);
}
return;
}
RemoveNilAttribute(node);
if (Type == typeof(Boolean))
{
node.SetValue((bool)Value ? "1" : "0");
}
else if (Type == typeof(DateTime))
{
node.SetValue(((DateTime)Value).ToString("s", CultureInfo.CurrentCulture));
}
else if (Type.IsEnum)
{
node.SetValue(((int)Value).ToString());
}
else
{
node.SetValue(Value.ToString());
}
}
传入要设置值的节点和值,它将使用类型来确定字符串的格式。这很容易,因为最终所有节点的值都是字符串。
目前我只处理基本类型,但随着时间的推移,我会继续添加更多类型。任何尚未处理的类型将只产生一个字符串属性。
TryGetValue
方法有点棘手:
public static bool TryGetValue<T>(XPathNavigator node, out T result)
{
// If there is no node to get a value from then conversion fails automatically.
// An exception is not thrown because this method provides checking via the returned value.
if (node == null)
{
result = default(T);
return false;
}
if (typeof(T) == typeof(string))
{
// Node values are stored as string so no conversion is needed.
// We still need to use ValueAs because this is a generic method.
result = (T)node.ValueAs(typeof(T));
return true;
}
if (typeof(T).IsEnum)
{
// Enum types do not have TryParse methods so if T is an enum then we
// use Enum.Parse.
// TODO: If re-targeting .net framework to version 4.0+ in future use Enum.TryParse.
try
{
T enumValue = (T)Enum.Parse(typeof(T), node.Value);
if (Enum.IsDefined(typeof(T), enumValue) || enumValue.ToString().Contains(","))
{
// The node value is defined in the enum so we can set result to the value
// and return true as conversion succeeded.
result = enumValue;
return true;
}
}
catch (ArgumentException) { }
// If the value was not defined in the enum or there was an exception
// conversion failed so set result to default and return false.
result = default(T);
return false;
}
MethodInfo TryParse = typeof(T).GetMethod("TryParse", new[] { typeof(string), typeof(T).MakeByRefType() });
if (TryParse == null)
{
throw new InvalidOperationException("The supplied type cannot be parsed");
}
T Result = default(T);
var Parameters = new object[] { node.Value, Result };
bool Success = (bool)TryParse.Invoke(null, Parameters);
if (Success)
{
Result = (T)Parameters[1];
}
result = Result;
return Success;
}
基本上;如果它是字符串,我们就返回字符串,很简单!如果它是枚举,我们就解析枚举。如果它是其他任何类型,我们就尝试使用内置的 TryParse 方法进行解析。
也许可以做得更好,但目前为止它能胜任工作 :)
核心部分!
您不会认为它有那么容易吧?
嗯,最难的部分是找出每个属性需要是什么类型。为了做到这一点,我们真的需要查看架构。所以我又写了一组辅助函数(在 FormInfo 下),可用于获取节点信息或以各种方式打开架构。
不幸的是,读取架构似乎非常困难,因为我无法使用 XmlSchemaSet
,因为 minOccurs
和 maxOccurs
属性总是为 1!相反,我 resort 到将架构作为 XPathDocument 打开(快速,只读)。
我注意到了一些将被假定为始终正确的事情:
- 根元素在架构中始终逻辑上位于第一位。
- 文档中的每个节点都有一个对应的架构节点,其中一个带有“ref”属性,另一个带有“name”属性。
- InfoPath 命名空间始终是“my”,而架构命名空间始终是“xsd”。
解释遍历架构以获取所需信息的整个方法将是一项艰巨的工作,但您可以随意自行查看源代码。
同时,知道如果从表单代码中调用 FormInfo.GetAllNodeInfo(this)
,它应该会返回一个整洁的 List<NodeInfo>
,其中包含 Name, XPath, IsRepeating, IsNillable, XmlType, InfoPathType
等方便的属性,这就足够了。
然后可以使用这些属性来确定如何创建属性。
未来...
- 为组或根返回 XPathNavigator?
- 我想为重复节点提供索引集合,这些集合可以用作 Property[x] 来获取位置 x 处节点的值。更新: 已完成!
- 在遥远的未来,也有可能为复杂节点创建类。并能在代码隐藏中从文档中添加或删除它们。
最终注释
我已经对该代码进行了大量的测试……但是它仍然是一个 WIP!使用风险自负。我建议它在开发阶段非常有用,当表单设计需要快速更改时。但是,一旦发布,在这个阶段最好手动进行代码更改。这确实取决于您表单的复杂性以及代码隐藏的要求……
希望您喜欢看!:)