STA线程,Rtf到HTML转换器问题

时间:2014-03-28 15:43:44

标签: c# wpf multithreading mvvm

我正在使用this code尝试将我的Rtf文本从我的UI中的RichTextBox转换为Html。我的解决方案中有2个项目:UI&用MVVM- IsesTextEditor编写的应用程序和链接中提供的预先编写的转换器 - MarkupConverter

我视图中的btn绑定到我的视图模型中的命令,该命令将RichTextBox文本传递给ConvertRtfToHtml方法,如下例所示:

 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; }
    }

然后MarkupConverter.ConvertRtfToHtml方法调用实例化新ConvertRtfToXaml对象的RichTextBox

    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();
            }
        }

    }
}

在创建RichTextBox对象时,我收到了The calling thread cannot access this object because a different thread owns it.例外。

有人可以建议修复此问题吗?我敢肯定这是一个相对简单的线程问题,但我是一个初级开发人员。没有太多线程或STA的经验...

2 个答案:

答案 0 :(得分:1)

如异常建议,将GUI相关代码从工作线程移动到GUI线程。

请参阅此post,我从中复制了一些文字:

与用户界面的框架一样,与许多Windows窗体一样,WPF也强制使用单个线程模型,这意味着您只能访问创建它的指定派生DispatcherObject线程。在实现接口ISynchronizeInvoke的Windows窗体控件中,此接口公开一组方法(如Invoke和BeginInvoke)以强制执行合约公共线程同步,我们可以使用该同步从另一个线程访问控件。在WPF中,我们也有这样的东西,但这些操作都涉及一个名为Dispatcher的类,Dispatcher WPF是允许这种线程同步模型的方式。

答案 1 :(得分:1)

要在不是主UI线程的线程上使用WPF控件,您需要执行一些管道工作,例如启动和完成WPF Dispatcher循环。

我已经整理了一个示例应用应用,展示了如何执行此操作,使用我之前发布的一些帮助代码here

这是一个控制台应用,尽管您应该能够在任何其他执行环境中使用RunOnWpfThreadAsync(() => ConvertRtfToXaml(RTF)).Result

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()));
        }
    }
}