How to apply the Model-View-Presenter pattern in a WinForms app centered around a TabControl?

35 Views Asked by At

I'm developing a single window, digital signal processing application using WinForms, centered around tabs from a TabControl. The user can switch between tabs and do stuff. Each tab represents a stage of the data analysis (acquiring a signal -> clustering -> runnning a filter and looking at some graphs -> looking at some more graphs and data). The tabs are called: Acquisition, Clustering, Filtering and Identification.

I have defined a Signal class. You can run analysis (on the same window) for multiple signals (loaded or acquired) - naturally, I have a RuntimeData class holding a List<Signal> LoadedSignals and a List<Signal> AcquiredSignals. You must switch to the ListViewItem representing the Signal you want to analyze, and the UI updates accordingly.

I did some research on the Model-View-Presenter structure and how it fits well to WinForms, but I'm having a hard time understanding how to apply it in my case: if my Views are Tabs, how would they implement the IRespectiveTabView interface? And how would that work when I have multiple data subjects?

For example, I begin refactoring the old FilterRegion.cs class:

public class FilterRegion
{
    public double PhiMin { get; set; }
    public double PhiMax { get; set; }
    public double VMin { get; set; }
    public double VMax { get; set; }

    // Interactive lines
    public VLine LinePhiMin { get; set; } = new();
    public VLine LinePhiMax { get; set; } = new();
    public HLine LineVMin { get; set; } = new();
    public HLine LineVMax { get; set; } = new();

    public bool InvertedPhiLines()
    {
        return PhiMin > PhiMax;
    }

    public bool InvertedVLines()
    {
        return VMin > VMax;
    }

    public Color FilterPolyColor { get; set; }
    public Polygon MainPoly { get; set; }
    public Polygon LeftPoly { get; set; }
    public Polygon RightPoly { get; set; }

    double[] XsMainPoly;
    double[] YsMainPoly;
    double[] XsLeftPoly;
    double[] XsRightPoly;

    double[] nullVals = { 0, 0, 0, 0 };

    public void EnableLinesInteraction()
    {
        LinePhiMin.DragEnabled = true;
        LinePhiMax.DragEnabled = true;
        LineVMin.DragEnabled = true;
        LineVMax.DragEnabled = true;
    }

    public void DisableLinesInteraction()
    {
        LinePhiMin.DragEnabled = false;
        LinePhiMax.DragEnabled = false;
        LineVMin.DragEnabled = false;
        LineVMax.DragEnabled = false;
    }

    public bool IsEmpty()
    {
        return PhiMin == 0 && PhiMax == 0 && VMin == 0 && VMax == 0;
    }

    public bool IsBroken()
    {
        return PhiMin > PhiMax;
    }

    public void Plot(FormsPlot targetPlot)
    {
       // Plotting logic.
    }

    public void UpdatePolys(FormsPlot targetPlot)
    {
       // Logic for the update of the filter polygons. 
    }

    public void SaveManualFilter()
    {
        PhiMin = LinePhiMin.X;
        PhiMax = LinePhiMax.X;

        VMin = LineVMin.Y;
        VMax = LineVMax.Y;
    }

    public void SetLineEvents(FormsPlot targetPlot)
    {
        LinePhiMin.Dragged += (s, e) =>
        {
            UpdatePolys(targetPlot);
            SaveManualFilter();
        };

        LinePhiMax.Dragged += (s, e) =>
        {
            UpdatePolys(targetPlot);
            SaveManualFilter();
        };

        LineVMin.Dragged += (s, e) =>
        {
            UpdatePolys(targetPlot);
            SaveManualFilter();
        };

        LineVMax.Dragged += (s, e) =>
        {
            UpdatePolys(targetPlot);
            SaveManualFilter();
        };
    }
}

The model class:

namespace App.Models
{
    public class FilterRegionModel
    {
        public double PhiMin { get; set; }
        public double PhiMax { get; set; }
        public double VMin { get; set; }
        public double VMax { get; set; }

        public bool InvertedPhiLines()
        {
            return PhiMin > PhiMax;
        }

        public bool InvertedVLines()
        {
            return VMin > VMax;
        }

        public bool IsEmpty()
        {
            return PhiMin == 0 && PhiMax == 0 && VMin == 0 && VMax == 0;
        }

        public bool IsBroken()
        {
            return PhiMin > PhiMax;
        }
    }
} 

The view interface:

namespace App.Views
{
    public interface IFilterRegionView
    {
        public VLine LinePhiMin { get; set; } 
        public VLine LinePhiMax { get; set; } 
        public HLine LineVMin { get; set; } 
        public HLine LineVMax { get; set; }
        public Color FilterPolyColor { get; set; }

        public Polygon MainPoly { get; set; }
        public Polygon LeftPoly { get; set; }
        public Polygon RightPoly { get; set; }

        public double[] XsMainPoly { get; set; }
        public double[] YsMainPoly { get; set; }
        public double[] XsLeftPoly { get; set; }
        public double[] XsRightPoly { get; set; }
    }
}

The presenter class:

namespace App.Presenters
{
    public class FilterRegionPresenter
    {
        IFilterRegionView View;
        public FilterRegionModel Model { get; set; }

        public FilterRegionPresenter(IFilterRegionView view, FilterRegionModel model)
        {
            this.View = view;
            Model = model;
        }
            
        public void Plot(FormsPlot targetPlot)
        {
            // Plotting logic.
        }

        public void Update(FormsPlot targetPlot)
        {
            // Polygon updating logic.
        }

        public void SetLineEvents(FormsPlot targetPlot)
        {
            // Adding functions that will run when events are raised.
        }
    }
} 

But now I'm stuck on the problem that is applying it to the Form, given that I have TabPages. Each Signal has multiple Cluster objects the user can select, and each Cluster object will print a graph to a single plot (the same plot refreshes when selecting another Cluster). This graph will contain a pair of FilterRegion objects.

0

There are 0 best solutions below