Simple Windows Forms MVVM Framework
MIT License
In development...but working...
Formidable is a simple MVVM framework for .NET Windows Forms solutions that aims to provide a simple way of isolating your State and Business logic from your Presentation logic while reinforcing re-usability and refactoring.
Well, this is a good question. Even if the tendency is for Windows Forms to die the slow death of platform-specific technologies used to build Pyramids, in the Javascript era, it is still being used by multiple companies to support custom "business-critical" enterprise solutions. This project was born out of my experience dealing with maintaining legacy Winforms applications at previous jobs.
The Purpose of this project is not make you fall in love with Windows Forms all over again, if you ever loved it, but to help you deal with it when there is way around it :).
You may follow these rules or not. Formidable will not enforce any of these rules, but if you follow them it will help you better take advantage of the pattern.
[CallerMemberName]
attribute is present in Formidable.dll
and mscorlib.dll
Form. This is your form which should always inherit from a custom FormViewPlug.
Inherits from a Custom Form Plug.
public partial class MainForm : MainFormViewPlug
{
public MainForm() : base()
{
InitializeComponent();
this.BindControls();
}
public override void Initialize()
{
// After View and View models are created, but before controls are initialized.
this.View.ViewModel.LabelText = "Enter a text here...";
}
private void BindControls()
{
this.View.Bind(tbText, "Text", "LabelText");
this.View.Bind(label2, "Text", "LabelText");
}
}
You should manage all Presentation specific logic at this level.
Form View Plug. The Form Plug is a custom form required in order to let you inherit from a Form Base which relies in generics. Unfortunately due to Visual Studio Windows Forms Designer Limitations you are not able to open a Windows Forms that uses generics in design mode. Now, if you do not care about design mode, be my guest and inherit from the FormBase class.
Inherits from a FormBase.
public class MainFormPlug : FormBase<MainFormView, MainFormViewModel>
{
public MainFormPlug() : base()
{
}
protected override void initializeForm()
{
this.Initialize();
}
public virtual void Initialize();
}
This form you be an empty shell. No logic should be placed at this level. You should be able to copy and paste this class only changing the Name and the View and ViewModel Types.
API
WithControl<TControl>(TControl, Action<TControl>, bool)
Returns void. This method allows you to execute operations on the provided Generic TControl type (of type Control) in an asynchronous manner.
View
Returns Generic Form View Type. This property returns the Form View instance.
Form View. The Form View is where all the business logic resides and where all required state modification and retrieval methods and functions should live.
Inherits from a FormViewBase.
public class MainFormView : FormViewBase<FormViewModel>
{
public MainFormView() : base()
{
}
protected override void Initialize()
{
}
protected override void InitializeOnDesignMode()
{
}
}
No Presentation specific logic should ever be passed into this level. Example: Do not pass a Control instance or type into any function on the view.
API
Initialize()
Returns void. This method is executed by the FormViewBase constructor at runtime.
InitializeOnDesignMode()
Returns void. This method is executed by the FormViewBase constructor when in design mode. (Unit Testing).
VieModel
Returns Generic ViewModel Type. This property returns the ViewModel instance.
IsDesignMode()
Returns Boolean. This property determines if the FormView is being used in design mode.
Form View Model. This is where your form state lives. All properties and variables that are affected across your application should live here.
Inherits from a FormViewModelBase.
public class MainFormViewModel : FormViewModelBase
{
private string _text;
public MainFormViewModel() : base()
{
this._text = string.Empty;
}
public string LabelText
{
get
{
return this._text;
}
set
{
if (this._text != value)
{
this._text = value;
NotifyPropertyChanged();
}
}
}
}
API
ResetState()
Returns void. Resets the View Model State.
HasChanges()
Returns Boolean. Determines if the View Model State was changed.
Control. This is your custom control which should always inherit from a custom ControlViewPlug.
Inherits from a Control Plug.
public partial class MembersGridControl : MembersGridPlug
{
public MembersGridControl() : base()
{
InitializeComponent();
this.BindControls();
}
public override void Initialize()
{
// After View and View models are created, but before controls are initialized.
this.View.ViewModel.Text = "Data";
}
private void BindControls()
{
this.View.Bind(btnAdd, "Enabled", "HasChanges");
}
}
You should manage all Presentation specific logic at this level.
Control View Plug. The Control Plug is a custom form required in order to let you inherit from a Form Base which relies in generics. Unfortunately due to Visual Studio Windows Forms Designer Limitations you are not able to open a Windows Forms that uses generics in design mode. Now, if you do not care about design mode, be my guest and inherit from the ControlBase class.
Inherits from a ControlBase.
public class MembersGridPlug : ControlBase<MembersGridView, MembersGridViewModel>
{
public MembersGridPlug() : base()
{
}
protected override void initializeForm()
{
this.Initialize();
}
public virtual void Initialize();
}
This form you be an empty shell. No logic should be placed at this level. You should be able to copy and paste this class only changing the Name and the View and ViewModel Types.
API
WithControl<TControl>(TControl, Action<TControl>, bool)
Returns void. This method allows you to execute operations on the provided Generic TControl type (of type Control) in an asynchronous manner.
View
Returns Generic Control View Type. This property returns the Control View instance.
Control View. The Control View is where all the business logic resides and where all required state modification and retrieval methods and functions should live.
Inherits from a ControlViewBase.
public class MembersGridView : ControlViewBase<MembersGridViewModel>
{
public MembersGridView() : base()
{
}
protected override void Initialize()
{
}
protected override void InitializeOnDesignMode()
{
}
}
No Presentation specific logic should ever be passed into this level. Example: Do not pass a Control instance or type into any function on the view.
API
Initialize()
Returns void. This method is executed by the ControlViewBase constructor at runtime.
InitializeOnDesignMode()
Returns void. This method is executed by the CViewBase constructor when in design mode. (Unit Testing).
VieModel
Returns Generic ViewModel Type. This property returns the ViewModel instance.
IsDesignMode()
Returns Boolean. This property determines if the FormView is being used in design mode.
Control View Model. This is where your form state lives. All properties and variables that are affected across your application should live here.
Inherits from a ControlViewModelBase.
public class MembersGridViewModel : MembersGridViewModelBase
{
private string _text;
public MainFormViewModel() : base()
{
this._text = string.Empty;
}
public string LabelText
{
get
{
return this._text;
}
set
{
if (this._text != value)
{
this._text = value;
NotifyPropertyChanged();
}
}
}
}
API
ResetState()
Returns void. Resets the View Model State.
HasChanges()
Returns Boolean. Determines if the View Model State was changed.
You should be able to use this method anywhere where you would like your Presentation to make asynchronous calls without locking the main thread hence the UI. It should be fairly easy to use in conjunction with WithNewTask<>()
In order to achieve sequential operations on a separate Task it uses a callback design.
WithNewTask<>
Creates a new task to execute a provided anonymous action, a callback and an exception handler action. The 1st operation is usually to load data or perform any long operation, the second to update the control wrapped into a WithControl<>
and the third to handle exceptions during execution.
// Called From Inside a Form inheriting from the FormPlug or FormBase
// Creates a New Task (Parallel to main thread)
this.WithNewTask(() =>
{
tsOngoingOperation.Text = $"Loading {this.View.ViewModel.GetMemberNumberInt()} Members...Please wait...";
this.setupGridView();
// Load Content
this.View.LoadMembers();
}, () =>
{
// Load Data into grid without locking the UI
this.WithControl(this.dgMembers, (gridView) =>
{
gridView.DataSource = this.View.ViewModel.Members;
tsOngoingOperation.Text = $"{this.View.ViewModel.GetMemberNumberInt()} were loaded.";
});
}, (ex) =>
{
// Exception
MessageBox.Show("Error", ex.Message, MessageBoxButtons.OK, MessageBoxIcon.Error);
});
Pending Documentation.
As you may already know Visual Studio Winforms designer is not very resilient when using form inheritance, specially when the abstract classes live in a separate assembly. When you get a designer exception when opening the form please make sure to follow the steps below:
This should usually take care of the most common designer exception scenarios.
NONE
MIT
That's it... I will try to document more scenarios but in general I strive to keep it as simple as possible.
Godspeed!