Подтвердить что ты не робот

Обновление GUI (WPF) с использованием другого потока

Просто у меня проблема, я не знаю, как исправить. Я делаю небольшой проект, который включает графический интерфейс и последовательные данные. GUI запускается главным потоком, и поскольку переменные данных, которые содержат мои входящие последовательные данные, необходимо постоянно обновлять, они обновляются во втором потоке. Проблема в том, что когда мне нужно обновить некоторые текстовые поля в графическом интерфейсе, их необходимо обновить данными из вторичного потока, и именно там моя проблема. Я не могу обновить их непосредственно из вторичного потока, и я понятия не имею, как я буду передавать данные из моего вторичного потока и выработать систему их обновления из основного потока. Я поставил свой код ниже:

Любая помощь будет большой.

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();
            }
        }
4b9b3361

Ответ 1

Вы можете использовать делегата для решения этой проблемы. Ниже приведен пример, показывающий, как обновлять текстовый блок с помощью другого потока

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 используется тестом thread named для обновления textBox

Ответ 2

есть.

Я также разрабатываю инструмент тестирования последовательного порта с использованием WPF, и я хотел бы поделиться своим опытом.

Я думаю, вам нужно реорганизовать исходный код в соответствии с шаблоном проектирования MVVM.

В начале я встретил ту же проблему, что и вы, и решил ее с помощью этого кода:

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

Это работает, но слишком уродливо. Я не знаю, как его реорганизовать, пока я не увидел это: http://www.codeproject.com/Articles/165368/WPF-MVVM-Quick-Start-Tutorial

Это очень любезное пошаговое руководство по MVVM для начинающих. Нет блестящего интерфейса, без сложной логики, только базовый MVVM.

Ответ 3

Вы можете использовать Dispatcher.Invoke, чтобы обновить свой графический интерфейс из вторичного потока.

Вот пример:

    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();
               });    
        } 
    }

Ответ 4

Используйте следующий метод обновления графического интерфейса.

     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 { }));
     }

Сохраняйте это в виду, когда вы используете этот метод в то время, не обновляйте один и тот же объект прямо из потока диспетчера, иначе вы получите только эту обновленную строку, и этот метод бесполезен/бесполезен. Если все еще не работает, то комментируйте, что строка внутри метода и комментарий не комментируются, оба имеют почти такой же эффект, что и другой способ доступа к нему.

Ответ 5

Как говорят akjoshi и Julio, речь идет о отправке Action для обновления GUI в том же потоке, что и элемент GUI, но из метода обработки фоновых данных. Вы можете увидеть этот код в определенной форме в ответе akjoshi выше. Это общая версия.

myTextBlock.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal,
                                   new Action(delegate() 
                                      {
                                      myTextBlock.Text = Convert.ToString(myDataObject.getMeData());
                                      }));

Важнейшей частью является вызов диспетчера вашего объекта пользовательского интерфейса, который гарантирует, что у вас есть правильный поток.

Из личного опыта кажется намного проще создать и использовать Action inline, как это. Объявление его на уровне класса дало мне много проблем со статическими/нестатическими контекстами.

Ответ 6

Вам нужно использовать Dispatcher.BeginInvoke. Я не тестировал его, но вы можете проверить эту ссылку (это та же ссылка, предоставленная Julio G), чтобы лучше понять, как обновить Элементы управления пользовательского интерфейса из разных потоков. Я изменил код ReadData()

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.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal,
        new Action(delegate() { SpeedTextBox.Text = "Current Wheel Speed = " + Convert.ToString(CurrentSpeed) + "Km/h"; });//update GUI from this thread


                DistanceTravelled = DistanceTravelled + (Convert.ToInt16(CurrentSpeed) * Time);

    DistanceTravelledTextBox.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal,
        new Action(delegate() {DistanceTravelledTextBox.Text = "Total Distance Travelled = " + Convert.ToString(DistanceTravelled) + "Km"; });//update GUI from this thread

            //}
            //catch (Exception) { }
        }
        if (counter == 1)
        {
            try
            {
                RiderInput = Convert.ToInt16(SerialData.ReadLine());
                if (RiderInput > maximumRiderInput)
                {
                    maximumRiderInput = RiderInput;
                }                       
    RiderInputTextBox.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, 
        new Action(delegate() { RiderInputTextBox.Text = "Current Rider Input Power =" + Convert.ToString(RiderInput) + "Watts"; });//update GUI from this thread
            }
            catch (Exception) { }
        }
        if (counter == 2)
        {
            try
            {
                MotorOutput = Convert.ToInt16(SerialData.ReadLine());
                if (MotorOutput > MaximumMotorOutput)
                {
                    MaximumMotorOutput = MotorOutput;
                }
    MotorOutputTextBox.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, 
        new Action(delegate() { MotorOutputTextBox.Text = "Current Motor Output = " + Convert.ToString(MotorOutput) + "Watts"; });//update GUI from this thread                        
            }
            catch (Exception) { }
        }
        counter++;
        if (counter == 3)
        {
            counter = 0;
        }
    }
}

Ответ 7

У вас есть пара вариантов, я думаю.

Можно использовать BackgroundWorker. Это общий помощник для многопоточности в приложениях. Он предоставляет событие DoWork, которое обрабатывается в фоновом потоке из пула потоков и событие RunWorkerCompleted, которое вызывается обратно в основной поток при завершении фонового потока. Он также имеет преимущество try/catch кода, запущенного в фоновом потоке, так что необработанное исключение не убивает приложение.

Если вы не хотите идти по этому маршруту, вы можете использовать объект диспетчера WPF для вызова действия для обновления GUI обратно в основной поток. Случайная ссылка:

http://www.switchonthecode.com/tutorials/working-with-the-wpf-dispatcher

Существует много других вариантов, но это два наиболее распространенных момента, которые приходят на ум.