2010-11-23 6 views
26

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

Любая помощь была бы замечательной.

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

Возможный контратип http://stackoverflow.com/questions/1684341/c-how-does-a-background-thread-tell-a-ui-thread- что-то-еще-готовой делать-SOMET –

ответ

0

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

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

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

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

Есть много других вариантов вокруг тоже, но эти два наиболее часто, что приходит на ум.

26

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

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 используется резьба по имени теста обновить TextBox

1

Вы должны использовать Dispatcher.BeginInvoke. Я не тестировал его, но вы можете проверить ссылку this (это та же ссылка, предоставленная 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; 
     } 
    } 
} 
23

0.

Я также разрабатываю инструмент тестирования последовательного порта с использованием 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.

6

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

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

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

2

Как говорят 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, как это. Объявление его на уровне класса дало мне много проблем со статическими/нестатическими контекстами.

9

Вы можете использовать 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(); 
       });  
     } 
    } 
Смежные вопросы