65.9K
CodeProject 正在变化。 阅读更多。
Home

为 InfoPath 2007 自动生成属性

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2013年10月7日

CPOL

6分钟阅读

viewsIcon

18495

downloadIcon

358

为 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。最后,我决定在运行时生成代码没有什么用,因为您希望在开发期间能够访问它。但是,在表单运行时生成代码却很困难……

最终,我的解决方案如下:

  1. 我添加了一个新类 PropertyGenerator,其中包含一个静态方法 Build(XmlFormHost form)
  2. 从 FormCode 的 InternalStartup() 方法中调用 PropertyGenerator.Build(this)
  3. 将新生成的 FormCode.Properties.cs 文件添加到解决方案中。
  4. 瞧,我现在可以在代码中访问属性了 :)

如果我不想每次都重新生成,我只需注释掉对 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 都使用库中包含的方法(TryGetValueSetValue)。

正如我提到的,我使用了 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,因为 minOccursmaxOccurs 属性总是为 1!相反,我 resort 到将架构作为 XPathDocument 打开(快速,只读)。

我注意到了一些将被假定为始终正确的事情:

  • 根元素在架构中始终逻辑上位于第一位。
  • 文档中的每个节点都有一个对应的架构节点,其中一个带有“ref”属性,另一个带有“name”属性。
  • InfoPath 命名空间始终是“my”,而架构命名空间始终是“xsd”。

解释遍历架构以获取所需信息的整个方法将是一项艰巨的工作,但您可以随意自行查看源代码。

同时,知道如果从表单代码中调用 FormInfo.GetAllNodeInfo(this),它应该会返回一个整洁的 List<NodeInfo>,其中包含 Name, XPath, IsRepeating, IsNillable, XmlType, InfoPathType 等方便的属性,这就足够了。

然后可以使用这些属性来确定如何创建属性。

未来...

  • 为组或根返回 XPathNavigator?
  • 我想为重复节点提供索引集合,这些集合可以用作 Property[x] 来获取位置 x 处节点的值。更新: 已完成!
  • 在遥远的未来,也有可能为复杂节点创建类。并能在代码隐藏中从文档中添加或删除它们。

最终注释

我已经对该代码进行了大量的测试……但是它仍然是一个 WIP!使用风险自负。我建议它在开发阶段非常有用,当表单设计需要快速更改时。但是,一旦发布,在这个阶段最好手动进行代码更改。这确实取决于您表单的复杂性以及代码隐藏的要求……

希望您喜欢看!:)

© . All rights reserved.