Matt McManis
Matt McManis

Reputation: 4675

How to Open Window in New Thread and Pass Data?

I have a Window with a Rich Text Box. How can I open it in a New Thread, then write to the Text Box?

I've tried many different ways with Dispatcher.BeginInvoke and using Thread and BackgroundWorker, but I'm not setting it up right. I get errors such as "The calling thread cannot access this object because a different thread owns it." when trying to open the window or write.

I start a hidden background window using Hide(), open with Show():

MainWindow mainwindow = this;

myWindow = new NewWindow(mainwindow);
myWindow.Left = this.Left + 605;
myWindow.Top = this.Top + 0;
myWindow.Hide();

myWindow.myRichTextBox.Cursor = Cursors.Arrow;

I write to it using Actions that are saved to a list, then called at a certain time:

List<Action> LogActions = new List<Action>();
Action WriteAction;

// Create Multiple Actions 
WriteAction = () =>
{
    myWindow.myRichTextBox.Document = new FlowDocument(paragraph);
    paragraph.Inlines.Add(new LineBreak());
    paragraph.Inlines.Add(new Bold(new Run("Example")) { Foreground = Brushes.White });
};
// Add Actions to List
LogActions.Add(WriteAction);

// Write All Actions in List
foreach (Action Write in LogActions)
{
    Write();
}

To simplify, it is basically doing:

myWindow.myRichTextBox.AppendText("Example");

It is able to pass data without new thread right now. But the mouse freezes for a few seconds when the Rich Text Box is being written to, I thought this might free it up. Would anyone advise against opening in new thread?

Upvotes: 0

Views: 343

Answers (1)

Peter
Peter

Reputation: 684

It's usually best practice for the user interface to stay in the main application thread, where the UI can spawn background threads to perform longer-running tasks. This way, the UI thread can still respond to user input while the background work is underway.

In the case of a Window, myWindow with a Rich Text Box that you want to append messages to, myWindow would use a BackgroundWorker to gather the messages in a separate thread and then update the RTF control through a callback on thread termination (for a BackgroundWorker this is the OnRunWorkerCompleted method).

If the window must run as a separate thread, this helper class will wrap a WPF Window in a new thread and set it up with a new SynchronizationContext, however, you will still need thread-safe messaging if you want to communicate between windows in different threads. The ConcurrentQueue<T> collection provides a thread-safe collection that may be useful for this purpose.

public static class WindowThreadHelpers
{
    public static void LaunchWindowInNewThread<T>() where T : Window, new()
    {
        Dispatcher.CurrentDispatcher.Invoke(new ThreadStart(ThreadStartingPoint<T>));
    }

    private static void ThreadStartingPoint<T>() where T : Window, new()
    {
        SynchronizationContext.SetSynchronizationContext(
            new DispatcherSynchronizationContext(
                Dispatcher.CurrentDispatcher));

        var win = new T();
        win.Closing += (sender, args) =>
        {
            Dispatcher.ExitAllFrames();
            win.Dispatcher.InvokeShutdown();
        };

        win.Show();
        Dispatcher.Run();
    }
}

Here's an example of how you would use it from the main thread

WindowThreadHelpers.LaunchWindowInNewThread<NewWindow>();

Upvotes: 2

Related Questions