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

本文关键字:HTML 转换器 问题 RTF 线程 STA | 更新日期: 2023-09-27 17:55:02

我正在使用此代码尝试将我的 Rtf 文本从 UI 中的RichTextBox转换为 Html。我的解决方案中有 2 个项目:用 MVVM- IsesTextEditor 编写的 UI 和应用程序以及链接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 方法调用实例化新RichTextBox对象的ConvertRtfToXaml

    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 方面没有太多经验......

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

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

请参考这篇文章,我从中复制了一些文字:

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

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

我使用之前在此处发布的一些帮助程序代码整理了一个示例应用应用,展示了如何执行此操作。

这是一个控制台应用,尽管您应该能够在任何其他执行环境中使用 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()));
        }
    }
}