Как обновить декартову диаграмму LiveCharts в WinForm при изменении переменной во внешнем классе?

#c# #winforms #livecharts

#c# #winforms #живые диаграммы

Вопрос:

Недавно я приобрел гарнитуру EEG (NeuroSky MindWave Mobile). Это просто устройство, надетое на голову для сбора данных о мозговых волнах. Устройство передает эти данные в режиме реального времени через Bluetooth, которые затем могут быть прочитаны / проанализированы программой.

NeuroSky предоставляет простой в использовании набор API, который я использовал для написания базового класса для чтения потоковых данных гарнитуры. Сокращенная версия этого выглядит следующим образом:

 using System;
using NeuroSky.ThinkGear;


namespace MindWave_Reader
{
    class ReadEEG
    {
        public double AlphaValue { get; set; }
        private Connector connector;

        public ReadEEG()
        {
            // Initialize a new Connector and add event handlers
            connector = new Connector();
            connector.DeviceConnected  = new EventHandler(OnDeviceConnected);

            // Scan for headset on COM7 port
            connector.ConnectScan("COM7");
        }


        // Called when a device is connected
        public void OnDeviceConnected(object sender, EventArgs e) {
            Connector.DeviceEventArgs de = (Connector.DeviceEventArgs)e;

            Console.WriteLine("Device found on: "   de.Device.PortName);
            de.Device.DataReceived  = new EventHandler(OnDataReceived);
        }


        // Called when data is received from a device
        public void OnDataReceived(object sender, EventArgs e) {
            Device.DataEventArgs de = (Device.DataEventArgs)e;
            DataRow[] tempDataRowArray = de.DataRowArray;

            TGParser tgParser = new TGParser();
            tgParser.Read(de.DataRowArray);

            /* Loops through the newly parsed data of the connected headset */
            for (int i = 0; i < tgParser.ParsedData.Length; i  ) {
                if(tgParser.ParsedData[i].ContainsKey("EegPowerAlpha")) {
                    AlphaValue = tgParser.ParsedData[i]["EegPowerAlpha"];
                    Console.WriteLine("Alpha: "   AlphaValue);
                }
            }
        }
    }
}
  

Приведенный выше код сначала пытается подключиться к гарнитуре EEG. После подключения вызывается каждый раз, когда данные поступают с гарнитуры OnDataReceived() . Этот метод выводит на консоль соответствующие значения данных потоковой гарнитуры (альфа-волны).

Теперь я хочу отобразить эти значения альфа-волны в режиме реального времени на декартовой диаграмме, и я обнаружил LiveCharts, который выглядит как аккуратная графическая библиотека. Этот пример WinForms соответствует тому, чего я пытаюсь достичь.

Значения альфа-волны должны быть нанесены на ось Y против времени на оси X. Однако вместо обновления диаграммы каждые 500 мс, как в примере, я бы хотел, чтобы она обновлялась только при получении данных с гарнитуры (другими словами, когда AlphaValue переменная обновляется OnDataReceived() в классе ReadEEG).

Я хотел бы знать, как я могу заставить мою WinForm взаимодействовать с классом ReadEEG, чтобы обновить его декартову диаграмму таким образом. Я немного новичок, поэтому буду признателен за любую помощь.

Я действительно надеюсь, что я выразился ясно и постарался максимально упростить объяснения. Если у вас есть какие-либо вопросы, не стесняйтесь задавать. Заранее спасибо за всю вашу помощь!

Ответ №1:

Вот один из способов, которым вы могли бы это сделать. Я добавил новое событие в ReadEEG класс. Это новое событие возникает всякий раз, когда появляется новое AlphaValue . В основной форме он подписывается на это событие и добавляет новые значения в ChartValues коллекцию, что и показано на LiveChart.

 using System;
using System.Data;
using NeuroSky.ThinkGear;

namespace MindWave_Reader
{
  class ReadEEG
  {
    public double AlphaValue { get; set; }
    private Connector connector;

    public ReadEEG()
    {
      // Initialize a new Connector and add event handlers
      connector = new Connector();
      connector.DeviceConnected  = new EventHandler(OnDeviceConnected);

      // Scan for headset on COM7 port
      connector.ConnectScan("COM7");
    }

    // Called when a device is connected
    public void OnDeviceConnected(object sender, EventArgs e)
    {
      Connector.DeviceEventArgs de = (Connector.DeviceEventArgs)e;

      Console.WriteLine("Device found on: "   de.Device.PortName);
      de.Device.DataReceived  = new EventHandler(OnDataReceived);
    }

    // Called when data is received from a device
    public void OnDataReceived(object sender, EventArgs e)
    {
      Device.DataEventArgs de = (Device.DataEventArgs)e;
      DataRow[] tempDataRowArray = de.DataRowArray;

      TGParser tgParser = new TGParser();
      tgParser.Read(de.DataRowArray);

      /* Loops through the newly parsed data of the connected headset */
      for (int i = 0; i < tgParser.ParsedData.Length; i  )
      {
        if (tgParser.ParsedData[i].ContainsKey("EegPowerAlpha"))
        {
          AlphaValue = tgParser.ParsedData[i]["EegPowerAlpha"];
          Console.WriteLine("Alpha: "   AlphaValue);
          // Raise the AlphaReceived event with the new reading.
          OnAlphaReceived(new AlphaReceivedEventArgs() { Alpha = AlphaValue });
        }
      }
    }

    /// <summary>
    /// The arguments for the <see cref="AlphaReceived"/> event.
    /// </summary>
    public class AlphaReceivedEventArgs : EventArgs
    {
      /// <summary>
      /// The alpha value that was just received.
      /// </summary>
      public double Alpha { get; set; }
    }

    /// <summary>
    /// Raises the <see cref="AlphaReceived"/> event if there is a subscriber.
    /// </summary>
    /// <param name="e">Contains the new alpha value.</param>
    protected virtual void OnAlphaReceived(AlphaReceivedEventArgs e)
    {
      AlphaReceived?.Invoke(this, e);
    }

    /// <summary>
    /// Event that gets raised whenever a new AlphaValue is received from the
    /// device.
    /// </summary>
    public event EventHandler AlphaReceived;
  }
}
  

Form1 Я добавил LiveCharts.WinForms.CartesianChart с помощью конструктора. Код, лежащий в основе, выглядит следующим образом:

 using LiveCharts;
using LiveCharts.Configurations;
using LiveCharts.Wpf;
using System;
using System.Windows.Forms;
using static MindWave_Reader.ReadEEG;

namespace MindWave_Reader
{
  public partial class Form1 : Form
  {
    /// <summary>
    /// Simple class to hold an alpha value and the time it was received. Used
    /// for charting.
    /// </summary>
    public class EEGPowerAlphaValue
    {
      public DateTime Time { get; }
      public double AlphaValue { get; }

      public EEGPowerAlphaValue(DateTime time, double alpha)
      {
        Time = time;
        AlphaValue = alpha;
      }
    }

    private ReadEEG _readEEG;

    /// <summary>
    /// Contains the alpha values we're showing on the chart.
    /// </summary>
    public ChartValues<EEGPowerAlphaValue> ChartValues { get; set; }

    public Form1()
    {
      InitializeComponent();

      // Create the mapper.
      var mapper = Mappers.Xy<EEGPowerAlphaValue>()
          .X(model => model.Time.Ticks)   // use Time.Ticks as X
          .Y(model => model.AlphaValue);  // use the AlphaValue property as Y

      // Lets save the mapper globally.
      Charting.For<EEGPowerAlphaValue>(mapper);

      // The ChartValues property will store our values array.
      ChartValues = new ChartValues<EEGPowerAlphaValue>();
      cartesianChart1.Series = new SeriesCollection
      {
        new LineSeries
        {
          Values = ChartValues,
          PointGeometrySize = 18,
          StrokeThickness = 4
        }
      };

      cartesianChart1.AxisX.Add(new Axis
      {
        DisableAnimations = true,
        LabelFormatter = value => new DateTime((long)value).ToString("mm:ss"),
        Separator = new Separator
        {
          Step = TimeSpan.FromSeconds(1).Ticks
        }
      });

      SetAxisLimits(DateTime.Now);
    }

    private void StartButton_Click(object sender, EventArgs e)
    {
      _readEEG = new ReadEEG();
      _readEEG.AlphaReceived  = _readEEG_AlphaReceived;
    }

    /// <summary>
    /// Called when a new alpha value is received from the device. Updates the
    /// chart with the new value.
    /// </summary>
    /// <param name="sender">The <see cref="ReadEEG"/> object that raised this
    /// event.</param>
    /// <param name="e">The <see cref="AlphaReceivedEventArgs"/> that contains
    /// the new alpha value.</param>
    private void _readEEG_AlphaReceived(object sender, EventArgs e)
    {
      AlphaReceivedEventArgs alphaReceived = (AlphaReceivedEventArgs)e;

      // Add the new alpha reading to our ChartValues.
      ChartValues.Add(
        new EEGPowerAlphaValue(
          DateTime.Now,
          alphaReceived.Alpha));

      // Update the chart limits.
      SetAxisLimits(DateTime.Now);

      // Lets only use the last 30 values. You may want to adjust this.
      if (ChartValues.Count > 30)
      {
        ChartValues.RemoveAt(0);
      }
    }

    private void SetAxisLimits(DateTime now)
    {
      if (cartesianChart1.InvokeRequired)
      {
        cartesianChart1.Invoke(new Action(() => SetAxisLimits(now)));
      }
      else
      {
        // Lets force the axis to be 100ms ahead. You may want to adjust this.
        cartesianChart1.AxisX[0].MaxValue =
          now.Ticks   TimeSpan.FromSeconds(1).Ticks;

        // We only care about the last 8 seconds. You may want to adjust this.
        cartesianChart1.AxisX[0].MinValue =
          now.Ticks - TimeSpan.FromSeconds(8).Ticks;
      }
    }
  }
}
  

Комментарии:

1. Большое спасибо! Это делает именно то, что я искал. Я поддержал, но пользователи с репутацией менее 15, по-видимому, не влияют на оценку. Просто из интереса, это самый «чистый» способ считывания изменения переменной в классе ReadEEG?

2. Вы должны иметь возможность пометить этот ответ как принятый, даже если вы не можете проголосовать. Это использование немного отличается от того, что я обычно делаю, если вы хотите получать уведомления об изменении свойства. Для этого вы захотите прочитать о INotifyPropertyChanged. Вы могли бы изменить то, как я сделал это выше, и поместить список альфа-значений в качестве свойства внутри класса ReadEEG, а затем привязать диаграмму к этому списку.

3. Просто отметил ответ как принятый. Я здесь новичок, поэтому все еще привыкаю к вещам. Не слишком ли я вас беспокою, запрашивая код с INotifyPropertyChanged? Я хотел бы посмотреть, как вы это сделаете. Спасибо за ваши вдумчивые ответы!