Part 2 – Postbacks and conditions that cause them Welcome to the second article in our series on optimizing the performance of browser-enabled forms designed with Microsoft Office InfoPath 2007. In our first article, we defined performance in terms of responsiveness. In this article, we’ll focus on postbacks, one of the most common causes of reduced performance Postbacks are probably the most important factor affecting form performance because the effect of other conditions is amplified with each postback. There are two significant aspects of postbacks which we’ll examine in this series. First, which form features cause postbacks? Those features are the subject of this article. Second, what kinds of postbacks are more expensive to process, thus imposing an additional burden on the performance and scalability of the form system? We’ll examine those factors in the next article. What is a postback? Certain form controls, actions, and features require the browser to communicate with the server during the form-filling session. This exchange of data during the session is called a postback, and usually occurs when a form feature needs to send data to the server for processing. After sending the postback request, the browser must then wait for a response before it can update and re-render the form with the new data. In the case of a full-page postback, the browser must stop rendering completely until the whole updated page is returned, blocking user interaction with the form until it has been re-rendered. Alternately, to improve the user experience in the browser, some features use a partial postback, where only a specific part of the form is updated. This allows the user to continue interacting with other parts of the form while the update happens in the background. In either case, however, unnecessary postbacks impose an additional load on both the browser and the server. If you can reduce or eliminate postbacks as you design, develop, and test your forms, you can improve the performance and scalability of your entire form system. Browser-enabled form handling compared with InfoPath client forms To better understand the effects of postbacks on performance, it is helpful to compare basic form handling of the InfoPath-rich client with InfoPath Forms Services and browser-enabled forms. Whether you are new to InfoPath, or you are familiar with rich client forms but new to InfoPath Forms Services and browser-enabled forms, understanding these basic differences will help you improve your form design and troubleshooting strategies.The major difference between the InfoPath-rich client and browser-enabled form systems is that the InfoPath client doesn’t need to communicate with the form server as frequently as the browser does. This is because the InfoPath client processes most forms locally on the user’s computer. In contrast, the browser form relies on the server for a variety of form processing operations, requiring more communication to exchange data between the browser and the form server. Communication in the form of postbacks is a fundamental feature that allows InfoPath to support browser-enabled forms on the Web, but postbacks also make browser forms more sensitive to performance issues. Let’s look at two form loading and editing scenarios that further illustrate the difference between form processing in the InfoPath client and browser-enabled form systems. In the InfoPath-rich client scenario, when the user opens the form from a SharePoint form library, InfoPath first determines that the form should be opened in the client rather than the browser. After the initial download of a copy of the requested form template, InfoPath opens and renders an instance of the form on the user’s computer for editing. The form instance in the client is an XML document that the client renders in HTML for display. As the user interacts with the form, the InfoPath client processes and renders changes, and might run some custom form code, all within the context of the client running on the user’s computer. Generally, the only time the client needs to connect back to the server is to make additional data connection requests to query from or submit to a data source, to save a partially completed form, or to submit the final form. In the case of the browser-enabled form, when InfoPath Forms Services receives an initial request for the form from the browser, the server starts a session for that user’s form-filling session, allocates server resources, and processes the browser-enabled form template to load an instance of the form XML for the session. In addition, if the form requires it, the server might load and run custom form code and load data from a data source. After this initial processing, the server assembles the JavaScript and HTML that represent the form instance to be downloaded to and rendered by the browser. Finally, the server sends the response through a Web Front End server over the network to the browser on the user’s computer, which renders the initial download into the form view that the user interacts with. As the user interacts with the form in the browser, some form processing happens in the browser, but more complex form elements and events require a postback to the server for processing. During the transmission and processing of the form data, the browser instance of the form must wait for a response and then re-render the updated form. In this communication cycle, each postback request and response set constitutes a roundtrip. Each roundtrip affects the performance and scalability of the form system in terms of throughput, responsiveness, and resource utilization. This impacts both the browser side and the server side, as well as the network. As you can see from these two scenarios, a form-filling session in the browser-enabled form system requires substantially more communication than a similar session in the InfoPath client. In addition, more of the browser-enabled form processing is concentrated on the server, especially when multiple form-filling sessions are being served. In contrast, with an InfoPath client form, the form processing is distributed to the user systems. Consequently, reducing the number and complexity of postbacks as you’re designing browser-enabled forms can substantially boost performance. Ten common causes of postbacks
A calculation can cause a postback when there is a condition that cannot be evaluated in the browser and requires processing by the server. For example, if a calculation requires data that is not loaded in the DOM, then the browser must send a postback to the server and wait for the result. Another condition that will invoke a postback involves any calculation that is out of the context of the current node. For example, you could create an expression such as .\.\.\my:field1+. When the expression is invoked in the form, because it requires traversing up the tree several levels from the current node, it must be sent to the server for evaluation.
The function of a data adapter is to make a query connection to an external data source, such as an XML file, a database, a SharePoint list, or a Web service, so that a form can receive and be updated with some data from the data source. If a form invokes a data adapter when the form loads, the browser must wait for the data connection, increasing form load time. If the form invokes a data adapter after the form loads, a postback containing the query request is first sent to the form server, which executes the data connection request on behalf of the browser form, adding additional time to the roundtrip.
Multiple binding happens when one piece of data in the form is bound to two or more controls in the same view. This is usually done so that the same data can be displayed in multiple places. For example, you might design a report with several sections to include a single non-repeating field, such as "Report Date", inside a repeating section so that the same date is displayed in each report section. If you design a form this way, then changing the "Report Date" field in one location will update it everywhere because it is multiply bound. Unfortunately, the cost of this multiple binding might be a roundtrip to the server if the binding is more complex than the browser can resolve. In this case, server analysis is required to reliably identify all the controls bound to the same piece of data. In addition, if something is both multiply bound and out of context, then user edits to form fields will always require roundtrips. Other form actions that will cause roundtrips to the server when there is multiple binding include insertion or removal of repeating tables and repeating or optional sections.
Out-of-context binding describes a situation where a control inside a repeating or optional section is bound to a node that is in a different part of the XML data source tree, outside the group node that contains the repeating or optional section control. When an out-of-context situation occurs in a form, the correct binding may be too complex to determine within the browser. If either the validity or the kind of action—such as a calculation—cannot be determined in the browser, the out-of context binding will trigger a roundtrip to the server to resolve the binding.You are most likely to encounter this problem as you’re designing a form in the InfoPath Designer if you first insert a group or control, and then later decide to move that group or control into an optional section. To avoid creating an out-of-context binding, after you drag and drop the control into the optional section, in the data source pane, drag the node the control is bound to into the group for the new optional section. The group or control will then be bound to the correct context.
Multiple views are often used to present a specific view to a specific user audience in a workflow. Multiple views are also commonly used to improve the performance of a form; by careful grouping of related items and limiting the number of controls in each view you can improve the form loading and rendering performance compared to placing everything in a single view. However, each time the view is switched, there is a full postback to send the user’s changes to the server, get the new view, and reload it in the browser. If possible, try to group the items on a view so that any controls that might postback do so when the view is switched. Then only one postback occurs when switching the view. A good example of this is using a button to invoke a query connection that returns data for a list, an operation that requires a postback. If the view is also switched when the button is clicked, you improve the efficiency of the postback. Another benefit of this design is that it makes the view switch a user-invoked action, which improves the user’s perception of the form response. By coordinating the postback of controls in the view with a view switch, you not only improve the responsiveness of the form, but also reduce stress on the server and network by eliminating extra postbacks. When designing your form there is no formula for when to use multiple views, so you will need to balance your use of multiple views with the potential design and performance advantages and costs in your user scenarios.
Conditional formatting is commonly used to hide part of a form from view until some condition is met. However, conditional formatting expressions fire every time the form is rendered whether hidden or not. Additionally, all of the data, calculations, and expressions used by fields that are conditionally hidden are still downloaded and can cause extra performance overhead. Thus if anything else invokes a postback, your form will incur this additional overhead. Similarly, if the conditional formatting requires a postback, then there is a postback each time the form is rendered. Although the primary reason for using conditional formatting has more to do with your form design and presentation, there may also be a slight increase in the responsiveness of the form if it uses conditional formatting. This is possible because it can be slower to render something in the browser than to execute the conditional formatting. However, if you need to substantially limit the presentation, consider using multiple views instead of conditional formatting because multiple views generally perform better.
For forms that are filled out in Internet Explorer, signing a form uses an ActiveX control that communicates with the server using out-of-band postbacks. The signature operation involves 3 main steps:
Each step causes a postback, the first and the last being relatively expensive. This is because the first postback sends, among other things, the data corresponding to the non-repudiation image to be stored in the signature. The last is a full postback that refreshes the browser with the new signed document that has been prepared on the server.
Normally, when a user changes the values of several controls in the browser form, the JavaScript form logic in the browser can optimize the changes, batching them together to avoid a postback for each change. This reduces the number of postbacks to one. However, this optimization is not possible if the form has code or other logic, such as rules or conditional formatting, that listens to data changes in parts of the XML DOM. For example, if a control is bound to a particular DOM element and a listener is present, changes in that control are immediately communicated to the server and the corresponding event handler invoked. The number of extra postbacks depends not only on the number of event handlers, but also on the XPath they use to listen to DOM events. This is because InfoPath XML events bubble. For example, if some form code has business logic with an event handler that listens to a node close to the root node, there will be relatively more postbacks. This is because any browser change affecting one of the descendants of that node will trigger a postback to the server. Consequently, if your form uses form code or any other construct that involves listening to changes, it is important for performance and scalability that you understand and carefully scope events to reduce unnecessary postbacks and server stress.
If you are using advanced form techniques such as developing your own ASP.NET page to host the InfoPath Form control, then there are a couple of things you should be aware of. Submitting to a custom hosting page requires full page postback because the XmlFormView control resides on the server and it involves rebuilding the page controls by ASP.NET. In addition, when submitting the form, before the submit is finalized, data is sent back to the browser with a pending submit action added. This pending submit action must then be sent back to the server in another postback to actually complete the submission. If the page is hosted on a SharePoint site, it also must initialize the SharePoint page infrastructure as well. Also, if an XmlFormView control is hosted on a custom ASPX page and the page listens to events from the control, then every operation triggering an event in the XmlFormView control will require full page postback.
In some advanced form design scenarios, you might build your own complex custom expressions or calculations using the "Edit XPath (advanced)" option. Although most common expressions or calculations can run in the browser, those that require more complex XPaths to evaluate may need to postback to the server. The following complex XPath conditions will cause extra postbacks:
What’s next?
In our next article we’ll examine some of the conditions that make postbacks more expensive and that affect the performance and scalability of a server system running InfoPath Forms Services.
When entering phone numbers in InfoPath, you can validate that it is a phone number easily enough (Data Validation->field1 “matches pattern” Phone number), but what do you do if the input does not match that pattern? Asking users to exactly enter the format “(000) 000-0000” may be a little constraining. Therefore, you may want a rule so that any combination of spaces, dashes, parenthesis and 10 digits will be reformatted nicely. Below I will describe how to do this in a rule, although you could do the same in business logic.
For the rule’s action, you would want to use translate, substring and concat functions. Logically, as your first action you would translate to remove all unwanted characters (spaces, dashes and parenthesis). Then, as your second action substring and concat everything.
However, you cannot break it into two actions. When the translate is executed, it changes the text field. InfoPath immediately reruns the rule on that field (even before the rest of the actions are run from the first time). For the second run of this rule, the conditions will all pass (as you would have a number like “0123456789” due to the translate that already happened), and the translate action would be run again. This will happen repeatedly and causes a recursion error.
Therefore, you would need to do this all in one action (the rule will be rerun here, but the conditions will not pass since it will match the phone number pattern).
On your text box, you will need:
1. One condition with 3 parts:
- Field “does not match pattern” phone number “and”
- Without the “()- “ characters, it has length 10:“The expression” string-length(translate(., "()- ", "")) = 10 “and”
- There are no characters other than digits and “()- “ characters “The expression” string-length(translate(translate(., "()- 0123456789", "")) = 0
2. One action that removes (), - and spaces; takes substrings; and concatenates it all together at once:
Set a field’s value “.” to concat("(",substring(translate(., "()-", ""), 1, 3), ")", substring(translate(., "()-", ""), 4, 3), "-", substring(translate(., "()-", ""), 7, 4)))
And you're done! Note that this technique will work in InfoPath 2003 and 2007, and it is supported in browser-enabled form templates. Download the form template that has this trick implemented; make sure to save it to your desktop.
You can now download and install the official InfoPath SP1. It is part of the Office 2003 SP1 download.
More details: http://office.microsoft.com/officeupdate/ . English SP1 download is here.
Please pay special attention to the following if you have installed the InfoPath 2003 SP1 Preview:
InfoPath 2003 SP1 Preview and Office 2003 SP1If you previously installed the InfoPath 2003 SP1 Preview, you should uninstall the InfoPath 2003 SP1 Preview prior to installing Office 2003 SP1. You will need your original InfoPath 2003 product CD to upgrade to the final version of InfoPath 2003 SP1. For more information, see the Microsoft Knowledge Base article (873040): Information to consider before you apply Office 2003 Service Pack 1 to a computer that has the InfoPath 2003 Service Pack 1 Preview installed.
The less time it takes to fill out your forms, the more time your users can spend on other tasks. Also, odds go up that form fillers will complete forms when they take less time to fill out. This blog post will give you a few tips for speeding up form filling and improving accuracy so your forms are completed faster.
In this post we will cover
For questions where one answer is much more common than others, it can be useful to set up default starting values. The value you choose will appear when the form is opened, but the form filler is still able to change it.
You can set default values by selecting the control and clicking on the properties pane, and then choosing “Default Value”.
Let’s say I had a sailboat rental form, and when boats are rented employees need to fill out who checked out the boat, when it was checked out, what type of boat it was, the boat #, and customer information.
Because the sailboat rental place has mostly 24 foot boats, we can set the default Size to be 24 feet. Now employees won’t have to fill this field out most of the time. To set a default value, select the control (the boat size dropdown), choose the Properties tab, and select Default Value. Type the default value in the box provided. You can set the defaults for text boxes, combo boxes, check boxes and many other controls this way.
When a boat is being checked out, we need to log the person who checked it out and the date. Most commonly, boats are checked out for today’s date. We want to set the default value of “checked out by” date to be today’s date.
To set the checked out date to today, select the date control, go to the properties tab, and choose “default value”. Choose the function builder at the right .
Choose the “Insert Function” button and pick the now() function from the Date and Time Category. Click “OK” until you return to the form. Now, when users open the form today’s date will be automatically filled in. When using today() or now(), the form will update the date when you re-open it unless you uncheck the “Refresh value when formula is recalculated”. Make sure to uncheck this box.
today() and now() can both be used to set the default value of a date picker. However, the type of form you have can affect which one you use.
- Use today() when you have a date-only field.
- Use now() when your field requires a date and time.
In general, when using a SharePoint list form, use now(). When using a form library or filler-only form, use today(), unless you have changed the data format of the date field to date and time.
Setting the default value of the Person/Group picker is done through the fields task pane. Show the fields task pane by selecting “Show Fields” on the data tab and expand your Person/Group picker field (in my case, outBy). Click “Show advanced view” to see the detailed view. Right-click on the AccountId and choose properties.
Under default value, choose the function builder and type userName(). Again, make sure that “Refresh value when formula is recalculated” is unchecked, so that this rule is only run once and not every time the form is opened. Click Ok.
Repeat this process with the DisplayName (optional) to get the best experience. Now the people picker will default to the person who opens the form. When we load the form, the date, boat type, and checked out by fields are already filled out. With half of the fields completed, it will take employees less time to check out boats.
Hopefully, these tips will help you set good defaults and your users will spend less time filling out forms!
Kate Everitt
Want to export your InfoPath form view to a Microsoft Office Word document?
The easiest way to do this is to export your InfoPath form to an MHT file using File | Export To | Web... while filling out your form. Then, just open the MHT file in Microsoft Office Word. The Word document that you create based on the MHT file exported from InfoPath will look very similar to the InfoPath form itself.
In a recent post, I touched upon the reasons why you might want to go with SQL Server XML columns as the storage for your InfoPath forms. In this article, we'll talk about actually making it work.
InfoPath's strength is working with XML data sources: web services. In the implementation below, we'll write a simple web service that will help us retrieve and submit the data to Yukon.
Scenario
Build a resume database for the HR department. We want to store highly structured resumes in a database. Highly structured here is the opposite of freeform: a resume in our scenario isn't a blob of text; we have the graduation date, employment dates, actual titles and other things parsed into data structures.
Data Structure
We will store candidate records as XML blobs in a Yukon XML column. Each resume will be stored as a separate record; each record will also have a primary key - a JobCandidateID - to simplify our development. We'll define only one table, called JobCandidate:
Populate the table with a few Resume XML files that all satisfy the same schema. Store the schema somewhere where the web server can access it.
Core Implementation in English: we'll write a web service as a middle tier between SQL Server 2005 and the InfoPath form. This web service will have two methods:
1) GetCandidateRecord: given a JobCandidateID (an integer), return a DataSet (for our purposes, an XSD-typed XML blob) that contains the candidate record. Give me the job candidate ID, I'll give you his resume.
2) UpdateCandidateRecord: take a JobCandidateID (an integer) and a Resume (an XML document), and update the resume of the candidate with that particular ID to the resume passed in as a second parameter. Nothing fancy, really.
Core Implementation in C#
I promised you two methods, here they are. First, GetCandidateRecord.
[WebMethod]public DataSet GetCandidateRecord(int JobCandidateID){ DataSet result = null; using (SqlConnection conn = new SqlConnection(connString)) { conn.Open(); SqlCommand command = conn.CreateCommand(); command.CommandText = @" SELECT Resume FROM " + tableName + @" WHERE JobCandidateID = @x"; command.Parameters.Add("@x", SqlDbType.Int); command.Parameters[0].Value = JobCandidateID; SqlDataReader reader = command.ExecuteReader(); if (reader.Read()) { DataSet ds = new DataSet(); ds.ReadXmlSchema(@"C:\Inetpub\wwwroot\infopath_yukon\Resume.xsd"); XmlDataDocument xd = new XmlDataDocument(ds); xd.Load(new StringReader((string)reader.GetValue(0))); result = xd.DataSet; } conn.Close(); return result; }}
Things are fairly straightforward here:
- Open a SqlConnection using ASP.NET credentials (make sure the ASPNET user has read/write rights to the database).
- Build a simple SELECT statement to return a resume. Recall that the resume is just an XML document stored as-is in the database.
- Cast the resume dataset into a typed dataset by applying a schema stored somewhere on the web server. Oh, I forgot to tell you - you need a schema :-). Why? InfoPath form needs to know what to expect from the web service, and while InfoPath can infer the shape of the data from the instance, this method is very much error prone. For example, how can InfoPath know of a repeating structure if only one instance was present in a sample XML document? How about choice or optional structures? Because of all of these reasons, you need to provide a typed dataset through your web service.
- Return the typed dataset for the Resume record.
Next, let's look at UpdateCandidateRecord.
[WebMethod]public void UpdateCandidateRecord(XmlDocument xml, int JobCandidateID){ using (SqlConnection conn = new SqlConnection(connString)) { conn.Open(); SqlCommand command = conn.CreateCommand(); command.CommandText = @" UPDATE " + tableName + @" SET Resume = @x WHERE JobCandidateID = @y"; command.Parameters.Add("@x", SqlDbType.Xml); command.Parameters[0].Value = xml.InnerXml.ToString(); command.Parameters.Add("@y", SqlDbType.Int); command.Parameters[1].Value = JobCandidateID; command.ExecuteNonQuery(); conn.Close(); }}
- Open a SqlConnection
- Build a simple UPDATE statement to save the resume for a given candidate. Note that you must use SqlCommand Parameters: just concatenating the XML blob won't do.
- Execute the UPDATE statement. Note that we are replacing the entire resume with the new one; no partial updates are done. This means that simultaneous editing of Resume records won't be possible.
Basic Form Template
Now that the web service is set up, we can easily build a form template based on it. The template may or may not be browser-enabled; the method described here works with both. Just launch InfoPath designer, and pick "start from web service" as your data source. Specify GetCandidateRecord as the "receive" piece of the web service, and UpdateCandidateRecord as the submit part.
InfoPath will ask you for sample JobCandidateID values for the receive web service; since our database already has a few Resumes, we can type in the JobCandidateID for one of them. You may be wondering - wait, I thought InfoPath won't do the schema inference by example! It won't - the dataset returned by your web service will contain a schema (that's why we called DataSet.ReadXmlSchema() in GetCandidateRecord), and InfoPath will use that schema to build your data source tree.
After you've gone through the initial data connection setup, you'll notice that your main data source tree is correctly populated with the data types from your schema. Complex structures should show up just fine - repeating, optional, choice structures, non-string datatypes, XSD validation... And the Submit button should be configured to save the modified Resumes back to SQL Server.
FAQ
1. Why do we have to use a custom web service, and not built-in Yukon web services?There are unfortunate technical limitations that currently require you to write a custom web service to work with SQL Server 2005 in a manner described above. The web service is, as you saw, very easy; we know that this is something that can be made better, and will consider addressing this in future versions of InfoPath and SQL Server.
2. Why not XSD-typed XML columns?When InfoPath submits datasets to the web service, it adds dataset tracking information; while you can add optional attributes to your InfoPath-generated schema and upload it to Yukon, this would complicate maintenance quite a bit.
3. What other resources are available on the topic? Be sure to check out this article by S.Y.M. Wong-A-Ton.
Alex WeinsteinProgram Manager
In this short video demo, I show how you can add the ability to sort repeating tables in your forms using code. I use picture buttons in the heading of the repeating table to trigger the code which sorts the table based on the values in the columns.
About the Code
In a nutshell, the code puts the XML from the repeating table into an array, sorts the array using built-in .Net functionality, and then writes back into the XML to update the table.
//Sorts a repeating table. Takes the parent group node and the index of the column to sort by.
private void SortTable(string xpathToSort, int columnToSortBy)
{
//get the values in an array
string[][] ValuesToSort = GetRepeatingTableValues(xpathToSort);
//sort the array
Array.Sort(ValuesToSort, new StringArrayComparer(columnToSortBy));
//write the values back to the xml
SetRepeatingTableValues(xpathToSort, ValuesToSort);
}
//Takes an array of arrays and inserts it into a repeating table
//Assumes that the array and table are the same size
private void SetRepeatingTableValues(string xpathToSet, string[][] tableValues)
//Create a navigator on main node supplied in the parameters
XPathNavigator mainGroup = this.CreateNavigator().SelectSingleNode(xpathToSet, this.NamespaceManager);
//Clone the first child node of the main navigator and populate it with data
XPathNodeIterator tableRows = mainGroup.SelectChildren(XPathNodeType.Element);
//iterate through the existing XML and update the values from the sorted array
for (int i = 0; i < tableValues.Length; i++)
tableRows.MoveNext();
XPathNodeIterator thisRow = tableRows.Current.SelectChildren(XPathNodeType.Element);
for (int j = 0; j < tableValues[0].Length; j++)
thisRow.MoveNext();
thisRow.Current.InnerXml = tableValues[i][j];
//Returns an array of arrays of strings representing the values in a repeating table
private string[][] GetRepeatingTableValues(string xpathToGet)
XPathNavigator myNav = this.CreateNavigator();
int rows, cols;
XPathNodeIterator tableNodes;
//figure out the dimensions of the table
tableNodes = myNav.SelectSingleNode(xpathToGet, this.NamespaceManager).SelectChildren(XPathNodeType.Element);
rows = tableNodes.Count;
//move to the first row to count the columns
tableNodes.MoveNext();
cols = tableNodes.Current.SelectChildren(XPathNodeType.Element).Count;
//create an array to store the values
string[][] tableValues = new string[rows][];
//get all the rows in the table
//iterate through the rows and write the inner xml of each element in each row to the array
for (int i = 0; i < rows; i++)
XPathNodeIterator childNodes = tableNodes.Current.SelectChildren(XPathNodeType.Element);
string[] rowValues = new string[cols];
for (int j = 0; j < cols; j++)
childNodes.MoveNext();
rowValues[j] = childNodes.Current.InnerXml;
tableValues[i] = rowValues;
return tableValues;
//Comparison implementation for array or arrays
class StringArrayComparer : IComparer
private int iColumn;
public StringArrayComparer(int iColumn)
this.iColumn = iColumn;
int IComparer.Compare(Object x, Object y)
string[] xAsString = (string[])x;
string[] yAsString = (string[])y;
return xAsString[iColumn].CompareTo(yAsString[iColumn]);
Requirements for publishing your forms with code as Sandboxed solutions:
Complete sandboxed solution documentation is available here. Note that InfoPath takes care of packaging and activating the solution, all you need to do is publish your form to a document library or as a site content type.
I look forward to hearing your comments about this new feature. Let me know what you think!
Phil
In the 2nd installment of our "5 for Forms" video demo series, Charlie Han, a program manager intern on the InfoPath team shows how you can use our new picture button control to create tabs to more easily navigate your forms.
(There are additional steps required to create tabs in display views. Click here to find out more.)
Enjoy!
Hi, my name is Anson Hidajat and I’m a program manager on the InfoPath team. In this week's “5 for forms” video demo, I will show how you can use rules and filters to create cascading dropdown lists in a tennis tournament bracket application.
Enjoy and please send us your feedback!
In this week's “5 for forms” video demo, Trey Brumley, a software design engineer in test shows how you can use InfoPath to customize the form for an external list on SharePoint that connects to a SQL Employee database through Business Connectivity Services.
This week’s cool form displays your local weather forecast by using a REST Web Service data connection to pull in weather information from an online weather service. There are two views to the form, one minimal and one extended. The form contains linked picture controls that use rules to concatenate the Web service data and generate a URL pointing to images on the weather site. By hosting this form inside the InfoPath form Web part, you can display the latest weather forecast information on your SharePoint portal pages.
Minimal View:
Extended View:
For more information about using REST Web Service data connections with InfoPath forms, see our earlier blog post http://blogs.msdn.com/infopath/archive/2010/02/11/add-a-dynamic-map-to-a-contact-form-using-rest-web-services.aspx.
If you have a “cool” form that you would like to share with us, please send an e-mail with the following details to coolform@microsoft.com -
The most popular submissions will be featured on our blog in future posts.
Check out other Cool Forms here.
With all the wonderful features in the new Office apps, it's easy to get lost! InfoPath is no exception, and when you make clever use of the new features, you'll want to make sure that users understand how your form is supposed to work. Wouldn't it be great if you could display contextual help information as the user navigates to fields in your form. With the "Context Changed" event, you can execute custom code when the user filling the form causes the context node to change. This will fire when the user focuses a control bound to a different DOM node than the current context node.
Files you'll need
The solution we provide here uses some resource files. Download the attached files to accomplish the scenario.
1. "CustomTaskPaneHelp.htm" - The HTML file that will be displayed in the custom task pane. We will manipulate this document at runtime from custom form code to display the contextually appropriate help information. Just update the styles in this file to change the appearance of the contextual help in the custom taskpane.
2. "Help Strings.xml" - This file is a basic xml file that contains mappings from local node names to the title and help contents for the node. This file will be consulted to retrieve the help information to be displayed in the custom task pane.
3. "CustomTaskPane.cs" - This helper class wraps the HtmlTaskPane object to access elements in the custom task pane. This will be added to your VSTA project.
4. "FormCode.cs" - This contains the code to integrate the custom taskpane into the form template.
Note that this method works in InfoPath 2003 and 2007, but this tutorial will walk you through the steps in for InfoPath 2007 - the UI is slightly different in 2003, but you can still make it work. Since task panes are a rich-client only feature, this method is not supported in browser-enabled form templates.
Construct your help resource file
Assuming you've designed your form template already, you'll have a nicely populated data source, replete with variously named nodes. Now add contextual help data for nodes in the main and secondary data sources:
1. Open the "Help Strings.xml" file in notepad (or another text editor.)
2. Add a "Help" element for the data source node:
3. Repeat step 2 for all nodes for which you want to display contextual help so that you have something like this:
<?xml version="1.0" encoding="utf-8" ?><HelpStrings> <HelpString> <Help nodeName="my:TextField"> <Title>Text Field</Title> <Contents>Help for my text field.</Contents> </Help> <Help nodeName="my:RichTextField"> <Title>Rich Text Field</Title> <Contents>Help for my rich text field.</Contents> </Help> </HelpString></HelpStrings>
4. Save the file to a shared location
NOTE: If you want to be able to update this help data without having to re-deploy the form template, the shared location should be accessible to all users that will fill out the form. If, on the other hand, you do not care about that, then you can just save the file locally and add it as a resource file to the form template. See below…
Add the data source that will provide your contextual help data
Now that you're help strings are all set, we need to add a reference to the help strings so that the form template can access them. We'll do this by adding a data connection to query the xml file from a shared location or resource file.
1. Tools >> Data Connections >> Add
2. Select Receive Data, then XML Document
3. Specify the location of the help strings document (Help Strings.xml)
4. Check the "Automatically retrieve data when form is opened" check box and click "Finish" / "Close".
Enable the custom task pane in your form template
Custom task panes are not enabled by default. We'll specify that the custom task pane should be displayed, and its content should be rendered from the "CustomTaskPaneHelp.htm" file.
1. Tools >> Form Options >> Programming
2. Check the "Enable custom task pane" check box. Type a name for the custom task pane in the "Task pane name" field.
3. Add the "CustomTaskPaneHelp.htm" file as a resource file.
4. Select "CustomTaskPaneHelp.htm" from the "Task pane location" drop-down list.
Add the code to manage the context change and display the contextual help information
1. Tools >> Programming >> Context Changed Event; this will create the VSTA project and insert the "Context Changed" event handler.
2. Add the "CustomTaskPane.cs" file to your project
3. Integrate the code in the attached "FormCode.cs" file into your VSTA project. Make sure you copy the contents of the "FormEvents_ContextChanged" method, as well as the private properties and variables.
4. Build and save your form template.
Make the form template full trust
Now, when users fill out your form, contextual help will be displayed for each node as the user clicks on a control bound to it. As long as you've included help information in the help strings file, you'll see the help information in your custom task pane!
Forrest DillawaySoftware Design Engineer in Test
What are Roles? Without going much in detail, InfoPath roles is a functionality where you can define user tags like "Sales", "Management" and utilize them in your form logic. This enables some very interesting scenarios in the forms space. For a detailed discussion on this topic, take a look at this MSDN lab. If you have worked with InfoPath 2003, you would notice that Roles have not changed much in 2007, and that they are not supported by InfoPath Forms Services. My focus will be in how we can enable role-related scenarios in browser forms.
For the purpose of this article, lets borrow the scenario from the lab. - "The management team at Contoso Corporation reviews each of the Sales Report filled out by sales representatives. Because the management team is interested only in sales deals that exceed a certain amount of money, your department is asked to add an additional view to the sales form that shows only those deals"
STEP 1: Find a way to store role-related data
You can store information about which users belong to which grounps in an XML file, included as resource as a part of the XSN. Here's one way to organize this XML file:
<?xml version="1.0" encoding="utf-8"?> <roles> <role> <name>Sales</name> <users> <username>john</username> <username>jane</username> </users> </role> <role> <name>Management</name> <users> <username>bill</username> <username>abby</username> </users> </role> <role> … </role> </roles>
Create a data source pointing to this resource file and use this data source to make decisions in step 2.
Variation: Roles and Users relationship might be stored in a database (say HR system). You would then create a secondary data source connecting to your HR System instead of the XML file mentioned above; everything else stays the same.
STEP 2: Determine the role of the current user
1. Add hidden fields to the main data source of your form. These will store:
2. Set the detault value of CurrentUser to the username() function.
3. Set "CurrentRole" value to
name[username = CurrentUser]
STEP 3: Use rules and CurrentRole
To accomplish our scenario, we will create two views: one for "Sales", and one for "Management". Using Rules, you can switch to the appropriate view on load of the form:
1. With the form template in design mode, on the Tools menu, click Form Options.2. Click the Open and Save tab, and then click Rules, and the following two rules:
And that's it! You can use CurrentRole the same way you'd have used Roles in the InfoPath smart client. Note that this trick works on the smart client, too - so if you want to create a roles-based InfoPath application, this method will help.
Pradeep RasamProgram Manager
If you’re a server administrator for Microsoft Office InfoPath Forms Services 2007, there may be a time when you’re tried to perform some action on a form template and received an error message that looks like the following:
“Form template was deployed as part of the 9b518781-2fcd-40fe-a1f4-964b2cd4c0b8 feature”
This feature name probably doesn’t mean a whole lot to you, and the error message could be a bit more actionable, right? What the heck is this feature thing that the message refers to? You might also have come across other IDs or other weird timing issues on a multiple server farm.
Starting the Tour
So, to shed some light on this, let’s go back-stage to see a little of what’s going on behind the scenes. The stage is Windows SharePoint Servers Central Administration, where you will find 5 options in the Microsoft Office InfoPath Forms Services group on the Application Management landing page. This tour will mainly be concerned with the Upload Form Template page and a little bit with the Manage Form Templates page.
Let’s start at the Upload Form Template page, which is how a form template gets approved by the administrator. Each new form template uploaded to the farm also adds a new FormTemplate object to the FormsService.FormTemplates which is the singleton FormTemplateCollection object for the farm’s administration object model. This FormTemplate object allows the same manipulation that is available through the UI. It also contains a lot of information used only internally to InfoPath Forms Services. The most important internal property is the converted file, which is essentially a compiled version of the form template that can quickly render as a form template in the browser.
When InfoPath form templates are uploaded to a server, the Windows SharePoint Services solution deployment and featurization infrastructures are being used to turn the form template into an administrator-approved form template.. Behind the simple click of the Upload button, InfoPath Forms Services creates a feature to deliver the form template (.xsn file). The feature is then wrapped in a Windows SharePoint Services solutions package which is a .wsp file (just a renamed .cab file) that contains the feature and some other packaging information.
The Solution Package
This solution package is the means of deployment to all of the servers in a farm. All Web front-end servers will have the form template propagated to their file systems, via the solution’s package. So, there may be a delay in showing up on a multiple-server farm, hence the ellipsis in the “Installing…” status which remains until the form template is deployed to all machines in the farm. These are propagated via the SPTimerv3 service which runs on each box and is scheduled to pick up jobs from servers running the central administration Web Application (where the Upload took place) and it uses the SPAdmin service to install the solution on the machine, via the administrator account. So, InfoPath Forms Services deployment needs these services running on each machine, just like WSS solution deployment, because it’s actually the same thing. Once deployment is completed, the form template is marked with “Ready” status.
[Side Note: An implementation just like what we use for solution deployment is also available to you. Our shipping documentation covers how to create features and solutions from scratch. You might want to do this to bulk deploy many form templates at the same time, to indicated InfoPath form templates in other features or solutions, or to create a custom workflow that contains custom InfoPath form templates. These custom-created solutions are still registered into the InfoPath Forms Services OM, but will be treated a little differently in IPFS, for instance, you cannot activate/deactivate/remove/upgrade these form templates through IPFS management, because that might invalidate the overall solutions that were constructed. The whole features must be activated the same way that other custom features are activated (see Ready for Activation, below).]
Ready For Activation
The Ready status indicates that the form template can be used and is in normal operation. The feature created, is a site collection-scoped feature (that’s SPSite-scoped in WSS OM-speak), which can be activated on an site collection like most other site collection-scoped features. If you are a site collection owner, navigate to Site Settings, and see Site Collection Features under the Site Collection group heading. Every form template that was administrator-approved through the upload page is available here along with the other site collection scoped features.
You can also activate and deactivate features from the Manage Form Templates page in Central Administration. This is exactly the same thing, and is provided as a courtesy for farm administrators who are also site collection owners.
Behind the Scenes of Activation and Invocation
An activated form template is added as an item in the Form Templates library at the root site of the site collection (SPSite.rootWeb) in the /formservertemplates path. The form template is not actually present in the content database for the library, it is stored as a ghosted entry. A user invoking the form template with the Microsoft Office InfoPath 2007 rich client is actually pulling the form template from the file system of the Web front-end server where it was placed by solution deployment. Invoking the form template via the browser will pull the converted file from the FormTemplate object in the administration object model.
The Form Templates library is special in a few ways. The most important special aspect is that activated form templates cannot be unghosted. This is to ensure that the converted file version and the on-disk version cannot be different, so that no matter what client you fill the form in, it’s the same form.
Be a Better IPFS Admin with this Knowledge
Our hope is that this article will make our behavior more clear and expose a few tricks to help you be better Administrators. Here are a few tricks in how to use that information to your advantage.
Handle the error: “Form template was deployed as part of the 9b518781-2fcd-40fe-a1f4-964b2cd4c0b8 feature”
If you receive this, you most likely want to remove the form template and start over. Here’s the action to take, and it’s only available from the command line:"%ProgramFiles%\Common Files\Microsoft Shared\web server extensions\12\BIN\STSADM.EXE" -o uninstallfeature -filename 9b518781-2fcd-40fe-a1f4-964b2cd4c0b8\feature.xml
Form template’s status never leaves “Upgrading…” or “Removing…” states
These states seem to indicate that you're running a multiple-machine server farm, and on your server farm, you're running into some issues in propagating changes to all of the machines. If you have not done so already, I highly recommend turning on the following services on each machine: SPAdmin, and SPTimerV3 You can do this by running: net start SPTimerV3 net start SPAdminOn each machine. Net start is a ensure semantic, so this will not inadvertently toggle or cause any damage if run on a machine where the service is already started. Now that that's done, you can go on to correcting the problems that you have.
From Central Administration, go to the Operations page, under the Global configuration group, click on Timer Job Status. On that page, look for timer jobs that have the name in the following formatting. If you filename is FOO.xsn, it will look like:Windows SharePoint Services Solution Deployment for "form-FOO.wsp"
See if there was a failure. If so, go back a page, and go to Timer Job Definitions. Drill down in the timer job definition that you care about and you can perform the following:
>> For the case of status stuck on “Uploading”:1. Try to restart the job if that is available.2. If restart is not available, delete the job, then attempt to upgrade again.
>> For the case of status stuck on "Upgrading":1. Try to restart the job if that is available.2. If restart is not available, delete the job, then attempt to upgrade again.
>> For the case of status stuck on "Removing":1. Try to restart the job if that is available.2. Else, Remove the job. (continue to step 3) 3. Then, go back to the Manage Form Templates and try again to Remove the form template.
One of the problems with InfoPath controls is their limited availability. For example, there is no such control like the NumericUpDown control in WinForms. Or you may have a predefined complex UI, which you want use multiple times.
In these circumstances, what we need is a mechanism for control extensibility. Today, InfoPath provides two different control extensibility mechanism: Template parts and ActiveX controls.
Basically, template parts are predesigned InfoPath form templates, that can be imported into other templates. This mechanism provides a good way of reusing the same controls again and again, but on the dark side, you are still restricted with the control set of InfoPath.
Other mechanism, which is extensibility via ActiveX controls lets you go beyond the control set of InfoPath and use whichever ActiveX control you want. In this blog, we will drill down into using a Windows control in InfoPath forms, and more importantly provide a way of binding data to these controls. This blog will give details of using WinForm controls that are registered for COM Interop, inside InfoPath forms. Two mainly used types for binding ActiveX controls to InfoPath data sources are 'Simple Binding', ‘Stream Binding’ and 'Node Binding'. In this article we will cover node binding. You can find more information about types of binding between ActiveX controls and InfoPath data source here:
http://blogs.msdn.com/ajma/archive/2004/07/07/175568.aspxhttp://blogs.msdn.com/ajma/archive/2004/07/08/177512.aspxhttp://blogs.msdn.com/ajma/archive/2004/07/09/178857.aspx
Simple Binding
If your external control needs to be bound to a single field in InfoPath main data source, then you can use single binding between your ActiveX control and data source. An excellent simple binding example by InfoPath team can be found here: Creating an InfoPath Custom Control using C# and .NET. I highly recommend you to read this article first both to understand how to register our Windows Forms controls for Com Interop, and how simple binding is done for a primitively typed single field. Rest of the article will assume you read and applied the principles told in this article.
Node Binding
When binding to a single field or attribute is not enough, or you need your ActiveX controls to interact with data coming from different fields, node binding is the way to go. A classical example would be an ActiveX control capable of showing an Address type complex structure composed of Street, City, State and ZipCode fields.
In this case, you need an Xml node in InfoPath form template as a data source that obeys the schema of Address object.
InfoPath form
We will design the data source of InfoPath first. Let's just write a schema file that corresponds to the structure of Address class. A simple schema file just including address data would be similar to this:
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <xsd:element name="fields"> <xsd:complexType> <xsd:sequence> <xsd:element ref="address" minOccurs="0"/> </xsd:sequence> </xsd:complexType> </xsd:element> <xsd:element name="address"> <xsd:complexType> <xsd:sequence> <xsd:element ref="street" minOccurs="0"/> <xsd:element ref="city" minOccurs="0"/> <xsd:element ref="state" minOccurs="0"/> <xsd:element ref="zipCode" minOccurs="0"/> </xsd:sequence> </xsd:complexType> </xsd:element> <xsd:element name="street" type="xsd:string"/> <xsd:element name="city" type="xsd:string"/> <xsd:element name="state" type="xsd:string"/> <xsd:element name="zipCode" type="xsd:integer"/> </xsd:schema>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:element name="fields">
<xsd:complexType>
<xsd:sequence>
<xsd:element ref="address" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="address">
<xsd:element ref="street" minOccurs="0"/>
<xsd:element ref="city" minOccurs="0"/>
<xsd:element ref="state" minOccurs="0"/>
<xsd:element ref="zipCode" minOccurs="0"/>
<xsd:element name="street" type="xsd:string"/>
<xsd:element name="city" type="xsd:string"/>
<xsd:element name="state" type="xsd:string"/>
<xsd:element name="zipCode" type="xsd:integer"/>
</xsd:schema>
Start to design a new InfoPath form template from XmlSchema and use this schema as your source. InfoPath should parse the schema, and should create the data source entries in Data Sources tab in design task pane for you. At this point, let's create 4 text boxes that will display each of these fields. For the educational purposes of demo, do not drag and drop the whole address node onto the surface. Instead, drag and drop individual fields on to the design surface. This is because, after designing the ActiveX such that it is bound to whole DOM node, we will get a binding structure like this:
UserControl
Since we finished the main part of the form template, let's start to prepare the ActiveX object that we will use in the template. Assuming you read the article Creating an InfoPath Custom Control using C# and .NET, I will just touch the points not told there.
Design the controlSimply drag and drop four Labels and four TextBoxes on to the user control for each of the fields in Address type.
Register for ComInterop
As told in the article, we will need a Guid attribute for Visual Studio to register the control for Com Interop. What is not told in the article is, we need one more attribute to be able to register the control via Visual Studio: ComVisible. Although, UserControl class is donated with ComVisible(true) attribute in .Net framework, we have to state it explicitly one more time, because ComVisible attribute is created with AttributeUsage(…, Inherited = false), which prevents the value of the attribute to be inherited to children classes. So, our control should look similar to this for a successful registration:
[Guid("08E623D3-BEAD-4bd3-8401-EFF51FD754CD")] [ComVisible(true)] public class ComplexUserControl : UserControl
[Guid("08E623D3-BEAD-4bd3-8401-EFF51FD754CD")]
[ComVisible(true)]
public class ComplexUserControl : UserControl
After building this code, a quick search through registry for Guid, will prove that class is really registered.
InterfacesYou should import the IObjectSafety interface exactly as told in the article. Before discussing IPropertyNotifySink, let's first take a look at the control interface, that will define the Value and Enabled property. In simple binding example, we had a primitive property in ICOMControl interface. Since we are no longer binding to a primitive field, we have to define a new interface.
Let's define an IComplexControl interface similar to IComControl interface in the article. For Value property, we will use IXMLDOMNode type defined in Microsoft.Office.Interop.InfoPath.Xml.dll assembly that you will find in your Office 12 installation directory. This corresponds to a node in InfoPath main DOM data source. At the end, our interface should be similar to this:
[InterfaceType(ComInterfaceType.InterfaceIsDual)] public interface IComplexCOMControl { [DispId(ComplexUserControl.DISPID_VALUE)] IXMLDOMNode Value { get; set; } [DispId(ComplexUserControl.DISPID_ENABLED)] bool Enabled { get; set; } }
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IComplexCOMControl
[DispId(ComplexUserControl.DISPID_VALUE)]
IXMLDOMNode Value { get; set; }
[DispId(ComplexUserControl.DISPID_ENABLED)]
bool Enabled { get; set; }
As stated in the article, Constants.DISPID_VALUE should be 0, and Constants.DISPID_ENABLED should be 1, in order for InfoPath to correctly communicate with our control.
Now let's inspect IPropertyNotifySink interface. One of the main differences between Simple Binding and Node Binding is the type of the Value property. Simple binding deals with primitive types, whereas node binding deals directly with DOM. In simple binding a rough sequence of events would be like this:
Property change in data source --> … -->Set Value property of ActiveX
And the other way around:
Property change in ActiveX --> Raise OnChanged -->InfoPath queries for Value property --> … -->Set Value property of ActiveX
However, since InfoPath sends the whole DOM node as our Value property by reference, we don’t need to set an intermediary Value property. Instead we can directly change DOM values from DOM node reference. So we can safely ignore RaiseOnChanged and InfoPath queries for Value property steps. Since IPropertyNotifySink interface is used mainly for property change notification purposes will not implement it in this demo. If you care about Enabled property changes, you still have to implement this interface and notify InfoPath when Enabled property changes.
Value property implementation
When InfoPath first calls the setter of Value property after instantiating our control, it will pass in the IXMLDOMNode representing the main DOM of InfoPath. We have to keep this node in our control, in order to be able to change the data values corresponding to changes in UI.
In the getter of the property, since we are not changing the reference of DOM node, it is safe to return this kept value.Whenever some field (or subfields in our group field)changes in DOM, InfoPath will call the setter of our Value property. In the setter, we should query the DOM according to XPaths of our fields, and then update the UI according to the values if necessary. At the end, our Value field should look like this:
private IXMLDOMNode domNode = null; public IXMLDOMNode Value { get { return domNode; } set { if (domNode != null) { ChangeUI(streetTbx, domNode.selectSingleNode("/fields/address/street").text); ChangeUI(cityTbx, domNode.selectSingleNode("/fields/address/city").text); ChangeUI(stateTbx, domNode.selectSingleNode("/fields/address/state").text); ChangeUI(zipCodeTbx, domNode.selectSingleNode("/fields/address/zipCode").text); } } } private void ChangeUI(TextBox tbx, string text) { if (tbx.Text != text) { tbx.Text = text; } }
private IXMLDOMNode domNode = null;
public IXMLDOMNode Value
get
return domNode;
set
if (domNode != null)
ChangeUI(streetTbx, domNode.selectSingleNode("/fields/address/street").text);
ChangeUI(cityTbx, domNode.selectSingleNode("/fields/address/city").text);
ChangeUI(stateTbx, domNode.selectSingleNode("/fields/address/state").text);
ChangeUI(zipCodeTbx, domNode.selectSingleNode("/fields/address/zipCode").text);
private void ChangeUI(TextBox tbx, string text)
if (tbx.Text != text)
tbx.Text = text;
At this point we have completed the one direction of binding, originating from data source and going to UI. Now, let's handle the other way around, from UI to data source. In order to do this, we have to create a simple event handler that will catch change events in UI. Then register for all four text boxes' TextChanged events, such that each of them will be handled by this event handler. Code should look like this:
public ComplexUserControl() { this.InitializeComponent(); // Register for UI change events this.streetTbx.TextChanged += this.HandleChangesInUI; this.cityTbx.TextChanged += this.HandleChangesInUI; this.stateTbx.TextChanged += this.HandleChangesInUI; this.zipCodeTbx.TextChanged += this.HandleChangesInUI; } private void HandleChangesInUI(object sender, EventArgs e) { TextBox senderTbx = (TextBox)sender; if (senderTbx == this.streetTbx) this.ChangeDOM("/fields/address/street", this.streetTbx.Text); else if (senderTbx == this.cityTbx) this.ChangeDOM("/fields/address/city", this.cityTbx.Text); else if (senderTbx == this.stateTbx) this.ChangeDOM("/fields/address/state", this.stateTbx.Text); else if (senderTbx == this.zipCodeTbx) this.ChangeDOM("/fields/address/zipCode", this.zipCodeTbx.Text); } private void ChangeDOM(string xPath, string value) { if (domNode != null) { IXMLDOMNode singleNode = domNode.selectSingleNode(xPath); if (singleNode.text != value) { singleNode.text = value; } } }
public ComplexUserControl()
this.InitializeComponent();
// Register for UI change events
this.streetTbx.TextChanged += this.HandleChangesInUI;
this.cityTbx.TextChanged += this.HandleChangesInUI;
this.stateTbx.TextChanged += this.HandleChangesInUI;
this.zipCodeTbx.TextChanged += this.HandleChangesInUI;
private void HandleChangesInUI(object sender, EventArgs e)
TextBox senderTbx = (TextBox)sender;
if (senderTbx == this.streetTbx)
this.ChangeDOM("/fields/address/street", this.streetTbx.Text);
else if (senderTbx == this.cityTbx)
this.ChangeDOM("/fields/address/city", this.cityTbx.Text);
else if (senderTbx == this.stateTbx)
this.ChangeDOM("/fields/address/state", this.stateTbx.Text);
else if (senderTbx == this.zipCodeTbx)
this.ChangeDOM("/fields/address/zipCode", this.zipCodeTbx.Text);
private void ChangeDOM(string xPath, string value)
IXMLDOMNode singleNode = domNode.selectSingleNode(xPath);
if (singleNode.text != value)
singleNode.text = value;
Integration
In order to be able to use the ActiveX Control in the design of a form template, we have to make one final trick: Placing the .ict file that describes the control and its binding, to our controls directory. In order to add custom controls design task pane in InfoPath, generally you first go to the Controls tab, then click Add or Remove Custom Controls at the bottom of this tab, select to add a new ActiveX control, and finally select the ActiveX you want to add from the list of registered controls. If you have ever done this before, there should be a directory named
C:\Documents and Settings\username\Local Settings\Application Data\Microsoft\InfoPath\Controls
In this directory there should be a .ict file for each of the ActiveX controls that you have done this operation for. We have to create a new .ict file for our control. If above directory does not exist, create it. Then create a file with name "{yourClassGUIDHere}.ict". This file should be in this format:
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <ict:control name="Complex C# Control, v1.0" xmlns:ict="http://schemas.microsoft.com/office/infopath/2003/ict" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xdSdl="http://schemas.microsoft.com/office/infopath/2003/xct" xmlns:ict2="ict2" xmlns:xd="http://schemas.microsoft.com/office/infopath/2003"> <ict:designTimeProperties> <ict:iconBitmap>Qk3uAAAAAAAAAHYAAAAoAAAAEAAAAA8AAAABAAQAAAAAAAAAAADEDgAAxA4AABAAAAAQAAAAAAAA/wAAgP8AgAD/AICA/4AAAP+AAID/gIAA/4CAgP/AwMD/AAD//wD/AP8A/////wAA//8A/////wD//////4iIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiP//////////eIiIiIiIiI94RIRIRIiIj3hEhEhEiIiPeESESESIiI94RIRIRIiIj3iIiIiIiIiPd3d3d3d3d3eIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiA==</ict:iconBitmap> </ict:designTimeProperties> <ict:deployment> <ict:controlPackage classid="{yourClassGUIDHere}"/> </ict:deployment> <ict:shapes> <!--default shape--> </ict:shapes> <ict:schemaEditing> <ict:any/> </ict:schemaEditing> <ict:view> <ict:controlObject bindingProperty="Value" bindingType="xmlNode" enabledProperty="Enabled" enabledValue="true"/> </ict:view> </ict:control>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<ict:control name="Complex C# Control, v1.0" xmlns:ict="http://schemas.microsoft.com/office/infopath/2003/ict" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xdSdl="http://schemas.microsoft.com/office/infopath/2003/xct" xmlns:ict2="ict2" xmlns:xd="http://schemas.microsoft.com/office/infopath/2003">
<ict:designTimeProperties>
<ict:iconBitmap>Qk3uAAAAAAAAAHYAAAAoAAAAEAAAAA8AAAABAAQAAAAAAAAAAADEDgAAxA4AABAAAAAQAAAAAAAA/wAAgP8AgAD/AICA/4AAAP+AAID/gIAA/4CAgP/AwMD/AAD//wD/AP8A/////wAA//8A/////wD//////4iIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiP//////////eIiIiIiIiI94RIRIRIiIj3hEhEhEiIiPeESESESIiI94RIRIRIiIj3iIiIiIiIiPd3d3d3d3d3eIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiA==</ict:iconBitmap>
</ict:designTimeProperties>
<ict:deployment>
<ict:controlPackage classid="{yourClassGUIDHere}"/>
</ict:deployment>
<ict:shapes>
<!--default shape-->
</ict:shapes>
<ict:schemaEditing>
<ict:any/>
</ict:schemaEditing>
<ict:view>
<ict:controlObject bindingProperty="Value" bindingType="xmlNode" enabledProperty="Enabled" enabledValue="true"/>
</ict:view>
</ict:control>
Note the bolded text. You should place the correct GUID for your control, and note that we are actually specifying 'xmlNode' as bindingType. Rest of the file is almost standard except the iconBitmap part, which specifies the icon that will be displayed next to your control in design task pane.
Since we have a ready control now, we can safely use it in our form template. Open the InfoPath form you have designed in design mode again. Go to Controls tab in design task pane. At the bottom, under Custom section, you should see the 'Complex C# Control, v1.0' with icon I have copied from Microsoft Forms Progress Bar control. Drag and drop this control to design surface. According to your security settings, it may ask you if you want to run ActiveX control. If so, press 'Yes' and continue. Since we have already defined a data source in the form, InfoPath will ask which field or group do we want our control to bind to. Choose address here. Now you should see the control on the surface, and its binding field is specified as /fields/address. Press Ctrl+Shift+B to preview the form. You should see the corresponding InfoPath TextBoxes and our UserControl's TextBoxes are synchronized, because they are bound to same data source fields.
Now we have a functional complex WinForm UserControl, that we can display in an InfoPath form template, and even better, this UserControl is bound to InfoPath main DOM.
Improving ActiveX Discrepancies
If you played with the form a few minutes, you must have noticed that there is something weird going on. If you are changing the text value of InfoPath TextBoxes, you see a flicker in the UserControl before data is reflected to it. Similarly, if you change the value of a TextBox in your UserControl, you also see a flicker in the UserControl, and focus is continuously given to first TextBox added to UserControl.
Main reason for this issue is, whenever a data field that is bound to your ActiveX is changed, InfoPath destroys your control and create a new one. This is mainly because InfoPath has to apply your view xsl to your data source to create the view you see on the display. Every time data source changes, InfoPath re-applies the xsl file and creates the view from scratch for controls that are bound to changed data fields. If you are debugging the form, you can easily see this by putting a breakpoint in the constructor of UserControl. Clearly, this leads us to write stateless controls, since each time the controls are destroyed, state is destroyed too. Or, as a second approach, we can take advantage of a helper interface, InfoPathControl interface, to save the state to some persistent storage just before destruction, and retrieve it after re-creation. Let's inspect how this approach works.
InfoPathControl interface is defined in Microsoft.Office.Interop.InfoPath primary interop assembly. Add a reference to this assembly to your project and implement the interface on your UserControl. I advise you to read the explanation of all four methods of the interface, and how they are used in MSDN. Here, we will only concentrate on two of them, which are Init and SaveState methods.
SaveState is called by InfoPath, just before the destruction of the older control, which makes it a perfect point to save the state to some storage. Init is called after creating the new control, which lets us to use it to retrieve the saved state and apply it to our control.
Init method takes a parameter of type InfoPathControlSite. This interface gives us the main DOM node and XDocument of InfoPath form template, which basically enables us to access pretty much all of the object model of InfoPath. So, we don't have to wait for the setter of Value property anymore. We can cache the domNode here in Init method. We will also cache the XDocument for storing the state in, since we will need a persistent storage for our state while we are destroyed. In general, Init method body should be similar to this:
private XDocumentClass xDocument = null; public void Init(InfoPathControlSite pControlSite) { this.domNode = pControlSite.Node; this.xDocument = (XDocumentClass)pControlSite.XDocument; this.RestoreState(); }
private XDocumentClass xDocument = null;
public void Init(InfoPathControlSite pControlSite)
this.domNode = pControlSite.Node;
this.xDocument = (XDocumentClass)pControlSite.XDocument;
this.RestoreState();
We will inspect RestoreState method after SaveState method.
For simplicity, we will assume that we only want to keep which control has the focus in UserControl and give focus to that control after re-creation. Saving the name of the active control should be enough for state restoration. In reality in a control like this we should also keep the location of the cursor inside the TextBox as well as which TextBox is active.
We said that we will use XDocument to save the state. This is because XDocument lives with the form template, so it is persistent during the lifetime of our control, and XDocument has a property bag that we can use for this purpose. We can update this property bag by calling SetNamedNodeProperty and querying it by calling GetNamedNodeProperty method on XDocument object. At the end, SaveState and RestoreState methods should be similar to this:
public void SaveState() { if (this.xDocument != null) { this.xDocument.SetNamedNodeProperty(this.domNode, "ActiveControl", this.ActiveControl.Name); } } private void RestoreState() { if (this.xDocument != null) { Control activeControl = this.Controls[this.xDocument.GetNamedNodeProperty(domNode, "ActiveControl", this.Controls[0].Name)]; if (activeControl != null) { activeControl.Focus(); } } }
public void SaveState()
if (this.xDocument != null)
this.xDocument.SetNamedNodeProperty(this.domNode, "ActiveControl", this.ActiveControl.Name);
private void RestoreState()
Control activeControl = this.Controls[this.xDocument.GetNamedNodeProperty(domNode, "ActiveControl", this.Controls[0].Name)];
if (activeControl != null)
activeControl.Focus();
In the call to GetNamedNodeProperty in RestoreState method, third parameter is the default value, which will be returned back if specified property name can't be found in property bag. This will be the case when control is first created.
Now, each time data source has changed, we save which control was active in UserControl before it is destroyed. Then InfoPath instantiates a new instance, we restore the state if there is any. Then InfoPath sets the Value property with updated fields.
Saving the whole state in a WinForm control is a non-trivial task and maybe somewhat cumbersome. I'm optimistic about this re-creation of ActiveX controls will be optimized by InfoPath team in the next release.
At the end, our code will look like this attached file.
Deniz DemirciogluSoftware Design Engineer
Folks frequently ask whether it is possible to customize the InfoPath user interface around digital signatures. Some want to show the signature at the very bottom of the form; others want to show signatures side-by-side; others want to disable form submissions when the document wasn't digitally signed. In this article, we'll look at different ways you can tweak the form design to make it happen.
Trick 1: Display signature at the bottom of the view
InfoPath lets you show digital signature UI ("click here to sign this form") under a signable section; however, this section doesn't have to include any controls! This means that you can have your signable section with controls at the top of the form, some extra content in the middle, and then another section bound to the same nodes in the data source without any controls in it.
Your design view would look like this:
Note how the signableGroup here is multiply bound; the first section has the "allow users to digitally sign this section" checkbox unchecked:
The second one has it checked, which makes the "click here to sign this form" show up; this is how the end result looks at edit-time:
Trick 2: Disallow Submit if form was not signed
InfoPath digital signatures are appended to form XML, just like form data. For example, in the form above, nodes under signature1 will store the digital signature when the user adds it:
Using this fact, we can enforce business rules in our form: for example, what if we don't want to allow form submissions for cases when form is not signed? Let's go to Tools | Submit Options and create two rules:
1) Show must-sign warning:
- condition: signatures2 node is blank (this will evaluate to true when no signature was added)
- action: show a dialog box message "you must sign the form before submitting it"
- check "stop processing rules when this rule finishes"
2) Submit to main data source:
- condition: always applies (unless the first rule fired - we wouldn't get to this execution point then)
- actions: submit to main data source + show dialog box message "submission was successful"
Trick 3: Show signatures side-by-side
Challenge: make a form that has signatures side-by-side at the bottom; additionally, person 3 should only be able to add their signature if previous signer (person 2) already signed the document.
We already know how to make the signatures appear at the bottom of view (trick 1); we also know how to determine if a signature was added to the document (trick 2). Let's put all of these tricks together into one powerful solution:
- Create 3 different signed data blocks, one for each person that will be signing the form. Second data block needs to signing a superset of data that the first one signed, etc. Your data source task pane will look like this:
- Place empty sections bound to the items that you want to sign in the columns of that table. - Use containing conditionally-formatted sections to show the signing UI only when necessary.
The resulting layout will look like this:
I'm attaching a sample form template that has this trick implemented (works in InfoPath 2003 or 2007; save the XSN to your computer before opening it).
The SP1 update of InfoPath 2003 added calculation support – the value of a node can be set to the result of an XPath expression. This makes it possible to avoid writing code (script or managed) in many InfoPath forms. Date calculations, however, still require knuckling down and writing old fashioned procedural code.
InfoPath stores dates according to the W3C XML Schema standard, which in turn uses a variant of ISO 8601 dates. The XPath expression language, however, has no special support for date types – just strings, numbers, Booleans, node-sets and fragments. This means that while you can manipulate dates as strings – you can’t do calculations with them.
Before we dive into some sample code, though, a few notes:
You can do date comparisons with XPath! The date format is “yyyy-mm-dd” – always 4-2-2 – which means you can do lexical (“string”) comparisons on two dates and determine ordering and equality.
As a guiding rule, you should be as paranoid with date calculations as you are with financial calculations. Identify and test your edge cases thoroughly, and make sure your code matches cultural interpretations, not code convenience. For example, if you compare two dates the context and desired result matters. The relationship between a duration in days and an age in years is not simply 1/365 (or 1/365.25, or … ) – the convention for age in most cultures is “has the person had a birthday yet?” so you’d better make sure the code matches. Who wants to miss their birthday?
A good rule of software development is that if you have to think too much about a problem you’re writing too much code, and the more code you write the more likely you are to have bugs. So the moral of this story is: make someone else do all the work.
The general pattern for dealing with date calculations in InfoPath is to use an existing library. The two handy libraries for this are the Windows Scripting engine and the .NET Framework. Since we have a lot of script examples on this blog let’s use .NET this time.
The .NET Framework has a DateTime struct type and if you look in MSDN you’ll find it has plenty of methods and operator overloads for doing calculations such as adding days and computing TimeSpans. Looks good – I bet the .NET people know what they’re doing.
So basically we just want to convert an XML date into a DateTime, do some stuff with it, then convert back.
Here are the functions you need:
private static DateTime Iso8601ToDate( string iso8601Date )
if( iso8601Date == null )
throw new ArgumentNullException( "iso8601Date" );
return DateTime.ParseExact( iso8601Date, "yyyy-MM-dd", null );
private static string DateToIso8601( DateTime dateTime )
return dateTime.ToString( "yyyy-MM-dd" );
Wow – after that preamble I bet that was a bit of a let down!
Now let’s use it. I built a simple calculation form that looks like this:
The button handlers look like this:
[InfoPathEventHandler(MatchPath="date1_add1", EventType=InfoPathEventType.OnClick)]
public void date1_add1_OnClick(DocActionEvent e)
IXMLDOMNode dateNode = thisXDocument.DOM.selectSingleNode( "/my:myFields/my:Date1" );
try
DateTime dt = Iso8601ToDate( dateNode.text );
dt = dt.AddDays( 1 );
dateNode.text = DateToIso8601( dt );
catch( FormatException ) {}
Then I added OnAfterChange handlers for the date fields which call a sync method:
[InfoPathEventHandler(MatchPath="/my:myFields/my:Date2", EventType=InfoPathEventType.OnAfterChange)]
public void Date2_OnAfterChange(DataDOMEvent e)
if (e.IsUndoRedo)
return;
SyncDifference();
private void SyncDifference()
IXMLDOMNode date1Node = thisXDocument.DOM.selectSingleNode( "/my:myFields/my:Date1" );
IXMLDOMNode date2Node = thisXDocument.DOM.selectSingleNode( "/my:myFields/my:Date2" );
IXMLDOMNode diffNode = thisXDocument.DOM.selectSingleNode( "/my:myFields/my:Difference" );
IXMLDOMNode ageNode = thisXDocument.DOM.selectSingleNode( "/my:myFields/my:Age" );
if( date1Node != null && date2Node != null && diffNode != null )
DateTime dt1 = Iso8601ToDate( date1Node.text );
DateTime dt2 = Iso8601ToDate( date2Node.text );
TimeSpan ts = dt2 - dt1;
diffNode.text = ts.Days.ToString();
You might notice that this is computing the difference in days. The TimeSpan structure represents an interval of time, and days are the maximum granularity that a pure duration can have – month and year durations require a fixed point in time to calculate from; even weeks can be ambiguous – is that whole weeks or calendar-weeks-spanned? And whose calendar in the first place?
So how do you go from two DateTime structures to an age? The old fashioned way – “have I had a birthday yet this year?”
int ageInYears = dt2.Year - dt1.Year;
if( ( dt2.Month < dt1.Month ) ||
( dt2.Month == dt1.Month && dt2.Day < dt1.Day ) )
ageInYears--;
ageNode.text = ageInYears.ToString();
Time calculations are even more fun. No-one mention leap seconds and we’ll get by just fine.
(Update 1/25/05 @ 11:30 AM PST - having a problem uploading the screenshot to our images site. We'll fix that ASAP.)
(Update 1/25/05 @ 11:50 AM PST - Fixed!)