TX Text Control .NET for Windows Forms

Data Binding and Database Storage

Topic

Windows Forms data binding connects a property of one object to a property of another, so that a value shown in a control follows the data without event handlers. With a TextControl, it serves two purposes: controls such as check boxes and combo boxes follow the formatting at the input position, and the document itself comes from and goes back to a record of a table. The sections below cover both and end with reading and writing documents in a database.

Filling a template with the values of a database, rather than editing a stored document, is mail merge: see Mail Merge and DataSets and DataTables.

What Can Be Bound

BindingSourcePage section
Formatting at the input position, such as bold text or the fontInputFormat, returned by TextControl.InputFormatBind Controls to the Formatting
The document of the current recordA property of your own that calls Load and SaveBind the Document to a Record
A document column of a table, loaded and saved when the record changesThe CurrentChanged event of a BindingSourceLoad and Save When the Record Changes

TextControl has no documented property that holds the formatted document, the way the Rtf property of a RichTextBox does. The content therefore reaches a data source through Load and Save, either inside a bindable property or in an event handler.

Bind Controls to the Formatting

InputFormat represents the formatting attributes at the current input position. Its properties are updated when the input position moves, which makes the object a data source for the DataBindings of other controls. If text is selected and the selection has no common value for an attribute, the property is null.

The following Form1.cs belongs to a form with a TextControl named textControl1, a CheckBox named checkBoxBold and two ComboBox controls named comboBoxFont and comboBoxSize, all added in the designer:

using System;
using System.Windows.Forms;

namespace MyWinFormsApp
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            Load += Form1_Load;
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            // A check box that shows and sets bold text
            checkBoxBold.DataBindings.Add("Checked", textControl1.InputFormat, "Bold",
                true, DataSourceUpdateMode.OnPropertyChanged);

            // A combo box with the fonts of the editor
            comboBoxFont.DataSource = textControl1.GetSupportedFonts();
            comboBoxFont.DataBindings.Add("Text", textControl1.InputFormat, "FontFamily",
                true, DataSourceUpdateMode.OnPropertyChanged);

            // A combo box for the font size: InputFormat uses twips, the box shows points
            Binding sizeBinding = new Binding("Text", textControl1.InputFormat, "FontSize",
                true, DataSourceUpdateMode.OnPropertyChanged);
            sizeBinding.Format += SizeBinding_Format;
            sizeBinding.Parse += SizeBinding_Parse;
            comboBoxSize.DataBindings.Add(sizeBinding);
        }

        private void SizeBinding_Format(object sender, ConvertEventArgs e)
        {
            // Twips to points for the display
            if (e.Value is int twips)
            {
                e.Value = (twips / 20).ToString();
            }
        }

        private void SizeBinding_Parse(object sender, ConvertEventArgs e)
        {
            // Points to twips for the editor
            if (int.TryParse(e.Value as string, out int points))
            {
                e.Value = points * 20;
            }
        }
    }
}

Keep the namespace Visual Studio created for your project. Set the Appearance of the check box to Button in the designer if it should look like a toolbar button.

The code works as follows:

  • DataBindings.Add connects a property of the control, such as Checked, to a property of the data source, here InputFormat.Bold. DataSourceUpdateMode.OnPropertyChanged writes a change of the check box to the editor at once, so that clicking it makes the selected text bold.
  • GetSupportedFonts returns the names of the fonts the editor can use. It returns null before the control is completely initialized, which is why the bindings are set up in the Load event of the form. The Text of the combo box follows InputFormat.FontFamily.
  • InputFormat.FontSize is in twips, a twentieth of a point. Windows Forms has no value converters for this, so the Format event of the Binding converts twips to points for the combo box and the Parse event converts the typed value back.

The same pattern works for every property of InputFormat, from Italic and Underline to the paragraph alignment and the list format.

Bind the Document to a Record

A property that loads and saves the document makes the content bindable like any other property. The following class derives from TextControl and adds an Rtf property, which reads and writes the document as an RTF string. Add it as RtfTextControl.cs to the project:

using System.ComponentModel;
using TXTextControl;

namespace MyWinFormsApp
{
    public class RtfTextControl : TXTextControl.TextControl
    {
        public RtfTextControl()
        {
            // Create the control now, so that Load and Save work before the form is shown
            CreateControl();
        }

        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public string Rtf
        {
            get
            {
                string data;
                Save(out data, StringStreamType.RichTextFormat);
                return data;
            }
            set
            {
                if (string.IsNullOrEmpty(value))
                {
                    ResetContents();
                }
                else
                {
                    Load(value, StringStreamType.RichTextFormat);
                }
            }
        }
    }
}

Build the project, and RtfTextControl appears in the toolbox. Add it to a form with a BindingSource named bindingSource1 and a BindingNavigator named bindingNavigator1, and bind it in the Load event of the form:

private void Form1_Load(object sender, EventArgs e)
{
    // Three records with an RTF column
    DataTable table = new DataTable("Letters");
    table.Columns.Add("Body", typeof(string));
    table.Rows.Add(@"{\rtf1 First letter\par}");
    table.Rows.Add(@"{\rtf1 Second letter\par}");
    table.Rows.Add(@"{\rtf1 Third letter\par}");

    bindingSource1.DataSource = table;
    bindingNavigator1.BindingSource = bindingSource1;

    // Bind the Rtf property to the Body column
    rtfTextControl1.DataBindings.Add("Rtf", bindingSource1, "Body");
}

It needs using System.Data; for DataTable. Run the application and click the arrows of the navigator: the editor shows the letter of each record.

A Windows Forms form with a BindingNavigator at record 2 of 3 above a TextControl with its button bar, rulers and status bar, showing the text of the second record.

The code works as follows:

  • The getter of Rtf calls Save with StringStreamType RichTextFormat; the setter calls Load, or ResetContents for an empty value. HTMLFormat works the same way for an HTML column.
  • The two attributes keep the property out of the Properties window and out of the code the designer writes.
  • Windows Forms reads the getter when it writes the value back to the record, by default when the control is validated, for example when the user moves the focus to another control. The edits of a letter are then in its row of the table.

Text Control publishes this approach as the sample TextControl.WindowsForms.PropertyBinding, described in the blog article RichTextBox Compatible Data Binding with TX Text Control.

Load and Save When the Record Changes

Instead of a derived control, an event handler can move the document between the editor and the table. The CurrentChanged event of a BindingSource occurs when the user moves to another record: the handler first stores the document of the record that is left and then loads the one of the new record. The following form keeps the documents in a byte array column, in the TX Text Control format:

using System;
using System.Data;
using System.Windows.Forms;
using TXTextControl;

namespace MyWinFormsApp
{
    public partial class Form1 : Form
    {
        private readonly DataTable documents = new DataTable("Documents");
        private DataRowView currentRow;

        public Form1()
        {
            InitializeComponent();
            Load += Form1_Load;
            FormClosing += Form1_FormClosing;
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            // Id, name and content of each document
            documents.Columns.Add("Id", typeof(int));
            documents.Columns.Add("Name", typeof(string));
            documents.Columns.Add("Content", typeof(byte[]));

            // Fill the table from your database here
            documents.Rows.Add(1, "Offer", null);
            documents.Rows.Add(2, "Contract", null);

            bindingSource1.DataSource = documents;
            bindingSource1.CurrentChanged += BindingSource1_CurrentChanged;
            BindingSource1_CurrentChanged(bindingSource1, EventArgs.Empty);
        }

        private void BindingSource1_CurrentChanged(object sender, EventArgs e)
        {
            // Keep the edits of the document the user leaves
            StoreCurrentDocument();

            currentRow = bindingSource1.Current as DataRowView;
            if (currentRow == null)
            {
                return;
            }

            // Show the document of the new record
            if (currentRow["Content"] is byte[] content)
            {
                textControl1.Load(content, BinaryStreamType.InternalUnicodeFormat);
            }
            else
            {
                textControl1.ResetContents();
            }
        }

        private void StoreCurrentDocument()
        {
            if (currentRow == null)
            {
                return;
            }

            byte[] content;
            textControl1.Save(out content, BinaryStreamType.InternalUnicodeFormat);
            currentRow["Content"] = content;
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            StoreCurrentDocument();
            // Write the changed rows of documents to your database here
        }
    }
}

The form has a TextControl named textControl1 and a BindingSource named bindingSource1; a BindingNavigator or a ListBox bound to bindingSource1 lets the user choose the record.

The code works as follows:

  • BinaryStreamType InternalUnicodeFormat is the TX Text Control format. It keeps everything the editor supports, so a document looks the same the next time it is loaded. Store Documents in a Database explains why Text Control recommends it for documents that are still edited.
  • StoreCurrentDocument runs before a new record is loaded and when the form closes, so that no edit is lost.
  • A new record with an empty Content column gets an empty document.

A Microsoft SQL Server sample of Text Control, described in Using TX Text Control As a Bound Control with MS SQL Server, uses the same event with a TableAdapter created by the Visual Studio data wizards and stores RTF strings.

Read and Write Documents in a Database

A document column holds binary data: varbinary(max) in SQL Server, for example, or BLOB in other databases. Pass the byte array as a parameter of the command; the classes of System.Data.Common work with the provider of any database:

using System.Data;
using System.Data.Common;

namespace MyWinFormsApp
{
    public static class DocumentStore
    {
        public static void SaveDocument(DbConnection connection, int id, byte[] content)
        {
            using DbCommand command = connection.CreateCommand();
            command.CommandText = "UPDATE Documents SET Content = @content WHERE Id = @id";

            DbParameter contentParameter = command.CreateParameter();
            contentParameter.ParameterName = "@content";
            contentParameter.DbType = DbType.Binary;
            contentParameter.Value = content;
            command.Parameters.Add(contentParameter);

            DbParameter idParameter = command.CreateParameter();
            idParameter.ParameterName = "@id";
            idParameter.Value = id;
            command.Parameters.Add(idParameter);

            command.ExecuteNonQuery();
        }

        public static byte[] LoadDocument(DbConnection connection, int id)
        {
            using DbCommand command = connection.CreateCommand();
            command.CommandText = "SELECT Content FROM Documents WHERE Id = @id";

            DbParameter idParameter = command.CreateParameter();
            idParameter.ParameterName = "@id";
            idParameter.Value = id;
            command.Parameters.Add(idParameter);

            return command.ExecuteScalar() as byte[];
        }
    }
}

Open the connection with the provider of your database, such as SqlConnection of the package Microsoft.Data.SqlClient, and pass it in. The @ prefix of the parameter names is the one SQL Server and SQLite use; other providers may expect another. The editor side stays the same: Save(out content, BinaryStreamType.InternalUnicodeFormat) before SaveDocument, and Load(content, BinaryStreamType.InternalUnicodeFormat) after LoadDocument.

To store documents for display or exchange rather than for further editing, save them as DOCX or PDF with BinaryStreamType.WordprocessingML or AdobePDF instead; these formats require the Professional or Enterprise edition, see Editions and Feature Availability.

Next Steps