TX Text Control .NET for WPF

Dispatcher and UI Thread Access

Topic

In WPF, every control belongs to the thread that created it, and the WPF TextControl is a control like any other: the UI thread of its window owns it, and the Dispatcher of that thread is the only way in from anywhere else. Knowing this model tells you which TX Text Control objects your background code may touch, how a document crosses from one thread to another, and how a result gets back into the editor.

How WPF Assigns Controls to Threads

TextControl in the TXTextControl.WPF namespace derives from System.Windows.Controls.Control. Like every WPF element, it is bound to the thread that created it, the UI thread of the window, and WPF throws an InvalidOperationException when another thread calls it. The Dispatcher of that thread runs the window's work one item at a time: input, layout, rendering and your event handlers. As long as one of your handlers runs, nothing else in the window happens.

ObjectMay be used onReason
The WPF TextControl, ButtonBar, RulerBar, StatusBar and ribbonThe UI thread of their windowWPF controls, bound to the thread that created them
ServerTextControlAny thread, one instance per threadA System.ComponentModel.Component without a user interface, meant for work in the background
MailMergeThe thread of the text component it works onIt merges into the document of its TextComponent
Byte arrays with documentsAny threadPlain data, which your code passes between threads

Load, Save and the merge methods of MailMerge are synchronous. Called in a handler, they occupy the dispatcher until they return, and the window freezes for that time. Background Operations moves such work to other threads.

Hand a Document to Another Thread

The document of the editor can't leave the UI thread, but a copy of it can. The following handler in MainWindow.xaml.cs copies the editor's document into a byte array, creates a PDF file from it in a ServerTextControl on a thread of the thread pool and returns to the window afterward. It needs a button named exportPdfButton in MainWindow.xaml:

using System;
using System.Threading.Tasks;
using System.Windows;
using TXTextControl;

private async void ExportPdf_Click(object sender, RoutedEventArgs e)
{
    // On the UI thread: copy the editor's document
    textControl1.Save(out byte[] document, BinaryStreamType.InternalUnicodeFormat);

    exportPdfButton.IsEnabled = false;

    try
    {
        await Task.Run(() =>
        {
            // On a thread of the thread pool: no WPF element in here
            using (ServerTextControl tx = new ServerTextControl())
            {
                if (!tx.Create())
                {
                    throw new InvalidOperationException("TX Text Control could not be initialized.");
                }

                tx.Load(document, BinaryStreamType.InternalUnicodeFormat);
                tx.Save("document.pdf", StreamType.AdobePDF);
            }
        });

        MessageBox.Show(this, "document.pdf created.");
    }
    finally
    {
        // Back on the UI thread after await
        exportPdfButton.IsEnabled = true;
    }
}

The code works as follows:

  • WPF.TextControl.Save with BinaryStreamType InternalUnicodeFormat copies the complete document, in the TX Text Control format, into a byte array. The user can keep editing while the copy is exported.
  • The lambda of Task.Run uses only the byte array and a ServerTextControl it creates itself. Create returns false if the instance couldn't be initialized.
  • await in an event handler of the window continues on the UI thread, so the message box and the button are used on the thread that owns them. An exception of the task is thrown again there; Catch Errors of Background Work shows how to handle it.

Get Back to the UI Thread With the Dispatcher

Code that runs on another thread and isn't awaited from a handler, such as a callback of another component or a timer of System.Threading, reaches the editor through its Dispatcher. CheckAccess tells whether the current thread owns the control, and InvokeAsync queues a call on the owning thread:

using System;
using System.Windows;
using TXTextControl;

private void ShowDocument(byte[] document)
{
    // Called on another thread: queue the call on the UI thread
    if (!textControl1.Dispatcher.CheckAccess())
    {
        textControl1.Dispatcher.InvokeAsync(() => ShowDocument(document));
        return;
    }

    textControl1.Load(document, BinaryStreamType.InternalUnicodeFormat);
}

The code works as follows:

  • Dispatcher, CheckAccess and InvokeAsync are members that every WPF control inherits. InvokeAsync returns at once, so the background thread doesn't wait for the window.
  • WPF.TextControl.Load replaces the document of the editor and runs on the UI thread.

For reports that arrive regularly, such as the progress of a long task, Progress<T> does the same with less code: created on the UI thread, it calls its handler there for each report, as Background Operations shows.

Keep the Dispatcher Free

  • Don't wait on the UI thread. A handler that calls Wait() or reads Result of a task blocks the dispatcher just as Load does, and when the task itself needs the UI thread, neither can go on. Use await instead.
  • Split work that must stay in the editor. Some work needs the TextControl itself, such as building a document the user watches. Build larger parts in a ServerTextControl and insert them in one step, as Large Documents shows.
  • Start after Loaded. The control works with documents once WPF has loaded it into the window, so startup code belongs in its Loaded event, as Control Initialization explains.
  • Catch what nobody catches. An exception on the UI thread that no handler catches ends the application. The TX Words samples handle Application.Current.DispatcherUnhandledException, as Dispatcher and Thread Access in Troubleshooting describes.

Next Steps