MVP and passing CancelEventArgs - c#

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.

Related

C# override event with custom eventargs

I need a custom NumericUpDown where the event ValueChanged should pass CancelEventArgs instead of EventArgs as I want to be able to cancel the editing when certain conditions are verified (e.g. I have two NumericUpDown that must have always different values). If I try to override OnValueChanged I obviously get an error.
protected override void OnValueChanged(CancelEventArgs e)
{
if (e.Cancel)
return;
else
{
EventArgs args = (EventArgs)e;
base.OnValueChanged(args);
}
}
Is there a way to do this?
I would propose to change a little bit your implementation of the cancel behavior, instead of trying to pass the information of Cancellation through the event arguments, you can query it on demand by introducing a new event to your custom component. Here is a simple example:
class CustomNumericUpDown : NumericUpDown
{
protected override void OnValueChanged(EventArgs e)
{
if (QueryCancelValueChanging != null && QueryCancelValueChanging())
return;
else
{
EventArgs args = (EventArgs)e;
base.OnValueChanged(args);
}
}
public event Func<bool> QueryCancelValueChanging;
}
In this situation, the host of your component can subscribe to the new event in order to decide to cancel or not the "ValueChanged" event.
EDIT:
Usage example:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
CustomNumericUpDown nudTest = new CustomNumericUpDown();
nudTest.QueryCancelValueChanging += NudTest_QueryCancelValueChanging;
}
private bool NudTest_QueryCancelValueChanging()
{
return true;/* Replace by custom condition here*/
}
}
Perhaps you need to learn how to create and manage custom events if you have never done it before, it should be easy to find tutorials on this topic on the web (like this one )

Different parents for new Winform with a single constructor

Im running into a bit of an issue regarding Children and parents.
I have 2 forms which have the same dropdown menus, both of which have the ability to add additional options to them. When the "(add new)" option is selected in any of the combo boxes my third form is loaded which enables the addition of a new option.
This is the code for that third window (as it stands)
public partial class taskNewDropdownEntry : Form
{
taskWindow _owner;
applianceWindow _owner2;
int windowType;
int manufacturer_id;
sqlMod data = new sqlMod();
public int setManufacturerID {get { return manufacturer_id; } set { manufacturer_id = value; } }
public taskNewDropdownEntry(taskWindow owner, int type)
{
InitializeComponent();
this._owner = owner;
this.windowType = type;
}
public taskNewDropdownEntry(applianceWindow owner, int type)
{
InitializeComponent();
this._owner2 = owner;
this.windowType = type;
}
private void taskNewDropdownEntry_Load(object sender, EventArgs e)
{
if (windowType == 1)
{
instructionLabel.Text = "Input the new appliance type below";
}
else if (windowType == 2)
{
instructionLabel.Text = "Input the new manufacturer below";
}
else if (windowType == 3)
{
instructionLabel.Text = "Input the new model below";
}
}
private void btnOK_Click(object sender, EventArgs e)
{
if (windowType == 1)
{
data.insertApplianceType(textField.Text);
_owner.refreshTypeCombo();
}
else if (windowType == 2)
{
data.insertManufacturerSimple(textField.Text);
_owner.refreshManuCombo();
}
else if (windowType == 3)
{
data.insertModelSimple(manufacturer_id, textField.Text);
_owner.refreshModelCombo();
}
this.Close();
}
private void btnCancel_Click(object sender, EventArgs e)
{
this.Close();
}
}
Now, my issue is that the 2 forms that call this third form are different - thus my only thought of how to solve this would be to duplicate some of the code and modify the methods (you can see the second constructor already added).
Instead of having multiple constructors, and duplicated methods (in this class, or in a seperate one) is there a way whereby I can use the same constructor but different owners depending on the form that calls it?
You have too much implementation in your child form. The way I would tackle this is to
Add a property to your child form:
public string InstructionLabel { get; set; }
This allows your parent forms to individually set the label text when instantiating the form, and also set up an event handler for when the form is closing. So your parent form would have code something like
var newItemForm = new taskNewDropdownEntry();
newItemForm.InstructionLabel = "Input the new appliance type below";
newItemForm.FormClosing += new FormClosingEventHandler(ChildFormClosing);
Then somewhere early in your child form's life cycle (FormLoading event) set
instructionLabel.Text = InstructionLabel;
Then also add a property in the child form for
public string NewItem { get; set; }
your child form should set this public property in the btnOK_Click event
private void btnOK_Click(object sender, EventArgs e)
{
this.NewItem =textField.Text;
}
Then your parent form listens for a FormClosing event, and when it hits that event it takes the NewItem text, adds it to the relevant combo and refreshes it. So in the parent form, the handler looks like
private void ChildFormClosing(object sender, FormClosingEventArgs e)
{
sqlMod data = new sqlMod();
data.insertApplianceType(textField.Text);
refreshTypeCombo();
}
Pretty hard to understand the question but code speaks for all.
There are 2 options, worse (because keeping the parent reference is not a good practice first of all):
create an interface that both classes taskWindow and applianceWindow (where is the naming convention for god's sake!) implement, ex
intrerface IRefreshable {
void refreshManuCombo();
}
then constructor and your poperty can have type of IRefreshable
IRefreshable _owner;
public taskNewDropdownEntry(IRefreshable owner, int type)
{
InitializeComponent();
this._owner = owner;
}
better option, use child form events like Closed to implement refreshing logic in parent. You just need to register event handler before showing the form and voila. Check examples here:
https://msdn.microsoft.com/en-us/library/system.windows.forms.form.closed(v=vs.110).aspx
You can also implement your own public form event for more custom usage (ex. DataChanged, ResultGenerated).

How to pass EventArgs from View to Presenter in MVP?

I have an application based on MVP, WinForms and EntityFramework.
At one form I need to validate cell value, but I don't know proper way to pass EventArgs from Validating event of DataGridView to my presenter.
I have this Form (unrelated code omitted):
public partial class ChargeLinePropertiesForm : Form, IChargeLinePropertiesView
{
public event Action CellValidating;
public ChargeLinePropertiesForm()
{
InitializeComponent();
dgBudget.CellValidating += (send, args) => Invoke(CellValidating);
}
private void Invoke(Action action)
{
if (action != null) action();
}
public DataGridView BudgetDataGrid
{
get { return dgBudget; }
}
}
Interface:
public interface IChargeLinePropertiesView:IView
{
event Action CellValidating;
DataGridView BudgetDataGrid { get; }
}
And this presenter:
public class ChargeLinePropertiesPresenter : BasePresenter<IChargeLinePropertiesView, ArgumentClass>
{
public ChargeLinePropertiesPresenter(IApplicationController controller, IChargeLinePropertiesView view)
: base(controller, view)
{
View.CellValidating += View_CellValidating;
}
void View_CellValidating()
{
//I need to validate cell here based on dgBudget.CellValidating EventArgs
//but how to pass it here from View?
//typeof(e) == DataGridViewCellValidatingEventArgs
//pseudoCode mode on
if (e.FormattedValue.ToString() == "Bad")
{
View.BudgetDataGrid.Rows[e.RowIndex].ErrorText =
"Bad Value";
e.Cancel = true;
}
//pseudoCode mode off
}
}
Yes, I could expose a property through interface and set my EventArgs to this property in View to get them from Presenter, but this is ugly, isn't it?
public interface IChargeLinePropertiesView:IView
{
event Action CellValidating;
// etc..
}
Using Action is the problem here, it is the wrong delegate type. It doesn't permit passing any arguments. More than one way to solve this problem, you could use Action<CancelEventArgs> for example. But the logical choice is to use the same delegate type that the Validating event uses:
event CancelEventHandler CellValidating;
Now it is easy. In your form:
public event CancelEventHandler CellValidating;
public ChargeLinePropertiesForm() {
InitializeComponent();
dgBudget.CellValidating += (sender, cea) => {
var handler = CellValidating;
if (handler != null) handler(sender, cea);
};
}
In your presenter:
void View_CellValidating(object sender, CancelEventArgs e)
{
//...
if (nothappy) e.Cancel = true;
}

Get difference between Window ShowDialog cancel and close

In our app we use WPF and Caliburn Micro. We use a custom WindowManager:
public class OurWindowManager : Caliburn.Micro.WindowManager
{
protected override Window EnsureWindow(object model, object view, bool isDialog)
{
var window = base.EnsureWindow(model, view, isDialog);
if (isDialog) window.ResizeMode = ResizeMode.NoResize;
window.Icon = new BitmapImage(new Uri("pack://application:,,,/NWB.ico"));
// TODO: Change to dynamic minWidth/minHeight based on window
window.MinWidth = 600;
new WindowSettingsBehavior().Attach(window);
return window;
}
}
In our code we mostly use this WindowManager like so:
public void SomeMethod()
{
var result = _windowManager.ShowDialog(new ConfirmDialogViewModel("some title",
"some text"));
if(result == true){ // if OK is pressed
// do something on OK
}
// do nothing
}
In one of my recent methods I want to do the following (in semi pseudo-code):
public void SomeOtherMethod()
{
_windowManager.ShowDialog(new ConfirmDialogViewModel("some title", "some text"));
//if window is closed without pressing any of the buttons
return; // do nothing
//if OK is pressed {
// do something on OK
}
// if Cancel is pressed: do something else
}
Unfortunately, ShowDialog also returns false if the Window is closed (even though the ShowDialog returns a Nullable bool (bool?)).
So, what I did so far is just completely remove the Close Button by making a new Window-Behavior, and I've added it to the OurWindowManager class inside the if(isDialog):
if (isDialog)
{
window.ResizeMode = ResizeMode.NoResize;
new WindowHideBarBehavior().Attach(window);
}
This works, and I now got a Window with just a title, and without a Close (X) button. Unfortunately, the window can still be closed with Alt+F4 and such. I thought about catching Alt+F4 and cancel the closing, but since Alt+F4 is standard Window behavior, I don't think users will appreciate it a lot and I find it a bit unintuitive for the users to disable it..
So, my question: How can I accomplish the pseudo-code in SomeOtherMethod mentioned above. Is there a way to get the difference between closing a Dialog or canceling a Dialog. (NOTE: As mentioned above, keep in mind we use Caliburn.Micro.WindowManager, not the default C# WPF one. Don't know if there are a lot of differences, but I guess there are at least some.)
EDIT:
I also know I can catch the closing event and cancel the closing:
window.Closing -= DisableDialogClosing;
if (isDialog)
{
window.ResizeMode = ResizeMode.NoResize;
new WindowHideBarBehavior().Attach(window);
window.Closing += DisableDialogClosing;
}
...
private static void DisableDialogClosing(object sender, CancelEventArgs e)
{
e.Cancel = true;
}
But then it also cancels the closing when I want it to close (for example when the Cancel/OK button is pressed). Maybe I can add some kind of Property-flag to this overridden Closing-EventHandler, but perhaps you guys/girls have other suggestions to accomplish the same results.
You can fulfil your requirements if you just implement your own dialog Window by extending the Window class. From inside your custom Window, you can handle the Closed event and set the Window.DialogResult property to null in that case. For the normal Ok and Cancel states, you can simply attach Click handlers to those Buttons and set the Window.DialogResult property to true and false accordingly.
private void CustomDialogWindow_Close(object sender, RoutedEventArgs e)
{
DialogResult = null;
}
private void OkButton_Click(object sender, RoutedEventArgs e)
{
DialogResult = true;
}
private void CancelButton_Click(object sender, RoutedEventArgs e)
{
DialogResult = false;
}
You could then check for the Window closed state like this:
if (CustomDialogWindow.DialogResult == null) DoSomethingUponDialogWindowClose();
You can find further helpful information in the following pages on MSDN:
Dialog Boxes Overview
Window.DialogResult Property
After #Sinatr's suggestion I've added a ClosedBy property to my ConfirmDialogViewModel:
(before):
public sealed class ConfirmDialogViewModel : Screen
{
public ConfirmDialogViewModel(string title, string message)
{
DisplayName = title;
Message = message;
}
public string Message { get; set; }
public void Ok()
{
TryClose(true);
}
public void Cancel()
{
TryClose(false);
}
}
(after):
public sealed class ConfirmDialogViewModel : Screen
{
public ClosedBy CloseReason { get; private set; }
public ConfirmDialogViewModel(string title, string message)
{
DisplayName = title;
Message = message;
CloseReason = ClosedBy.Other;
}
public string Message { get; set; }
public void Ok()
{
CloseReason = ClosedBy.Ok;
TryClose(true);
}
public void Cancel()
{
CloseReason = ClosedBy.Cancel;
TryClose(false);
}
}
public enum ClosedBy
{
Other,
Ok,
Cancel
}
And I now use it like so:
public void SomeOtherMethod()
{
var confirmDialog = new ConfirmDialogViewModel("some title", "some text");
var result = _windowManager.ShowDialog(confirmDialog);
if(result == null || confirmDialog.CloseReason == ClosedBy.Other) return;
if(result == true && confirmDialog.CloseReason == ClosedBy.Ok){
// Do something on OK
}
// Do something on cancel
}
I still kept the behavior to remove the close button, and also added window.ShowInTaskbar = false; to the OurWindowManager inside the if(isDialog).

MVP pattern in winforms - Handling events

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
}

Categories