我正在将大量富文本加载到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的回答:InvalidateArrange上的MSDN说
失效后,元素将更新其布局,这将是 除非随后由UpdateLayout强制,否则将异步发生。
甚至
richTextBox.InvalidateArrange();
richTextBox.InvalidateMeasure();
richTextBox.UpdateLayout();
不等待:在这些调用之后,richtextbox仍然会添加更多文本并异步地将其放在自身内部。 ARG!
答案 0 :(得分:8)
我有一个相关的情况:我有一个打印预览对话框,可以创建一个花哨的渲染。通常,用户将单击一个按钮来实际打印它,但我也想用它来保存图像而无需用户参与。在这种情况下,创建图像必须等到布局完成。
我使用以下方法进行了管理:
Dispatcher.Invoke(new Action(() => {SaveDocumentAsImage(....);}), DispatcherPriority.ContextIdle);
密钥是DispatcherPriority.ContextIdle
,等待后台任务完成。
编辑:根据Zach的请求,包括适用于此特定案例的代码:
Dispatcher.Invoke(() => { richTextBox.ScrollToEnd(); }), DispatcherPriority.ContextIdle);
我应该注意到,我对此解决方案并不满意,因为它感觉非常脆弱。但是,它似乎确实适用于我的具体情况。
答案 1 :(得分:2)
特别:
如果调用此方法无效 布局不变,或两者都没有 安排或测量状态 布局无效
因此,根据您的需要调用InvalidateMeasure或InvalidateArrange应该可以正常工作。
但考虑到你的代码。我认为这不会奏效。很多WPF加载和创建都被保留,因此向Document.Blocks添加内容并不直接更改UI。但我必须说,这只是猜测,也许我错了。
答案 2 :(得分:1)
您应该可以使用Loaded事件
如果您这样做的时间超过一次,那么您应该查看LayoutUpdated事件
myRichTextBox.LayoutUpdated += (source,args)=> ((RichTextBox)source).ScrollToEnd();
答案 3 :(得分:1)
尝试添加richTextBox.ScrollToEnd();调用RichTextBox对象的 LayoutUpdated事件处理程序。
答案 4 :(得分:1)
使用.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;
}
答案 5 :(得分:1)
@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);
答案 6 :(得分:0)
试试这个:
richTextBox.CaretPosition = richTextBox.Document.ContentEnd;
richTextBox.ScrollToEnd(); // maybe not necessary
答案 7 :(得分:0)
对我的WPF项目唯一有效的解决方案是触发一个单独的线程,该线程休眠了一段时间,然后要求滚动到最后。
重要的是不要尝试在主GUI上调用此“ sleepy”线程,以免用户被暂停。因此,请调用一个单独的“ sleepy”线程并定期执行Dispatcher。在主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: https://stackoverflow.com/questions/6614718/wait-until-control-layout-is-finished
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();
}