I have this setup in my AvaloniaUI application:
<ScrollViewer VerticalScrollBarVisibility="Auto"
AllowAutoHide="True"
Name="MessageLogScrollViewer">
<TextBlock HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
TextWrapping="NoWrap"
Text="{Binding ReceivedMessages}"></TextBlock>
</ScrollViewer>
The TextBlock basically displays log messages and I would like it to autoscroll to the bottom, when I append new lines to the String bound to the text property.
The ScrollViewer has a method called ScrollToEnd()
, that I would need to call whenever I update the Text. So I tried to define a function in my code behind like so:
private ScrollViewer messageLogScrollViewer;
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel(this);
messageLogScrollViewer = this.FindControl<ScrollViewer>("MessageLogScrollViewer");
}
...
public void ScrollTextToEnd()
{
messageLogScrollViewer.ScrollToEnd();
}
I then tried to call that function from my ViewModel:
private string receivedMessages = string.Empty;
public string ReceivedMessages
{
get => receivedMessages;
set => this.RaiseAndSetIfChanged(ref receivedMessages, value);
}
...
private MainWindow _window;
public MainWindowViewModel(MainWindow window)
{
_window = window;
}
...
ReceivedMessage += "\n";
ReceivedMessages += ReceivedMessage;
_window.ScrollTextToEnd(); // Does not work.
But unfortunately, this the ScrollToEnd()
function needs to be called from the UI-Thread, because I get an Exception:
System.InvalidOperationException: 'Call from invalid thread'
My question is how can I autoscroll my ScrollViewer to the end, whenever I update the TextBlocks Text property via DataBinding?
Ok, I figured it out, but leaving this here for reference.
In order to execute the function from another thread on the UI-Thread one needs to call
Dispatcher.UIThread.InvokeAsync(_window.ScrollTextToEnd);
But this scrolled only to the penultimate line, so it seems, the control is not updated yet, when I call ScrollTextToEnd()
. Therefore I added a short timeout in between updating the text and calling the scroll function like this
ReceivedMessages += ReceivedMessage;
// A short timeout is needed, so that the scroll viewer will scroll to the new end of its content.
Thread.Sleep(10);
Dispatcher.UIThread.InvokeAsync(_window.ScrollTextToEnd);
It works now just like that.