Home > Enterprise >  Insert text into a WPF textblock from the top
Insert text into a WPF textblock from the top

Time:04-09

i have a textblock that displays multiline messages that are received continuously from the network along with the time it was received. here is the code:

private async Task ReadMessage(TcpClient client, bool ownsClient)
    {
        
            using NetworkStream stream = client.GetStream();

            byte[] buffer = new byte[4096];

            int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);

            string message = Encoding.UTF8.GetString(buffer, 0, bytesRead);
            
            DateTime now = DateTime.Now;
            string receptiontime = now.ToString("HH:mm");
            Dispatcher.Invoke(new Action(() =>
            {
                NotamsTextBlock.Text  = "-->"   receptiontime   Environment.NewLine;
                NotamsTextBlock.Text  = message;
               
            }), DispatcherPriority.Background);
            
    }

Here is how it is displayed in the app: enter image description here

By default the new message received is inserted in the textblock after the old message. What i want to do is change it. The new message should be inserted from the top which means that when reading the content of the textblock you always start with the newest message.

Any idea on how i can achieve that?

Thanks.

Ps: i'm not using MVVM

CodePudding user response:

I put a TextBlock and a button on a WPF window as test. And this works:

using System.Windows;

namespace WpfApp7;

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        myTextBlock.Text = "Before";
    }

    private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
    {
        myTextBlock.Text = myTextBlock.Text.Insert(0, "after");
    }
}

CodePudding user response:

Don't use a TextBlock for this task. The TextBlock is optimized to display only a few lines at max. It is not designed to display multiline documents.

The recommended solution is to use a ListBox. It offers virtualization to provide a smooth scrolling experience.
You can override the default template of the ListBoxItem to remove the mouse over and highlight effects. This way the ListBox will look like a plain text document or a common console output.
Don't use the Dispatcher in this case too. If you need to update the ObservableCollection, which is the data source for the ListBox, from a background thread, use the BindingOperations.EnableCollectionSynchronization.

MessageLine.cs

public class MessageLine : INotifyPropertyChanged
{
  public MessageLine(string message) => this.Message = message;

  public string Message { get; }

  public event PropertyChangedEventHandler PropertyChanged;
}

MainWindow.xaml.cs

partial class MainWindow : Window
{
  public ObservableCollection<MessageLine> Messages { get; }

  publiv MainWindow()
  {
    InitializeComponent();
  
    this.Messages = new ObservableCollection<MessageLine>();
  }

  private void InsertLineAtBeginning(MessageLine message) => this.Messages.Insert(0, message);

  private void AppendLine(MessageLine message) => this.Messages.Add(message);

  private async Task ReadMessageAsync(TcpClient client, bool ownsClient)
  {
    await using NetworkStream stream = client.GetStream();
    
    ...

   // Since we insert at the beginning but want to have a proper line order of the multi-line message,
   // we must insert the last line first (reverse order - you can use a Stack to collect the lines)

    string messageBody = Encoding.UTF8.GetString(buffer, 0, bytesRead);
    InsertLineAtBeginning(new MessageLine(messageBody));   

    var messageHeader = $"-->{receptiontime}";
    InsertLineAtBeginning(new MessageLine(messageHeader));   
  }
}

MainWindow.xaml

<Window>
  <ListBox ItemsSource="{Binding RlativeSource={RelativeSource AncestorType Window}, Path=Messages}">
    <ListBox.ItemTemplate>
      <DataTemplate DataType="{x:Type MessageLine}">
        <TextBlock Text="{Binding Message}" />
      </DataTemplate>
    </ListBox.ItemTemplate>
    
    <!-- Remove interaction effects -->
    <ListBox.ItemContainerStyle>
      <Style TargetType="ListBoxItem">
        <ControlTemplate TargetType="ListBoxItem">
          <ContenPresenter />
        </ControlTemplate>
      </Style>
    </ListBox.ItemContainerStyle>
  </ListBox>
</Window>

You can also move the ListBox to a UserControl or custom Control.

  • Related