Microsoft InfoPath 2010
The official blog of the Microsoft InfoPath team

February, 2007

  • Microsoft InfoPath 2010

    Using the Contact Selector Control

    • 398 Comments

    (This post applies to InfoPath 2007. If you're using InfoPath 2010, then you should check out this post instead.)

    We have seen a number of requests on how to allow a user to enter (or select) a person’s name or logon alias and then validate that user against their Active Directory without using code. This has been especially true in workflow scenarios where User A needs to enter in the name of User B – the next person in the workflow process.

    Well, InfoPath 2007 ships with a control called Contact Selector Control that will resolve these issues! You may have seen our older article on the subject; this one aims to dive in deeper.

    The Contact Selector control is an ActiveX control but it is a special cased control, in that it can also be used in InfoPath browser forms. To use this control there are specific steps that need to be taken – let’s take a look at those now.



    Step 1: Add the Contact Selector control to your Controls Task Pane

    1) From the Controls Task Pane click the Add or Remove Custom Controls link

    2) Click the Add button

    3) On the first screen of the Add Custom Control Wizard select ActiveX control and click Next

    4) From the list of controls, choose Contact Selector and click Next

    5) Select “Don’t include a .cab file” and click Next

    6) For Binding Property select Value and click Next

    7) From the Field or group type box choose Field or group (any data type) and click Finish

    8) Click Close and then click OK

     

    Step 2: Create the data structure for the Contact Selector Control

    The Contact Selector control needs to have a specific data structure to work properly – this is documented on the “Items” tab of the Properties screen for the control; however, we’ll include that information here as well.

    **IMPORTANT!** Spelling and capitalization must be exactly the same, starting with the “Person” group!

    1) Add a non-Repeating Group named: gpContactSelector

    2) Add a Repeating Group named: Person

    3) Add the following 3 text fields to the Person group: DisplayName, AccountId and AccountType



    Step 3: Add and bind the Contact Selector control to the View

    1) Drag the gpContactSelector Group to the View and select “Contact Selector” from the list of controls

    2) You’re almost done…! :-)



    Step 4: Add a secondary data source XML file which specifies the SharePoint server

    The Contact Selector control needs to know the “context” of where the user validation should occur. These steps are not necessary if you are only displaying the form in a browser from SharePoint – in this case, it uses the context of the site from where it was provisioned; however, if you are in a mixed client/browser scenario you will need to include this XML file so forms opened in the client can use this functionality.

    1) Launch Notepad

    2) Copy and paste this one-line XML:

    <Context siteUrl="http://<servername>"/>

    **NOTE: Replace <servername> with the name of your server

    3) Save this as: Context.xml (again – naming and capitalization are important)

    4) Add Context.xml as a “Receive” type Secondary Data Connection to your form template and make sure the option “Include the data as a resource file” is enabled



    Step 5: Test!

    You should now be able to Preview the form, enter a name or logon alias, click the “Check Names” button and resolve the name! Alternatively you could click the “To” button to perform a Search if you do not know the complete name of the user.

    One other important point: if this control is something you will use numerous times, this process works great to create a “Contact Selector Template Part” – then you only have to complete these steps one time!

    Scott Heim
    Support Engineer

  • Microsoft InfoPath 2010

    Passing Data into a Form: Input Parameters

    • 29 Comments

    This blog article discusses a new feature of Microsoft InfoPath 2007 that makes it possible to pass data into an InfoPath form at load time. A typical example would be retrieving records from a database for a particular user. At load time a ‘userID’ can be passed into the form. This userID can then be used to query the database and load the form with the user's data.

    Parameters can be passed into InfoPath form templates (XSNs) or InfoPath Forms (XMLs). The syntax for specifying input parameters is the same for both. This article focuses primarily on InfoPath client scenarios, but should apply for the most part to server scenarios as well.

    How to Pass Parameters into an InfoPath Form:

    There are two ways of launching an InfoPath form with parameters

    1) URL

    The syntax for passing parameters via the URL is the standard syntax for query parameters. For example:

    http://www.foo.com/bar.xsn?baz=1&biz=2

    Here two input parameters have been passed into the form namely 'baz' and 'biz'. Their respective values are 1 and 2. The 'Accessing Input Parameters in Form Code' section talks about how these values are stored and accessed in InfoPath code.

    The URL syntax can be used in a number of places like

    • Launching an InfoPath form in a browser by typing the URL into the address bar
    • Pasting the URL to a form or a form template into an email
    • Using the URL inside methods like NewFromFormTemplate, New, Open (XmlForms collection)
    2) Command Line

    The syntax for passing parameters via the command line is as follows:

    infopath.exe “C:\foo\bar.xml” /InputParameters "baz=1&biz=2"

    The switch /InputParameters is used to specify that parameters are being passed into the form, followed by the name/value pairs of input parameters.

    Accessing Input Parameters in Form Code

    Parameters passed into an InfoPath form template or form, are available during the Loading event as a read-only collection of name/value pairs. In order to access these parameters you will need to write code that reads from this collection. The InputParameters collection is exposed in all three InfoPath programming models – it is thus available in JScript, InfoPath 2003 SP1 managed code or InfoPath 2007 managed code. This example uses C# and the InfoPath 2007 managed object model. Syntax for the legacy models follows. The steps below outline how to add code that access the Input Parameters passed into a form.

    1. In the InfoPath designer, click on Tools menu -> Programming menu item.
    2. In the fly-out menu select the Loading event (will be On Load in older InfoPath versions).
    3. This will launch the appropriate IDE (MSE or VSTA or the Visual Studio Toolkit) with the code spit for the loading event inserted.
    4. Add the following code to access the input parameters ‘baz and ‘biz used in the examples above (example is in C# using InfoPath 2007 Managed Object Model)

    public void FormEvents_Loading(object sender, LoadingEventArgs e)

    {

       // Assign the value of the parameter 'baz' to the string 'bazValue'. bazValue = 1

       string bazValue = e.InputParameters["baz"];

     

       // Assign the value of the parameter 'biz' to the string 'bizValue'. bi000zValue = 1

       string bizValue = e.InputParameters["biz"];

     

       // Code that uses the parameters passed in to do whatever needs to be done

       // Example would be to create a custom query using these values and populate a table based

       // on the data returned
    }

    Input Parameter Syntax for Legacy Object models

    The following two code samples contain code for the InfoPath 2003 SP1 Managed Object Model (C#) and Jscript (InfoPath 2003 Object Model)

    1) C# InfoPath 2003 SP1

    In the InfoPath 2003 SP1 Object Model the InputParameters collection is exposed off the XDocument object not off the eventArgs object. Also since this is a new feature the XDocument object needs to be cast to the newer _XDocument3 interface to get access to the InputParameters collection. Hence the first line of code in the sample below.

     [InfoPathEventHandler(EventType = InfoPathEventType.OnLoad)]

     public void FormEvents_OnLoad(DocReturnEvent e)

     {
        
    // Cast XDocument to _XDocument3 to get access to the InputParameters collection
        
    _XDocument3 infopath2007XDocument = (_XDocument3)e.XDocument;

         string bazValue = infopath2007XDocument.InputParameters["baz"].Value;

         string bizValue = infopath2007XDocument.InputParameters["biz"].Value;

     }

    2) JScript

    function XDocument::OnLoad(eventObj)
    {
          var bazValue = eventObj.XDocument.InputParameters["baz"].Value;
          eventObj.XDocument.UI.Alert(bazValue);
    }

    Help

    Comprehensive documentation and additional code samples for this feature can be found under MSE Help or VSTA Help.

    Aditi Desai
    Software Design Engineer in Test

  • Microsoft InfoPath 2010

    New Resources on Office Online

    • 1 Comments

    Our colleagues at Office Online released several interesting InfoPath topics:

    1. Form Data Aggregation and Merging:
      - Design a form for merging 
      - Merge actions for fields and groups
      - Merge InfoPath e-mail form data in Outlook

    2. Document Information Panel:
      - Introduction to designing a Document Information Panel by using InfoPath
      - Design a Document Information Panel by using InfoPath

    3. Workflow:
      - Introduction to using workflows with InfoPath forms
      - Design a form to respond to a workflow status

    Alex

  • Microsoft InfoPath 2010

    Calculating Elapsed Time…without code!

    • 59 Comments

    UPDATE: Due to the number of requests for adding more columns to this sample, I have re-designed the sample. If I have time in the future I will update the steps to create this new design; however, for now you can download the updated design here to see the changes. The expressions are very similar to the original design but I now use a repeating table for entering "break" times so you can have as many as you need! 

    How many times have you needed to calculate the difference between time entries…only to find out the only way to accomplish this was to write custom code? Well no more! If your time entries will not cross days, then you can use a number of functions and expressions to parse the entered times and calculate the difference. In the sample below we will show you the types of expressions that are needed for these calculations to work – I would encourage you to first create this sample as documented as the expressions are not for the “faint of heart!” :-)

    Note: if you choose to copy/paste the expressions for this sample, you will use the Default Value box for each field and after clicking the “fx” button, be sure to also enable the “Exit XPath” option before you paste these expressions.

    If you'd like to take a look at the completed version of this sample, here is the .xsn file - make sure to save it locally before opening it.

    Create the data structure

    1) Add a Repeating Table to your View with 4 columns

    2) Rename the fields and specify the data types as follows, from left to right:

    Name Data Type
    myDate Date (date), Format: *3/14/2001
    StartTime Time (time), Format: *9:46 AM
    EndTime Time (time), Format: *9:46 AM
    ActualTime Text (string)

    3) Change the first field in the table to a Date Picker control

     
    4)  Add 2 additional fields to the Repeating Group node (these will not be shown on the view – they are used to aid in the calculations)

    Name Data Type
    ElapsedTime Text (string)
    TotalMinutes Decimal (double)


    5) Add one final field to calculate the sum of all the entries – this should be added to the myFields node…not the Repeating node

    Name Data Type
    TotalTime Text (string)

    Your final data structure should look like this:

    And your form could look like this:

     

    Adding the expressions…let the fun begin!

    The first step is to convert the StartTime and EndTime values to minutes – this makes the calculation a bit easier. In addition, we don’t want to execute this calculation if either the StartTime or EndTime is blank. So to perform this “conditional statement”, we’ll use the logic explained in this blog entry.

    Step 1: Parse the Hours and Minutes

    1) We first need to parse the “hours” and convert this to minutes by multiplying that value by 60. To do this we will use the “substring-before” function to look for the colon (“:”) in the time field and extract the value before the colon:

    substring-before(../my:EndTime, ":") * 60)
    substring-before(../my:StartTime, ":") * 60

    2) To each of these values, we need to add the minutes that were entered after the colon. Now when time values are stored in the underlying XML, they are stored in this manner: hh:mm:ss. So to pull just the minutes entered, we will use a combination of the “substring-before” and “substring-after” functions since we need the value entered “after” the first colon and “before” the last colon:

    substring-before(substring-after(../my:EndTime, ":"), ":")
    substring-before(substring-after(../my:StartTime, ":"), ":")

    3) Now, at this point we could place each of these expressions together to get the total hours and minutes for each time entry:

    ((substring-before(../my:EndTime, ":") * 60) + substring-before(substring-after(../my:EndTime, ":"), ":"))
    ((substring-before(../my:StartTime, ":") * 60) + substring-before(substring-after(../my:StartTime, ":"), ":"))

    4) Keep in mind, we don’t want this expression to execute if either the StartTime or EndTime fields are empty and we also need to subtract the StartTime total minutes from the EndTime total minutes. So to do this we will use the “substring” function in conjunction with a “condition.” The substring function has the following signature:

    substring([String Value], [Starting Position], [Condition/Length])

    To incorporate our expressions into the substring function, it would look like this:

    Substring([EndTime – StartTime], 1, EndTime != “” and StartTime != “”)

    EndTime expression:

    ((substring-before(../my:EndTime, ":") * 60) + substring-before(substring-after(../my:EndTime, ":"), ":"))

    StartTime expression:

    ((substring-before(../my:StartTime, ":") * 60) + substring-before(substring-after(../my:StartTime, ":"), ":"))

    Starting position: 1

    Condition expression (that if true, evaluates to the length of what we want to return so we use the “string-length” function to get the length of our initial expression):

    (../my:StartTime != "" and ../my:EndTime != "") * string-length(((substring-before(../my:EndTime, ":") * 60) + substring-before(substring-after(../my:EndTime, ":"), ":")) - ((substring-before(../my:StartTime, ":") * 60) + substring-before(substring-after(../my:StartTime, ":"), ":")))

    So our final expression for the TotalMinutes field would look like this:

    substring(((substring-before(../my:EndTime, ":") * 60) + substring-before(substring-after(../my:EndTime, ":"), ":")) - ((substring-before(../my:StartTime, ":") * 60) + substring-before(substring-after(../my:StartTime, ":"), ":")), 1, (../my:StartTime != "" and ../my:EndTime != "") * string-length(((substring-before(../my:EndTime, ":") * 60) + substring-before(substring-after(../my:EndTime, ":"), ":")) - ((substring-before(../my:StartTime, ":") * 60) + substring-before(substring-after(../my:StartTime, ":"), ":"))))

     

     

    Step 2: Convert resulting Total Minutes to hours and minutes

    This expression will use the same basic logic we used above: first divide the resulting total minutes field by 60 to see how many hours have elapsed. If the result of that division contains a decimal point (i.e. 90 minutes / 60 = 1.5) then parse the value before the decimal point (for the hours) and then use the “mod” function to return the balance of the minutes.

    1) We need to divide the TotalMinutes field by 60 to get how many hours have elapsed; however, we also need to check if the resulting value contains the decimal point. So the first expression will use the “substring” function so we can incorporate the conditional test. For the conditional test, we will use the “contains” function to see if the result contains the decimal point:

    contains(../my:TotalMinutes div 60, ".")

    So our initial expression would be as follows: this incorporates the “concat” function so if the resulting value contains a decimal point, we will parse that value, concatenate a colon and the concatenate the result of TotalMinutes mod 60:

    substring(concat(substring-before(../my:TotalMinutes div 60, "."), ":", ../my:TotalMinutes mod 60), 1, contains(../my:TotalMinutes div 60, ".") * string-length(concat(substring-before(../my:TotalMinutes div 60, "."), ":", ../my:TotalMinutes mod 60)))

    2) If the resulting expression does not contain a decimal point, then we will simply concatenate a colon with the results of TotalMinutes mod 60:

    substring(concat(../my:TotalMinutes div 60, ":", ../my:TotalMinutes mod 60), 1, not(contains(../my:TotalMinutes div 60, ".")) * string-length(concat(../my:TotalMinutes div 60, ":", ../my:TotalMinutes mod 60)))

    3) So our final expression for the ElapsedTime field would be as follows:

    concat(substring(concat(substring-before(../my:TotalMinutes div 60, "."), ":", ../my:TotalMinutes mod 60), 1, contains(../my:TotalMinutes div 60, ".") * string-length(concat(substring-before(../my:TotalMinutes div 60, "."), ":", ../my:TotalMinutes mod 60))), substring(concat(../my:TotalMinutes div 60, ":", ../my:TotalMinutes mod 60), 1, not(contains(../my:TotalMinutes div 60, ".")) * string-length(concat(../my:TotalMinutes div 60, ":", ../my:TotalMinutes mod 60))))

     

     

    Step 3: Final Formatting - the ActualTime field

    The way the expression is written for the ElapsedTime field, if the resulting minutes is less than 10, only a single value will be returned (i.e. 0:9). Because of this, we want to test for this condition and if the minutes are less than 10, then concatenate a zero (“0”) before that value so the time appears correct. Now this expression could possibly have been incorporated into the ElapsedTime expression but for ease and clarity, I separated these two steps into different fields.

    This expression uses the “string-length” function to determine the length of the string after the colon. If the length is 2 (minutes are greater than 9) then simply concatenate the hours, a colon and the minutes. However, if the length is 1 (minutes are less than 10) then concatenate the hours, a colon with a zero (“:0”) and the minutes. Like before, we will use the “substring” function with a condition statement to determine what to return:

    concat(substring(concat(substring-before(../my:ElapsedTime, ":"), ":", substring-after(../my:ElapsedTime, ":")), 1, (string-length(substring-after(../my:ElapsedTime, ":")) = 2) * string-length(concat(substring-before(../my:ElapsedTime, ":"), ":", substring-after(../my:ElapsedTime, ":")))), substring(concat(substring-before(../my:ElapsedTime, ":"), ":0", substring-after(../my:ElapsedTime, ":")), 1, (string-length(substring-after(../my:ElapsedTime, ":")) = 1) * string-length(concat(substring-before(../my:ElapsedTime, ":"), ":0", substring-after(../my:ElapsedTime, ":")))))

    Step 4: Create the TotalTime expression for keeping a running total of the elapsed time

    The last step is to create the TotalTime expression – this is similar to the ActualTime expression except we now incorporate the “sum” function to get the running total:

    concat(substring(concat((sum(../my:group1/my:group2/my:TotalMinutes) - sum(../my:group1/my:group2/my:TotalMinutes) mod 60) div 60, ":", sum(../my:group1/my:group2/my:TotalMinutes) mod 60), 1, (sum(../my:group1/my:group2/my:TotalMinutes) mod 60 > 9) * string-length(concat((sum(../my:group1/my:group2/my:TotalMinutes) - sum(../my:group1/my:group2/my:TotalMinutes) mod 60) div 60, ":", sum(../my:group1/my:group2/my:TotalMinutes) mod 60))), substring(concat((sum(../my:group1/my:group2/my:TotalMinutes) - sum(../my:group1/my:group2/my:TotalMinutes) mod 60) div 60, ":0", sum(../my:group1/my:group2/my:TotalMinutes) mod 60), 1, (sum(../my:group1/my:group2/my:TotalMinutes) mod 60 < 10) * string-length(concat((sum(../my:group1/my:group2/my:TotalMinutes) - sum(../my:group1/my:group2/my:TotalMinutes) mod 60) div 60, ":0", sum(../my:group1/my:group2/my:TotalMinutes) mod 60))))

    Step 5: Test!
    Enter a Start and End Time in the form of: h:mm AM(PM), such as: 8:00 AM and 5:00 PM – the ActualTime field should display the difference (as 9:00) and if you continue adding rows, the TotalTime field should reflect the running total.

    This functionality allows you to create elapsed time scenarios that work in both client and browser forms without writing one line of code!

    Scott Heim
    Support Engineer

  • Microsoft InfoPath 2010

    Calculating new, default date and time values

    • 2 Comments

    Similar to performing elapsed time calculations, creating a new date or time value based on a previous value used to require code – not anymore! With InfoPath 2007 you can use the new “addDays()” and “addSeconds()” functions to calculate a new date or time based on a previous value.

    To illustrate this, think of a daily task planner: you enter a start time for the first task and specify a duration time and interval. The starting value for the next task should be the prior tasks’ starting time plus the prior tasks’ duration. Using the addDays and addSeconds functions will allow you to specify the new starting date and time based on a duration specified in days, hours, minutes or seconds without writing any code!

    The following steps will create a sample Daily Task Planner to demonstrate how to implement these functions. A sample form template that implements this functionality is attached; make sure to save it locally before running it.

    1. Create the data structure

    a. Create a new browser-compatible form template
    b. Add a repeating group named: gpTasks to the myFields collection
    c. Add the following nodes to gpTasks:

    Name Data Type
    StartDateTime Date and Time (dateTime)
    TaskDesc Text (string)
    Duration Decimal (double)
    Interval Text (string)

    d. Add the following node to the myFields collection:

    Name Data Type
    NextStartTimeValue Date and Time (dateTime)

    Note: The “NextStartTimeValue” field is needed because using XPATH functions such as “preceding-sibling”, which could be used to get the prior elements values, are not supported in browser forms.

     

    2. Create the View

    a. Add gpTasks to the View as a Repeating Table
    b. Change the Interval Text Box to a Dropdown List Box
    c. Add the following entries to the Interval Dropdown list box:
            Days
            Hours
            Minutes
    d. Add a new column to the right of the StartDateTime field and re-bind the newly created field to StartDateTime (this field will be used to display just the time value)
    e. Change the format of this new field to: *9:46 AM

     

    3. Add the expressions

    a. Set a default value for the StartDateTime field to the current date and time when the form is first launched (note - we don’t want the current date and time to be entered once we have rows of data:)
          Add a Rule to the Open event of the form
          Add the following Conditions to the Rule:
              - The “count” of gpTasks = 1 (i.e. count(my:gpTasks) = 1)
              - The NextStartTimeValue field is blank
          Add an Action to set the StartDateTime field to the “now()” function

    b. Add a “Set a field’s value” Rule to the Interval field to set the NextStartTimeValue field, with a condition that the rule should only fire if the Duration field is not blank. In the Value property for this Rule, we need to check the Duration and what Interval was selected so we can populate the NextStartTimeValue field with the correct date and time. Let’s take a look at how these expressions are created. (This information is based on using the advanced default value logic documented in this blog post)

    You can use the “concat”, “substring” and “string-length” functions to create an “if/else” type of statement. First, we want to see if the selected Interval was “Days” – to do this, we use the “contains” function along with the substring function.

    substring(xdDate:addDays(../my:StartDateTime, ../my:Duration), 1, contains(../my:Interval, "Days") * string-length(xdDate:addDays(../my:StartDateTime, ../my:Duration))

    You would actually interpret this expression right-to-left in this manner: “If the Interval field contains “Days” then use the addDays function to add the value in the Duration field to the value in the StartDateTime field.”

    We use a similar expression to check for the value of “Hours” in the Interval field and then use the “addSeconds” function to add the appropriate number of seconds:

    substring(xdDate:addSeconds(../my:StartDateTime, ../my:Duration * 60 * 60), 1, contains(../my:Interval, "Hours") * string-length(xdDate:addSeconds(../my:StartDateTime, ../my:Duration * 60 * 60))

    Lastly, if the Interval field does not contain Days or Hours then it must contain “Minutes”:

    substring(xdDate:addSeconds(../my:StartDateTime, ../my:Duration * 60), 1, contains(../my:Interval, "Minutes") * string-length(xdDate:addSeconds(../my:StartDateTime, ../my:Duration * 60))

    We then use the “concat” function to bring all of these expressions together to set the Value property:

    concat(substring(xdDate:addDays(../my:StartDateTime, ../my:Duration), 1, contains(../my:Interval, "Days") * string-length(xdDate:addDays(../my:StartDateTime, ../my:Duration))), substring(xdDate:addSeconds(../my:StartDateTime, ../my:Duration * 60 * 60), 1, contains(../my:Interval, "Hours") * string-length(xdDate:addSeconds(../my:StartDateTime, ../my:Duration * 60 * 60))), substring(xdDate:addSeconds(../my:StartDateTime, ../my:Duration * 60), 1, contains(../my:Interval, "Minutes") * string-length(xdDate:addSeconds(../my:StartDateTime, ../my:Duration * 60))))

    c. Add a “Set a field’s value” Rule to the Duration field to also set the NextStartTimeValue field – again, add a condition where we only want this rule to fire if the Interval field is not blank. Use the same expression as above to set the Value property:

    concat(substring(xdDate:addDays(../my:StartDateTime, ../my:Duration), 1, contains(../my:Interval, "Days") * string-length(xdDate:addDays(../my:StartDateTime, ../my:Duration))), substring(xdDate:addSeconds(../my:StartDateTime, ../my:Duration * 60 * 60), 1, contains(../my:Interval, "Hours") * string-length(xdDate:addSeconds(../my:StartDateTime, ../my:Duration * 60 * 60))), substring(xdDate:addSeconds(../my:StartDateTime, ../my:Duration * 60), 1, contains(../my:Interval, "Minutes") * string-length(xdDate:addSeconds(../my:StartDateTime, ../my:Duration * 60))))

    d. Add a “Set a field’s value” Rule to the Repeating Table to set the StartDateTime field to the value stored in the NextStartTimeValue field (this is what will set the Start Date and Time fields when a new row is added to the table)

     

    4. Test!

    Here is a sample completed Task Planner:

    NOTE: Because this is a “no code” solution, if you modify an existing start date or time there is no way to enumerate the balance of the nodes to reflect this change. A feature such as this would require code.

    Scott Heim
    Support Engineer

  • Microsoft InfoPath 2010

    InfoPath Team is Hiring Program Managers

    • 0 Comments

    If you’re reading this blog, you’re probably passionate about InfoPath. You built several InfoPath applications, and think the technology has lots of potential. But you also realize that InfoPath is still a fairly new product, so there’s a lot to be done to realize this potential into customer value.

    Why not take your passion to the next level? InfoPath is gearing up for vNext, and now is a perfect time to influence your favorite XML form designer from the inside. Come work for the InfoPath Team!

    We’re looking for experienced Program Managers to help define the next, revolutionary wave of InfoPath client and Forms Services.

    We have three positions open:

    1) Programmability PM: seeking individual familiar with programmability and/or enterprise development who is passionate about making InfoPath easier and more powerful for developers. Formal job description coming soon.

    2) Forms Services PM: define and drive the v2 of Forms Services. Are you passionate about XML and AJAX technologies? If so, this position is for you.

    3) Program Manager who will help us make it easier to create and use electronic forms and integrate them into business applications.

    Please submit your resumes to our staffing consultant, Michael Ashe, at [micashe at microsoft]. Make sure you meet the minimum posted requirements before you apply.

  • Microsoft InfoPath 2010

    All you wanted to know about browser compatibility

    • 5 Comments

    These articles will help you answer many of the questions you might have about Forms Services browser compatibility, rich-client only features, and the Design Checker.

    Title

    Details

    Audience

    Plan browser support (OSS)

    Describes the different levels of browser support for MOSS.

    Admin

    Creating InfoPath Form Templates That Work With Forms Services

    Describes the following:

    1. Features supported by both InfoPath and Forms Services
    2. Features not supported by Forms Services
    3. Object Model that works in Both InfoPath and Forms Services
    4. Object Model that works only in InfoPath

    Dev

    Web browser compatibility in InfoPath Forms Services

    Describes the following:

    1. Which browsers are compatible with Forms Services?
    2. Which InfoPath 2007 features are supported in browser-enabled form templates?

    End-user

    InfoPath 2007 features that are unavailable in InfoPath Forms Services

    Describes the following:

    1. Controls that work in both InfoPath and Forms Services
    2. Controls that work only in InfoPath (lists Design Checker errors and messages)
    3. Features that work in both InfoPath and Forms Services
    4. Features that work only in InfoPath (lists Design Checker errors and messages)

    End-user

    Microsoft Office server system requirements

    Describes system requirements for all server products, including browser requirements.

    All

     

    Anneliese
    Senior Writer

  • Microsoft InfoPath 2010

    Forms Services and multiple IIS Web Applications

    • 1 Comments

    We often get questions asking if the XmlFormView control can be used in a particular environment. Usually you will use it on a regular page that belongs to a WSS Site or on a page in _layouts folder of a WSS site. These scenarios are supported and covered in the following MSDN article on the XmlFormView control at http://msdn2.microsoft.com/en-us/library/ms778201.aspx. If you want to create an IIS application inside of your WSS site, hosting XmlFormView on the pages of this site is not supported (and actually does not work properly).


    How you can get into this unsupported state

    In Visual Studio, you create New -> ”Web Site” inside http://myWSSserver/_layouts. Then follow the steps to host an XmlFormView control on one of the pages. You add a SubmitToHost handler to the XmlFormView control on your hosting page. When viewing the form, everything seems to be working fine until a post back to the host happens. You either get no results or the “submitted successfully” dialog without your hosted page’s handler called.


    Why it does not work (so you don’t try to use XmlFormView in this way)

    Here are two things that contribute to the issue:

    1. XmlFormView control uses a helper page which is located in the _layouts folder. This page handles AJAX post backs from the control. Every post back that the control makes by itself will be handled by that page. This page can also request that the hosting page performs a full page post back (i.e. for Submit to host). In this case, the Form Services code in the browser will call submit on the page which will then be directed to the original hosting page code for processing.

    2. Forms Services uses the ASP.Net session state to store all of the information about the current document. The ASP.Net session state data is not shared across IIS applications even if the ASP.Net session cookies are the same.

    Let’s combine these two facts with the fact that the “Web Site” that you just create under _layouts is in a different IIS web application than _layouts itself. You have the main page (i.e. _layouts/myWebSite/MyHostingPage.aspx) in one IIS web application but the Forms Services’ post back page (_layouts/postback.aspx) is in a different (default WSS) application.

    From reason 1 above, we can see that most of requests will go to the _layouts/postback.aspx page. Based on reason 2, we will get one Session State data blob for all post backs going to the postback.aspx page (since it goes to the same application). This Session State data blob will contain information about the document you are editing including all changes you are making in the browser.

    When the request needs to go to the hosting page (i.e. when the post back page detects a call to the SubmitToHost handler) then another Session State data blob will be created for these requests (even if they share an ASP.Net session cookie, the request goes to a different application). This new Session State data blob does not have information about the document that was created and edited during the calls to postback.aspx.

    At this point, there are two unrelated Session State data blobs which are holding data about part of your editing session. As you can guess, neither of them contains complete information. Depending on what pieces of editing went to each copy of your Session State data blobs, you can get unexpected behavior after the post back. Often the Forms Services’ code will detect a complete mismatch between the data in the Session State and the information from the browser. It will end up starting a new document to get to a consistent state.


    How to fix it

    Note that the steps below will change your site from living in a separate application to sharing the same application as WSS. Pease make sure that you understand effect of this change (and check if you need a separate application in the first place).

    1. Run IIS Manager (i.e. Start->Run->inetmgr).
    2. In the IIS Manager, chose the web site (in the left pane) where you created the web site in Visual Studio (shown in figure 1).
    3. Right click on the selected web site, choose “Properties”, and on the “Virtual Directory” tab, click the “Remove” button next to “Application name” (shown in figure 3).

    Following image highlights what icon you should see next to supported (green) and unsupported (red) folders:

    Setting on Property page for the folder where hosting of XmlFormView will work properly:

    Here is sample of properties where hosting will be broken. Click Remove button to fix it. Again, note that you need to understand effect of it for your application:

    Alexei Levenkov, Nicholas Lovell, and Balbir Singh
    Software Design Engineers

  • Microsoft InfoPath 2010

    UDC File Authoring Tool

    • 6 Comments

    Some time ago, we blogged about the reasons why you'd use UDC files and the anatomy of UDC files. You may be wondering, however, if it's possible to author these files in InfoPath - after all, UDC files are just XML files.

    You're right - this is definitely possible. Attached to this article is a form template that lets you author and edit UDC files. Here are the steps you need to take to make it work:

    1) Download the attached form template, save it to your desktop

    2) Right-click on the downloaded .xsn file, then select Design. Ignore any warnings about the published location.

    3) Go to File | Publish | Network Location, and republish the form template. Then open the form template from its published location.

    Enjoy, and let us know if you have any feedback on the tool.

    Nick Dallett
    Lead Program Manager
    Travis Rhodes
    Software Design Engineer in Test
  • Microsoft InfoPath 2010

    InfoPath-Related Blogs: OPML Compilation

    • 4 Comments

    We promised, and here it is: the OPML compilation of RSS feeds of bloggers that write about InfoPath. Just download the file and import it into your favorite news aggregator.

    Sneak peek:

    Cheers!
    Alex

  • Microsoft InfoPath 2010

    Using the Events Manager of the InfoPath Hosted Control

    • 6 Comments

    The InfoPath hosted control gives developers of third party hosted applications the ability to respond to events in the form.  The InfoPath event manager provides this functionality.  Through the event manager a host application can respond to 5 form events, 3 xml events and 1 control event:

    Form Events

    Xml Events

    Control Events

    Saving event
    Context Changed event
    Sign event
    Merging event
    View Switched event

    Changing event
    Validating event
    Changed event

    Clicking event

    How can third party host applications use the event manager?

    Step 1: Implement an InternalStartup method

    First, add code to handle the InternalStartup event. InternalStartup method will execute when a form loads.

    public Form1()
    {
                InitializeComponent();
    
                //sync to the startup event where you can register for individual events
                formControl1.InternalStartup += new Microsoft.Office.InfoPath.FormControl.EventHandler<EventArgs>(InternalStartup);
    }

    Next, implement your InternalStartup method. The function signature should look similar to:

    void formControl1_InternalStartup(object sender, EventArgs e)

     

    Step 2: Register to receive events

    In your InternalStartup method add code to register for events. For form events this code looks like this.

    void InternalStartup(object sender, EventArgs e)
    {           
           ((FormControl)sender).EventManager.FormEvents.ViewSwitched += new ViewSwitchedEventHandler(OnSwitchView);
           ((FormControl)sender).EventManager.FormEvents.Submit       += new SubmitEventHandler(OnSubmit);
           ((FormControl)sender).EventManager.FormEvents.Sign         += new SignEventHandler(OnSign);
           ((FormControl)sender).EventManager.FormEvents.Save         += new SaveEventHandler(OnSave);
           ((FormControl)sender).EventManager.FormEvents.Merge        += new MergeEventHandler(OnMerge);
     }

    For xml events you must provide the XPath of the node whose events you wish to respond to. Below is a sample of how to register to receive xml events.

     

    void InternalStartup(object sender, EventArgs e)
    {
          ((FormControl)sender).EventManager.XmlEvents["/my:myFields/my:field1"].Changed    += new XmlChangedEventHandler(FieldChanged);
          ((FormControl)sender).EventManager.XmlEvents["/my:myFields/my:field1"].Changing   += new XmlChangingEventHandler(FieldChanging);
          ((FormControl)sender).EventManager.XmlEvents["/my:myFields/my:field1"].Validating += new XmlValidatingEventHandler(FieldValidating);
    }

    To receive the click event for a button you must specify the control id of the button. Below is a sample of how to register to receive control events.

    void InternalStartup(object sender, EventArgs e)
    {
          ((ButtonEvent)((FormControl)sender).EventManager.ControlEvents["CTRL2_5"]).Clicked += new ClickedEventHandler(ButtonClicked);
    }
    

     

    Step 3: Implement methods to handle each event registered

    The final step is to implement handlers for the events you have registered for.
    The handlers for the events have the following method signatures.

    Form Events

    public delegate void ViewSwitchedEventHandler(object sender, Microsoft.Office.InfoPath.ViewSwitchedEventArgs e)
    public delegate void SubmitEventHandler(object sender, Microsoft.Office.InfoPath.SubmitEventArgs e)
    public delegate void SignEventHandler(object sender, Microsoft.Office.InfoPath.SignEventArgs e)
    public delegate void SaveEventHandler(object sender, Microsoft.Office.InfoPath.SaveEventArgs e)
    public delegate void MergeEventHandler(object sender, Microsoft.Office.InfoPath.MergeEventArgs e)

    Xml Events

    public delegate void XmlChangedEventHandler(object sender, Microsoft.Office.InfoPath.XmlEventArgs e)
    public delegate void XmlChangingEventHandler(object sender, Microsoft.Office.InfoPath.XmlChangingEventArgs e)
    public delegate void XmlValidatingEventHandler(object sender, Microsoft.Office.InfoPath.XmlValidatingEventArgs e)
    

    Control Events

    public delegate void ClickedEventHandler(object sender, Microsoft.Office.InfoPath.ClickedEventArgs e)

    Example: changed event, view switched event, button clicked event.

    void FieldChanged(object sender, XmlEventArgs e) {}
    void OnSwitchView(object sender, ViewSwitchedEventArgs e) {}
    void button1_Click(object sender, EventArgs e) {}

     

    Things to know about handling events

    1. Events are handled first by the form’s business logic, then by others who register to handle events. Since events are handled asynchronously it is possible that the code in the business logic may cancel the event, and deny the host the opportunity to handle the event.
    2. In order to handle the submit, save and merging events, the designer of the form template must specify that the event is to be handled through the use of code. Without enabling this in the form template the event will not be handled in code.
    3. Buttons must specify that clicks be handled through code or the event will not be handled through code.
    4. The sign event will only take place when the form is fully trusted.
    5. The Loading and VersionUpgrade events are not available from third party hosted applications as these events occur before the form is loaded in the hosted application.

    DeVere Dyett
    Software Design Engineer in Test

  • Microsoft InfoPath 2010

    Rate Our Content!

    • 1 Comments

    Is this blog useful for you? Which articles are most useful? One very important way you can tell us is by rating the content that we publish: just use the little "star" control next to the title, in the detail view for each article -

    This will help us improve the quality; plus, when we get enough ratings, we'll be publishing the "top rated articles" list - so if you rate an article, you're helping the community as well.

    Thanks!

    Alex Weinstein
    Program Manager

  • Microsoft InfoPath 2010

    InfoPath and Yukon: The Details

    • 9 Comments

    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 Weinstein
    Program Manager

  • Microsoft InfoPath 2010

    New MSDN resources

    • 1 Comments

    Some new MSDN resources to check out:

    1) Microsoft Office Forms Server 2007 SDK

    2) InfoPath Developer Reference for Managed Code

    Alex

Page 1 of 1 (14 items)