Подождите, пока макет управления не закончен - PullRequest
8 голосов
/ 07 июля 2011

Я загружаю довольно много форматированного текста в RichTextBox (WPF) и хочу прокрутить до конца содержимого:

richTextBox.Document.Blocks.Add(...)
richTextBox.UpdateLayout();
richTextBox.ScrollToEnd();

Это не работает, ScrollToEnd - этовыполняется, когда макет не закончен, поэтому он не прокручивается до конца, он прокручивается примерно до первой трети текста.

Есть ли способ заставить ожидание, пока не закончится RichTextBoxего операции рисования и разметки, так что ScrollToEnd фактически прокручивается до конца текста?

Спасибо.

Материал, который не работает:

РЕДАКТИРОВАТЬ : Я пробовал событие LayoutUpdated, но оно срабатывает немедленно, та же проблема: элемент управления все еще выкладывает больше текста внутри richtextbox, когда он запускается, поэтому даже ScrollToEnd там неработа ... Я попробовал это:

richTextBox.Document.Blocks.Add(...)
richTextBoxLayoutChanged = true;
richTextBox.UpdateLayout();
richTextBox.ScrollToEnd();

и в обработчике события richTextBox.LayoutUpdated:

if (richTextBoxLayoutChanged)
{
    richTextBoxLayoutChanged = false;
    richTextBox.ScrollToEnd();
}

Событие запускается правильно, но слишком рано, richtextbox все еще добавляет большетекст, когда он запущен, макет не закончен, поэтому ScrollToEnd снова не удается.

РЕДАКТИРОВАТЬ 2 : Следуя ответу dowhilefor: MSDN в InvalidateArrange говорит:

После аннулирования у элемента будет обновлена ​​его компоновка, что будет происходить асинхронно, если впоследствии не будет принудительно инициирован UpdateLayout.

Тем не менее, даже

richTextBox.InvalidateArrange();
richTextBox.InvalidateMeasure();
richTextBox.UpdateLayout();

НЕ ожидает: после этих вызовов richtextbox все еще добавляет текст и асинхронно размещает его внутри себя.ARG!

Ответы [ 8 ]

8 голосов
/ 09 февраля 2015

У меня была похожая ситуация: у меня есть диалоговое окно предварительного просмотра печати, которое создает необычный рендеринг. Обычно пользователь нажимает кнопку, чтобы фактически напечатать ее, но я также хотел использовать ее для сохранения изображения без участия пользователя. В этом случае создание изображения должно ждать завершения макета.

Мне удалось это с помощью следующего:

Dispatcher.Invoke(new Action(() => {SaveDocumentAsImage(....);}), DispatcherPriority.ContextIdle);

Ключ DispatcherPriority.ContextIdle, ожидающий завершения фоновых задач.

Редактировать: согласно запросу Зака, включая код, применимый для этого конкретного случая:

Dispatcher.Invoke(() => { richTextBox.ScrollToEnd(); }), DispatcherPriority.ContextIdle);

Я должен отметить, что я не очень доволен этим решением, так как оно кажется невероятно хрупким. Однако в моем конкретном случае это действительно работает.

2 голосов
/ 07 июля 2011

Посмотрите на UpdateLayout

, особенно:

Вызов этого метода не имеет никакого эффекта, если макет не изменился, или если ни расположение, ни состояние измеренияневерный макет

Так что вызов InvalidateMeasure или InvalidateArrange, в зависимости от ваших потребностей, должен работать.

Но учитывая ваш кусок кода.Я думаю, что это не сработает.Большая часть загрузки и создания WPF отложена, поэтому добавление чего-либо в Document.Blocks не обязательно напрямую изменяет пользовательский интерфейс.Но я должен сказать, что это всего лишь предположение, и, возможно, я ошибаюсь.

1 голос
/ 16 мая 2016

Ответ @Andreas работает хорошо.

Однако, что, если элемент управления уже загружен? Событие никогда не сработает, и ожидание может зависнуть навсегда. Чтобы это исправить, немедленно вернитесь, если форма уже загружена:

/// <summary>
/// Intent: Wait until control is loaded.
/// </summary>
public static Task WaitForLoaded(this FrameworkElement element)
{
    var tcs = new TaskCompletionSource<object>();
    RoutedEventHandler handler = null;
    handler = (s, e) =>
    {
        element.Loaded -= handler;
        tcs.SetResult(null);
    };
    element.Loaded += handler;

    if (element.IsLoaded == true)
    {
        element.Loaded -= handler;
        tcs.SetResult(null);
    }
        return tcs.Task;
}

Дополнительные подсказки

Эти подсказки могут или не могут быть полезны.

  • Код выше действительно полезен в прикрепленном свойстве. Присоединенное свойство срабатывает только при изменении значения. При переключении присоединенного свойства для его запуска используйте task.Yield(), чтобы перевести вызов в конец очереди диспетчера:

    await Task.Yield(); // Put ourselves to the back of the dispatcher queue.
    PopWindowToForegroundNow = false;
    await Task.Yield(); // Put ourselves to the back of the dispatcher queue.
    PopWindowToForegroundNow = false;
    
  • Приведенный выше код действительно полезен в прикрепленном свойстве. При переключении присоединенного свойства для его запуска вы можете использовать диспетчер и установить приоритет Loaded:

    // Ensure PopWindowToForegroundNow is initialized to true
    // (attached properties only trigger when the value changes).
    Application.Current.Dispatcher.Invoke(
    async 
       () =>
    {
        if (PopWindowToForegroundNow == false)
        {
           // Already visible!
        }
        else
        {
            await Task.Yield(); // Put ourselves to the back of the dispatcher queue.
            PopWindowToForegroundNow = false;
        }
    }, DispatcherPriority.Loaded);
    
1 голос
/ 20 апреля 2016

С помощью .net 4.5 или пакета async blc вы можете использовать следующий метод расширения

 /// <summary>
    /// Async Wait for a Uielement to be loaded
    /// </summary>
    /// <param name="element"></param>
    /// <returns></returns>
    public static Task WaitForLoaded(this FrameworkElement element)
    {
        var tcs = new TaskCompletionSource<object>();
        RoutedEventHandler handler = null;
        handler = (s, e) =>
        {
            element.Loaded -= handler;
            tcs.SetResult(null);
        };
        element.Loaded += handler;
        return tcs.Task;
    }
1 голос
/ 07 июля 2011

Попробуйте добавить свой richTextBox.ScrollToEnd ();вызов обработчика события LayoutUpdated вашего объекта RichTextBox.

1 голос
/ 07 июля 2011

вы сможете использовать событие Loaded

если вы делаете это более одного раза, то вам следует посмотреть на LayoutUpdated событие

myRichTextBox.LayoutUpdated += (source,args)=> ((RichTextBox)source).ScrollToEnd();
0 голосов
/ 16 февраля 2019

Единственное (kludge) решение, которое работало для моего проекта WPF, состояло в том, чтобы запустить отдельный поток, который некоторое время спал, а затем попросил прокрутить его до конца.

Важно не пытаться вызыватьэтот "сонный" поток в основном графическом интерфейсе, чтобы пользователь не был приостановлен.Поэтому вызывайте отдельный «сонный» поток и периодически отправляйте Dispatcher.Invoke на основной поток GUI и просите прокрутить до конца.

Работает отлично, и взаимодействие с пользователем не страшно:

using System;
using System.Threading;    
using System.Windows.Controls;

try {

    richTextBox.ScrollToEnd();

    Thread thread       = new Thread(new ThreadStart(ScrollToEndThread));
    thread.IsBackground = true;
    thread.Start();

} catch (Exception e) {

    Logger.Log(e.ToString());
}

и

private void ScrollToEndThread() {

// Using this thread to invoke scroll to bottoms on rtb
// rtb was loading for longer than 1 second sometimes, so need to 
// wait a little, then scroll to end
// There was no discernable function, ContextIdle, etc. that would wait
// for all the text to load then scroll to bottom
// Without this, on target machine, it would scroll to the bottom but the
// text was still loading, resulting in it scrolling only part of the way
// on really long text.
// Src: /4733176/podozhdite-poka-maket-upravleniya-ne-zakonchen
    for (int i=1000; i <= 10000; i += 3000) {

        System.Threading.Thread.Sleep(i);

        this.richTextBox.Dispatcher.Invoke(
            new ScrollToEndDelegate(ScrollToEnd),
            System.Windows.Threading.DispatcherPriority.ContextIdle,
            new object[] {  }
            );
    }
}

private delegate void ScrollToEndDelegate();

private void ScrollToEnd() {

    richTextBox.ScrollToEnd();
}
0 голосов
/ 12 января 2015

Попробуйте это:

richTextBox.CaretPosition = richTextBox.Document.ContentEnd;
richTextBox.ScrollToEnd(); // maybe not necessary
...