1 year ago

#159947

test-img

Andromecles

In an MVC design, how should the Model communicate with the View?

Edit TL;DR: I have an MVC application that has a Controller which is aware of the Model and the View, but the View and Model do not have any references to each other and the Model does not have a reference to the Controller. I need to know best practice for requesting user input and telling the View to update from inside the Model. Should I somehow have the Controller bind properties, methods, and events between the Model and View or should I just push as much logic as possible into the controller so I don't have to worry about communicating with the View from the Model?


Original Post:

I am writing a WinForms application for interfacing with a device over a USB to I2C adapter. I am trying to use the Model-View-Controller pattern, but currently have an issue with Model to View communication that I haven't been able to think through.

Upon a user action my program needs to be able to read registers from the connected I2C device and display them for the user to view. At the moment I have a view interface IView, a concrete view class (the Form), a concrete controller class CommunicationController, a model interface ISerialCommunicationModel, and a concrete model class I2CCommunicator. The view obviously takes care of displaying the data, the model handles communication and processing of the data from the connected device, and the controller manages communication between model and view in response to user events. The issue that I am running into is when I am writing my concrete model class I2CCommunicator, I don't know how to handle events that require user input to continue or how to notify the View if a UI element needs to be updated during the execution of a model method. Since my model Interface does not have an IView or a controller reference, I don't know how to get feedback to or from the view mid-method.

See code below for a simplified representation of my program that highlights the following issues.

Problem 1: In my concrete View class I want to increment a progress bar position when the model writes to registers in the connected I2C device. How do I send a message from the model to the view to update the progress bar after each register is read (before the model's WriteDataToSerialDevice method is finished executing)?

Problem 2: Before data is read from the serial device, its type is checked. When the model finds that the type of the connected device is not what is expected, it requires the user to decide whether or not they still want to read from this device. How can the model ask the View to query the user and return the result?

Potential solutions I have thought of for this issues are as follows:

  • Give the model interface ISerialCommunicationModel an IView field to which the controller assigns the appropriate concrete view. Then the model could call methods of the view through the IView field, which can update UI elements or query the user. However, I'm not sure if giving the model interface an IView dependency is best practice. In the examples I have seen the model has been independent of controllers, views, or even view interfaces.

  • Give the model interface ISerialCommunicationModel some Func<> properties to which the controller assigns methods of the IView. This would allow the model to call methods of the view that could interact with the UI without taking on an IView dependency. However, this is a bit cumbersome and feels like an awkward way of circumventing the above solution.

  • For UI element update only: Have the controller subscribe methods of the IView implementation to specific ISerialCommunicationModel events. This would allow at least one way communication from the model to the view, but would not allow the view to return anything to the model.

  • For user querying issue only: Give the model interface a method that is meant to be used to retrieve the device type separately from read function. Then the controller can call that method before calling the read function (which will no longer require the device type check) and deal with handling of proper/improper device type on the controller level rather than on the model level. I don't hate this idea, but it puts the responsibility of checking the device type on the controller and that seems like business logic rather than just mediating View-Model communication.

Can anyone give me some advice on these issues. Are any of the above solutions considered good practice? Or is there a more "canonical" MVC way of doing this?


interface IView
{
    public bool GetUserInput(string message); // Used to raise a notifying window and request
                                              // binary input form user.
    public void UpdateDisplayedRegisters(byte[] registerData);
    public byte[] GetDisplayedRegisterData(); // Used to get register data from view so it can 
                                              // be written to the serial device.
    public void UpdateProgressBar(int fillPercent);
}

interface ISerialCommunicationModel // I make the interface a generic serial interface in case 
                                    // the adapter or communication protocol changes in the future
{
    public byte[] ReadDataFromSerialDevice();
    public void WriteDataToSerialDevice(byte[] dataToWrite);
}

public class CommunicationController // Concrete controller class used for responding to user 
                                     // input. For the sake of the example imagine that the 
                                     // methods in this class are subscribed to GUI events.
{
    private IView _view;
    private ISerialCommunicationModel _serialCommunicator;

    public void ReadDeviceData()
    {
        byte[] deviceData = _serialCommunicator.ReadDataFromSerialDevice();
        _view.UpdateDisplayedRegisters(deviceData);
    }
    public void WriteViewDataToDevice()
    {
        byte[] dataForDevice = _view.GetRegisterData();
        _serialCommunicator.WriteDataToSerialDevice(dataForDevice);
    }
}

public class I2CCommunicator : ISerialCommunicationModel
{
    // ExampleI2CDevice is introduced just so I can portray interaction with a device class
    private ExampleI2CDevice _exampleI2CDevice = new ExampleI2CDevice();
    private bool CheckDeviceTypeIsCorrect(ExampleI2CDevice deviceToCheck);
    public byte[] ReadDataFromSerialDevice()
    {
        if (CheckDeviceTypeIsCorrect(_exampleI2CDevice) == false)
        {
            // This is where I need to send a message to the View to notify user that the device
            // type is wrong and to ask if they would like to attempt to read anyway. How should
            // this be done? Essentially I want to call IViewGetUserInput(string message), but 
            // I have no instance available in the model through which to call it.
        }
        else
        {
        byte[] dataFromDevice = _exampleI2CDevice.Read();
        }
    }

    public void WriteDataToSerialDevice(byte[] dataToWrite);
    {
        foreach (byte register in dataToWrite)
        {
            _exampleI2CDevice.Write(register);
            // This is where I need to send a message to the view to increment the progress bar
            // How should this be done?
        }
    }
}

c#

winforms

model-view-controller

separation-of-concerns

0 Answers

Your Answer

Accepted video resources