i'm working in a new project and i want to implement MVP pattern. There is a framework for winforms that use this pattern? I checked CAB but my project isn't complex to implement it, i search for something more simple to implement and use.
Thanks!
If you are looking for something simple... then you really don't need a framework. You can roll your own MVP pattern.
Writing the base classes takes only a few minutes.
//Base Presenter Class
public class Presenter<TView> where TView : class, IView {
public TView View { get; private set; }
public Presenter(TView view) {
if (view == null)
throw new ArgumentNullException("view");
View = view;
View.Initialize += OnViewInitialize;
View.Load += OnViewLoad;
}
protected virtual void OnViewInitialize(object sender, EventArgs e) { }
protected virtual void OnViewLoad(object sender, EventArgs e) { }
}
//Base View
public interface IView {
event EventHandler Initialize;
event EventHandler Load;
}
That is all you need to get started. You can then define a new view to suit your needs.
public interface IPersonView : IView {
String PersonName { get; set; }
DateTime? DOB { get; set; }
event EventHandler SavePerson;
}
Create a presenter that uses the view.
public class PersonPresenter : Presenter<IPersonView> {
private IPersonDb PersonDB { get; set; }
public PersonPresenter(IPersonView view, IPersonDb personDB)
: base(view) {
if (personDB == null)
throw new ArgumentNullException("personDB");
PersonDB = personDB;
}
protected override void OnViewInitialize(object sender, EventArgs e) {
base.OnViewInitialize(sender, e);
View.PersonName = "Enter Name";
View.DOB = null;
View.SavePerson += View_SavePerson;
}
void View_SavePerson(object sender, EventArgs e) {
PersonDB.SavePerson(View.PersonName, View.DOB);
}
}
And finally put it into use in a new form.
public partial class Form1 : Form, IPersonView {
private PersonPresenter Presenter { get; set; }
public Form1() {
Presenter = new PersonPresenter(this, new PersonDb());
InitializeComponent();
InvokeInitialize(new EventArgs());
}
public string PersonName {
get { return tbName.Text; }
set { tbName.Text = value; }
}
public DateTime? DOB {
get {
return String.IsNullOrWhiteSpace(tbDOB.Text) ?
(DateTime?) null :
DateTime.Parse(tbDOB.Text);
}
set {
tbDOB.Text = String.Format("{0}", value);
}
}
public event EventHandler Initialize;
public void InvokeInitialize(EventArgs e) {
EventHandler handler = Initialize;
if (handler != null) {
handler(this, e);
}
}
public event EventHandler SavePerson;
public void InvokeSavePerson(EventArgs e) {
EventHandler handler = SavePerson;
if (handler != null) {
handler(this, e);
}
}
}
I like Jeremy Miller's stuff a lot. And I have used the Smart Client Software Factory... but those are about solving very large complicated problems. There are so many other patterns mixed in that it overshadows the simplicity of the MVP pattern to begin with.
Start simple and as you start to run into rough spots, then you can begin to add in things like Service Locators and Event Aggregators.
The MVP pattern is really very trivial to implement. I hope this can help to get you off to a running start more quickly.
Cheers,
Josh
This is not a framework, but I would read Jeremy Miller's Build Your Own Cab series before you settle on your design. He covers the various presentation patterns in WinForms.
Related
I have this code where I have my ViewModel and the ViewModel has a property where it gets all of its properties.
This is rough pseudo-code:
public class MyClassViewModel : INotifyPropertyChanged
{
public MyClassViewModel ()
{
}
public BaseClass myClassBase { get ; set; }
public string Title
{
get
{
return myClassBase.Title;
}
set
{
myClassBase.Title = value;
RaisePropertyChanged("Title");
}
}
public string Description
{
get
{
return myClassBase.Description;
}
set
{
myClassBase.Description = value;
RaisePropertyChanged("Description");
}
}
}
And this is the BaseClass:
public class BaseClass
{
public BaseClass()
{
}
public string Title {get;set;}
public string Description {get;set;}
}
CheckItemViewModel is the one binded to UI. So if I do something like MyClassViewModel .Title = "Test"; it properly refreshes the UI.
However, I need to do something like MyClassViewModel.myClassBase.Title = "Test" for specific reasons (Javascript - Chakra interface). The problem with this then is that the UI does not Refresh anymore since it doesn't have RaisePropertyChanged.
Even when I implemented RaisePropertyChanged inside the BaseClass itself, it still doesn't work. It doesn't work because PropertyChanged in BaseClass is always null.
I suspect it's because MyClassViewModel is the one binded to UI. So PropertyChanged in BaseClass is never binded.
Is there a way to trigger the Parent's RaisePropertyChanged?
Thank you
I would suggest implementing INotifyPropertyChanged on both classes, then have MyClassViewModel subscribe to the event in BaseClass and forward it to the UI:
public class MyClassViewModel : INotifyPropertyChanged, IDisposable
{
private BaseClass myClassBase;
public void Dispose()
{
if (myClassBase != null) myClassBase.PropertyChanged -= OnBaseClassPropertyChanged;
}
public BaseClass MyClassBase {
get {
return myClassBase;
}
set {
if (myClassBase != null) myClassBase.PropertyChanged -= OnBaseClassPropertyChanged;
myClassBase = value;
myClassBase.PropertyChanged += OnBaseClassPropertyChanged;
}
}
private void OnBaseClassPropertyChanged(object sender, PropertyChangedEventArgs args) {
RaisePropertyChanged(args.PropertyName);
}
// forwarded properties (Title and Description) go here
}
First of all, you can simplify the RaisePropertyChanged this way:
public void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
So you don't need to write RaisePropertyChanged("Description"), but only: RaisePropertyChanged(), and the propertyName is automatically injected. That's awesome if you refactor frequently: you don't have to deal with the nightmare of remembering all the "Title" and "Description" strings in the whole solution :)
Second, if the BaseClass has the PropertyChangedEvent, you can listen to it in the MyClassViewModel.
myClassBase.PropertyChanged += (s, e) => { RaisePropertyChanged(e.PropertyName); };
But, if you don't inject myClassBase immediately in the constructor of MyClassViewModel, or if the myClassBase can change sometime, things get a bit more complicated.
You have to make MyClassViewModel also to implement INotifyPropertyChanging:
public event PropertyChangingEventHandler PropertyChanging;
public void RaisePropertyChanging([CallerMemberName] string propertyName = null)
{
PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(propertyName));
}
You have to raise notifications also for the myClassBase:
public BaseClass myClassBase
{
get { return _myClassBase; }
set
{
RaisePropertyChanging();
_myClassBase = value;
RaisePropertyChanged();
}
}
private BaseClass _myClassBase;
Then, all you need is this code:
public MyClassViewModel()
{
PropertyChanging += OnPropertyChanging;
PropertyChanged += OnPropertyChanged;
}
private void OnPropertyChanging(object sender, PropertyChangingEventArgs e)
{
if (e.PropertyName != nameof(MyClassViewModel.myClassBase))
return; //or do something with the other properties
if (myClassBase == null)
return;
myClassBase.PropertyChanged -= OnMyBaseClassPropertyChanged;
}
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName != nameof(MyClassViewModel.myClassBase))
return; //or do something with the other properties
if (myClassBase == null)
return;
myClassBase.PropertyChanged += OnMyBaseClassPropertyChanged;
}
private void OnMyBaseClassPropertyChanged(object sender, PropertyChangedEventArgs e)
{
RaisePropertyChanged(e.PropertyName);
}
NB: I use the C#-6.0 nameof() operator, I hope you can use it, it's simply awesome!
EDIT:
Here you have a simple test method that demonstrates the correct functionality:
[TestMethod]
public void ChildClassPropertyChanged()
{
var bc = new BaseClass();
var c = new MyClassViewModel();
bc.Title = "t1";
c.myClassBase = bc;
Assert.AreEqual("t1", c.Title);
c.Title = "t2";
Assert.AreEqual("t2", c.Title);
c.myClassBase.Title = "t3";
Assert.AreEqual("t3", c.Title);
c.myClassBase = new BaseClass();
bc.Title = "t4";
Assert.AreEqual(null, c.Title);
c.myClassBase.Title = "t5";
Assert.AreEqual("t5", c.Title);
}
Keep in mind that if you set a null myClassBase, inside your properties' getters and setters the code throws a NullReferenceException. Maybe you should modify it this way:
public string Title
{
get
{
return myClassBase?.Title;
}
set
{
if (myClassBase != null)
myClassBase.Title = value;
RaisePropertyChanged();
}
}
Summary
I'm experimenting with the MVP pattern in a Windows Forms application.
I'd like to make both my Presenters and Views platform agnostic, so if I wish to port my application to another platform, say the Web or mobile, I simply need to implement the views with platform dependant GUI, and my presenters can still be platform independent.
And now I wonder, how to ShowDialog() using MVP and Passive Views?
To my understanding so far, passive views shouldn't know/care about any presenter. They don't even know it exists. So, the solution presented in this question's answer is not suitable, according to me:
Refactoring Form.ShowDialog() code to MVP
Some code samples to help on the understanding:
ApplicationView
public partial class ApplicationForm : Form, IApplicationView {
// I ensure that all the IApplicationView events are raised
// upon button clicks text changed, etc.
// The presenter, which this view ignores the existence,
// is subscribed to the events this view raises.
}
ApplicationPresenter
public class ApplicationPresenter
: Presenter<IApplicationView>
, IApplicationPresenter {
public ApplicationPresenter(IApplicationView view) : base(view) {
View.OnViewShown += OnViewShown;
}
public void OnViewShown() {
IAuthenticaitonView authView = new AuthenticationForm();
IAuthenticationPresenter authPresenter = new AuthenticationPresenter(authView);
authPresenter.ShowDialog(); // 1.
}
}
This is where I'm struggling. The ApplicationPresenter is like the master in the universer and may be aware of the user authentication through both the IAuthenticationView and IAuthenticationPresenter.
IAuthenticationView
public interface IAuthenticationView : IDialogView {
string ErrorMessage { get; set; }
string Instance { get; set; }
IEnumerable<string> Instances { get; set; }
string Login { get; set; }
string Password {get; set; }
void EnableConnectButton(bool enabled);
event VoidEventHandler OnConnect;
event SelectionChangedEventHandler OnDatabaseInstanceChanged;
event VoidEventHandler OnLoginChanged;
event VoidEventHandler OnPasswordChanged;
}
IDialogView
public interface IDialogView : IView {
void ShowDialog();
}
IView
public interface IView {
void Show();
event VoidEventHandler OnViewInitialize;
event VoidEventHandler OnViewLoad;
event VoidEventHandler OnViewShown;
}
IAuthenticationPresenter
public interface IAuthenticationPresenter : IPresenter<IAuthenticationView> {
void OnConnect();
void OnViewDatabaseInstanceChanged(SelectionChangedEventArgs e);
void OnViewLoginChanged();
void OnViewPasswordChanged();
}
IPresenter<V>
public interface IPresenter<V> where V : IView {
V View { get; }
OnViewInitialize();
OnViewLoad();
ShowView();
}
Based on these premisses:
The presenter shall be platform agnostic
Only the view knows how to show/display itself (WPF, Mobile, Silverlight, Web, WinForms...)
The view MUST provide a way to show itself
The view doesn't have to be platform agnostic, since the display will differ from a platform to another
But the presenter shall order the view when to show itself
I came to this:
IView
public interface IView {
void OnShowView();
}
IPresenter<V>
public interface IPresenter<V>where V : IView {
void ShowView();
event VoidEventHandler OnShowView;
}
Presenter<V>
public abstract class Presenter<V> : IPresenter<V> {
public Presenter(V view) {
View = view;
OnShowView += View.OnShowView;
}
public void ShowView() { raiseShowViewEvent(); }
public event VoidEventHandler OnShowView;
private void raiseShowViewEvent() { if (OnShowView != null) OnShowView(); }
}
So, following the logic of where I struggled so far, I solved it by doing this:
ApplicationForm
public partial class ApplicationForm : Form, IApplicationView {
private void ApplicationForm_Shown(object sender, EventArgs e) { raiseOnViewShown(); }
private void raiseOnViewShownEvent() { if (OnViewShown != null) OnViewShown(); }
}
ApplicationPresenter
public void OnViewShown() {
// This method is the subscriber of the IView.OnViewShown event
// The event is raised with the ApplicationForm_Shown event.
IAuthenticationView authView = new AuthenticationForm();
IAuthenticationPresenter authPresenter = new AuthenticationPresenter(authView);
authPresenter.ShowView(); // 1.
}
This raises the OnShowView event which the IAuthenticationView has subscribed. Then, back in the form, the view's response to the event is:
AuthenticationForm
public partial class AuthenticationForm : Form, IAuthenticationView {
public void OnShowView() { ShowDialog(); }
}
Then, the view shows itself as a dialog/modal window.
I have a WPF application that displays a window with various information in it. In my code I create an instance of a custom class that I created which reads information from RFID card reader. To keep it simple - every now and then someone would swipe their card using the card reader which would generate a string that I successfully capture using my custom class.
The problem that I have is that I need to return that value to the window application so that I can update the information displayed in the window based on the value read. This is not as simple as calling a function in the custom class and returning a value as I don't know when exactly someone would swipe their card.
One solution that I could think of was to make a timer and pool the custom class every second or so to check if someone swiped their card, however, I don't think that's an effective solution.
Since I'm relatively new to WPF I'm assuming that the right way to do it is using INotifyProperyChanged but I'm unsure how to do it. Open to any other suggestions as well, thank you!
Create an event on your CardReader class that you can listen to on your ViewModel.
class CardInfo
{
public string CardDetails { get; set; }
}
class CardSwipedEventArgs
: EventArgs
{
public CardInfo SwipedCard { get; set; }
}
interface ICardReader
{
event EventHandler<CardSwipedEventArgs> CardSwiped;
}
class MyViewModel : INotifyPropertyChanged
{
private ICardReader _cardReader;
private string _lastCardSwiped;
public ICardReader CardReader
{
get
{
return _cardReader;
}
set
{
_cardReader = value;
_cardReader.CardSwiped += OnCardSwiped;
}
}
private void OnCardSwiped(object sender, CardSwipedEventArgs e)
{
LastCardSwiped = e.SwipedCard.CardDetails;
}
public string LastCardSwiped
{
get
{
return _lastCardSwiped;
}
set
{
_lastCardSwiped = value;
this.OnPropertyChanged("LastCardSwiped");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Thank you all for your posts. Using events was definitely the way but it wasn't easy to understand how they worked. Your feedback definitely helped but this article helped me understand how events worked best and how to implement them so I could deal with the issue successfully:
http://www.codeproject.com/Articles/9355/Creating-advanced-C-custom-events
Create an event on the class that reads the data from RFID.
public class CardSweepedEventArgs : EventArgs {
private readonly string _data;
public string Data { get { return _data; } }
public CardSweepedEventArgs(string data) {
_data = data;
}
}
public class YourReadinClass {
public EventHandler<CardSweepedEventArgs> CardSweeped;
// rest of logic.
}
In your class then subscribe to the event and do the necessary.
Can someone tell me how can I have a feature in my UserControl, that can let the host windowsform know what is the control is doing?
For example my usercontrol has a filebrowser, and if user uses this file browser to open a file I want in the statusstrip bar of my form to write "Loading file(s)".
Will this require using events? if so, how can I have a single event inside usercontrol to report anything it does (then I guess I have to call this event in all methods in the usercontrol).
Simple
Yes, expose an event on the user control that the Form can subscribe to. You should use the standard event pattern:
class MyUserControl : UserControl
{
public event EventHandler<EventArgs> FileOpened;
protected virtual void OnFileOpened(EventArgs e)
{
EventHandler<EventArgs> handler = FileOpened;
if (handler != null)
handler(this, e);
}
}
Then when the file is opened you call OnFileOpened(EventArgs.Empty) which fires the event.
With custom EventArgs
Now the Form probably needs to know what file was opened. You could expose a property on the user control that the Form can use to find out, or you can provide that information in your event like so:
public class FileOpenedEventArgs : EventArgs
{
private string filename;
public FileOpenedEventArgs(string filename)
{
this.filename = filename;
}
public string Filename { get { return filename; } }
}
class MyUserControl : UserControl
{
public event EventHandler<FileOpenedEventArgs> FileOpened;
protected virtual void OnFileOpened(FileOpenedEventArgs e)
{
EventHandler<FileOpenedEventArgs> handler = FileOpened;
if (handler != null)
handler(this, e);
}
}
Then you fire the event with OnFileOpened(new FileOpenedEventArgs(filename)).
Optimal
When you create an event handler public event delegate Name;, you are allocating storage for the delegate on your object. Objects (especially Controls) often have a huge number of events that are never subscribed to. That's a whole lot of allocated storage not being used. There's an optimization built into the framework in the form of a EventHandlerList. This handy object stores event handlers only when they are actually used. All System.Windows.Forms.Control objects derive from System.ComponentModel.Component and it already provides an (protected) EventHandlerList that you can access in your derived Control.
To use it, you first create a static object that uniquely identifies your event, and then you provide the add {} and remove {} methods manually. Like so:
class MyUserControl : UserControl
{
private static readonly object FileOpenedKey = new Object();
public event EventHandler<FileOpenedEventArgs> FileOpened
{
add { Events.AddHandler(FileOpenedKey, value); }
remove { Events.RemoveHandler(FileOpenedKey, value); }
}
protected virtual void OnFileOpened(FileOpenedEventArgs e)
{
var handler = (EventHandler<FileOpenedEventArgs>)Events[FileOpenedKey];
if (handler != null)
handler(this, e);
}
}
Yes, you will need to create an event and subscribe to it. One suggestion following the standard pattern for events:
enum ControlStatus {Idle, LoadingFile, ...}
class StatusChangedEventArgs : EventArgs
{
public ControlStatus Status {get; private set;}
public StatusChangedEventArgs(ControlStatus status)
: base()
{
this.Status = status;
}
}
partial class MyControl : UserControl
{
public ControlStatus Status {get; private set;}
public event EventHandler<StatusChangedEventArgs> StatusChanged;
protected virtual void OnStatusChanged(StatusChangedEventArgs e)
{
var hand = StatusChanged;
if(hand != null) hand(this, e);
}
void LoadFiles()
{
...
Status = ControlStatus.LoadingFiles;
OnStatusChanged(new StatusChangedEventArgs(this.Status));
...
Status = ControlStatus.Idle;
OnStatusChanged(new StatusChangedEventArgs(this.Status));
}
}
partial class MyHostWindowsForm : Form
{
public MyHostWindowsForm()
{
var ctl = new MyControl();
...
ctl.StatusChanged += ctl_StatusChanged;
}
void ctl_StatusChanged(object sender, StatusChangedEventArgs e)
{
switch(e.Status)
{
case ControlStatus.Idle:
statusStripBar.Text = null;
break;
case ControlStatus.LoadingFiles:
statusStripBar.Text = "Loading file(s)";
break;
...
}
}
}
I've been using WPF for a while but I'm new to Commands, but would like to start using them properly for once. Following a code example, I've established a separate static Commands class to hold all of my commands, and it looks like this.
public static class Commands
{
public static RoutedUICommand OpenDocument { get; set; }
static Commands()
{
OpenDocument = new RoutedUICommand("Open Document", "OpenDocument", typeof(Commands));
}
public static void BindCommands(Window window)
{
window.CommandBindings.Add(new CommandBinding(OpenDocument, OpenDocument_Executed, OpenDocument_CanExecute));
}
private static void OpenDocument_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
// Should be set to true if an item is selected in the datagrid.
}
private static void OpenDocument_Executed(object sender, ExecutedRoutedEventArgs e)
{
}
}
My problem is that although the command is going to be bound to a Button control in MainWindow.xaml, the OpenDocument_CanExecute method needs to look at a DataGrid in MainWindow.xaml to see if an item is selected.
How can I wire things up such that the method can see the DataGrid?
SOLUTION
Inspired by Ken's reply (thanks again!), I put the following in place, which works perfectly.
MainWindow.xaml.cs
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
Loaded += delegate
{
DataContext = ViewModel.Current;
Commands.BindCommands(this);
};
}
}
ViewModel.cs
public class ViewModel
{
private static ViewModel _current;
public static ViewModel Current
{
get { return _current ?? (_current = new ViewModel()); }
set { _current = value; }
}
public object SelectedItem { get; set; }
}
Commands.cs
public static class Commands
{
public static RoutedUICommand OpenDocument { get; set; }
static Commands()
{
OpenDocument = new RoutedUICommand("Open Document", "OpenDocument", typeof(Commands));
}
public static void BindCommands(Window window)
{
window.CommandBindings.Add(new CommandBinding(OpenDocument, OpenDocument_Executed, OpenDocument_CanExecute));
}
private static void OpenDocument_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = ViewModel.Current.SelectedItem != null;
}
private static void OpenDocument_Executed(object sender, ExecutedRoutedEventArgs e)
{
}
}
ICommand implementations work best in the MVVM pattern:
class ViewModel : INotifyPropertyChanged {
class OpenDocumentCommand : ICommand {
public bool CanExecute(object parameter) {
return ViewModel.ItemIsSelected;
}
public OpenDocumentCommand(ViewModel viewModel) {
viewModel.PropertyChanged += (s, e) => {
if ("ItemIsSelected" == e.PropertyName) {
RaiseCanExecuteChanged();
}
};
}
}
private bool _ItemIsSelected;
public bool ItemIsSelected {
get { return _ItemIsSelected; }
set {
if (value == _ItemIsSelected) return;
_ItemIsSelected = value;
RaisePropertyChanged("ItemIsSelected");
}
}
public ICommand OpenDocument {
get { return new OpenDocumentCommand(this); }
}
}
Obviously, I left out a whole bunch of stuff. But this pattern has worked well for me in the past.
why even implement a command if you are tightly coupling it to UI implementation? Just respond to datagrid.SelectionChanged and code in what supposed to happen.
Otherwise, put it in the ViewModel. Have the ViewModel monitor it's state and evaluate when CanExe is true.
Edit
On the other hand, you can pass a parameter to your command, as well as Exe() & CanExe() methods
//where T is the type you want to operate on
public static RoutedUICommand<T> OpenDocument { get; set; }
If you are doing an MVVM solution, this would be the perfect time to implement a publish / subscribe aggregator that allows controls to "talk" to each other. The gist behind it is that the datagrid would publish an event, 'Open Document'. Subsequent controls could subscribe to the event and react to the call to 'Open Document'. The publish / subscribe pattern prevents tightly coupling the datagrid and the control. Do some searches for event aggregators and I think you'll be on your way.