MVP pattern in winforms - Handling events - c#

I just started with C# and MVP design pattern.
I'm in doubt about concrete implementation when it comes to event handling. I'm aware of that, view shouldn't know about presenter and presenter should control a view through view interface.
Let's say I have 2 text boxes and would like to check for errors. If an error occurs I want to change text box Text property. Is it wrong approach to create one EventHandler and use sender object to verify witch text box is user currently using?
Something like:
IView:
interface IMainView
{
event KeyPressEventHandler KeyPressed;
}
View:
public partial class MainView : Form, IMainView
{
public frmInterakcija()
{
InitializeComponent();
this.textBox1.Name = "textBox1";
this.textBox2.Name = "textBox2";
new MainPresenter();
Bind();
}
private void Bind()
{
this.textBox1.KeyPress += KeyPressed;
this.textBox2.KeyPress += KeyPressed;
}
}
Presenter:
class MainPresenter
{
private IMainView _view;
public MainPresenter(IMainView view)
{
_view = view;
this.initialize();
}
public void initialize()
{
_view.KeyPressed += _view_textBoxKeyPressed;
}
public void _view_textBoxKeyPressed(object sender, EventArgs e)
{
if (sender.GetType() == typeof(TextBox))
{
TextBox textBox = (TextBox)sender;
if (textBox.Name.Equals("textbox1")
{...} // Do validation/changes on textbox1
else ...
}
}
}
Or instead of this above I should create event handler for every textbox I have and update/handle errors through properties? (this will make my code redundant I guess)
What would be right approach?

IMHO the presenter should be unaware of view specific objects (example textbox in your code). That kind of logic should not be in presenter. And presenter must not know about the Ids of controls in the UI, that's even worse. Remember one of the benefits of this should be that you can test the presenter by mocking the view, if you have UI specific code you won't be able to unit test the presenter.
It does seem like two different events to me since you are doing different logic. I'd raise two different events and one would do validation, the other would do its own logic. The presenter won't have to check if the sender is textbox or the id of the textbox. Also what if you have another textbox, you'll need another if condition in this current implementation.
Also, in the view, it should be new MainPresenter(this);

Your presenter should absolutely not have view-specific types in it (e.g. controls, events, etc.) since these are hard to fake when it comes time to test the presenter's logic. Instead, you should have something like the following.
IView:
interface IMainView
{
// give these better names based on what they actually represent (e.g. FirstName and LastName)
// you could also add setters if you needed to modify their values from the presenter
string Text1 { get; }
string Text2 { get; }
// provide a way to bubble up validation errors to the UI
string ErrorMessage { get; set; }
}
Presenter:
class MainPresenter
{
private IMainView _view;
public MainPresenter(IMainView view)
{
_view = view;
}
public void ValidateText1()
{
if (/* some validation is false */)
{
_view.ErrorMessage = "Text1 isn't valid";
}
}
public void ValidateText2()
{
if (/* some validation is false */)
{
_view.ErrorMessage = "Text2 isn't valid";
}
}
}
View:
public partial class MainView : Form, IMainView
{
var readonly MainPresenter _presenter;
public frmInterakcija()
{
InitializeComponent();
_presenter = new MainPresenter(this);
}
private void textBox1_KeyPress(object sender, KeyPressEventArgs eventArgs)
{
_presenter.ValidateText1();
}
private void textBox2_KeyPress(object sender, KeyPressEventArgs eventArgs)
{
_presenter.ValidateText2();
}
#region Implementation of IMainView
public string Text1
{
get { return textBox1.Text; }
}
public string Text2
{
get { return textBox2.Text; }
}
public string ErrorMessage
{
get { return labelErrorMessage.Text; }
set { labelErrorMessage.Text = value; }
}
#endregion
}

Related

Good practice to show Form from UserControl

I want to follow good practices design patterns when developing WinForms applications.
I have a UserControl with a button "Add" to open a new Form where de user can search Employees. How i can organize my code?
If you use WinForms you should use MVP (Model-View-Presenter) design pattern. In this case each view has own ISomethingView which contains the properties and the events, for example:
public interface IBaseView
{
void Show();
void Close();
}
public interface ILoginView : IBaseView
{
string Login { get; }
string Password {get; }
event EventHandler SignIn { get; }
}
And now your UserControl must implemented this interface.
Also for each view you have to create a presenter which is responsible for communication between the view and a business logic:
public LoginPresenter
{
// private variables
public LoginPresenter(ILoginView loginView, IOtherView otherView)
{
this.loginView = loginView;
this.otherView = otherView;
this.loginView.SignUp += OnSignUp;
}
private void OnSignUp(object sender, EventArgs eventArgs)
{
if (this.authService.Login(this.loginView.UserName, this.loginView.Password))
{
this.loginView.Close();
this.otherView.Show();
}
}
}
You can use DI container to resolve all I*Vies, for example:
public class LoginUserControl : UserControl, ILoginView
{
public LoginUserControl()
{
this.loginPresenter = new LoginPresenter(this, DIContainer.Resolve<IOtherView>());
}
}

Event circularity

I find myself quite often in the following situation:
I have a user control which is bound to some data. Whenever the control is updated, the underlying data is updated. Whenever the underlying data is updated, the control is updated. So it's quite easy to get stuck in a never ending loop of updates (control updates data, data updates control, control updates data, etc.).
Usually I get around this by having a bool (e.g. updatedByUser) so I know whether a control has been updated programmatically or by the user, then I can decide whether or not to fire off the event to update the underlying data. This doesn't seem very neat.
Are there some best practices for dealing with such scenarios?
EDIT: I've added the following code example, but I think I have answered my own question...?
public partial class View : UserControl
{
private Model model = new Model();
public View()
{
InitializeComponent();
}
public event EventHandler<Model> DataUpdated;
public Model Model
{
get
{
return model;
}
set
{
if (value != null)
{
model = value;
UpdateTextBoxes();
}
}
}
private void UpdateTextBoxes()
{
if (InvokeRequired)
{
Invoke(new Action(() => UpdateTextBoxes()));
}
else
{
textBox1.Text = model.Text1;
textBox2.Text = model.Text2;
}
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
model.Text1 = ((TextBox)sender).Text;
OnModelUpdated();
}
private void textBox2_TextChanged(object sender, EventArgs e)
{
model.Text2 = ((TextBox)sender).Text;
OnModelUpdated();
}
private void OnModelUpdated()
{
DataUpdated?.Invoke(this, model);
}
}
public class Model
{
public string Text1 { get; set; }
public string Text2 { get; set; }
}
public class Presenter
{
private Model model;
private View view;
public Presenter(Model model, View view)
{
this.model = model;
this.view = view;
view.DataUpdated += View_DataUpdated;
}
public Model Model
{
get
{
return model;
}
set
{
model = value;
view.Model = model;
}
}
private void View_DataUpdated(object sender, Model e)
{
//This is fine.
model = e;
//This causes the circular dependency.
Model = e;
}
}
One option would be to stop the update in case the data didn't change since the last time. For example if the data were in form of a class, you could check if the data is the same instance as the last time the event was triggered and if that is the case, stop the propagation.
This is what many MVVM frameworks do to prevent raising PropertyChanged event in case the property didn't actually change:
private string _someProperty = "";
public string SomeProperty
{
get
{
return _someProperty;
}
set
{
if ( _someProperty != value )
{
_someProperty = value;
RaisePropertyChanged();
}
}
}
You can implement this concept similarly for Windows Forms.
What you're looking for is called Data Binding. It allows you to connect two or more properties, so that when one property changes others will be updated auto-magically.
In WinForms it's a little bit ugly, but works like a charm in cases such as yours. First you need a class which represents your data and implements INotifyPropertyChanged to notify the controls when data changes.
public class ViewModel : INotifyPropertyChanged
{
private string _textFieldValue;
public string TextFieldValue {
get
{
return _textFieldValue;
}
set
{
_textFieldValue = value;
NotifyChanged();
}
}
public void NotifyChanged()
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(null));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Than in your Form/Control you bind the value of ViewModel.TextFieldValue to textBox.Text. This means whenever value of TextFieldValue changes the Text property will be updated and whenever Text property changes TextFieldValue will be updated. In other words the values of those two properties will be the same. That solves the circular loops issue you're encountering.
public partial class Form1 : Form
{
public ViewModel ViewModel = new ViewModel();
public Form1()
{
InitializeComponent();
// Connect: textBox1.Text <-> viewModel.TextFieldValue
textBox1.DataBindings.Add("Text", ViewModel , "TextFieldValue");
}
}
If you need to modify the values from outside of the Form/Control, simply set values of the ViewModel
form.ViewModel.TextFieldValue = "new value";
The control will be updated automatically.
You should look into MVP - it is the preferred design pattern for Winforms UI.
http://www.codeproject.com/Articles/14660/WinForms-Model-View-Presenter
using that design pattern gives you a more readable code in addition to allowing you to avoid circular events.
in order to actually avoid circular events, your view should only export a property which once it is set it would make sure the txtChanged_Event would not be called.
something like this:
public string UserName
{
get
{
return txtUserName.Text;
}
set
{
txtUserName.TextChanged -= txtUserName_TextChanged;
txtUserName.Text = value;
txtUserName.TextChanged += txtUserName_TextChanged;
}
}
or you can use a MZetko's answer with a private property

MVP and passing CancelEventArgs

I'm writing a simple input form using Model-View-Presenter, and I've encountered difficulty with handling the FormClosing event.
When dealing with a normal Form, it has an event that fires on closing called FormClosing that I can use to cancel the close if I deem it necessary. In this case, I'd like to cancel the form close if the input is bad. For instance:
public interface IView
{
event EventHandler<CancelEventArgs> Closing;
string Input { get; set; }
string ErrorMessage { set; }
}
public class Presenter
{
private IView view;
public Presenter(IView view)
{
this.view = view;
// bind to events
view.Closing += view_Closing;
}
private void view_Closing(object sender, CancelEventArgs e)
{
e.Cancel = !ValidateInput();
}
private bool ValidateInput()
{
bool validationSuccessful = true;
// perform validation on input, set false if validation fails
return validationSuccessful;
}
}
I created my own event handler (Closing) because my understanding of MVP states that utilizing anything in System.Windows.Forms is not a good idea (e.g. if someday I update my view to WPF). Thus, in the WinForms implementation, I pass the event forward, as such:
public partial class View : IView
{
public event EventHandler<CancelEventArgs> Closing;
public string Input { get { return textBoxInput.Text; } set { textBoxInput.Text = value; } }
public string ErrorMessage { set { errorProvider.SetError(textBoxInput, value) ; } }
public View()
{
InitializeComponent();
}
private void View_FormClosing(object sender, FormClosingEventArgs e)
{
if (Closing != null)
Closing(this, new CancelEventArgs(e.Cancel));
}
}
I've found that even though in my Presenter I tell e.Cancel to set to true when validation fails, it does not cause the form to cancel the close. I'm clearly doing something wrong here, but I'm not sure what the proper solution is.
I figured it out after experimenting with the solution in another StackOverflow question. I needed to create a new CancelEventArgs in the View as follows:
private void View_FormClosing(object sender, FormClosingEventArgs e)
{
CancelEventArgs args = new CancelEventArgs();
if (Closing != null)
Closing(this, args);
e.Cancel = args.Cancel;
}
args.Cancel properly updated after the Closing event was called, and successfully mapped the resultant boolean to e.Cancel.

Return value of a Property from child ViewModel to parent ViewModel

In my WPF MVVM app, using Caliburn.Micro, I have a ViewModel, CreateServiceViewModel that, on a button click, opens a GridView in a seperate window for the User to chose a Row from.
I created another ViewModel for this, MemberSearchViewModel which has two properties:
private Member selectedMember;
public Member SelectedMember
{
get { return selectedMember; }
set { selectedMember = value; }
}
private IList<Member> members;
public IList<Member> Members
{
get { return members; }
set { members = value; }
}
How do I get that SelectedMember value back to the calling ViewModel? That ViewModel has a property of Service.SelectedMember.
EventAggregator is what you could use... One of many solutions I am sure.
public class MessageNotifier{
public object Content{get;set;}
public string Message {get;set;}
}
//MEF bits here
public class HelloWorldViewModel: Screen, IHandle<MessageNotifier>{
private readonly IEventAggregator _eventAggregator
//MEF constructor bits
public YourViewModel(IEventAggregator eventAggregator){
_eventAggregator = eventAggregator;
}
public override OnActivate(){
_eventAggregator.Subscribe(this);
}
public override OnDeactivate(){
_eventAggregator.UnSubscribe(this);
}
//I Handle all messages with this signature and if the message applies to me do something
//
public void Handle(MesssageNotifier _notifier){
if(_notifier.Message == "NewSelectedItem"){
//do something with the content of the selectedItem
var x = _notifier.Content
}
}
}
//MEF attrs
public class HelloWorld2ViewModel: Screen{
private readonly IEventAggregator _eventAggregator
//MEF attrs
public HelloWorld2ViewModel(IEventAggregator eventAggregator){
_eventAggregator = eventAggregator;
}
public someobject SelectedItem{
get{ return _someobject ;}
set{ _someobject = value;
NotifyOfPropertyChange(()=>SelectedItem);
_eventAggregator.Publish(new MessageNotifier(){ Content = SelectedItem, Message="NewSelectedItem"});
}
}
One option is to utilize NotifyPropertyChanged. Since you are working with ViewModels, they most likely implement INotifyPropertyChanged, which you can make use of just as the framework does.
When your CreateServiceViewModel creates the MemberSearchViewModel, it would just subscribe to the PropertyChanged event:
//This goes wherever you create your child view model
var memberSearchViewModel = new MemberSearchViewModel(); //Or using a service locator, if applicable
memberSearchViewModel.PropertyChanged += OnMemberSearchPropertyChanged;
private void OnMemberSearchPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if(e.PropertyName == "SelectedMember")
{
//Code to respond to a change in the Member
}
}
And then in your MemberSearchViewModel, you simply raise the NotifyPropertyChanged event when the user has selected a member from the grid.
EDIT:
As #DNH correctly notes in the comments, using event handlers like this can lead to memory leaks if not properly cleaned up. So when you are finished with the MemberSearchViewModel, make sure to unsubscribe to the PropertyChanged event. So for example, if you only need it until the user selects a member, you could put it inside the Property Changed Handler itself (I've switched it to use a class-level variable to hold the ViewModel):
private void OnMemberSearchPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if(e.PropertyName == "SelectedMember")
{
//Code to respond to a change in the Member
//Unsubscribe so the view model can be garbage collected
_memberSearchViewModel.PropertyChanged -= OnMemberSearchPropertyChanged;
_memberSearchViewModel = null;
}
}
One option would be to store MemberSearchViewModel as a field of CreateServiceViewModel and define CreateServiceViewModel.SelectedMember property as follows:
public Member SelectedMember
{
get
{
return _memberSearchViewModel.SelectedMember;
}
set
{
_memberSearchViewModel.SelectedMember = value;
}
}
How about?
public interface INotifyMe<T>
{
T ResultToNotify { get; set; }
}
public class CreateServiceViewModel : ViewModelBase, INotifyMe<Member>
{
// implement the interface as you like...
}
public class MemberSearchViewModel : ViewModelBase
{
public MemberSearchViewModel(INotifyMe<Member> toBeNotified)
{
// initialize field and so on...
}
}
Now you could let listen CreateServiceViewModel to its own property and you won't have to think about the removal of the event listener.
Well of course to do the more classical way you could alternatively use an interface like this.
public interface INotifyMe<T>
{
void Notify(T result);
}
As a follow-up to my comment, here's an example using Prism - I've never used Caliburn.
Create an event - the event's payload will be your SelectedMember:
public class YourEvent:CompositePresentationEvent<YourEventPayload>{}
Publish the event:
EventAggregator.GetEvent<YourEvent>().Publish(YourEventPayload);
Subscribe to the event:
EventAggregator.GetEvent<YourEvent>().Subscribe((i) => ...);

Testing WinForms MVP and Event mechanism with moq

I've been using MVP pattern in my application. But I have problems with testing my method which are called after button is clicked. Here is the code:
public interface IControl
{
bool Enabled { get; set; }
string Text { get; set; }
}
public interface IButton : IControl
{
event EventHandler Click;
}
public class Button : System.Windows.Forms.Button, IButton
{ }
public interface IForm : IControl
{
void Show();
void Close();
}
public interface IView : IForm
{
IButton Button1 { get; }
}
public partial class View : Form, IView
{
public View()
{
InitializeComponent();
}
#region IView Members
public IButton Button1
{
get { return button1; }
}
#endregion
}
public class Presenter
{
IView view;
public Presenter(IView view)
{
this.view = view;
this.view.Button1.Click += ButtonClick;
this.view.Show();
}
private void ButtonClick(object sender, EventArgs e)
{
view.Button1.Text= "some text";
}
}
The problem is that I don't know how to write test so that my ButtonClick method get called. I tried like this:
var view = new Mock<IView>();
view.Setup(x => x.Button1).Returns(new Mock<IButton>().SetupAllProperties().Object);
Presenter presenter = new Presenter(view.Object);
view.Raise(x => x.Button1.Click+= null, EventArgs.Empty);
Assert.AreEqual("some text", view.Object.Button1.Text);
I think that problem is in this line:
this.view.Button1.Click += ButtonClick;
It seems that Click event doesn't remember ButtonClick method. How to make Click to be stub to work just normal.
Any suggestion is welcome.
Thanks in advance.
Regards,
Vajda
EDIT: I was able to do that when I created SubscribeOnClick(EventHandler click); method in my IButton interface instead of event EventHandler Click. And I made some ButtonMock where I remembered method. But still, if someone knows for better solution, please share with me.
Maybe it's not a bad idea to use the command pattern here. Your IView is very implementation specific because it has a prescribed number of controls that should have a Click event (I know it is an example, but still...).
A simple implementation of the command pattern would be to let IView have a List<Action> that is supplied by the presenter, and let a specific implementation of a view decide how to fire these actions, e.g. by doing
this.button1.Click += (sender, e) => this.Actions[0]();
A mock object would not need to have a Click event (which may not even be supported by Moq, I'm not sure). You could just have it fire one of its actions.
I Changed my IButton interface to this one:
public interface IButton : IControl
{
voie SUbscribeOnClick(EventHandler click);
}
public class ButtonStub : IButton
{
EventHandler click;
public bool Enabled { get; set; }
public void SubscribeOnClick(EventHandler click)
{
this.click = click;
}
public string Text { get; set; }
public void RaiseClickEvent()
{
click(this, EventArgs.Empty);
}
}
This way I was able to make stub class which have private event where I can subscribe and after that call method which fires event when needed.

Categories