Search code examples
c#wpfxamlmvvmdata-binding

How to get data from a classes data received event to MainWindow to update the UI


I am creating a program which reads in .csv data from an Arduino which is connected to 3 different sensors. This data is read from a Serial class and sent to a ParseData class which adds the .csv data to a List and sends it off to the 3 classes for each sensor. (The data from all three sensors is sent at the same time from the Arduino and parsed from the ParseData class)

Three Classes: Sensor1, Sensor2, Sensor3

DataModels: SensorDataModel1, SensorDataModel2, SensorDataModel3

I want to update a chart in MainWindow.xaml/MainWindow.xaml.cs with data from three different classes as a line on the graph. I want to be notified in MainWindow that these three sensors have new data and then to update the graph with the data. I am not sure if I have to subscribe to an event for each sensor or something similar.

// SerialCommunication.cs - read in data and send to ArduinoDataReceived() method for parsing
private void OnDataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
    try
    {
        SerialPort sp = (SerialPort)sender;
        string serialBuffer = sp.ReadLine();

        // Send the subscriber the data received
        if (DataReceived != null)
        {
            DataReceived?.Invoke(this, new SerialDataReceivedEventArgs()
            {
                dataReceived = serialBuffer
            });
        }
    }
    catch (Exception ex) { MessageBox.Show(ex.ToString()); }
}


// ParseData.cs - subscribed to SerialCommunication.cs event and receives the data string and sends data to each sensors class
private void ArduinoDataReceived(object sender, SerialDataReceivedEventArgs e)
{
    Sensor1 sensor1 = new Sensor1();
    Sensor2 sensor2 = new Sensor2();
    Sensor3 sensor3 = new Sensor3();

    // parse all sensor data from Arduino to a list which holds all three sensors data
    List<string> sensorData = e.ToString().Split(',').ToList();

    // parse list for each sensor and send off to each class to process the data
    sensor1.AddData(sensorData1);
    sensor2.AddData(sensorData2);
    sensor3.AddData(sensorData3);
}


// SensorData1.cs - an example of storing the data and adding it to a List of the Sensors data model
public List<SensorDataModel1> ListSensorData1 = new List<SensorDataModel1>();

public void AddData(List<string> data, long dataLength)
{
    SensorDataModel1 s1 = new SensorDataModel1();

    s1.Add(data[0]);
    s1.Add(data[1]);
    s1.Add(data[2]);

    ListSensorData1.Add(s1);
}

I expect all three Data classes to send the data back to MainWindow and for MainWindow to update the Chart with these values each time they are read in from the serial port.


Solution

  • Without knowing how your entire archetecture is setup I can only assume. What i would most likely do in your situation is the following:

    1) have your main window subscribe to the SerialDataReceivedEventArgs such that the event will be raised in the code behind of the main window. This question should be a good starting place to hook that together. This will allow you to update the Data from your MainWindow.

    2) If you are using a viewmodel and binding the data; then i would create a Command or method to add the new sensor data to your collections. In your viewmodel i would create an ObservableCollection for each Sensor and then use Binding to attach them to the MainWindow.

    //MainWindow XAML
    <Window.DataContext>
        <vm:MainWindowViewModel/>
    </Window.DataContext>
    <YourControl1 ItemSource={Binding Source=SensorData1}/>
    <YourControl2 ItemSource={Binding Source=SensorData2}/>
    <YourControl3 ItemSource={Binding Source=SensorData3}/>
    

    3) In your MainWindow code behind (or in XAML if you want to be fancy creating new events) you can then call the ViewModel Command or Method to update the data. Using the names from the link above:

    // inside MainWindow.xaml.cs
    private void DisplaySerDataHandler(object sender, SerialDataReceivedEventArgs e)
    {
       var newSensorData1;
       //...This is where you set all the new sensor data you received to the variables. 
       var dContext = DataContext as MainWindowViewModel;
       dContext?.SetNewSensorData(newSensorData1, newSensorData2, newSensorData3);
    }
    

    4) Inside MainWindowViewModel.cs you can then have the business logic to alter the ObservableCollections which will update when added to and automagically update the UI elements.

    //you need an ObservableCollection for each list
    public ObservableCollection<sensorType> SensorData1 {get;set;}
    
    //then create the command or method to add the data to their respective Collection
    public void SetNewSensorData(sensorType sData1, sensorType sData2, sensorType sData3){
    
      //...then add the new data (with extra logic as needed)
      SensorData1.add(sData1);
      SensorData2.add(sData2);
      SensorData3.add(sData3);
    }
    

    There are many other elements that can/should be done. This was meant as an example as to how you could update the mainwindow controls with the incoming serialized data. It would be up to you how you want everything to be designed. Your MainWindowViewModel would implement INotifyPropertyChanged. I prefer the use of Commands as I can then try to remove code from the code behind and bind everything in XAML and then all my logic can be contained in the ViewModel. That is not a right or wrong way to do it, just my preference. I prefer to have code behind that is only used to specifically change the Control elements themselves and all other business logic is in the ViewModel.