Freigeben über


Creating an InfoPath Custom Control using C# and .NET

In Office 2003 Service Pack 1 (SP1), new features and the InfoPathControl and InfoPathControlSite objects were added to InfoPath to support the development of custom controls implemented using Microsoft ActiveX technology. ActiveX controls are developed using unmanaged COM code, typically written in C++ or Visual Basic 6.0. With the increasing popularity of the Microsoft .NET Framework, many developers are switching to working with managed code, such as C# and Visual Basic .NET. As an alternative to using unmanaged code to create a custom ActiveX control, you can create a user control (a control derived from the .NET Windows Forms UserControl class) that will function as an InfoPath custom control by using COM Interop. COM Interop provides interoperability between the .NET assembly compiled for your user control and the unmanaged code of InfoPath. Although Windows Forms user controls are not natively supported by InfoPath SP1, once you handle the details required for COM Interop and security, writing your .NET code is really easy. In this blog entry, we'll give you an overview of how to get a .NET control to work in InfoPath. This entry won’t go over the basics of writing .NET user controls, so if you are not familiar with user controls, you will need to find that information before the discussion in this blog entry will be useful to you. The basic steps for creating a user control are described in Walkthrough: Authoring a User Control with Visual C#. For additional details on creating custom ActiveX controls for InfoPath, you can view the Creating Custom Controls for InfoPath SP1 webcast, and work with the ActiveX Controls in InfoPath 2003 hands-on training.
 
Adding the Right Attributes
To get a .NET user control to work with unmanaged code, certain attributes will need to be added to its source code. In the ActiveX world, all controls have GUIDs (globally unique identifiers). To do this in .NET, you will need to use the GuidAttribute attribute to specify a GUID. This attribute is part of the System.Runtime.InteropServices namespace.

COM interop will expose methods and properties based on the setting of the ClassInterface attribute. This attribute must be set to ClassInterfaceType.AutoDual in order for the control to work correctly in InfoPath.

[ClassInterface(ClassInterfaceType.AutoDual)]

But you will still need to expose the Value and Enabled properties of your control to InfoPath. To do this, you declare an interface for these properties that you will implement within the user control class. The InterfaceType attribute on this interface should be set to InterfaceIsDual as shown in the following line of code:

[InterfaceType(ComInterfaceType.InterfaceIsDual)]

This attribute setting will expose all of the properties on this interface.

Additionally, for the property notifications to fire you will need to specify COM dispatch identifiers (DISPIDs) for the Enabled and Value properties of your control. To assign DISPIDs using COM interop, you use the DispId attribute, which is also part of the System.Runtime.InteropServices namespace.
 
Putting all of this together, the skeletal code for your control should look something like the following example:

[InterfaceType(ComInterfaceType.InterfaceIsDual)]public interface ICOMControl {  [DispId(UserControl1.DISPID_VALUE)]  string Value { get; set; }   [DispId(UserControl1.DISPID_ENABLED)]  bool Enabled { get; set; }}

[Guid("6E6F8C69-2643-4f45-B111-3ABE034940D9")]
[ClassInterface(ClassInterfaceType.None)]
public class UserControl1 : System.Windows.Forms.UserControl, ICOMControl
{
   ...
}

Note that ICOMControl is the name we’ve given to the interface we defined which must be implemented within the control class to expose the Value and Enabled properties. The user control class derives from this interface, provides the actual implementation of the get and set methods of the properties, and specifies the values for the DISPID_VALUE and DISPID_ENABLED constants. See the full listing later in this blog entry for more details. 
 
The IPropertyNotifySink Interface
The COM IPropertyNotifySink interface is required for InfoPath to know when to update the XML field which is bound to the ActiveX control. Property notifications should be fired by the control for this to happen. .NET user controls do not have an equivalent interface that will work in COM Interop, but you can work around this by importing the unmanaged IPropertyNotifySink interface and then writing your own implementation of it in managed code. This is accomplished by using the ComImport and InterfaceType attributes as shown in the following example.

[ComImport][Guid("9BFBBC02-EFF1-101A-84ED-00AA00341D07")][InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]public interface IPropertyNotifySink{   int OnChanged(int dispId);   [PreserveSig]   int OnRequestEdit(int dispId);}

In addition to this code, you need to a create a delegate for the two events of your control class. The delegate should look like this:

public delegate int PropertyNotifySinkHandler(int dispId);

And your events should look like this:

public event PropertyNotifySinkHandler OnChanged;
public event PropertyNotifySinkHandler OnRequestEdit;

You also need to specify that the imported IPropertyNotifySink interface is exposed as a source of COM events. You do this by adding the ComSourceInterfaces attribute to your control's class. The attribute should look like this:

[ComSourceInterfaces(typeof(IPropertyNotifySink))]

And finally, when implementing the Value and Enabled properties of the control, don't forget to fire the OnChanged event when the Value property is changed.
 
Satisfying Security

Custom controls written with a .NET language still have the same security restrictions as unmanaged ActiveX controls used in InfoPath: the .CAB file for the control must be signed with a digital signature, and the IObjectSafety interface must be implemented on the control. The IObjectSafety interface is an unmanaged interface but can still be implemented if you import and rewrite the interface in .NET. This is similar to what we did for the IPropertyNotifySink interface above:

[ComImport][Guid("CB5BDC81-93C1-11CF-8F20-00805F2CD064")][InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]interface IObjectSafety{   [PreserveSig]   int GetInterfaceSafetyOptions(ref Guid riid, out int pdwSupportedOptions, out int pdwEnabledOptions);                           [PreserveSig]   int SetInterfaceSafetyOptions(ref Guid riid, int dwOptionSetMask, int dwEnabledOptions);}

The user control class must derive from the IObjectSafety interface and implement the GetInterfaceSafetyOptions and SetInterfaceSafetyOptions methods. See the complete listing in the following section for details on how to do this.

Coding Checklist
We've gone over quite a few things that need to be done to create a .NET user control that works with InfoPath. Below is a checklist of the things you should have already done:

  • Add the Guid attribute to your control class
  • Add the ComSourceInterfaces attribute to your control class
  • Set the control’s ClassInterface attribute to ClassInterfaceType.None
  • Declare an interface for the Value and Enabled properties of your control, setting the InterfaceType attribute to ComInterfaceType.InterfaceIsDual
  • Import and implement the COM IPropertyNotifySink interface
  • Import and implement the COM IObjectSafety interface

The following listing provides the code behind a simple .NET user control that contains a read-only TextBox control that can be bound to a field in an InfoPath form.

using System;using System.Collections;using System.ComponentModel;using System.Drawing;using System.Data;using System.Windows.Forms;using System.Runtime.InteropServices;

namespace WindowsControlLibrary1
{
   /// <summary>
   /// Summary description for UserControl1.
   /// </summary>

   [InterfaceType(ComInterfaceType.InterfaceIsDual)]
   public interface ICOMControl
   {
      [DispId(UserControl1.DISPID_VALUE)]
      string Value { get; set; }

      [DispId(UserControl1.DISPID_ENABLED)]
      bool Enabled { get; set; }
   }

   [ComImport]
   [Guid("CB5BDC81-93C1-11CF-8F20-00805F2CD064")]
   [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
   interface IObjectSafety
   {
      [PreserveSig]
      int GetInterfaceSafetyOptions(ref Guid riid, out int pdwSupportedOptions, out int pdwEnabledOptions);
                       
      [PreserveSig]
      int SetInterfaceSafetyOptions(ref Guid riid, int dwOptionSetMask, int dwEnabledOptions);
   }

   [ComImport]
   [Guid("9BFBBC02-EFF1-101A-84ED-00AA00341D07")]
   [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
   public interface IPropertyNotifySink
   {
      [PreserveSig]
      int OnChanged(int dispId);
 
      [PreserveSig]
      int OnRequestEdit(int dispId);
   }

   public delegate int PropertyNotifySinkHandler(int dispId);

   [Guid("1FEE489F-A555-4408-8FBF-3F69F8C57A43")]
   [ClassInterface(ClassInterfaceType.None)]
   [ComSourceInterfaces(typeof(IPropertyNotifySink))]
   public class UserControl1 : System.Windows.Forms.UserControl, ICOMControl, IObjectSafety
   {
      public event PropertyNotifySinkHandler OnChanged;
      public event PropertyNotifySinkHandler OnRequestEdit;

      private System.Windows.Forms.TextBox textBox1;
      /// <summary>
      /// Required designer variable.
      /// </summary>
      private System.ComponentModel.Container components = null;

      // Constants for implementation of the IObjectSafety interface.
      private const int INTERFACESAFE_FOR_UNTRUSTED_CALLER = 0x00000001;
      private const int INTERFACESAFE_FOR_UNTRUSTED_DATA = 0x00000002;
      private const int S_OK = 0;

      // Constants for DISPIDs of the Value and Enabled properties.
      internal const int DISPID_VALUE = 0;
      internal const int DISPID_ENABLED = 1;

      public UserControl1()
      {
         // This call is required by the Windows.Forms Form Designer.
         InitializeComponent();
         // TODO: Add any initialization after the InitComponent call
      }

      // Implementation of the IObjectSafety methods.
      int IObjectSafety.GetInterfaceSafetyOptions(ref Guid riid, out int pdwSupportedOptions, out int pdwEnabledOptions)
      {
         pdwSupportedOptions = INTERFACESAFE_FOR_UNTRUSTED_CALLER | INTERFACESAFE_FOR_UNTRUSTED_DATA;
         pdwEnabledOptions = INTERFACESAFE_FOR_UNTRUSTED_CALLER | INTERFACESAFE_FOR_UNTRUSTED_DATA;
         return S_OK;   // return S_OK
      }
 
      int IObjectSafety.SetInterfaceSafetyOptions(ref Guid riid, int dwOptionSetMask, int dwEnabledOptions)
      {
         return S_OK;   // return S_OK
      }

      protected int Fire_OnRequestEdit(int dispId)
      {
         if (this.OnRequestEdit != null)
            return this.OnRequestEdit(dispId);
         else return 0;
      }

      protected int Fire_OnChanged(int dispId)
      {
         if (this.OnChanged != null)
            return this.OnChanged(dispId);
         else return 0;
      }

      // Implementation of the Value property get and set methods.
      public string Value
      {
         get { return textBox1.Text; }
         set { textBox1.Text = value; }
      }

      /// <summary>
      /// Clean up any resources being used.
      /// </summary>
      protected override void Dispose( bool disposing )
      {
         if( disposing )
         {
            if( components != null )
               components.Dispose();
         }
         base.Dispose( disposing );
      }

      // Component Designer generated code goes here.
      ...

      private void textBox1_TextChanged(object sender, System.EventArgs e)
      {
         Fire_OnChanged( UserControl1.DISPID_VALUE );
      }
   }
}

Compiling a .NET User Control for COM Interop
To compile a .NET user control for COM Interop, follow these steps:

  1. In Visual Studio .NET 2003, open the Solution Explorer and right-click on the project item.
  2. Click Properties to display the properties pane for the project.
  3. Under Configuration Properties, click Build.
  4. Under Outputs, change Register for COM Interop to True.

The next time you compile, your user control will be available to unmanaged code.
 
Adding a .NET User Control to the InfoPath Controls Task Pane
When you add a new custom control using the InfoPath Add Custom Control Wizard, InfoPath will look only for controls that are in the "Controls" category. However, when .NET Controls are compiled, they are categorized as ".NET Controls", which InfoPath does not look for. To manually add a .NET control, you must create an .ICT file and store it in the C:Documents and SettingsusernameLocal SettingsApplication DataMicrosoftInfoPathControls folder. If you have not added any custom controls to InfoPath's Controls task pane, you will need to create this Controls folder yourself. The easiest way to create an .ICT file is to add an ActiveX control to the Controls task pane in InfoPath, and then copy the .ICT file which is created automatically by InfoPath.
 
Getting a .NET User Control into a Self-Registering CAB file
InfoPath requires custom controls to be packaged in CAB files for deployment. The normal way for .NET Controls to be deployed is to add a Setup Project to the solution in Visual Studio, which will produce an MSI file when the solution is compiled. An MSI file is required for a .NET control to be registered for COM Interop. The MSI file that is generated by the Setup Project can then be packaged in a CAB file, but CAB files do not automatically run and register MSI files. You can work around this by creating an .INF file similar to the following example which has hooks to execute the .MSI file after the CAB file is extracted:

[Setup Hooks]hook1=hook1

[hook1]
run=msiexec.exe /i %EXTRACT_DIR%MSI.msi /qn

[Version]
; This section is required for compatibility on both Windows 95 and Windows NT.
Signature="$CHICAGO$"
AdvancedInf=2.0

Note   There is a bug in the .NET Framework that will cause any Label controls used in a .NET user control to throw GDI+ exceptions. You can workaround this by using GDI+ to draw your own text, or you can use a TextBox control instead and set its ReadOnly property to True.

Comments

  • Anonymous
    July 15, 2005
    A while ago, I helped write an article for the InfoPath team on how to write InfoPath Custom Controls...

  • Anonymous
    September 07, 2006
    How can i get more infomation about the ict file schema ?
    I need to build a complex control to binding a complex type xml.So I need more help,Thx.

  • Anonymous
    February 27, 2007
    Is it possible using infopath can i able to create dynamic webform. If it is possible is there any URL to show the demo. If it is not please suggest how i create the dynamic webform(like master screens). My intension is create all ASP.NET web controls  using meta data. Meta data is nothing but information about the controls in the form where is stored in the database. (If anybody know how to read and write the viewstate web controls) Thanks & Regards H.Ashok

  • Anonymous
    February 28, 2007
    To H.Ashok: the best place to ask this question is the official support forum. We try to keep the questions that we answer on this blog relevant to the topic of the blog article - for all other questions, please use the forum. Thanks, Alex

  • Anonymous
    July 03, 2007
    Are these instructions valid for IP2007?

  • Anonymous
    August 18, 2007
    Just a note to add to the "Adding a .NET User Control to the InfoPath Controls Task Pane" section. The classid specified inside the ICT file must match the GUID that prefixes the UserControl1 class declaration, in the above example this is '1FEE489F-A555-4408-8FBF-3F69F8C57A43'. If they don't match you get a 'Infopath cannot open the selected form' Regards Des

  • Anonymous
    September 17, 2007
    转:http://blogs.msdn.com/infopath/archive/2005/04/15/creating-an-infopath-custom-control-using-c-and-...

  • Anonymous
    May 07, 2008
    I created an Infopath template as you mentioned, but when I open the template in Infopath, I get the following message: "This page provides potentially unsafe information to an ActiveX control. You current security settings prohibit running controls in this manner. As a result, this page may not display correctly." Once I press OK it displays the activeX control. Is it possible to suppress this message? Both the Infopath template and the CAB file are signed and the Infopath template is set to full trust mode. Appreciate you help. Regards, sanju

  • Anonymous
    February 17, 2010
    Sanju, I was experiencing the same error, and it was because I had not properly implemented IObjectSafety. Check again that your .net definition for the interface is correct and has the attributes in the code in the article. I was missing the ComImport attribute! (BobC - these instructions are valid for InfoPath 2007) Thanks, David

  • Anonymous
    February 17, 2010
    THE CODE LISTING WILL NOT WORK! After spending ages on this and being unable to get the data to bind, I finally noticed the first part of the article that says that the user control class needs the [ClassInterface(ClassInterfaceType.AutoDual)] attribute. The subsequent code listings display it with the [ClassInterface(ClassInterfaceType.None)], which will prevent the InfoPath from interacting with the control. Thanks, Dave

  • Anonymous
    May 07, 2010
    We have an asp.net applications which uses infopath custom control(C#, .NET) using Microsoft.Office.InfoPath.FormControl for viewing and editing InfoPath documents. The spell checker of the above custom control only works for the first document  opening in that . If we open a new browser window the spell checker will work again for the first document.Any ideas or suggestions to make spell checker  work always with out opening new browser window.

  • Anonymous
    January 10, 2012
    Has this been updated to work with 2010? The title says it's InfoPath 2010 but the article has been written back in 2005

  • Anonymous
    July 25, 2012
    Thanks, nice article. Works smoothly. What about WPF though? I tried to do the same with WPF control and it didn't work. Infopath renders it as gray area and when I try to host my control in a simple html page I get a white space, instead of my control. And I don't mean the whitepsace with the little red X in the top-left corner. Just whitespace. Any ideas?