Microsoft InfoPath 2010
The official blog of the Microsoft InfoPath team

  • 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

  • Microsoft InfoPath 2010

    InfoPath and SQL Server 2005: Intro

    • 2 Comments

    SQL Server 2005, also known as Yukon, comes with greatly improved XML support. Just look at it: native XML columns - store XML blobs in your records, along with other data types. These XML columns can be typed or untyped; typed here refers to XSD typing, which provides backend data validation. If an XML blob that you're trying to put into an XML column doesn't comply with the schema associated with the column, the insert will fail. Cool, huh?

    There's more cool to it. Meet XQuery - a query sub-language of SQL that can be used to query your XML columns. What does this mean to you? It means that you can do a structure-aware search through XML.

    Imagine: you need to create a training management system. It's a system with a non-trivial schema - each employee will need to take some mandatory courses, there will be education fields for their secondary education... One of the reports you must generate from the system is "show all employees that graduated before year X". How would you have done this yesterday?

    You could create your InfoPath form against a relational database. this would mean that you'd need to start with ERD design (looong... and messy - not all XML structures translate nicely into relational systems: think of choice, recursive, optional sections). Report generation would have been easy afterwards: just do "SELECT employee_id FROM employees WHERE ...". You all know SQL, no need to go too deep here. But recognize the pain you would have to go through with this approach: ERD design, shredding of the XML structures into relational structures. Plus, if you need to make changes to the schema, this becomes painful: non-trivial changes to relational systems are tough.

    You could create an employee profile management form in InfoPath, and store the resulting XML blobs in SharePoint. You'd pull in basic employee data - names, positions, hire dates - from your HR database (let's assume it lives on a separate SQL database). But how would you generate that report? Built-in SharePoint full-text search is obviously not an option here. What you'd probably do is create a custom app that reads in the XML's, parses them, verifies the criteria ("graduation date > X"), and if the criteria is satisfied, spits out the employee id. Scary and complicated.

    Entering today. XML Columns, XQuery and InfoPath.

    Jeez, that sounded too much like a marketing slogan :-).

    What you can today is create an XML column and store entire InfoPath there. No ERD design. Just drop an entire form (one record) into a SQL column. Then, when you want to report on several forms, just use XQuery.

    This not only allows you to answer trivial queries like "show all employees that graduated before year X", but perform true heterogeneous query. Recall: the basic HR database is a separate relational system. The training management system you just built stores XML blobs. How do you connect the two? Just do a JOIN like you would have otherwise! Yes, SQL Server 2005 allows you to do joins between relational and XML data!

    It's hard to underestimate the benefits here. Structure-aware search will frequently give you much better search results, if you know what you're searching for. It can answer semantic queries that full-text search can't answer - and you don't need to shred your data into relational structures.

    You're probably wondering how exactly do you make InfoPath store data in an XML column in Yukon.. Coming soon, in blog theatres near you :-). UPDATE: the next chapter of the story is here.

    Alex Weinstein
    Program Manager

  • Microsoft InfoPath 2010

    Submitting to a Database (via Web Services) in InfoPath Forms Services

    • 38 Comments

    If you've ever designed and deployed a form that will be frequently used or require significant data analysis, you have probably looked into maintaining data in a SQL database.  With InfoPath 2003 and the InfoPath 2007 rich client, you get what you expect.  You create a main database data connection to the SQL server and pick tables and columns that meet the requirements for submit functionality.  When you open your form and click the "Run Query" button, you see the data pulled from the database as you'd expect.  You then happily insert, update, or delete records and, when the time is right, hit "Submit".  Luckily for you, the InfoPath client took care of maintaining the list of changes that you made while editing your form.  With this list of changes intact, your updated data streaks back to the database to await the next query.

    Enter InfoPath Forms Server... Here we don't get the change tracking for free, so we'll need to do some work to simulate change tracking outside of the form's execution.  Basically, what we're going to try to accomplish is to use an intermediate web service that will handle querying and submitting the data from and to the target database.  The web service will timestamp the data at query time and send the data to the form for editing.  Then the form filling user will edit the data and click "Submit".  When the data arrives back at the web service, we need to figure out what changed in the meantime.  This means that we'll have to check to see if anything has changed in the database since the time when we queried the data.  If it has, then the submitted data should be rejected and the user should re-query before re-applying her edits.  If it hasn't, we'll diff the submitted data with the database data and submit the difference back to the database!  Let's get started!

     

    Create the Web Service and Setup the Database

    Since the InfoPath data connection wizard is easiest to use when your web service is already established and available, let's start with creating the web service and setting up the database.

    1) Download the attached archive and extract it somewhere on your hard drive

    2) Create a new web site in Internet Information Services (IIS)
    NOTE:  IIS must be enabled as a Windows Component through "Add or remove Windows components" in the "Control Panel")

    • Launch IIS ("Start" >> "Run", type 'inetmgr')
    • Right-click the "Web Sites" node and select "New" >> "Web Site…"
    • Click "Next >" on the first page of the wizard.
    • Type a name for your web site (e.g., "IPFSDiffGram") and click "Next >"
    • Type a unique port number (referred to, hereafter, as '<portNum>') for the web site and click "Next >"
    • Enter the path to a folder where the site contents will be stored and click "Next >"
    • Check "Read" and "Run scripts (such as ASP)" permissions and click "Next >"
    • Click "Finish".
    • You may want to create a new Application Pool for this web site "to make your server more efficient and reliable".

    3) Create the web service

    • Launch Visual Studio 2005.
    • Click "File" >> "New" >> "Web Site…"
    • Select "ASP.Net Web Service" in the "Visual Studio Installed Templates" section.
    • In the "Location" drop-down, select HTTP and then click "Browse…"
    • Select the Web Site created in step 1 and then click "Open"
    • Click "OK" on the "New Web Site" dialog

    4) Add the code and service asmx files to the project

    1. Open the Solution Explorer task pane (click "View" >> "Solution Explorer")
    2. Right-click the top-level project icon (labeled http://localhost:<portNum>) and select "Add Existing Item…"
    3. Navigate to the "DiffGramService.asmx" file and then click "Add". Refer to the files you downloaded for the contents of this file.
    4. Right-click the "App_Code" icon and select "Add Existing Item…"
    5. Navigate to the "DiffGramService.cs" file and then click "Add". Refer to the files you downloaded for the contents of this file.

    5) Customize the web service code for your database

    1. Instance variables
      1. DBConnectionString -- The connection string to connect to your database.
      2. DBTable -- The name of the table to which the web service should connect.  The table must feature a surrogate single-column primary key with an integer data type.
      3. ColumnNames -- An array that contains the names of the data columns in your database.
    2. WebMethod query parameters
      1. DBData(parameter_list)

        • parameter_list should be the columns, with appropriate System.Types to match your database
        • You'll need to specify the appropriate SqlDbTypes that correspond to the columns in your database
      2. UpdateDBData(DataSet, parameter_list)

        • Do not change the DataSet parameter
        • parameter_list should be the columns, with appropriate System.Types to match your database
        • You'll need to specify the appropriate SqlDbTypes that correspond to the columns in your database

    6) Create the database table and DML trigger
    The web service includes logic to update the database table and create a DML trigger to maintain a timestamp of Last Update for each record.  However, you may want to create the timestamp column and trigger yourself.

    • Example SQL script to create a database named "DBTable" that is compatible with this web service is in the attached files, named "CreateDBTable.sql". The table will have a structure similar to the following:

    • Example SQL script to create a DML trigger that will maintain the timestamp of last update for each record is also attached, and called "CreateDMLTrigger.sql".

    7) Build the Visual Studio solution and publish the web site

     

    Design the InfoPath Form Template

    Now that we've setup our database and constructed our web service to do the querying and submitting for us, it'll be a breeze to design an InfoPath form template based on the web service.

    1) Design a new, browser-enabled form template, based on the web service that will query/submit the DataSet.

    1. Launch InfoPath and select "Design a Form Template…" on the "Getting Started" dashboard dialog.
    2. Check "Enable browser-compatible features only" (required only for browser-enabled form templates)
    3. Select the "Web Service" icon and click "OK".
    4. Select "Receive and submit data" and click "Next >"
    5. Type the WSDL URL for your web service (e.g., http://<server>:<port>/DiffgramService.asmx?WSDL) and click "Next >"
    6. Select the web method that will return the DataSet and click "Next >".
    7. Click "Next >" on the DataSet change-tracking page.
    8. Click "Next >" to name the query data connection and move on to define the submit data connection.
    9. If the submit WSDL URL is different than the query WSDL URL, enter it here and click "Next >".  Else, just click "Next >".
    10. Select the web method to which the DataSet will be submitted and click "Next >".
    11. Select the DataSet parameter in the "Parameters" listView.
    12. In the "Parameter options" section, click the button next to the "Field or group" field.
    13. In the "Select a Field or Group" dialog that pops up, expand the "dataFields" node until you see the node with the name of the DataSet (it's the parent of the repeating "(Choice)" node).

       

    14. Select the DataSet node and click "OK"
    15. If your web service takes parameters to constrain the select command, map these parameters to the queryFields generated during the creation of the query connection.
    16. Click "Next >" on the submit parameter mapping page.
    17. Click "Finish" to name the submit data connection and finish the Data Connection Wizard.

     

    2) Set the default values for the "ID" and "QueryTime" fields

    1. In the InfoPath Designer window, click the "View" menu and select the "Data Source..." option.
    2. In the "Data Source" taskpane, expand the "dataFields" node completely.
    3. Double-click the "QueryTime" node to open the properties dialog.
    4. Type "1/1/1900 12:00:00 AM" (without the quotes) in the "Value" field in the "Default Value" section and click "OK"
    5. Repeat steps 2.a-2.c for the "ID" field.
    6. Type "-1" (without the quotes) in the "Value" field in the "Default Value" section and click "OK"

     

    3) Insert the controls into the View.

    1. Click inside the dataFields controls area (it says "Drag data fields here" if you haven't clicked there)
    2. Click the "Insert" menu and select the "Repeating Table..." option.
    3. In the "Repeating Table Binding" dialog, completely expand the "dataFields" node.
    4. Select the group node that has the same name as your database table and click "Next >"
    5. For each of your data columns (e.g., 'Column1', ..., 'Column5'), select the column name on the left, and then click the "Add >>" button to make the column show up in the Repeating Table.

      NOTE:  If you include the 'ID' column, the user will be able to edit the values, and the DataSet may not be validated or merged correctly.
    6. Click "Finish" to accept the repeating table binding and insert the repeating table into the View.

    4) Publish the form template to your InfoPath Forms Server

    At this point, you have a form template that will work correctly when you open it in the InfoPath rich client.  But this post is all about getting things to work correctly in the InfoPath Forms Server.  So you'll need to configure your data connections to work in the browser by converting the main query and submit data connections to use Universal Data Connection (UDC) files in a Data Connection Library (DCL).  Now you should be all set.  The web service will query and submit the data to the database, and we'll make our best attempt at a diff of the database data against the submitted data.

    From here on out, it's up to you.  If you want to, for example, modify the database structure or change the way the trigger works, then you're going to need to modify the web service code.  You'll also need to use "Convert Main Data Source" to update your form template whenever you modify your web service.  You might also want to add support for multiple tables.  All this will take some exploration of ADO.Net DataSets, but it is a reasonable exercise as long as you're comfortable writing managed code.

    Forrest Dillaway
    Software Design Engineer in Test

  • Microsoft InfoPath 2010

    Resource Compilations

    • 1 Comments

    A few folks put together good resource compilations of everything InfoPath-related: articles, screencasts, help topics... Check them out:

    1) InfoPath 2007 resources by Joris Poelmans

    2) Office 2007 Spotlight: InfoPath 2007 by Michael Gannotti

    3) Building Web Forms with Office InfoPath Forms Services by Dave Glover

    Alex

  • Microsoft InfoPath 2010

    Survey Creator

    • 12 Comments

    Scenario

    A school department wants to create a standard digital template to use when administering tests to students. Since the template is to be a standard format across multiple classes, the department requires that teachers be able to enter questions appropriate to their individual classes. To simplify things, the department decides to allow three types of questions: True/False, Multiple Choice, and Free-Form Essay (write-in). So, in short, teachers create questionnaires for the students, determining what type of answer to collect for each. The form is then given to the students taking the test, who step through the questions one-by-one. After the test, the teachers compile the answers, creating a master form for grading. The two main views of the form will look similar to the following:

     

    Wizard-Style Form: One Question at a Time

    The heart of the wizard-like functionality of the solution to this scenario is the use of an index counter by which a view is then filtered when the form is being edited. The "Next" and "Back" buttons we see in so many cases appear on this InfoPath solution but they simply change the value of the counter, adding one to the index if "Next" is clicked, subtracting one if "Back" is clicked. In this case, the index counter is placed on the repeating section that is the container for test questions in this form. 

    Data Source

    We will use a repeating group of questions; each question will have a questionText (what's being asked) and one of the following subnodes (implemented as a choice group):

    1) trueFalse: keeps track of the student's answer to this question, if this question is a true/false question. Boolean.

    2) writeIn: keeps track of the student's essay answer to a write-in question. Text or Rich-Text (XHTML).

    3) multipleChoice: we need to remember two things here:

     - possible choices (provided by the teacher; repeating group of text fields)

     - student answer

    Since we want to show students only one question at a time, we will need an auxiliary index field to remember the current question that's being shown. The index counter used by the filter is a field that does not take any direct user input. Give the field a value of 1, which will then be incremented using the buttons.

    All-in-all, the data source looks similar to the following:

    Views

    This forms solution is structured around a repeating section field that appears in both views. To create the duplicate repeating sections, create two views and place a repeating section in the first view. Next, open the second view, open the data source task pane, and drag the repeating section field onto the canvas.

    To show the students one question at a time, we need to apply a filter: open the student view and then open the Properties of the repeating section in that view. Click the display tab and then click the "Filter Data…" button. Since we will set the counter to be the question number, and since we want to display only the current question, set the filter to display data for which index = position().

    Navigation among the questions is enabled by "Next" and "Back" buttons included in the student view. Insert two buttons into the student view. Double-click the first button: Select "Rules and Custom Code" in the action menu, give the button the label "<< Back" and click the "Rules…" button. Add an action as appears below:

    For the "Next >>" button, follow a similar procedure as for the back button but add 1 to the value of index in the above dialogue.

    Now we need to take care of the actual question display: each question requires a serial number, the text of the question, and the field for response (which could be one of several pre-defined types.) The teacher's view contains user-editable fields for question text and a choice of answer type; the Student view displays the text of the question and contains the editable response field.

    In detail:

    Question Text: collected through a Text Box in the Teacher view and then displayed using an Expression Box in the Student view.

    Response data is contained in a choice group. The Teacher selects the most appropriate of the three response types permitted by the form and, in the case of multiple choice, adds the necessary choices to a numbered list. No "answer" data field appears in the Teacher view of any of the answer types because all the teacher need do is choose the type of answer. The default Choice Group appears with two Choice Sections but can contain as many as necessary, simply insert the Choice Group and then add more Choice Sections. For this scenario, add the Choice Group and Sections to the Teacher view and then label the Choice Sections with the appropriate answer type. For the multiple-choice Choice Section, add a Numbered List control to the section to contain the options for any multiple-choice section.

    The controls to hold student responses to questions are contained in a Choice Group placed into the Student view by dragging the existing Choice Group into that view from the Data Source Task Pane. This action creates a mirror of the Choice Group structure contained in the Teacher view but without the controls or text placed into the Teacher view. Into the Choice Sections in the Student view place, respectively, a set of 3 Option Buttons for True/False/I Don't Know, a Rich Text field for written response, and a List Box for multiple choice. This last has the slight complication that we want to pull in the options the teacher set for the particular question. To do this go to the "Data" tab of List Box Properties and select the second option under "Data": "Look up values in the form's data source." Select the field that holds the values from the Numbered List and these will populate the List Box for each multiple choice question.

    To display the question number, add an expression box that displays the value of the index. Type "Auto" into the width property of the expression box and the control will size itself to the value. To display the total number of questions, use an expression box in which you use the following formula where the fields used are the Choice Sections within the Choice Group used to contain responses:

    The basic mechanics of this test form are now complete. What remains to be set up are things like submission and review options and settings. Some examples of possible additions to this scenario are as follow:

    • View permissions so the students can only see the view intended for them.
    • User roles limiting what members of the student body are permitted to take the test. 
    • A review and submission function that shows the "Review and Hand-In Test" button only after all questions have been viewed. This button would then take students to a summary view of all of their question-answer pairs along with links to return to a particular question should they want to change their answers and a button to finally submit the test. (This option is a logical product of the calculation and display of the total number of questions and of the current question - making it simple to determine when the user has reached the end of the slate of questions.)
    • Settings for the teacher to merge all answers into a single master form for grading, especially if marks depend on comparison among all the answers. The summary field, once marked, could produce individual reports for students that could be e-mailed out automatically (they would be required to authenticate themselves with their school e-mails to be able to take the test in the first place. 
    • Submit settings to send all the tests to a MOSS document Library.

    The sample form template that has this technique is attached; make sure to download the XSN to your computer before opening it. Note that this technique works in InfoPath 2003 and 2007, but will not work in browser-enabled form templates.

    Jonathan
    Program Manager

    Thanks to our brilliant ex-colleague Ned Friend for building the sample form template.

  • Microsoft InfoPath 2010

    Sniffing Code in Form Templates

    • 6 Comments

    With the introduction of InfoPath Forms Services for MOSS 2007, clever management of form template deployment will probably become a must for most IT departments.  You'll want to be sure that form templates are not draining server resources.  You'll especially want to keep an eye on administrator-deployed form templates, as those can achieve fully trusted status and execute arbitrary code on the server.

    With that in mind, it's probably a good idea to set up a code review process for InfoPath form templates that will be deployed by the administrator.  To facilitate the process, it might be nice to have a tool around that will tell you whether or not a form template uses custom code.  This is pretty easy to determine, and a tool can be coded up rather quickly, but we figured we'd facilitate the process and provide a sample to get you going.  And here it is…

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Xml;
    using System.Xml.XPath;
    using System.IO;
    using System.Diagnostics;

    namespace CheckForCode
    {
    class Program
    {
    /// <summary>
    /// The Main method entry point for the code-checking algorithm.
    /// </summary>
    /// <param name="args">The command-line arguments for the code-checking algorithm.</param>
    public static void Main(string[] args)
    {
    if (args == null || args.Length == 0)
    {
    Console.WriteLine("No arguments specified.");
    }
    else
    {
    string filePath = args[0];
    try
    {
    Uri fileUri = new Uri(filePath, UriKind.Absolute);
    filePath = fileUri.AbsoluteUri;
     
    // If the file is an http URL, download it to the local machine.
    if (fileUri.Scheme.Equals(Uri.UriSchemeHttp))
    {
    filePath = CopyFormTemplateLocally(filePath);
    }
     
    if (File.Exists(filePath))
    {
    string extension = Path.GetExtension(filePath);
    if (extension.Equals(".xsn", StringComparison.CurrentCultureIgnoreCase))
    {
    // Extract the xsf and check for the root assembly.
    string pathToXSF = ExpandManifest(filePath);
    CheckXSFForCode(pathToXSF);
    }
    else if (extension.Equals(".xsf", StringComparison.CurrentCultureIgnoreCase))
    {
    // Check for the root assembly.
    CheckXSFForCode(filePath);
    }
    }
    }
    catch(ArgumentException exn)
    {
    Console.WriteLine("The file path argument is invalid.");
    Console.WriteLine("Exception message: " + exn.Message);
    }
    catch(UriFormatException exn)
    {
    Console.WriteLine("The file path is not a valid Uri: '" + filePath + "'");
    Console.WriteLine("Exception message: " + exn.Message);
    }
    }
     
    Console.WriteLine("Hit any key to exit.");
    Console.ReadKey();
    }
     
    /// <summary>
    /// Get the path to the temp folder for the current user.
    /// </summary>
    private static string TempFolder
    {
    get
    {
    string tempFolder = Environment.ExpandEnvironmentVariables("%temp%");
    return tempFolder;
    }
    }
     
    /// <summary>
    /// Copy a form template at an http URL to a local path.
    /// </summary>
    /// <param name="absoluteFileUri">The absolute Uri of the form template.</param>
    /// <returns>The local path where the form template was downloaded.</returns>
    private static string CopyFormTemplateLocally(string absoluteFileUri)
    {
    string fileName = Path.GetFileName(absoluteFileUri);
    string tempFilePath = Path.Combine(TempFolder, fileName);
     
    // Download the form template source from the server.
    System.Net.WebClient client = new System.Net.WebClient();
    client.UseDefaultCredentials = true;
    client.Headers.Add("Translate:f");
    client.DownloadFile(absoluteFileUri, tempFilePath);
     
    return tempFilePath;
    }
     
    /// <summary>
    /// Expand the manifest.xsf file from the specified xsn.
    /// </summary>
    /// <param name="pathToXSN">The absolute path to the xsn.</param>
    /// <returns>The path to the expanded xsf.</returns>
    private static string ExpandManifest(string pathToXSN)
    {
    string tempFolder = TempFolder;
     
    Process expand = new Process();
    expand.StartInfo.ErrorDialog = false;
    expand.StartInfo.UseShellExecute = false;
    expand.StartInfo.RedirectStandardOutput = true;
    expand.StartInfo.CreateNoWindow = true;
    expand.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
    expand.StartInfo.FileName = "expand.exe";
    expand.StartInfo.Arguments = pathToXSN + " -F:manifest.xsf " + tempFolder;
    expand.Start();
     
    // Note that the xsf may not be named "manifest" so this could fail if the user
    // has extracted the form template files and changed the name of the file.
    return Path.Combine(tempFolder, "manifest.xsf");
    }
     
    /// <summary>
    /// Check the xsf at the specified path for a root assembly dll.
    /// </summary>
    /// <param name="pathToXSF">The absolute path to the xsf document.</param>
    private static void CheckXSFForCode(string pathToXSF)
    {
    string rootAssemblyNameXPath = "/xsf:xDocumentClass/xsf:package/xsf:files/xsf:file[xsf:fileProperties/xsf:property/@value='rootAssembly']/@name";
     
    // Load the xsf document.
    XmlDocument xsfDocument = new XmlDocument();
    xsfDocument.Load(pathToXSF);
     
    // Load the xsf namespace.
    XmlNamespaceManager nameSpaceManager = new XmlNamespaceManager(xsfDocument.NameTable);
    nameSpaceManager.AddNamespace("xsf", "http://schemas.microsoft.com/office/infopath/2003/solutionDefinition");
     
    // Navigate to the root assembly, if it exists.
    XPathNavigator xsfRootNavigator = xsfDocument.CreateNavigator();
    XPathNavigator assemblyFile = xsfRootNavigator.SelectSingleNode(rootAssemblyNameXPath, nameSpaceManager);
     
    // Alert for code review.
    if (null == assemblyFile)
    {
    Console.WriteLine("No custom code in form template.");
    }
    else
    {
    Console.WriteLine("Code review required! Root assembly name: " + assemblyFile.Value);
    }
    }
    }
    }

    Just slap this into a console application in Visual Studio 2005, and you'll have a simple application that will accept the Uri of the form template or xsf as input and output "No code" or "Code found!"  With a little customization, something like this could be worked into a workflow that would govern the deployment of administrator-deployed form templates.

    And taking it a bit further, you could automatically seek out the source code folder in the "manifest.xsf" file, in the "projectPath" attribute of the "xsf2:managedCode" element.  Note here, though, that it will only be accessible if the form designer stored the VSTA project in a shared location, so you may have to implement some administrator policies to guarantee that this location is accessible.

    Forrest Dillaway
    Software Design Engineer in Test

  • Microsoft InfoPath 2010

    Do you blog about InfoPath?

    • 11 Comments

    I'd love to compile a list of blogs about InfoPath to help the developer community connect and share insights. If you wrote at least one InfoPath article in the past, and plan on writing more, please leave a link to your blog in the comments.

    Expect an OPML compilation in a week or two!

    UPDATE: OPML compilation is here.

    Alex

  • Microsoft InfoPath 2010

    E-Mailing a Custom View

    • 15 Comments

    Lately, we've had a lot of interest in e-mailing a custom view with InfoPath forms that are submitted via e-mail.  There are a few simple tricks that will allow you to do this.

    Before we start, note that, in the InfoPath rich client, you have the option to "Send to Mail Recipient" from the file menu in the Editor.  In this case, you're stuck sending the currently active view of the form.  The user filling the form would have to manually click or change data in order to fire an event to switch to a custom view.  While this is feasible, we can do better with a pre-defined e-mail submit data connection.

    To define an e-mail submit data connection in the InfoPath Designer:

    1. Click "Tools" >> "Data Connections…"

    2. Click "Add…"

    3. Select "Create a connection to" >> "Submit data" and click "Next >"

    4. Select "As an e-mail message" and click "Next >"

    5. Enter an e-mail address in the "To" header field, or bind it to a field in your form template.

    6. Fill out any other e-mail header information and finish the wizard.

    7. Click "OK" / "Finish" / "Close" to get out of the wizard and finalize your e-mail submit data connection.

    Now that we've defined the e-mail submit data connection for our form template, we can move on to the custom view portion…

    The easiest option is to send a blank view.  To accomplish this, you'll simply want to switch views before you submit.  This is easily accomplished using a rule on button click.  Here are the steps, in the InfoPath Designer, to configure the "Main Submit" toolbar button to send the blank view:

    1. Create a blank view

      1. On the "View" menu, click "Manage Views…"

      2. In the Views TaskPane, click "Add a new view…"

      3. Type a name for the view and click "OK".

    2. Configure main submit to submit the blank view with the e-mail

      1. Click "Tools" >> "Submit Options…"

      2. Check the "Allow users to submit this form" checkbox.

      3. Select the "Perform custom action using Rules" radio button.

      4. Click the "Rules..." button.

      5. Click "Add..." in the "Rules for Submitting Forms" dialog.

      6. Click "Add Action..." in the "Rule" dialog.

      7. Select "Switch Views" and select the blank view in the "View" drop-down list box.

      8. Click "OK" to close all the dialogs.

    Now, when the user clicks "Submit", the view will first be switched to the blank view, and then the form will be submitted to the mail recipients.  InfoPath sends the active view contents in the body of the e-mail message, so the blank view will be sent in the message body!

    But now the next obvious question is, "Wait a second, I thought we were sending custom views, not just blank ones!"  Good call.  InfoPath Forms Services treats the "switched-to" view a bit differently than the InfoPath rich client.  In browser-enabled form templates filled out in the browser, you can add controls, text, pictures, etc. to the blank view to which we switched before submitting, and the message body will display the data in the custom view, along with the Introduction specified in the e-mail submit data connection wizard.  In the InfoPath rich client, you'll still only see the blank view due to view-switching concurrency limitations.  So we need an alternate solution…

    Basically, instead of e-mailing a custom view, we'll e-mail custom view content.  We'll have two sections in one view:  one to hold the controls where the user can fill out the form, and one where the custom "view" contents will be stored.  Then, at submit time, we'll run a rule to first hide the editing section and show the custom e-mail body section, and then submit via our pre-defined e-mail submit data connection.  This works identically in the browser and the InfoPath rich client Editor.  Here are the high-level steps:

    1. Insert two "Section" controls into the form view.  Name one "EditSection" and the other "EmailSection".

    2. Fill the sections with the relevant controls, formatting, etc.  Create your "custom view" inside the "EmailSection" and create the normal form-filling "view" in the "EditSection".

    3. Create a "True/False (boolean)" data source node.  Name it "ShowCustomEmailSection", and set its default value to "FALSE".

    4. Add conditional formatting to hide and show the "EmailSection" and "EditSection" sections.

      1. Set the "EditSection" conditional formatting to "hide this control" when "ShowCustomEmailSection" is TRUE.

      2. Set the "EmailSection" conditional formatting to "hide this control" when "ShowCustomEmailSection" is FALSE.

    5. Configure your submit button as above with the blank view case, but, instead of adding a rule action to switch views before submitting, add a rule action to set the value of the "ShowCustomEmailSection" field to the "true()" function.  This will conditionally hide the "EditSection" and show the "EmailSection".  All that will show up in the message body is an HTML rendering of the contents of the "EmailSection".

    And that's it!  Note that you may have to set the "Postback Settings" for the relevant controls to "Always" for browser-enabled form templates to get the conditional formatting to work as expected.

    Forrest Dillaway
    Software Design Engineer in Test

  • Microsoft InfoPath 2010

    Populating form data from SharePoint List Views

    • 105 Comments

    The SharePoint list data connection in InfoPath is a great declarative way to bring in data from a SharePoint list, but it does have some limitations.  For those willing to put in a little extra effort, there’s much more that can be done with SharePoint lists: you can bind to views based on these lists. Here are the steps for creating the connection to a list view.

    First, let’s get the URL:

    1. Navigate to the SharePoint site that contains the list, for example:
    http://contoso/sites/sales/Sales%20Contacts/Forms/AllItems.aspx
    2. Go to “Modify settings and columns”
    3. Copy the List={GUID} portion of the URL and paste this into a buffer like a Notepad window.
    4. In Notepad, create the following URL (the blue portion us taken from the step 1 URL, and the red portion must be added.

    http://contoso/sites/sales/_vti_bin/owssvr.dll?Cmd=Display&List={GUID}&XMLDATA=TRUE&noredirect=true

    This will return an xml file that can be used in an XML file data connection, as if it came from a file system.  After this, you can use the URL as the location of an XML data file when creating a data connection.

     

    Two caveats:
    - Form users must have read access to the SharePoint list.
    - During creation of the data connection, do not include the file in the form template, as it should be dynamically generated from the SharePoint list.

     

    Some tricks:
    1. When you’re in “Modify settings and columns,” if you click on one of the views in the list at the bottom, you should note that the URL is “enriched” with &View={ANOTHER_GUID}.  If you would prefer to use the columns from that view, you should similarly enhance the URL you use above.
    2. You can also use the url to filter data rows based on column values. For example:

    http://contoso/sites/sales/_vti_bin/owssvr.dll?Cmd=Display&List={115BC7B7-0A82-403E-9327-F3C73E6D37F3}&XMLDATA=TRUE&noredirect=true&FilterField1=xd__x007b_52AE1EF8_x002d_28E7_x002d_4CE4_x002d_AE23_x002d_54E23E80DDB5_x007d_&FilterValue1=Approved

    Note: be sure to remove the “ows_” from the beginning of the FilterField ID.

     

    With this filter, the XML file returned will be filtered to only display those projects that have been approved.  Without the green portion, you would see all the projects.

     

    In order to populate other fields with data from WSS, you create the secondary data source just like above, then in form code or script, you can use GetDOM(“dataSource”) and walk the DOM normally, updating the main DOM as appropriate.

     

    Ed Essey
    Program Manager

  • Microsoft InfoPath 2010

    More articles from the community

    • 0 Comments

    It's Friday, and on Fridays I usually try to dig up a cool article or two from the community

    1) Patrick Tisseghem wrote a beautiful walkthrough on making browser forms show up in a web part - no code required, just follow the screenshots.

    2) S.Y.M. Wong-A-Ton published a very detailed paper on saving InfoPath forms to a SQL Server 2005 XML column. This is a very cool integration scenario that enables powerful data analysis using XQuery, and even crazy things like joins between your XML form data and LOB data stored in a relational system. Be sure to check it out!

    Alex Weinstein
    Program Manager

  • Microsoft InfoPath 2010

    Implementing Roles in Browser Forms

    • 8 Comments

    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:

    • Active Directory username (alias) of the currently logged on (let's call the field CurrentUser)
    • Role of the current user, as determined by our form logic (let's call the field CurrentRole)

    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:

    • If CurrentRole = "Sales", switch the view to the Sales view.
    • If CurrentRole = "Management", switch the view to the Management view.

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

  • Microsoft InfoPath 2010

    Data Connections in Template Parts

    • 1 Comments

    Template parts allow a form designer to combine view information, rules, schema, default data and other compatible components of a form template into a package that can be distributed to other form designers and inserted into form templates without manual recreation. Data connections, with some restrictions, are a compatible component that can be included in a template part.

    Adding a data connection to a template part is the same process as adding a data connection to a form template, with one exception - data connections in template parts cannot submit data. Once added to the template part, the data connection is added to the form template upon insertion. When inserted, the data connection name will be appended with the template part name, identifying the data connection as originating from the template part. The unique name of the data connection is used to identify the data connection during insertion and updating of the template part. When a template part with a data connection is inserted, the data connections are searched for the data connection name. If found, the data connection is removed and replaced with the data connection in the template part. When updating a template part with a data connection, the previous data connection name is searched, and if found, is removed and the data connection associated with the template is inserted. This gives the template part designer the ability to update the data connection when required to support changes made to the template part. For the template part user, this ability causes behavior that may feel counterintuitive:

    1. Multiple insertions of the same template part with data connections unbinds the controls in the previous template parts inserted due to the replacement of the data connection on insert.
    2. Any changes the user makes to a data connection installed as part of a template part insertion is lost when the template part is updated.

    In the case of inserted multiple instances of a template part with a data connection, the controls can be easily rebound after insertion. To prevent an update of a template part from changing the data connection, rename the data connection so that it is not found by the template part on update. There may be other cases where this behavior causes unexpected results. Awareness of this behavior should assist template part designers and users in identifying the cause and resolving it.

    The following example demonstrates adding a data connection to a template part (please note - the third party web services availability may not be consistent); I'm also attaching a template part that you'd get by following the instructions below.

    1. Create a new blank template part
    2. Click Data Connections… from the InfoPath Tools menu
    3. Click Add…
    4. Verify Receive data is selected and click Next
    5. Select Web service and click Next
    6. Enter the web service http://ws.invesbot.com/stockquotes.asmx?wsdl and click Next
    7. Select GetQuote and click Next
    8. Click Set Sample Value…,enter MSFT and click OK
    9. Click Next 3 times
    10. Uncheck Automatically retrieve data when form is opened
    11. Click Finish on Data Connection Wizard
    12. Click More Controls… from the InfoPath Insert menu
    13. Click on a Button control to insert it
    14. Right click on the Button control and click Button Properties…
    15. Set Action to Refresh and click OK
    16. Click Data Source… on the InfoPath View menu
    17. Select the GetQuote (Secondary) data source from the Data Source pane
    18. Expand the data source down to the following field: myFields>queryFields>tns:GetQuote>symbol
    19. Drag the symbol field onto the canvas (a text box control should be created)
    20. Expand the data source down to the following field:
      myFields>dataFields>tns:GetQuoteResponse>GetQuoteResult>StockQuote>Open
    21. Drag the Open field onto the canvas (a text box control should be created). The template part should look similar to this:
    22. Save the template part as StockQuote.xtp

    The template part now contains a data connection. To make the template part available to the form template design mode, do the following:

    1. Create a new blank form template
    2. Click More Controls… from the InfoPath Insert menu
    3. On the Controls pane click Add or Remove Custom Controls…
    4. Click Add…
    5. Select Template part and click Next
    6. Select the StockQuote.xtp file through Browse... and click Next
    7. Finish the Add Custom Controls wizard
    8. On the Controls pane a new control named StockQuote should appear under Custom
    9. Click StockQuote to insert it

    The template part is now part of the form template. Save and preview the form. Type MSFT in the Symbol text box and click the Refresh button (you may need to click Yes to a security dialog box). The Open text box should contain the requested data.

    Richard Witte
    Software Test Engineer

  • Microsoft InfoPath 2010

    New Office 2007 courses from MS Learning

    • 1 Comments

    MS Learning has some free Office 2007 courses. In particular, here’s a What’s New course for InfoPath. Here’s the outline:

    • What's New in Microsoft® Office InfoPath® 2007
    • Converting Office Word and Office Excel Forms to Office InfoPath
      • The Forms Converter
      • How to Convert Existing Forms to Office InfoPath
      • The Design Checker
    • Designing Forms in Office InfoPath 2007
      • Codeless Form Design
      • How to Create Template Parts
      • Multiple Views in an Office InfoPath Form
      • How to Create a Print Layout View
      • Guidelines for Designing Forms
    • Publishing and Managing Forms
      • Multiple Browser Support Using Office Forms Services
      • How to Publish Forms Through E-Mail
      • Creating List Views of Form Data in Office Outlook
      • How to Export Form Data to Office Excel

    Take a look, and let us know if you find it useful!

    Alex Weinstein
    Program Manager

  • Microsoft InfoPath 2010

    Digital Signatures: Layout Tricks

    • 9 Comments

    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).

    Alex Weinstein
    Program Manager

  • Microsoft InfoPath 2010

    InfoPath Forms Services and AJAX

    • 0 Comments

    There's been lots of well-deserved hype lately around Web 2.0 and the technology that's fueling it, AJAX. We're receiving lots of questions such as "do InfoPath browser forms support AJAX?". The answer is more complex than just yes :-); this article aims to clarify any ambiguities here. But first, some definitions.

    What is AJAX?

    Definition from Wikipedia: Ajax, shorthand for Asynchronous JavaScript and XML, is a web development technique for creating interactive web applications. The intent is to make web pages feel more responsive by exchanging small amounts of data with the server behind the scenes, so that the entire web page does not have to be reloaded each time the user makes a change. This is meant to increase the web page's interactivity, speed, and usability. 

    What is a postback?

    After a page has been loaded, a postback is defined as a subsequent browser request to the web server. Ajax is about asynchronous postbacks - those that allow the user to continue working with the page while the refresh happens.

    Does InfoPath Forms Services use AJAX?

    For a broad set of user actions while editing the form, there will be no postback to the server at all - way faster than an asynchronous postback – and the HTML will be incrementally updated, with no flash. This is because InfoPath Forms Services is capable of translating the declarative logic you've placed in the form into dynamic JavaScript snippets.

    Such client-side operations work for actions such as inserting and deleting sections or table rows, conditional visibility or formatting, validation errors, calculations, firing rules to modify data, etc.

    Cases where postback will occur have one thing in common: there's something that absolutely cannot be evaluated on the client, and thus has to be sent to the server for evaluation. For example:

    - there is managed code in the form which needs to run (e.g. AfterChange event)

    - there are complex XPath expressions used for a calculation, condition for a rule etc   

    When postback does need to happen it generally uses an XMLHTTP postback out-of-band; it does not involve a full page postback. The HTML can therefore be incrementally updated.

    How can I tell when the postbacks will happen?

    We'll only touch on this subject very briefly - expect more content on this soon. InfoPath Design Checker, in conjunction with InfoPath Forms Services, will provide you insights on the user actions that will cause a postback. It will also provide suggestions as to how to avoid these postbacks. Here's how to see these suggestions:

    1. Make sure that your form template is browser-enabled by going to Tools | Form Options | Compatibility, and checking "Design a form template that can be opened in a browser or in InfoPath".

    2. In the same dialog, provide a URL to a server running InfoPath Forms Services:

    3. At the bottom of the Design Checker task pane, click Options, then check the "Show Browser Optimization Messages" checkbox:

    4. Observe the postback optimization messages in the design checker task pane:

    We'll follow-up with a separate article on ways to interpret these messages, and tips and tricks around improving form postback experience.

    AJAX and forms hosted in a custom ASPX page

    You can create custom ASPX pages hosting the InfoPath form control (XmlFormView; more on it here). Everything within the form still uses the above AJAX architecture. But if you need code-behind the page to communicate between the InfoPath form and other ASP.NET controls in the page, then those events will trigger a full-page postback.

    Office Forms Services and ASP.NET AJAX framework

    In MOSS 2007, we don't offer integration with the ASP.NET AJAX framework (also known as Atlas) to allow for partial postbacks involving communication with other ASP.NET controls in the page. Another thing to note is that InfoPath XmlFormView control will not work inside an ASP.NET AJAX UpdatePanel. At this point, there is no workaround to make it work. Integration with the ASP.NET AJAX is a frequent customer request; we're carefully considering it for future releases.

    Alex Weinstein
    Program Manager
    Alexei Levenkov
    Software Design Engineer

  • Microsoft InfoPath 2010

    Using SQL Server 2005 Web Services with InfoPath

    • 11 Comments

    Here’s your problem: You want to use a stored procedure in a database to access your data through InfoPath, but you don’t really want to have to write script for every query to change the stored procedure’s query parameters.  We’ve all been there.

    Well, stop your coding, right now, because SQL Server 2005 allows you to create SOAP Web Service “endpoints” that act as a type of exposed stored procedure over HTTP.  Among other cool things, this will allow you to have the parameters you need exposed as query parameters in InfoPath’s data source.  It's easy to set up in SQL Server 2005 using the CREATE ENDPOINT T-SQL statement.  For example, let's say there is a simple Stored Procedure called "getAge" that takes an integer value and returns every person in a table that has that age.  The SQL statement to expose that Stored Procedure as a document literal SOAP Web Service could look like this:

    CREATE ENDPOINT getSpecifiedAgeEndpoint

    STATE = STARTED

    AS HTTP

    (

       SITE = 'myserver',

       PATH = '/getspecifiedagewebservice',

       AUTHENTICATION = ( NTLM ),

       PORTS = ( CLEAR )

    )

    FOR SOAP

    (

       WEBMETHOD 'GetRecordsWithSpecifiedAge'

       (

          NAME = 'AdventureWorks.dbo.getAge',

          SCHEMA = DEFAULT,

          FORMAT = ROWSETS_ONLY

       ),

       WSDL = DEFAULT,

       BATCHES = DISABLED,

       DATABASE = 'AdventureWorks'

    )

    The web service will then be located at http://myserver/getspecifiedagewebservice?wsdl.  Note that this web service will not be exposed through IIS; It’s all happening directly from SQL Server 2005.  One method will be exposed at the URL called “GetRecordsWithSpecifiedAge”.  For more information on getting the endpoint up and running correctly, see this MSDN article.

    Note that a few rules apply to the settings that you put in the CREATE ENDPOINT statement that will make it so your Web Service plays nice with the InfoPath Rich Client.  That is, you are probably better off keeping the FORMAT = ROWSETS_ONLY setting as it appears above.  If not, then your web service will return a good deal more information about the query itself than just the rowset data you want, but it will still work for you.  Additionally, setting BATCHES = DISABLED will disable the sqlbatch webmethod from being automatically available on the web service, a powerful webmethod that you should probably keep disabled if you don’t intend to use it.

    At that point, you should be able to design a form template against the web service like any other.  Using SQL Server 2005 rowsets with browser-enabled form templates is currently not a supported scenario; we’ll keep you posted on possible workarounds.

    Also note that when you are designing a main web service against one of these web services that returns row data, InfoPath will warn you in the Data Connection wizard that the web service may return multiple datasets, and that’s an InfoPath no-no.  Under most circumstances, only one dataset will be returned (a dataset can still have multiple tables) so you can usually safely thank InfoPath for letting you know, and continue on.

    Travis Rhodes
    Software Design Engineer in Test

  • Microsoft InfoPath 2010

    Creating Complex InfoPath Controls in C#

    • 8 Comments

    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.aspx
    http://blogs.msdn.com/ajma/archive/2004/07/08/177512.aspx
    http://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>

    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 control
    Simply 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

    After building this code, a quick search through registry for Guid, will prove that class is really registered.

    Interfaces
    You 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; }

    }

    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;

       }

    } 

    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;

            }

        }

    }

    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>

    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();

    }

    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();

            }

        }

    } 

    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 Demircioglu
    Software Design Engineer

  • Microsoft InfoPath 2010

    Another Way of Sorting Repeating Data

    • 12 Comments

    If you have ever attempted to sort repeating node data (i.e. a Repeating Table or Repeating Section) on an InfoPath form, you will find this functionality is not available through the UI. However, using .NET classes you can easily implement a sorting routine that will work in both client and browser scenarios. We discussed one way to make this happen through custom code in a recent article; this post will show a different way to make it happen. We will take a look at how to implement this functionality along with taking advantage of some new features in InfoPath 2007:

    • Dynamic button labels
    • Complex default value on the Button label to change the caption based on the sort order of the selected field (discussed in this article)

    In this sample scenario, let’s assume you are capturing the following data in a Repeating Table:

    • Last Name (name: LastName)
    • First Name (name: FirstName)
    • Age (name: Age)

    In addition, you want to allow your users to select the field they want to sort on (using a button in the column header) and clicking the button will toggle the sort between Ascending and Descending. Here is a sample form showing those options:

    ** NOTE: Notice the “(Asc)” in the Last Name button label? We’ll show you how to do that at the end of this post!

    The data structure for the above sample is as follows:

    So in this scenario, the user would simply click the button above the field they want to use to sort the data and by default, the first click would sort the data in Ascending order and clicking it again would sort the data in Descending order. Let’s now go ahead and take a look at the code on the click event of the buttons that implements this functionality.

    When each button is clicked, the first thing we do is set the value of the SortOrder and SortField nodes. For ease of implementation, we created a “SpecifySortOptions” procedure that is called from the click event of each button:

    SpecifySortOptions("LastName",XmlDataType.Text,e.ControlId);

    When each button is clicked we call this procedure passing it the field we want to use for sorting (in this case, LastName), the data type of this field (XmlDataType.Text) and the ControlID of the button that was clicked. (The ControlID is used in the Expression for the Default Value property of each button to determine how to change the label.)

    Here is the SpecifySortOptions procedure:

    public void SpecifySortOptions(string SortField, XmlDataType dataType, string ControlID)

    {

    //Create Navigator objects for the main DOM and

    //for the SortOrder and SortField fields

    XPathNavigator xn = this.MainDataSource.CreateNavigator();

    XPathNavigator xnSortOrder = xn.SelectSingleNode("/my:myFields/my:SortOrder", this.NamespaceManager);

    XPathNavigator xnSortField = xn.SelectSingleNode("/my:myFields/my:SortField", this.NamespaceManager);

     

    //Check to see if the value of the SortField is equal

    //to the ControlID that we passed to this procedure. If

    //it is the same and the SortOrder field is an SortOrder

    //emptry string or is set to "Desc" then set the field to

    //"Asc". If the SortField value does not equal the

    //ControlID that we passed to this procedure, then that

    //would mean either the SortField is an empty string or

    //it was set to another field - either way, we will

    //then want the SortOrder value to be "Asc"

    if (xnSortField.Value == ControlID)

    {

       if (xnSortOrder.Value == "" || xnSortOrder.Value == "Desc")

          xnSortOrder.SetValue("Asc");

       else

          xnSortOrder.SetValue("Desc");

    }

    else

       xnSortOrder.SetValue("Asc");

     

    //Call the SortTheData() procedure passing in the values

    //specified above

    SortTheData(SortField, xnSortOrder.Value, dataType);

     

    //Set the SortField value to the current ControlID

    xnSortField.SetValue(ControlID);

    }

    After calling the SpecifySortOptions procedure from the click event of each button, this procedure calls the SortTheData procedure, which accepts a string value for the sort field (strSortField), a string value for the sort order (strSortOrder) and an XmlDataType value (dataType) for the type of data being sorted. This is the code that will actually perform the sorting.

    The first thing we need to do is this procedure is create “XPathNavigator” objects for the main DOM:

    //Create a Navigator object for the main DOM

    XPathNavigator xn = this.MainDataSource.CreateNavigator();

    We then will create an XmlSortOrder object so we can specify either an Ascending or Descending sort. In this sample, we will specify an Ascending sort as the default; however, we will check the value of strSortOrder and if this is set to “Desc”, change the XmlSortOrder object accordingly:

    XmlSortOrder sortOrder = XmlSortOrder.Ascending;

    if (strSortOrder == "Desc")

    sortOrder = XmlSortOrder.Descending;

    To actually perform the sort, we will be using the “AddSort” method of an XPathExpression object – as such, we need to create an XPathExpression object for the repeating (group) node that we are going sort:

    XPathExpression xe = xn.Compile("/my:myFields/my:group1/my:group2");

    Now we can use the AddSort method on the Expression object using the field name (strSortField) that we passed into this procedure, the sort order using the sort order object (sortOrder) we created above and the data type using the data type object (dataType) we passed into this procedure:

    xe.AddSort("*[local-name()='" + strSortField + "']", sortOrder, XmlCaseOrder.None, "", dataType);

    We need to specify a NamespaceManager for the Expression object and for this we will use the SetContext method:

    xe.SetContext(this.NamespaceManager);

    The next step is to create an XPathNodeIterator object, passing it our XPathExpression object, so we can iterate all the nodes now that they are sorted - in addition, we will use this object (in the lastNode expression below) to get a count of the total nodes in this repeating group:

    XPathNodeIterator xi = xn.Select(xe);

    In the end, the way this procedure works is to delete the existing “un-sorted” nodes and add back the “sorted” nodes via the XPathNodeIterator object. So the next step is to now delete the existing “un-sorted” data. To do this, we will create XPathNavigator objects to reference the first and last nodes in this repeating group and then use the DeleteRange method to delete those nodes:

    XPathNavigator firstNode = xn.SelectSingleNode("/my:myFields/my:group1/my:group2[1]", this.NamespaceManager);

    XPathNavigator lastNode = xn.SelectSingleNode("/my:myFields/my:group1/my:group2[" + xi.Count + "]", this.NamespaceManager);

    firstNode.DeleteRange(lastNode);

    At this point, we have the sorted data in memory and the un-sorted data has been removed so we are ready to add that sorted data back to the form. For this process, we will use the XPathNodeIterator object we created earlier to iterate over the nodes.

    while (xi.MoveNext())

    {

    //Create string variables to hold the values of each field

    //as we iterate the nodes

    string strLastName = xi.Current.SelectSingleNode("my:LastName", this.NamespaceManager).Value;

    string strFirstName = xi.Current.SelectSingleNode("my:FirstName", this.NamespaceManager).Value;

    string strAge = xi.Current.SelectSingleNode("my:Age", this.NamespaceManager).Value;

     

    //Call the AddNewRow method to append a new row

    // to the repeating group

    AddNewRow(xn.SelectSingleNode("/my:myFields/my:group1", this.NamespaceManager));

     

    //Since we are continually appending new rows, the 

    //"last" row will always be the one where we need

    //to set the values - so here we will create a

    //Navigator object for this newly added row - we

    //will use this for setting the field values below

    XPathNavigator xnNewRow = xn.SelectSingleNode("/my:myFields/my:group1/my:group2[last()]", this.NamespaceManager);

     

    xnNewRow.SelectSingleNode("my:LastName", this.NamespaceManager).SetValue(strLastName);

    xnNewRow.SelectSingleNode("my:FirstName", this.NamespaceManager).SetValue(strFirstName);

     

    //Since the Age field is numeric, it will contain

    //the "nil" attribute. We need to remove this

    //arrtibute prior to programmatically setting the

    //value. To do this, we'll call the DeleteNil

    //procedure passing it the node that contains

    //(or may contain) the nil attribute

    DeleteNil(xnNewRow.SelectSingleNode("my:Age", this.NamespaceManager));

     

    //Now we can set the value of the Age field

    xnNewRow.SelectSingleNode("my:Age", this.NamespaceManager).SetValue(strAge);

    }

    In the while loop above, we used the “AddNewRow” and “DeleteNil” procedures – these are documented below:

    public void AddNewRow(XPathNavigator docXN)

    {

    //Create a Navigator object to reference the node

    //we will be adding. To do this, we can use the

    //templates' "Manifest.xsf" file to get the

    //appropriate node to add. As you can see, this is

    //specific to the control's "name", which you can

    //get from the Advanced tab on the Properties window

    //for the repeating control. Once you have this,

    //use the "Save As Source Files" command from the 

    //File menu in InfoPath and locate the appropriate

    //expression in your Manifest.xsf file

    XPathNavigator xnNode = this.Template.Manifest.SelectSingleNode("//xsf:xDocumentClass/xsf:views/xsf:view/xsf:editing/xsf:xmlToEdit[@name='group2_1']/xsf:editWith/xsf:fragmentToInsert/xsf:chooseFragment/my:group1", this.NamespaceManager);     

     

    //Append the node from the Manifest file to the main DOM

    docXN.SelectSingleNode("/my:myFields/my:group1", this.NamespaceManager).AppendChild(xnNode.InnerXml);

    }

     

    public void DeleteNil(XPathNavigator node)

    {

    //Check to see if the nil attribute exists

    //and if so, delete it

    if (node.MoveToAttribute("nil", http://www.w3.org/2001/XMLSchema-instance))

        node.DeleteSelf();

    }

    And that’s it! You now have the functionality of sorting data in a repeating node. For reference, the complete code for this sample is attached.

    Now – about that button label…how did we do that??!

    With InfoPath 2007, we have a new feature that allows you to specify a dynamic value for the button label. To do this, simply click the “fx” button next to the label property and you can choose to use an expression of a field/group from your form:

    However, for this sample the conditional logic for the button label is quite complex: we need to determine which button was clicked and whether we should show “(Asc)” or “(Desc)” next to the correct label. For this, we used the process demonstrated in this blog post: Conditional Default Values.
    So here is the logic that needed to be implemented when each button is clicked; for example, for the Last Name button:

    • See if the SortField value (which is set to a button’s ControlID in the SpecifySortOptions procedure) is equal to the clicked button’s ControlID and if the SortOrder value is either an empty string or equal to “Asc”
      • If it is, then set the label to: Last Name (Asc)
    • See if the SortField value is equal to the clicked button’s ControlID and the SortOrder value is “Desc”
      • If it is, then set the lable to: Last Name (Desc)
    • If neither of the above are true, then a different button must have been clicked so set the label to: Last Name

    This is the logic that needs to be implemented for each button. Here is a sample expression for the LastName field:

    concat(substring("Last Name (Asc)", 1, ((my:SortOrder = "" or my:SortOrder = "Asc") and my:SortField = "btnLastName") * string-length("Last Name (Asc)")), substring("Last Name (Desc)", 1, (my:SortOrder = "Desc" and my:SortField = "btnLastName") * string-length("Last Name (Desc)")), substring("Last Name", 1, not(my:SortField = "btnLastName") * string-length("Last Name")))

    Each of the above “substring” expressions are tested in order – so if the SortOrder field does not equal an empty string or does not equal “Asc” and the SortField value does not equal “btnLastName” then we test the next condition. If the SortOrder value does not equal “Desc” and the SortField value does not equal “btnLastName” then we test the last condition. And here we only need to check the value of my:SortField – if this does not equal “btnLastName” then we know a different button was clicked and we only want the label to display “Last Name”.

    So there you have it! A way to sort data in your repeating table and a really cool way to let the user know which field they clicked for sorting and in which order the data has been sorted!

    ** NOTE: It seems we may have a bug with our expression box in that it will accept the entire conditional statement noted above but once you close and re-open the box, the string gets truncated. Once you have this working, you may want to keep that expression saved in a text file.

    Scott Heim
    Support Engineer

  • Microsoft InfoPath 2010

    Information Rights Management: Protecting Forms

    • 5 Comments

    In a recent post, we discussed ways to protect sensitive data in your InfoPath forms. This article will drill down on one of these mechanisms - Information Rights Management, a.k.a. IRM. InfoPath 2007 features the ability to set permissions on both the form template that you’re designing and the forms that are based on it. You can choose to restrict who can read or write your form/form template, as well as whether read permissions include the ability to copy, print, etc. This feature is available in the both the designer and editor by clicking the Permission button on the Standard toolbar:

    Form instance permissions
    When filling out a form, users can set permissions for that particular form. Clicking on the Permissions button in the Standard toolbar will pop up the following dialog:

    The user that created that instance of the form will have full control permissions and can grant read or write permissions to other users. Users that do not have any permissions to that form will not be able to even open it.

    This feature can make InfoPath a great tool for scenarios where some degree of privacy is required, for example, feedback to a manager to peer.

    Form template permissions
    In addition to setting permissions for each individual form that is filled out, InfoPath also provides Permission functionality for the form template. When designing a form template, clicking on the Permissions button will pop up the Permission dialog:

    The ‘Restrict permission to this form template’ checkbox at the top will initially be unchecked – check it to turn on Permissions. You will then be able to select users or groups that will have different levels of permissions for this form template.

    Keep in mind that this area only allows you to control access to the form template itself, not to the forms that are based upon it. To restrict permissions to the forms based on this form template, you would check the second checkbox, ‘Restrict permission to forms based on this form template’:

    As mentioned for users setting these permissions in the editor, the user that created the instance of the form will always have Full Control permissions for that form instance, but you can get more specific by clicking ‘Change permissions…’. Here you’ll be able to select who will have Read and Change permissions and which additional features will be available to users.

    If you are designing a form for your specific team, you can take advantage of this feature to only give Write rights to members of the team. Everyone else can be given Read rights.

    Managing your credentials
    When working with InfoPath forms or form templates that have restricted permissions, you can select an account to access the content with. For example, if your personal e-mail address does not have permissions to view an expense reporting form, you can set InfoPath to use your work address instead.

    You can access this feature by clicking on Manage Credentials in the File menu:

    Thanks all. I hope you find this useful.

    One thing to keep in mind is that IRM is not a security feature – there are ways to get around it. It’s there to make it more difficult for users to accidentally or maliciously tamper with sensitive data.

    Bojana
    Program Manager

  • Microsoft InfoPath 2010

    Firing rules when removing InfoPath controls

    • 1 Comments

    The built-in support InfoPath offers for rules can be used to generate a relatively large and powerful set of conditions that trigger a rule action. There are some scenarios though were the default options available through the condition builder may not be sufficient. One of them is firing a rule when certain control has been deleted.

    The above limitation can be worked around by writing form code. However in some cases it may be possible to achieve this scenario only by using the declarative logic InfoPath provides through rules. The pre-condition for this is to have a document schema that allows for some extra helper nodes.

    The workaround uses two extra fields for each control on which we want to have a rule triggered on delete. The fields are simply counters of the old/new number of controls. One of them is bound through an XPath to the current number of the controls (i.e. repeating table rows) we are interesting in. For example, in the attached sample I want to know when a row in a repeating table has been deleted. The XPath expression is simply a count like:

    count(/my:myFields/my:group3/my:group4)

    The second field will keep track of the existing (“old”) number of the controls. We need to set a default value on it corresponding to the number of controls existing when the form is opened. There are two rules applied to the first field to keep the values in sync:

    The first one is used to perform the main action and it fires when the “new” counter is less than the old. The second rule updates the value of the old counter.

    Using the above procedure looks a little bit hacky but it works well for InfoPath 2003/2007. Note that the approach will work for browser-enabled forms also. The sample form template is attached; download XSN to your desktop, then right-click it and select "Open in Design Mode".

    Silviu Ifrim
    Software Design Engineer

  • Microsoft InfoPath 2010

    Two articles from the community

    • 0 Comments

    Making my Technorati rounds searching for InfoPath community content, I came across two very cool articles:

    1) InfoPath 2007, Forms Server, MOSS 2007 by Sahil Malik: start from a Word form, publish it to be filled out in the browser, play with the content type and configure the document library. Fun read, and lots of screenshots.

    2) InfoPath Forms for Mobile Web Browsers by David Gerhardt: good starting point if you're exploring your way around the mobile forms arena. (UPDATE: Fixed link)

    Alex Weinstein
    Program Manager

  • Microsoft InfoPath 2010

    Sorting Repeating and Tabular Data

    • 4 Comments

    InfoPath is a great way to easily gather and present XML data to the masses. But what about efficiently presenting a massive amount data? Tons of data (e.g., a list of hundreds product names) usually find their ways into lists that we’ll find on SharePoint, in a database, or just sitting in an XML file. Controls like Repeating Sections and Tables are useful mechanisms for displaying lists of data. But their usefulness is limited to how efficiently the user of your form can find and work with such large lists of data. An easy solution to effectively present large lists of data to your users is through sorting that data.

    No Sorting Options in InfoPath
    Try scouring the Repeating Table or Repeating Section controls’ properties dialogs for anything related to “sort”. Sorry to say, you’ll come up empty handed. That’s why we thought you’d find this blog entry handy. Our goal is to show you how to sort any repeating data and to do so in many different ways: sort by string, sort by number, sort ascending or descending, and even sort by different fields (or columns) in the repeating data. What we’ll show you, in both the code and the design of the view, is designed to be browser-compatible. This means you can publish this form template to a SharePoint server running Forms Services 2007 and fill out the form in a Web browser. Let’s get started.

     

    Figure 1: Filling out the SortingPeople sample form template

    The sample form template is attached; download the ZIP to your desktop, expand it, then right-click the XSN and select "Open in Design Mode". This sample requires InfoPath 2007, and will work in the browser when published to InfoPath Forms Services.

    The View for the SortingPeople Form Template
    To show you how to sort data in your form, we’ll use a concocted example to sort a list of people. As you can see in Figure 1, there are various options to sort the data. Clicking the “Sort” Button triggers the sort otherwise the data is not sorted automatically (more on this later). You can also sort by clicking any of the “Sort by this column” Buttons in the header and footer of any column. There are three columns in the Repeating Table representing our repeating data: Last Name, First Name, and Age. (The string data type is used for the first two columns and an integer data type for the last column.) In Figure 1, we’ve filled out the form with names of famous architects. (Their ages are fictitious.) While we’re not working with enough data to really justify a sort, this example is simply for demonstrative purposes. As you might expect, we can sort the Repeating Table data by any column and by any direction (ascending or descending). At any time we can change the existing data or even add or remove rows. But we would need to invoke the sorting functionality to put things into place again.

    The Data Source for the SortingPeople Form Template
    To begin designing this form template we started with controls and the data source. We inserted a Repeating Table control and renamed its data source groups and fields to map with our people example. The main data source is shown in Figure 2. To accommodate the various sorting options that we expose via controls at the top of the view (e.g., the Sort By Drop-Down List Box), we’ve added attribute fields under the SortingPeople document element. These attribute fields remember, for example, whether we’re sorting by the Last Name or the Age column.

    Figure 2: Data source for the SortingPeople sample form template

    Considerations When Sorting Data
    Now we’ll look at how we implemented sorting functionality behind this form template. This is accomplished through C# code behind the form. While we could have opted for an easier XSL-based implementation to sorting data, it would not have been compatible with Forms Services. So we’re left to actually sorting the data. There are advantages as well as disadvantages to our approach. Sorting the data is preferred when you want to persist the sorting in the saved or submitted form. While the initial cost is much higher to sort the data in the data source instead of the view, there is much less processing that occurs on subsequent visits to that data because it will already be sorted. If your form template has multiple views, switching to and from a view that performs an XSL sort is very expensive. On the contrary, a sorted data source adds no additional processing requirements on view switches. A final reason why you may want to sort the data in the data source instead of the view: submitting sorted data to a database or Web service may be optimal (or even a requirement) for that backend system.

    Sorting Data with C# Form Code
    To best understand how we designed the code behind form template to support sorting, we’ll be talking about its C# form code and how it interacts with the main data source. Let’s start by looking at some of the supporting form code that will make it easier for us to implement the sort feature. The properties are used within the sort itself to read the options at the top of the view about how to sort the data. The methods are very useful helpers that we use throughout our sample code.

    /// <summary>

    /// Returns the "Sort By" Drop-Down value.

    /// The value returned by this property MUST match with an item within the Repeating Table.

    /// </summary>

    private string SortBySelection

    {

        get { return GetValue("@my:SortBy").Replace(" ", string.Empty); }

    }

     

    /// <summary>

    /// Does the user want the SortBy by number (true) or string (false)?

    /// </summary>

    private bool SortAsNumber

    {

        get

        {

            return 0 == GetValue("@my:SortAs").CompareTo("number");

        }

    }

     

    /// <summary>

    /// Does the user want an ascending (asc) or descending (des) sort?

    /// </summary>

    private bool SortAscending

    {

        get

        {

            return 0 == GetValue("@my:Order").CompareTo("asc");

        }

    }

     

    /// <summary>

    /// Helper to wrap an int within brackets.

    /// </summary>

    /// <param name="intToWrap"></param>

    /// <returns></returns>

    private string WrapAsIndexer(int intToWrap)

    { return WrapAsIndexer(intToWrap.ToString()); }

     

    /// <summary>

    /// Helper to wrap a string within brackets.

    /// </summary>

    /// <param name="strToWrap"></param>

    /// <returns></returns>

    private string WrapAsIndexer(string strToWrap)

    { return "[" + strToWrap + "]"; }

     

    /// <summary>

    /// Helper to get an XPathNavigator's value.

    /// </summary>

    /// <param name="xpath"></param>

    /// <returns></returns>

    private string GetValue(string xpath)

    {

        return Root.SelectSingleNode(xpath, NamespaceManager).Value;

    }

     

    /// <summary>

    /// Helper to set an XPathNavigator's value.

    /// </summary>

    /// <param name="xpath"></param>

    /// <param name="value"></param>

    private void SetValue(string xpath, string value)

    {

        Root.SelectSingleNode(xpath, NamespaceManager).SetValue(value);

    }

     

    /// <summary>

    /// Helper to get the document element of the main data source.

    /// </summary>

    private XPathNavigator Root

    {

        get { return CreateNavigator().SelectSingleNode("/my:SortingPeople", NamespaceManager); }

    }

    Next, let’s take a look at the code behind all of the Buttons in our form template. We created these event handlers through each Button’s properties dialog. Their implementations are quite trivial. You can see that adding additional sorting columns to the Repeating Table is a simple task. If you wanted to add a column that doesn’t need to be sorted, there’s nothing to do beyond adding the column in the Table!

    public void SortButton_Clicked(object sender, ClickedEventArgs e)

    {     

        SortList();

    }

     

    public void LastNameSort_Clicked(object sender, ClickedEventArgs e)

    {

        SetValue("@my:SortBy", "Last Name");

        SortList();

    }

     

    public void FirstNameSort_Clicked(object sender, ClickedEventArgs e)

    {

        SetValue("@my:SortBy", "First Name");

        SortList();

    }

     

    public void AgeSort_Clicked(object sender, ClickedEventArgs e)

    {

        SetValue("@my:SortBy", "Age");

        SortList();

    }

    Now the million dollar question: what’s behind the SortList method? Let’s look and then we’ll explain how it works.

    /// <summary>

    /// Bubble sorts the list of people.

    /// </summary>

    private void SortList()

    {

        string sortBy = SortBySelection;

        string itemsToSort = "my:People/my:Person";

        System.Globalization.CultureInfo currentThreadCulture =

            System.Threading.Thread.CurrentThread.CurrentCulture;

     

        int numPeople = Root.Select(itemsToSort, NamespaceManager).Count;

     

        // basic bubble sort implementation

        for (int i = 1; i < numPeople; i++) // xpath is 1-based

        {

            for (int j = i + 1; j <= numPeople; j++) // keep j ahead of i; we can index [numPeople]

            {

                // swap (i,j) if necessary

                string iValue = GetValue(itemsToSort + WrapAsIndexer(i) + "/my:" + sortBy);

                string jValue = GetValue(itemsToSort + WrapAsIndexer(j) + "/my:" + sortBy);

     

                // Do we sort by number or string?

                if (SortAsNumber)

                {

                    int iNum, jNum;

                    if (!Int32.TryParse(iValue, out iNum) || !Int32.TryParse(jValue, out jNum))

                    {

                        // Let InfoPath take care of the invalid datatype with its own validation, we'll keep sorting the rest

                        continue;

                    }

     

                    if ((SortAscending && iNum > jNum) || (!SortAscending && iNum < jNum))

                    {

                        Swap(itemsToSort + WrapAsIndexer(i), itemsToSort + WrapAsIndexer(j));

                    }

                }

                else // SortAsString

                {

                    if ((SortAscending && String.Compare(

                        iValue, jValue, true /*ignoreCase*/, currentThreadCulture) > 0)

                        || (!SortAscending && String.Compare(

                        iValue, jValue, true /*ignoreCase*/, currentThreadCulture) < 0))

                    {

                        Swap(itemsToSort + WrapAsIndexer(i), itemsToSort + WrapAsIndexer(j));

                    }

                }

            } // end inner-for

        } // end outer-for

    }

    Analyzing the C# Form Code
    Let’s break down what we’re doing in this SortList method. First we get the column to use for sorting, the XPath to the repeating group that we want to sort, and the culture of the thread so we respect the current locale when sorting. Next we get the number of people that we’ll be sorting. We need this number because we’ll use it for our bubble sort implementation.

    The two nested for-loops implement the bubble sort algorithm. We chose bubble sort because of its simplicity and for demonstrative purposes. (We’d recommend you use the most efficient sorting algorithm based on your requirements.) The variables i and j iterate through the people. We use the iValue and jValue variables to select the data pointed at by i and j to determine if a swap is necessary as part of the sort loop.

    Next, we have an if-else statement that checks if the sort is by string or by number. A sort by number will attempt to parse out 32-bit integer values from the iValue and jValue fields. If the parse fails for any reason, we skip this specific comparison and continue trying to sort the rest of the data. Once we have integers, we do a simple comparison and swap if needed. If we’re sorting by string instead of numerical value, we use the static .NET library String.Compare method to make a culture sensitive comparison. A swap is performed if it’s necessary. (Note that we could have combined some code in the SortList method to make it more compact. We left the structure of the code unoptimized for maximum readability.)

    The last bit of code we have not yet revealed is the Swap method. This method, as its name suggests, simply swaps the positions of two XPathNavigator objects as identified by their XPaths. (An XPathNavigator is a pointer into an XML tree of data. You can read more about the XPathNavigtor class on MSDN.)

    /// <summary>

    /// Swaps two XPathNavigators at xpath1 and xpath2.

    /// </summary>

    /// <param name="xpath1">First XPath.</param>

    /// <param name="xpath2">Second XPath.</param>

    private void Swap(string xpath1, string xpath2)

    {

            XPathNavigator item1 = Root.SelectSingleNode(xpath1, NamespaceManager);

            XPathNavigator item2 = Root.SelectSingleNode(xpath2, NamespaceManager);

     

            // Make a copy of item1

            XPathNavigator item1Clone = item1.Clone();

            // Move item2 to item1

            item1.ReplaceSelf(item2);

            // Make the original item2 be item1 that we cloned earlier

            item2.ReplaceSelf(item1Clone);

    }

    Sorting Automatically
    One of the things you might ask is why we decided against sorting the data automatically. The most important reason is user experience and then followed by form responsiveness. Think about what would happen if the Repeating Table rows sorted themselves whenever data changed that required a sort. Say you were entering data into a new row. You start by typing a person’s last name and then either hit tab or click into the First Name field. If the form is sorting by the Last Name field, the row may have jumped to another location relative to the other rows in the Repeating Table! You would expect that you could fill in all of the data for a new row before it sorts itself. There are also other weird cases that automatic sorting would spoil. For example, you would not see a new row added within the middle of the Repeating Table. Why? As soon as you added it, it immediately jumped to the top (or bottom) of the Repeating Table as sorted data. Let’s consider for a moment why we didn’t automatically sort because of form responsiveness; for now let’s assume that we somehow worked out all of the kinks with the user model. Obviously the form will be less responsive in InfoPath, especially on a slow computer, with so much sorting. But the real problem is realized when filling out such an intense form template in a Web browser. Every time code runs while a form is filled out in a browser, the form data is posted back to the server for processing. Postbacks themselves can take several seconds or even minutes depending on many variables including the network connection speed as well as the client and server machines’ performance capabilities. As you can see, an automatically sorting form isn’t necessarily a better form.

    Hagen Green
    Software Design Engineer in Test

Page 5 of 12 (298 items) «34567»