Search code examples
c#wpfdata-bindingrichtextbox

how to dynamically add content to a rich text box in a wpf application using xaml


I am developing a GUI where a user can connect to server and read the data. The data needs to be displayed on the GUI. For this I am using TabControl whose ContentTemplate is set to RichTextBox. The XAML code is as below

<TabControl x:Name="tabControl1" HorizontalAlignment="Stretch" MinHeight="50" Margin="0,0,0,0.2" Width="884"
                            ItemsSource="{Binding Titles, Mode=TwoWay}" Height="454" VerticalAlignment="Bottom">
                    <TabControl.ItemTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding Header}"/>
                        </DataTemplate>
                    </TabControl.ItemTemplate>
                    <TabControl.ContentTemplate>
                        <DataTemplate>
                            <RichTextBox Margin="10" VerticalScrollBarVisibility="Visible" >
                                <FlowDocument>
                                    <Paragraph FontSize="12" FontFamily="Courier New">
                                        <Run Text="{Binding Content}"></Run>
                                    </Paragraph>
                                </FlowDocument>
                            </RichTextBox>
                        </DataTemplate>
                    </TabControl.ContentTemplate>
</TabControl>

The background code for adding new tab and setting its header/content (static) is below

    public class MainWindowVM : INotifyPropertyChanged
    {
        public MainWindowVM()
        {
            Titles = new ObservableCollection<Item>();
        }

        public class Item
        {
            public string Header { get; set; }
            public string Content { get; set; }
        }
        public ObservableCollection<Item> Titles
        {
            get { return _titles; }
            set
            {
                _titles = value;
                OnPropertyChanged("Titles");
            }
        }

        static int tabs = 1;
        private ObservableCollection<Item> _titles;
        private ICommand _addTab;
        private ICommand _removeTab;

        public ICommand AddTab
        {
            get
            {
                 _addTab = new TabRelayCommand(
                    x =>
                    {
                        AddTabItem();
                    });
                return _addTab;
            }
        }
        public ICommand RemoveTab
        {
            get
            {
                _removeTab = new TabRelayCommand(
                    x =>
                    {
                        RemoveTabItem();
                    });
                return _removeTab;
            }
        }
        private void RemoveTabItem()
        {
            if (Titles.Count > 0)
            {
                Titles.Remove(Titles.Last());
                tabs--;
            }
        }

        public Item AddTabItem()
        {
            var header = "Log_" + tabs;
            var content = "Content " + tabs;
            var item = new Item { Header = header, Content = content };
            Titles.Add(item);
            tabs++;
            OnPropertyChanged("Titles");
            return item;
        }

        public void AddTabItem(string strFileName, string strContent)
        {
            var header = strFileName;
            var content = strContent;
            var item = new Item { Header = header, Content = content };
            Titles.Add(item);
            tabs++;
            OnPropertyChanged("Titles");
        }


        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged(string propertyName)
        {
            var handler = PropertyChanged;
            handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

    }

However I need to set the content of the header dynamically (the data read from the socket). I can read the data I have it in string format. Kindly suggest me how do I set the content of RichTextBox by appending the string. I am new to C#. Thanks in advance

Edit: After button click event, my application gets connected to the server. Also I start a parallel task which reads data from the socket.

Problem facing: Too much of CPU time taken (I can see upto 80 under Processes in Task Manager).

 private void BtnConnect_Click(object sender, RoutedEventArgs e)
        {
            //connect
            TCPClientClass tcpClient = TCPConnHandler.ConnectToService(tbIPAddress.Text);
            if (tcpClient != null)
            {
                MessageBox.Show("Connected to " + tbIPAddress.Text);
                //open new tab
                var item = MainWindowVMObj.AddTabItem();


                //now run a task to display the data in the tab
                Thread thTabControl = new Thread(() =>
                {
                    while (tcpClient.Connected)
                    {
                        String str = tcpClient.GetDataFromServer();
                        if (!String.IsNullOrEmpty(str))
                            tabControl1.Dispatcher.BeginInvoke((Action)(() => item.Content += str));
                        Thread.Sleep(200);
                    }
                    //item.Dispatcher.BeginInvoke
                });

                thTabControl.Start();
            }

        }


Solution

  • You could look up the Item to update in Titles and set its Content property.

    For example, this sets the property of the first item (index 0) in the ObservableCollection<Item>:

    Titles[0].Content += "append...";
    

    The Item class should also implement INotifyPropertyChanged for you to see the changes without switching tabs:

    public class Item : INotifyPropertyChanged
    {
        public string Header { get; set; }
    
        private string _content;
        public string Content
        {
            get { return _content; }
            set { _content = value; OnPropertyChanged(nameof(Content)); }
        }
    
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged(string propertyName)
        {
            var handler = PropertyChanged;
            handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }