Winforms MVP Pattern w/ Multiple Views - c#

I recently created a winforms application that followed no specific design pattern. This application has 4 different "views", each implemented using a TableLayoutPanel. One view is a "main" view that allows the user to select input files and the other 3 views contain DataGridViews that allow the user to work with the data loaded from the input file.
The problem lies in the fact that I have a single form with 4 different panels within it that are hidden and made visible when needed. But this has caused my form class to become much larger than I would like considering I have different events and methods that operate on the data for each panel all within the same class. So I did some research and came across Model-View-Presenter, but I've only came across examples that show applications with single-views.
My question is, if I use MVP and each view has its own interface and presenter, and the concrete implementation of the view is done using a Form, what is the best way to switch between views (for example, when clicking "next").
Should the concrete implementation of my view even be a Form? Am I missing something here? I'd like to follow the MVP pattern but I am open to suggestions if there is a better alternative.

First of all, you want to create a UserControl for each of the three DataGridView forms. As you are using MVP, each one should have an interface that the control inherits. For example:
public interface IDataGridView1
{
// Properties, Methods, etc...
}
public interface IDataGridView2
{
// Properties, Methods, etc...
}
public interface IDataGridView3
{
// Properties, Methods, etc...
}
Here is an example of the DataGridView1 UserControl, which inherits from its interface, and also from Control:
public class DataGridView1 : Control, IDataGridView1
{
TableLayoutPanel layoutPanel;
public DataGridView1()
{
layoutPanel = new TableLayoutPanel();
// Set up the layoutPanel.
// Rest of constructor, define your controls.
// Add your controls to layoutPanel.
// Add layoutPanel to this control.
Controls.Add(layoutPanel);
}
// Methods etc...
}
The other two DataGridViews will be similar but with their own functionality.
You could then create an interface for the MainView, which includes properties for the three DataGridViews it should contain, and methods to show one DataGridView whilst hiding the rest:
public interface IMainView
{
IDataGridView1 DataView1 { get; set; }
IDataGridView2 DataView2 { get; set; }
IDataGridView3 DataView3 { get; set; }
void ShowOnlyDataView1();
void ShowOnlyDataView2();
void ShowOnlyDataView3();
// Other methods, properties, etc...
}
The MainView class would inherit from Form and its own interface. Here I have shown the instantiated DataGridViews being passed in via the form's constructor:
public class MainView : Form, IMainView
{
public IDataGridView1 DataView1 { get; set; }
public IDataGridView2 DataView2 { get; set; }
public IDataGridView3 DataView3 { get; set; }
TableLayoutPanel layoutPanel;
public MainView(IDataGridView1 dataView1, IDataGridView2 dataView2,
IDataGridView3 dataView3)
{
this.DataView1 = dataView1;
this.DataView2 = dataView2;
this.DataView3 = dataView3;
layoutPanel = new TableLayoutPanel();
// Define your layout panel here.
// Add your controls to layoutPanel.
// Add layoutPanel to the MainView.
Controls.Add(layoutPanel);
// Rest of constructor...
}
// Hides other views and show DataView1.
public void ShowOnlyDataView1()
{
DataView2.Hide();
DataView3.Hide();
DataView1.Show();
}
// Hides other views and show DataView2.
public void ShowOnlyDataView2()
{
// Etc...
}
// Hides other views and show DataView3.
public void ShowOnlyDataView3()
{
// Etc...
}
// Other Methods etc...
}
Here is an example of the your Main method. You will want to instantiate each DataGridView and pass these into your MainView:
public static void Main(string[] args)
{
IDataModel dataModel = new DataModel();
IDataGridView1 dataView1 = new DataGridView1();
IDataGridView2 dataView2 = new DataGridView2();
IDataGridView3 dataView3 = new DataGridView3();
IMainView mainView = new MainView(dataView1, dataView2, dataView3);
DataGridPresenter1 dataPresenter1 = new DataGridPresenter1(dataView1, dataModel);
DataGridPresenter2 dataPresenter2 = new DataGridPresenter2(dataView2, dataModel);
DataGridPresenter3 dataPresenter3 = new DataGridPresenter3(dataView3, dataModel);
MainPresenter mainPresenter = new MainPresenter(mainView, dataModel);
}
Something to that effect.
So your three DataGridViews are displayed within your MainView, and all four views are accessed by their own Presenters.

Related

Using other classes to modify panel within Windows Forms

The program i am trying to make involves one main form, which should be able to switch between 5 different menus. Programming all the functionality within the Form.cs file would make this an extremely long class, so what i want to do is call the Panel from another class to add control elements and load all the data from a MySQL database, depending on the menu chosen.
More specifically I have my ParentInterface.cs, where I want to show a ChoreUI within a dynamic Panel which will be modified in a new class called ChoreUI.cs.
I have tried making ChoreUI inherit from the ParentInterface, as well as making it the target. Though my lack of knowledge of Windows Forms is in the way.
ParentInterface.cs
namespace ChoreApplication.UI{
public partial class ParentInterface : Form
{
private ChoreUI ChoreUI;
private ParentInterface PUI;
public ParentInterface()
{
ChoreUI = new ChoreUI(PUI);
InitializeComponent();
}
private void ChoreNavButton_Click(object sender, EventArgs e)
{
var ChoreUI = new ChoreUI(PUI);
ChoreUI.DillerDaller();
}
}
ChoreUI.cs
namespace ChoreApplication.UI
{
class ChoreUI
{
public ParentInterface PUI;
public ChoreUI(ParentInterface PUI)
{
this.PUI = PUI;
}
public void DillerDaller()
{
PUI.dynamicPanel.Controls.Add(new Label { Location = new Point(10, 10), Name="textName", Text = "hello"});
}
}
I want to be able to add new control elements to the Panel, from the ChoreUI class instead of in the ParentInterface class. But as of now I am not succeeding in doing so.
What you have at the moment is not that bad, your child component has a reference to the parent and that's ok.
This, however, is the issue
public void DillerDaller()
{
PUI.dynamicPanel.Controls.Add(new Label { Location = new Point(10, 10), Name="textName", Text = "hello"});
}
At the basic level, you violate the encapsulation principle, where the dynamicPanel is protected inside the form so that it's not accessible from outside. Inheriting the child component from the main form is not the right solution.
At somewhat higher level, you violate here the so called Law of Demeter where the inner implementation details of a component should not be that misused. Changing the dynamicPanel visibility to public will not help. Rather, the rule says you should wrap such implementation details with a stable interface
public partial class ParentInterface : Form
{
...
public void AddDynamicPanelControl( Control control ) {
this.dynamicPanel.Controls.Add( control );
}
public void RemoveDynamicPanelControl( Control control ) {
this.dynamicPanel.Controls.Remove( control );
}
}
and use the newly introduced interface
public void DillerDaller()
{
var label = new Label { Location = new Point(10, 10), Name="textName", Text = "hello"};
this.PUI.AddDynamicPanelControl( label );
// if you store the reference to the label somewhere,
// you'll be able to call `Remove....` to remove this control from
// the form
}

Circular dependency when adding reference (Delegates)

Basically I have 2 projects, a form and a user control.
I need both of them to be in different projects but the form need to refer to the user control as it is using the user control. And the user control will need to refer to the form as it is using one of the form class. When I add the second one because it need the , VS will complain circular dependency which is understandable. How do I solve this?
Logically the form should depend on the user control. You could create an interface to replace the form within the user control project, and then have the form implement that interface.
Example user control project;
public interface IForm
{
string MyString { get; }
}
public class MyUserControl : UserControl
{
public IForm Form { get; set; }
private void ShowMyString()
{
String myString = Form.MyString;
...
}
}
Example Form project
public class MyForm : Form, IForm
{
public MYString { get "My String Value"; }
}
I think the root cause of your problem is that you haven't separated your concerns between the form and the control properly.
Since you have a (somewhat generic) control, it shouldn't depend on the form. All of the logic of the control should reside within the control itself. The form should only black-box consume the control: add it, set public fields, call public methods, etc. anything else is a violation of encapsulation.
Sometimes, controls may need to know things about their parent form. In this case, I would suggest something as simple as adding a Parent field to the child control.
if you need something more specific from the form, you can always add an interface; the interface should only list those things that the control needs from the form. For example, if you need the size, you can add:
public interface IControlParent {
int Width { get; }
int Height { get; }
}
This way, you clearly see the dependencies (what the control needs from the parent), and if the parent type/contract changes, you don't need to do as much to change your control class.
You must sepárate your code, its never a good idea to have a reference to an application assembly, if you try to reuse it in the future, the applications exe should go with the control.
So, take the class from the form project and move it to the control project or create a library project, put the class on it and reference it from your control and your app projects.
You should use an event (delegate). Let's assume that inside your form project you created one class: Form1. And inside user control you defined UserControl1.
UserControl1 needs to instantiate and call a method from Form1:
public class Form1
{
public void Execute(string sMessage)
{
Console.WriteLine(sMessage);
Console.ReadLine();
}
}
UserControl1:
public class UserControl
{
public Func<object, object> oDel = null;
public void Execute()
{
oDel?.Invoke("HELLO WORLD!");
}
}
And from the class that instantiate UserControl, let's call it ParentClass:
public class ParentClass
{
public void Execute()
{
UserControl oUserControl = new UserControl();
oUserControl.oDel = Form1Action;
oUserControl.Execute();
}
public object Form1Action(object obj)
{
string sObj = Convert.ToString(obj);
Form1 oForm = new Form1();
oForm.Execute(sObj);
return null;
}
}
This approach gives the responsibility of handling an event to the high level class.

Where to create the class

I'm trying to model a production system with "facility" as Class and some subclasses down to "Activity". The facility has a name as only parameter (at the moment), and I'd like to create an instance of the class reading the name as an input from a textbox. Since "activity" is inherit the properties from it's "parent classes" I'll create an instance of the class "activity" and not it's parent.
The problem is that I don't know where to create the class and how to pass it so that when I add the first subclass "Workstation" I can edit the properties of the same "activity" I created earlier.
I don't really have any code to add at this point unfortunately, but please tell me if there's anything special you'd like to see and I'll try to add it to the post.
And by the way, it's in the shape of a WinForm application with a GUI I'm trying to do this.
There are a couple things to note here. First, you'll want to use the Composite pattern to encapsulate the relationships between your classes. (For those who don't understand the OP's type hierarchy, it does make perfect sense in a factory context. There are many activities going on, which can be grouped into workstations and at a higher level into facilities.)
So, you should probably have a base Activity class (that supports the Composite pattern by exposing a collection of child activities), and then your "levels" (like Facility and Workstation) will inherit from Activity. Each of these classes will have unique properties.
The following classes should be created in their respective files, e.g. Activity.cs, Factory.cs, Workstation.cs:
class Activity
{
// An attribute that every Activity may need: a displayable name.
// This might be useful if you have a TreeView, e.g., showing all the activities.
public string Name { get; private set; }
// Every Activity could have child activities - this is the Composite pattern.
// You can loop through these to navigate through the hierarchy of your data.
// (This is often done using recursion; see example below with GetAllWorkstations().)
public List<Activity> ChildActivities { get; private set; }
public Activity()
{
ChildActivities = new List<Activity>();
}
public override string ToString() { return Name; }
}
class Factory : Activity
{
public string City { get; private set; }
public string Address { get; private set; }
}
class Workstation : Activity
{
public string WorkstationNumber { get; private set; }
}
The responsibility of loading your model then has to be handled somewhere. A good place to do it is in your main form. For example, you might write code like this:
class MainForm : Form
{
private readonly List<Factory> topLevelFactoryActivities;
public MainForm()
{
// ... other code
topLevelFactoryActivities = LoadTopLevelFactoryActivities();
}
private IEnumerable<Factory> LoadTopLevelFactoryActivities()
{
var factories = new List<Factory>();
// TODO: Load the factories, e.g. from a database or a file.
// You can load all the child objects for each factory here as well,
// or wait until later ("lazy-loading") if you want to.
// NOTE: If this becomes complex, you can move the LoadTopLevelFactoryActivities()
// method to its own class, which then becomes your "data access layer" (DAL).
return factories;
}
}
Now, if you want to find all the workstations that are part of a particular factory, you would write a method like the following on the Factory class:
class Factory : Activity
{
// ... other code
public IEnumerable<Workstation> GetAllWorkstations()
{
return GetWorkstationsRecursive(this);
}
private IEnumerable<Workstation> WorkstationsIn(Activity parentActivity)
{
foreach (var workstation in parentActivity.ChildActivities.OfType<Workstation>)
{
// Uses a C# feature called 'iterators' - really powerful!
yield return workstation;
}
foreach (var childActivity in parentActivity.ChildActivities)
{
// Using recursion to go down the hierarchy
foreach (var workstation in WorkstationsIn(childActivity))
{
yield return workstation;
}
}
}
}
You would call it like so, e.g. in your main form:
class MainForm : Form
{
// ... other code
public MainForm()
{
// ... other code
// Assume this is assigned to the factory that you want to get all the workstations for
Factory myFactory;
var workstations = myFactory.GetAllWorkstations();
// Now you can use 'workstations' as the items source for a list, for example.
}
}
As an example use case, you might want to show a second form (that belongs to the main form) which shows a list of all the workstations. (In practice you probably shouldn't create too many windows; prefer building a nonoverlapping layout. But just to show how you might pass the model instances around...)
class WorkstationListForm : Form
{
private IEnumerable<Workstation> workstations;
public WorkstationListForm(IEnumerable<Workstation> workstations)
{
this.workstations = workstations;
//TODO: You can now use 'workstations' as the ItemsSource of a list view in this form.
}
}
You could, of course, make topLevelFactoryActivities public on your MainForm and pass the variable this of the MainForm to the WorkstationListForm constructor instead. Then you could access the member on MainForm like this:
public WorkstationListForm(MainForm mainForm)
{
var topLevelFactoryActivities = mainForm.topLevelFactoryActivities;
// Now WorkstationListForm has full access to all the data on MainForm. This may or
// may not be helpful (it's usually best to minimize sharing and public fields).
}
Second, you'll want to use a proper separation between your view (user interface code/classes) and your model (the Activity hierarchy).
Third, if there's going to be any kind of live data being pushed to the user interface then you'll need a databinding mechanism to automatically update the view whenever the model changes.
In general, #2 & #3 are popularly addressed via the Model-View-ViewModel pattern. There is an excellent tutorial here for building an MVVM app using WinForms/C#.
That should get you started, at least. Also see an answer to a similar question. (Sorry about promoting my own answer, but I don't want to type out the whole example twice. Please forgive me. :))

Way of drawing on picturebox in different class

I'm currently working on a program in WinForms that will require a lot of special logic for rendering and mouse clicks on a PictureBox. I'd rather not have all of that logic stored in a custom control or even worse the main form that hosts the picture box. An added complication is there are certain objects that need to be known about in order to draw them correctly or handle clicking them.
Currently, the only idea I have is to make separate "Renderer" and "MouseHandler" classes that have a handle to the picture box and I can handle whatever events inside there. The issue is doing this while maintaining an MVP structure.
So, here's a little example code to better explain what's going on.
Main Form
public partial class Form1 : Form, IView
{
private Renderer _render;
private MouseHandler _mouseHandler;
public Form1() { InitializeComponent(); }
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
_render = new Renderer(Picbox);
_mouseHandler = new MouseHandler(Picbox);
}
#region Implementation of IView
public string FirstName { get; set; }
public string LastName { get; set; }
#endregion
}
Presenter for Main Form
public class Presenter
{
private readonly IView _view;
private readonly Model _model;
public Presenter(IView view)
{
_view = view;
_model = new Model("John", "Doe");
}
}
Renderer
public class Renderer
{
private readonly PictureBox _pictureBox;
public Renderer(PictureBox pictureBox)
{
_pictureBox = pictureBox;
_pictureBox.Paint += PictureBoxOnPaint;
}
private static void PictureBoxOnPaint(object sender, PaintEventArgs e)
{
// TODO: Draw stuff like First and Last Name
}
public void Refresh() { _pictureBox.Invalidate(); }
}
Model
public class Model
{
private string FirstName { get; set; }
private string LastName { get; set; }
public Model(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
}
I cannot figure out a way to have the Renderer draw FirstName and LastName from the Main View's model without:
A. Giving the parent hosting the PictureBox the job of pushing information to the auxiliary classes by pushing the model info to the view from the presenter, then having the view push that to the Renderer.
or B. Adding a reference to the Forms library in the presenter to tie everything together. This would allow me to make views out of the auxillary classes and add a PictureBox property, but I'd like to avoid this in case the view turns into an MVC Application or WPF Application and the view does not use windows forms.
Is there any way I can get these auxiliary classes to work? Or is there a better method for this entirely? I'd hate to think I'm doomed to cram all of it in a custom control. Thanks in advance for any suggestions!
In place where you instantiate the renderer, why don't you give it a reference to the model object that contains the data? Surely, it is the "A" way, but as you are 'manually' binding it hard to the PictureBox, so I do not see anything wrong in additionally passing it the model instance altogether with the picbox. It seems natural to me that when I'm creating an object and configuring it - I configure it fully. Otherwise, you'd have to add some renderer-building-layer, because, naturally, the renderer simply must get the data somehow..
Actually, giving the model to the renderer is not (at least for me) any breach in the architecture and does not place any extra burden/logic on the 'hoster', provided that you will pass 'the whole model' and the renderer will be responsible for reading the actual data it needs. IMHO, in such way, everything would be exactly right in its place.

Winforms Data Binding to Custom Class

I am trying to bind some Winform objects to a custom class, more specifically an instance of my custom class which I have added to the Form in the code. C#, .NET 2010 Express.
For example, here is a fragment of the class, and the UserInfoForm
public class UserInfo
{
[XmlAttribute]
public string name = "DefaultName";
[XmlAttribute]
public bool showTutorial = true;
[XmlAttribute]
public enum onCloseEvent = LastWindowClosedEvent.Exit;
}
public enum LastWindowClosedEvent
{
MainMenu,
Exit,
RunInBackground
}
public partial class Form1 : Form
{
UserInfo userToBind = new UserInfo();
TextBox TB_userName = new TextBox();
CheckBox CB_showTutorial = new CheckBox();
ComboBox DDB_onCloseEvent = new ComboBox();
public Form1()
{
InitializeComponent();
}
}
Now, I would like to bind the values of these form controls to their respective value in userToBind, but have had no luck. All the tutorials I can find are either way out of date (2002), or about binding controls to a dataset, or other type of database.
I am obviously overlooking something, but I haven't figured out what.
Thank you very much for any info you can share.
More info: UserInfo is designed to be XML-friendly so it can be saved as a user profile. UserInfo will contain other custom XML classes, all nested under the UserInfo, and many controls will only need to access these child classes.
You can use the DataBindings property of your controls (textbox, checkbox...) to add a binding to a specific control. For instance:
public Form1()
{
InitializeComponent();
TB_userName.DataBindings.Add("Text", userToBind, "name");
}
Also, IIRC, data binding only works on properties, so you'll first need to modify your UserInfo class accordingly. Moreover, if you want the UI to update automatically when modifying your objects in code, you must implement INotifyPropertyChanged in your custom classes.

Categories