Как установить несколько ссылок в RichEditBox без AccessViolationException

По сути, моя проблема заключается в том, что я получаю AccessViolationException, когда пытаюсь программно применить более 2 ссылок к ITextDocument, если пользователь редактировал содержимое. Я собрал простое демонстрационное приложение на основе шаблона пустого приложения для Windows Phone (8.1).

Я добавляю на главную страницу:

<StackPanel Margin="19,0,0,0">
    <Button
        Content="Apply Links"
        Click="Button_Click"
        />
    <RichEditBox
        x:Name="RtfBox"
        Height="300"
        Loaded="RtfBox_Loaded"
        Margin="0,0,19,0"
        TextWrapping="Wrap"
        />
</StackPanel>

И к коду для той же страницы, которую я добавляю (используя операторы, не включенные):

    private void RtfBox_Loaded(object sender, RoutedEventArgs e)
    {
        //RtfBox.Document.SetText(TextSetOptions.None, "Links to demo, example, test. More links to demo, demo, example, test and test.");
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        var pages = new Dictionary<Guid, string> { { Guid.NewGuid(), "demo" }, { Guid.NewGuid(), "example" }, { Guid.NewGuid(), "test" } };

        // NOTE: Avoid performance implications of many small updates
        RtfBox.Document.BatchDisplayUpdates();

        ITextRange range;
        foreach (var page in pages)
        {
            var link = string.Format("\"richtea.demo://pages/{0}\"", page.Key);
            var skip = 0;

            while ((range = RtfBox.Document.GetRange(skip, TextConstants.MaxUnitCount)).FindText(page.Value, TextConstants.MaxUnitCount, FindOptions.None) != 0)
            {
                if (range.Link == "")
                {
                    // TODO: Stop this throw exceptions
                    System.Diagnostics.Debug.WriteLine("Setting text at position {0} to link: '{1}'.", range.StartPosition, link);
                    range.Link = link;
                }

                skip = range.EndPosition;
            }
        }

        RtfBox.Document.ApplyDisplayUpdates();
    }

Если вы запустите это и напечатаете что-то вроде "Ссылка на демонстрационную страницу" и нажмете кнопку, она станет ссылкой правильно. Вы можете продолжать помещать один и тот же текст и нажимать кнопку, и он продолжает работать.

Однако если вы вставите три или более (по какой-то причине для меня это всегда 3 или более) слов demo, example или test (мои ключевые слова) и нажмете кнопку, произойдет ошибка AccessViolationException на настройке range.Link = link, Стоит отметить, что если вы проверяете во время отладки, свойство range.Link действительно установлено.

Более интересно, если вы раскомментируете RtfBox_Loadedсодержимое, и запустите приложение, и сразу же нажмите кнопку, все будет хорошо. Так что, похоже, это связано с выбором, установленным на RichEditBox? Я пытался отключить элемент управления перед применением ссылок, но это мне не помогло.

Вот некоторые другие вещи, которые затруднили мне диагностику этой проблемы:

  • Кажется, это работает чаще, если я отлаживаю построчно, так что может быть связано и с синхронизацией
  • Кажется, я не могу использовать ITextDocument не в потоке пользовательского интерфейса (COM-объект не может быть приведен), поэтому, хотя кажется, что асинхронность может быть лучшим подходом, я не добился здесь успеха.

Также, к сведению, причина, по которой я пытаюсь делать все обновления по массе, а не по мере того, как пользователь их вводит, заключается в том, что я не хочу заниматься очисткой, когда заметки переименовываются или удаляются, и я не Я действительно хочу, чтобы эти ссылки были во время редактирования или сохранены, но я мог бы жить позже.

1 ответ

Решение

Это решение было размещено на форумах MSDN Эриком Флеком и работает для меня:

RtfBox.Document.Selection.StartPosition = RtfBox.Document.Selection.EndPosition = range.StartPosition;
range.Link = link;
RtfBox.Document.Selection.StartPosition = RtfBox.Document.Selection.EndPosition = range.EndPosition;

Казалось, что это важно делать с каждой устанавливаемой ссылкой, потому что, если я не сильно ошибаюсь, я пробовал это перед обновлением всех своих ссылок, и это не помогло.

Я пока не использую возможность восстановить выделение в его исходном местоположении, но, возможно, я захочу сделать это в будущем, поэтому я создал этот небольшой служебный класс. Также, чтобы я мог обернуть такие места в блок using() для небольшого количества синтаксического сахара.

public static class ITextDocumentExtensions
{
    public static IDisposable SuppressSelection(this ITextDocument document)
    {
        var start = document.Selection.StartPosition;
        var end = document.Selection.EndPosition;

        var disposable = new ActionDisposable(() => document.Selection.SetRange(start, end));
        document.Selection.SetRange(0, 0);

        return disposable;
    }

    private sealed class ActionDisposable : IDisposable
    {
        private readonly Action dispose;

        public ActionDisposable(Action dispose)
        {
            this.dispose = dispose;
        }

        public void Dispose()
        {
            dispose();
        }
    }
}

Который позволяет мне писать

using (RtfBox.Document.SuppressSelection())
{
    range.Link = link;
}
Другие вопросы по тегам