0

I'm using this code to attempt to convert my Rtf text from a RichTextBox in my UI to Html. I have 2 projects in my solution: the UI & application written in MVVM- IsesTextEditor and the pre-written Converter as provided in the link- MarkupConverter.

A btn in my view is bound to a command in my view model which passes the RichTextBox text into the ConvertRtfToHtml method as per the example:

 private string ConvertRtfToHtml(string PastedText)
    {
        var thread = new Thread(ConvertRtfInSTAThread);
        var threadData = new ConvertRtfThreadData { RtfText = PastedText };
        thread.SetApartmentState(ApartmentState.STA);
        thread.Start(threadData);
        thread.Join();
        return threadData.HtmlText;
    }

    private void ConvertRtfInSTAThread(object rtf)
    {
        var threadData = rtf as ConvertRtfThreadData;
        threadData.HtmlText = markupConverter.ConvertRtfToHtml(threadData.RtfText);
    }

    private class ConvertRtfThreadData
    {
        public string RtfText { get; set; }
        public string HtmlText { get; set; }
    }

The MarkupConverter.ConvertRtfToHtml method then calls ConvertRtfToXaml which instantiates a new RichTextBox object:

    public static class RtfToHtmlConverter
    {
     private const string FlowDocumentFormat = "<FlowDocument>{0}</FlowDocument>";

     public static string ConvertRtfToHtml(string rtfText)
     {
        var xamlText = string.Format(FlowDocumentFormat, ConvertRtfToXaml(rtfText));

        return HtmlFromXamlConverter.ConvertXamlToHtml(xamlText, false);
    }

    private static string ConvertRtfToXaml(string rtfText)
    {
        var richTextBox = new RichTextBox();
        if (string.IsNullOrEmpty(rtfText)) return "";

        var textRange = new TextRange(richTextBox.Document.ContentStart, richTextBox.Document.ContentEnd);

        using (var rtfMemoryStream = new MemoryStream())
        {
            using (var rtfStreamWriter = new StreamWriter(rtfMemoryStream))
            {
                rtfStreamWriter.Write(rtfText);
                rtfStreamWriter.Flush();
                rtfMemoryStream.Seek(0, SeekOrigin.Begin);

                textRange.Load(rtfMemoryStream, DataFormats.Rtf);
            }
        }

        using (var rtfMemoryStream = new MemoryStream())
        {

            textRange = new TextRange(richTextBox.Document.ContentStart, richTextBox.Document.ContentEnd);
            textRange.Save(rtfMemoryStream, DataFormats.Xaml);
            rtfMemoryStream.Seek(0, SeekOrigin.Begin);
            using (var rtfStreamReader = new StreamReader(rtfMemoryStream))
            {
                return rtfStreamReader.ReadToEnd();
            }
        }

    }
}

At creating the RichTextBox object I'm getting a The calling thread cannot access this object because a different thread owns it. exception.

Can anyone suggest a fix for this? I'm sure it's a relatively simple threading issue but I'm a junior dev & don't have much experience with threading or STA...

noseratio
  • 59,932
  • 34
  • 208
  • 486
Hardgraf
  • 2,566
  • 4
  • 44
  • 77

2 Answers2

1

As the exception suggest, move the GUI related code from work thread to the GUI thread.

Please refer this post, I copied some text from it:

Like the frames of the user interface, like many Windows Forms, WPF also imposes a single threading model, which means you can only access a specified derivative DispatcherObject thread that creates it. In Windows Forms controls that implement the interface ISynchronizeInvoke, this interface exposes a set of methods such as Invoke and BeginInvoke to impose a contract common thread synchronization we can use to access a control from another thread. In WPF, we also have that kind of thing, but these operations are involved in a class called Dispatcher, Dispatcher WPF is the way to allow this kind of thread synchronization model.

Community
  • 1
  • 1
Matt
  • 6,010
  • 25
  • 36
1

To use a WPF control on a thread which is not the main UI thread, you need to do some plumbing, like start and finish the WPF Dispatcher loop.

I've put together a sample app app showing how to do this, using some helper code I posted earlier here.

This is a console app, although you should be able to use RunOnWpfThreadAsync(() => ConvertRtfToXaml(RTF)).Result in any other execution environment.

using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Threading;

namespace ConsoleApplication_22717365
{
    // by Noseratio - https://stackoverflow.com/q/22717365/1768303
    public class Program
    {
        const string RTF = @"{\rtf1\ansi{\fonttbl\f0\fswiss Helvetica;}\f0\pard This is some {\b bold} text.\par}";

        static void Main()
        {
            var xaml = RunOnWpfThreadAsync(() => ConvertRtfToXaml(RTF)).Result;
            Console.WriteLine(xaml);
        }

        // http://code.msdn.microsoft.com/windowsdesktop/Converting-between-RTF-and-aaa02a6e
        private static string ConvertRtfToXaml(string rtfText)
        {
            var richTextBox = new RichTextBox();
            if (string.IsNullOrEmpty(rtfText)) return "";
            var textRange = new TextRange(richTextBox.Document.ContentStart, richTextBox.Document.ContentEnd);
            using (var rtfMemoryStream = new MemoryStream())
            {
                using (var rtfStreamWriter = new StreamWriter(rtfMemoryStream))
                {
                    rtfStreamWriter.Write(rtfText);
                    rtfStreamWriter.Flush();
                    rtfMemoryStream.Seek(0, SeekOrigin.Begin);
                    textRange.Load(rtfMemoryStream, DataFormats.Rtf);
                }
            }
            using (var rtfMemoryStream = new MemoryStream())
            {
                textRange = new TextRange(richTextBox.Document.ContentStart, richTextBox.Document.ContentEnd);
                textRange.Save(rtfMemoryStream, DataFormats.Xaml);
                rtfMemoryStream.Seek(0, SeekOrigin.Begin);
                using (var rtfStreamReader = new StreamReader(rtfMemoryStream))
                {
                    return rtfStreamReader.ReadToEnd();
                }
            }
        }

        // https://stackoverflow.com/a/22626704/1768303
        public static async Task<TResult> RunOnWpfThreadAsync<TResult>(Func<Task<TResult>> funcAsync)
        {
            var tcs = new TaskCompletionSource<Task<TResult>>();

            Action startup = async () =>
            {
                // this runs on the WPF thread
                var task = funcAsync();
                try
                {
                    await task;
                }
                catch
                {
                    // propagate exception with tcs.SetResult(task)
                }
                // propagate the task (so we have the result, exception or cancellation)
                tcs.SetResult(task);

                // request the WPF tread to end
                // the message loop inside Dispatcher.Run() will exit
                System.Windows.Threading.Dispatcher.ExitAllFrames();
            };

            // the WPF thread entry point
            ThreadStart threadStart = () =>
            {
                // post the startup callback
                // it will be invoked when the message loop starts pumping
                System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke(
                    startup, DispatcherPriority.Normal);
                // run the WPF Dispatcher message loop
                System.Windows.Threading.Dispatcher.Run();
            };

            // start and run the STA thread
            var thread = new Thread(threadStart);
            thread.SetApartmentState(ApartmentState.STA);
            thread.IsBackground = true;
            thread.Start();
            try
            {
                // propagate result, exception or cancellation
                return await tcs.Task.Unwrap().ConfigureAwait(false);
            }
            finally
            {
                // make sure the thread has fully come to an end
                thread.Join();
            }
        }

        // a wrapper to run synchronous code
        public static Task<TResult> RunOnWpfThreadAsync<TResult>(Func<TResult> func)
        {
            return RunOnWpfThreadAsync(() => Task.FromResult(func()));
        }
    }
}
Community
  • 1
  • 1
noseratio
  • 59,932
  • 34
  • 208
  • 486