Updating GUI (WPF) using a different thread

161,907

Solution 1

You can use Dispatcher.Invoke to update your GUI from a secondary thread.

Here is an example:

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        new Thread(DoSomething).Start();
    }
    public void DoSomething()
    {
        for (int i = 0; i < 100000000; i++)
        {
               this.Dispatcher.Invoke(()=>{
               textbox.Text=i.ToString();
               });    
        } 
    }

Solution 2

You may use a delegate to solve this issue. Here is an example that is showing how to update a textBox using diffrent thread

public delegate void UpdateTextCallback(string message);

private void TestThread()
{
    for (int i = 0; i <= 1000000000; i++)
    {
        Thread.Sleep(1000);                
        richTextBox1.Dispatcher.Invoke(
            new UpdateTextCallback(this.UpdateText),
            new object[] { i.ToString() }
        );
    }
}
private void UpdateText(string message)
{
    richTextBox1.AppendText(message + "\n");
}

private void button1_Click(object sender, RoutedEventArgs e)
{
   Thread test = new Thread(new ThreadStart(TestThread));
   test.Start();
}

TestThread method is used by thread named test to update textBox

Solution 3

there.

I am also developing a serial port testing tool using WPF, and I'd like to share some experience of mine.

I think you should refactor your source code according to MVVM design pattern.

At the beginning, I met the same problem as you met, and I solved it using this code:

new Thread(() => 
{
    while (...)
    {
        SomeTextBox.Dispatcher.BeginInvoke((Action)(() => SomeTextBox.Text = ...));
    }
}).Start();

This works, but is too ugly. I have no idea how to refactor it, until I saw this: http://www.codeproject.com/Articles/165368/WPF-MVVM-Quick-Start-Tutorial

This is a very kindly step-by-step MVVM tutorial for beginners. No shiny UI, no complex logic, only the basic of MVVM.

Solution 4

Use Following Method to Update GUI.

     Public Void UpdateUI()
     {
         //Here update your label, button or any string related object.

         //Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Background, new ThreadStart(delegate { }));    
         Application.Current.Dispatcher.Invoke(DispatcherPriority.Background, new ThreadStart(delegate { }));
     }

Keep it in Mind when you use this method at that time do not Update same object direct from dispatcher thread otherwise you get only that updated string and this method is helpless/useless. If still not working then Comment that line inside method and un-comment commented one both have nearly same effect just different way to access it.

Solution 5

Here is a full example that updates UI textboxes

<Window x:Class="WpfThreading.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:WpfThreading"
    mc:Ignorable="d"
    Title="MainWindow" Height="450" Width="216.84">
<Grid Margin="0,0,2,0">
    <Button Content="Button" HorizontalAlignment="Left" Margin="10,10,0,0"
            VerticalAlignment="Top" Width="75" Click="Button_Click"/>
    <TextBox HorizontalAlignment="Left" Margin="10,35,0,10" TextWrapping="Wrap" Name="mtextBox" Width="87"   VerticalScrollBarVisibility="Auto"/>
    <TextBox HorizontalAlignment="Left" Margin="111,35,0,10" TextWrapping="Wrap" x:Name="mtextBox2" Width="87"   VerticalScrollBarVisibility="Auto"/>
</Grid></Window>

and in the code

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

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        new Thread(DoSomething).Start();
        new Thread(DoSomething2).Start();
    }


    public void DoSomething()
    {
        for (int i = 0; i < 100; i++)
        {
            Dispatcher.BeginInvoke(new Action(() => {
                mtextBox.Text += $"{i.ToString()}{Environment.NewLine}";
            }), DispatcherPriority.SystemIdle);

            Thread.Sleep(100);
        }

    }

    public void DoSomething2()
    {
        for (int i = 100; i > 0; i--)
        {
            Dispatcher.BeginInvoke(new Action(() => {
                mtextBox2.Text += $"{i.ToString()}{Environment.NewLine}";
            }), DispatcherPriority.SystemIdle);

            Thread.Sleep(100);
        }

    }
}
Share:
161,907
user517002
Author by

user517002

Updated on July 09, 2022

Comments

  • user517002
    user517002 almost 2 years

    Just have a problem here that I have no idea how to fix. I am doing a small project which involves a GUI and serial data. The GUI is being run by the main thread and since the data variables that hold my incoming serial data need to be updated continuously, these are being updated in a second thread. The problem is when I need to update some textboxes on the GUI, these need to be updated with data from the secondary thread and that is where my problem lies. I can't update them directly from the secondary thread and I have no idea how I would transfer data from my secondary thread and work out a system of updating them from main thread. I have put my code below:

    Any help would be great.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;
    using System.IO;
    using System.IO.Ports;
    using System.Threading;
    
    namespace GUIBike
    {
        /// <summary>
        /// Interaction logic for MainWindow.xaml
        /// </summary>
        public partial class MainWindow : Window
        {
            public static string inputdata;
            public static int MaximumSpeed, maximumRiderInput, RiderInput, Time, CurrentSpeed, DistanceTravelled, MaximumMotorOutput, MotorOutput, InputSpeed;
            public static string SaveDataString;
            public Thread Serial;
            public static SerialPort SerialData;
            public static string[] portlist = SerialPort.GetPortNames();
            public static string[] SaveData = new string[4];
            public static string directory = "C:\\";
    
            public MainWindow()
            {
                Serial = new Thread(ReadData);
                InitializeComponent();
                int Count = 0;
                for (Count = 0; Count < portlist.Length; Count++)
                {
                    ComPortCombo.Items.Add(portlist[Count]);
                }
            }
    
            private void StartDataButton_Click(object sender, RoutedEventArgs e)
            {
                SerialData = new SerialPort(ComPortCombo.Text, 19200, Parity.None, 8, StopBits.One);
                SerialData.Open();
                SerialData.WriteLine("P");
                Serial.Start();
                StartDataButton.IsEnabled = false;
                EndDataButton.IsEnabled = true;
                ComPortCombo.IsEnabled = false;
                CurrentSpeed = 0;
                MaximumSpeed = 0;
                Time = 0;
                DistanceTravelled = 0;
                MotorOutput = 0;
                RiderInput = 0;
                SaveData[0] = "";
                SaveData[1] = "";
                SaveData[2] = "";
                SaveData[3] = "";
                SaveDataButton.IsEnabled = false;
                if (SerialData.IsOpen)
                {
                    ComPortStatusLabel.Content = "OPEN";
                    SerialData.NewLine = "/n";
                    SerialData.WriteLine("0");
                    SerialData.WriteLine("/n");
                }
            }
    
            private void EndDataButton_Click(object sender, RoutedEventArgs e)
            {
                SerialData.Close();
                SaveDataButton.IsEnabled = true;
                SerialData.WriteLine("1");
                SerialData.WriteLine("0");
                if (!SerialData.IsOpen)
                {
                    ComPortStatusLabel.Content = "CLOSED";
                }
                int i = 0;
                for (i = 0; i < 4; i++)
                {
                    if (i == 0)
                    {
                        SaveDataString = "MaximumSpeed during the Ride was = " + Convert.ToString(MaximumSpeed) + "m/h";
                        SaveData[i] = SaveDataString;
                    }
                    if (i == 1)
                    {
                        SaveDataString = "Total Distance Travelled = " + Convert.ToString(DistanceTravelled) + "m";
                        SaveData[i] = SaveDataString;
                    }
                    if (i == 2)
                    {
                        SaveDataString = "Maximum Rider Input Power = " + Convert.ToString(maximumRiderInput) + "Watts";
                        SaveData[i] = SaveDataString;
                    }
                    if (i == 3)
                    {
                        SaveDataString = "Maximum Motor Output Power = " + Convert.ToString(MaximumMotorOutput) + "Watts";
                        SaveData[i] = SaveDataString;
                    }
                }
            }
    
            private void SaveDataButton_Click(object sender, RoutedEventArgs e)
            {
                //File.WriteAllBytes(directory + "image" + imageNO + ".txt", ); //saves the file to Disk    
                File.WriteAllLines(directory + "BikeData.txt", SaveData);
            }
    
            public void ReadData()
            {
                int counter = 0;
    
                while (SerialData.IsOpen)
                {
                    if (counter == 0)
                    {
                        //try
                        //{
                            InputSpeed = Convert.ToInt16(SerialData.ReadChar());
                            CurrentSpeed = InputSpeed;
                            if (CurrentSpeed > MaximumSpeed)
                            {
                                MaximumSpeed = CurrentSpeed;
                            }
                            SpeedTextBox.Text = "Current Wheel Speed = " + Convert.ToString(CurrentSpeed) + "Km/h";
                            DistanceTravelled = DistanceTravelled + (Convert.ToInt16(CurrentSpeed) * Time);
                            DistanceTravelledTextBox.Text = "Total Distance Travelled = " + Convert.ToString(DistanceTravelled) + "Km";
                        //}
                        //catch (Exception) { }
                    }
                    if (counter == 1)
                    {
                        try
                        {
                            RiderInput = Convert.ToInt16(SerialData.ReadLine());
                            if (RiderInput > maximumRiderInput)
                            {
                                maximumRiderInput = RiderInput;
                            }
                            RiderInputTextBox.Text = "Current Rider Input Power =" + Convert.ToString(RiderInput) + "Watts";
                        }
                        catch (Exception) { }
                    }
                    if (counter == 2)
                    {
                        try
                        {
                            MotorOutput = Convert.ToInt16(SerialData.ReadLine());
                            if (MotorOutput > MaximumMotorOutput)
                            {
                                MaximumMotorOutput = MotorOutput;
                            }
    
                            MotorOutputTextBox.Text = "Current Motor Output = " + Convert.ToString(MotorOutput) + "Watts";
                        }
                        catch (Exception) { }
                    }
                    counter++;
                    if (counter == 3)
                    {
                        counter = 0;
                    }
                }
            }
    
            private void ComPortCombo_SelectionChanged(object sender, SelectionChangedEventArgs e)
            {
                StartDataButton.IsEnabled = true;
            }
    
    
            private void Window_Closed(object sender, RoutedEventArgs e)
            {
                if (SerialData.IsOpen)
                {
                    SerialData.Close();
                }
            }
    
  • Ben
    Ben almost 5 years
    It's a little misleading that the only code in this answer is not your actual recommendation. Would be nice to have a working sample here. :)
  • Aetherus
    Aetherus over 4 years
    It's difficult to provide a working sample of the MVVM paradigm because it may require redesigning the whole program.
  • Ghotekar Rahul
    Ghotekar Rahul over 3 years
    Already Used Dispatcher but UI Hang after get data