TX Text Control .NET for WPF

Reading Form Fields and Values

Topic

Once a customer has filled in a PDF form and sent it back, your WPF application needs the values, not the pages. Forms.GetAcroFormFields reads them straight from the PDF file or a byte array, without loading the document into the TextControl of a window. The values can be shown, stored or transferred back into the form in the editor, where a user checks and corrects them.

The examples read form-filled.pdf, the form of Create a PDF Form with the fields name, country and newsletter, filled in and saved in a PDF reader. Creating AcroForms describes how such forms are made.

How GetAcroFormFields Reads a Form

Forms.GetAcroFormFields, introduced in version 29.0, is a static method of the TXTextControl.DocumentServer.PDF namespace. It takes the path of a PDF file or the PDF as a byte array and returns an array of FormField objects of the TXTextControl.DocumentServer.PDF.AcroForms namespace. Each object is of the class of its field:

ClassField in the PDFValue
FormTextFieldText field; also the date form fields of TX Text ControlValue: the text entered
FormCheckBoxCheck boxIsChecked
FormRadioButtonRadio button, derived from FormCheckBoxIsChecked
FormChoiceFieldThe base class of combo boxes and list boxesValue: the selected item; Options: all items
FormComboBoxCombo box or drop-down list, derived from FormChoiceFieldValue
FormListBoxList box, derived from FormComboBoxValue
FormButtonPush buttonNone

The base class gives every field these members:

  • FieldName: the name of the field, for forms of TX Text Control the Name of the form field.
  • AlternateFieldName: the alternate name, shown by PDF readers as the tooltip.
  • FieldType: Tx for text, Btn for buttons, Ch for choice fields and Sig for signature fields.
  • Bounds: the rectangle of the field in the document.

Forms filled in with any of the readers in Where Users Fill In the Form can be read. In Google Chrome, users save the filled form with the Download button and choose the version with their changes.

Show the Values of a Filled Form

Add a button with the handler ReadFormButton_Click to MainWindow.xaml. The following code in MainWindow.xaml.cs collects the values of a PDF form by field name and shows them in a message box:

private Dictionary<string, string> ReadFormValues(string path)
{
    Dictionary<string, string> values = new Dictionary<string, string>();

    foreach (AcroForms.FormField field in Forms.GetAcroFormFields(path))
    {
        switch (field)
        {
            case AcroForms.FormTextField textField:
                values[field.FieldName] = textField.Value;
                break;

            case AcroForms.FormCheckBox checkBox:          // check boxes and radio buttons
                values[field.FieldName] = checkBox.IsChecked ? "true" : "false";
                break;

            case AcroForms.FormChoiceField choiceField:    // combo boxes and list boxes
                values[field.FieldName] = choiceField.Value;
                break;
        }
    }

    return values;
}

private void ReadFormButton_Click(object sender, RoutedEventArgs e)
{
    OpenFileDialog dialog = new OpenFileDialog() { Filter = "PDF forms|*.pdf" };

    if (dialog.ShowDialog(this) != true)
    {
        return;
    }

    Dictionary<string, string> values;

    try
    {
        values = ReadFormValues(dialog.FileName);
    }
    catch (FilterException exception)
    {
        MessageBox.Show(this, "The form could not be read: " + exception.Reason, "PDF Form");
        return;
    }

    if (values.Count == 0)
    {
        MessageBox.Show(this, "The PDF document has no form fields.", "PDF Form");
        return;
    }

    MessageBox.Show(this, string.Join("\n", values.Select(value => value.Key + ": " + value.Value)), "PDF Form");
}

The code needs these using directives:

using System.Collections.Generic;
using System.Linq;
using System.Windows;
using Microsoft.Win32;
using TXTextControl;
using TXTextControl.DocumentServer.PDF;
using AcroForms = TXTextControl.DocumentServer.PDF.AcroForms;

The code works as follows:

  • The alias AcroForms keeps the PDF classes apart from the form fields of the document: both namespaces contain a class named FormField.
  • The switch statement handles check boxes and radio buttons through FormCheckBox, and combo boxes and list boxes through the base class FormChoiceField, because only their values are needed. To tell the derived classes apart, test FormRadioButton before FormCheckBox and FormListBox before FormComboBox.
  • A name can occur more than once in the array. The indexer of the dictionary keeps the last value, where ToDictionary would throw.
  • A file that can't be read throws a FilterException. GetAcroFormFields has no password parameter, so PDF forms that require a password to open can't be read.

For the form of Getting Started, the message box shows lines such as name: Tim Typer, country: Germany and newsletter: true.

Put the Values Back Into the Form

TX Text Control doesn't write values into an existing PDF. Load your document of the form, the one the PDF was created from, into textControl1, and transfer the values into its form fields:

private void FillEditorFormButton_Click(object sender, RoutedEventArgs e)
{
    Dictionary<string, string> values = ReadFormValues("form-filled.pdf");

    // The main text, headers, footers and text frames
    foreach (IFormattedText textPart in textControl1.TextParts)
    {
        foreach (FormField formField in textPart.FormFields)
        {
            if (string.IsNullOrEmpty(formField.Name) ||
                !values.TryGetValue(formField.Name, out string value) || value == null)
            {
                continue;
            }

            switch (formField)
            {
                case CheckFormField checkFormField:
                    checkFormField.Checked = value == "true";
                    break;

                case SelectionFormField selectionFormField:
                    // -1 selects nothing if the value isn't one of the items
                    selectionFormField.SelectedIndex = Array.IndexOf(selectionFormField.Items, value);
                    break;

                case TextFormField textFormField:
                    textFormField.Text = value;
                    break;
            }
        }
    }
}

The code works as follows:

  • TextParts of the WPF TextControl contains the main text, the headers and footers and the text frames, each with its own FormFields collection.
  • Each form field type receives the value through its own property: Checked, SelectedIndex of the Items or Text.
  • Date form fields are skipped, because their value arrives as the displayed text. To set one, parse the text with the culture of the form and assign DateFormField.Date.

The user sees the values in the form at once. Saved again as PDF, the document is a new fillable form; flattened first, it has fixed values, as Exporting Interactive and Flattened Forms shows. A MailMerge with FormFieldMergeType.Preselect fills form fields from data as well, as Merge Fields describes.

Next Steps