-2

I have a Form with several methods for input validation. I want this validation logic to be shared between several Forms and UserControls. What should be the type of the base class? It cannot be Form since it will also be used for UserControls and it cannot be UserControl since it will also be used for Forms.

My instinct tells me that a solution might involve generics, but I am not sure.

Note: By "validation logic" I refer to the implementation, so interfaces are not a solution.


Edit (to make the question a bit clearer): I do have a separate class for actually validating the input. However, I use an ErrorProvider to indicate the user that the input is invalid. My InputValidator class calls a method inside the Form/UserControl that shows the error to the user, using the ErrorProvider. I just do not want to copy-paste this method for every Form/UserControl.

Michael Haddad
  • 4,085
  • 7
  • 42
  • 82
  • Forms do inherit from Control somehwere down the line. But is this the best place to put the validation logic remains debatable. – user6144226 Nov 28 '17 at 14:35
  • Don't include validation in your form. Create separate class for validation logic. All data from forms can be passed to it as method parameters or properties (can be also grouped into new class). – Pablo notPicasso Nov 28 '17 at 14:35
  • @Sipo - the question might be a bit poorly phrased/constructed - since the inhertiance hierarchy is something you can easily check (docs/visualStudio). All you want to know is where to put/how to hookup reusable validaiton logic. – user6144226 Nov 28 '17 at 14:43
  • 1
    @user6144226 - I believe that what you wrote is how one might understand the title. However, after reading the question (even before the edit) it is clear that I am not searching one class in the inheritance hierarchy that is the base of both `Form`s and `UserControl`s, but I am trying to create my own class and have both `Form`s and `UserControls` inherit from it. This is a perfectly good question, there is really no real reason for the downvotes. – Michael Haddad Nov 28 '17 at 14:48
  • Use composition instead of inheritance. https://en.wikipedia.org/wiki/Composition_over_inheritance – Olivier Jacot-Descombes Nov 28 '17 at 14:52
  • @OlivierJacot-Descombes - Could you please elaborate in an answer? – Michael Haddad Nov 28 '17 at 14:52

3 Answers3

4

You cannot create a base class that handles both Forms and UserControls. However, you could create a base class for your Forms and another one for your UserControls.

In addition, create an interface that both base classes implement and that publishes the members that you need to use in the validator.

By this approach, the amount of code that is duplicated is at least reduced to two locations. Please note, that there are some OOP practices that will not work in the context of Windows Forms (e.g. abstract classes) because the designer introduces some limitations.


Original answer:
In this case, the best option is to move the validation logic to a separate class of its own. You can use this new class from both the Forms and the UserControls.

If you need to refer to the Form or UserControl during validation, you can use a parameter of type ContainerControl. This class is the base class of both Form and UserControl. This way, you can create shared logic that works for all classes that inherit from ContainerControl.

Markus
  • 20,838
  • 4
  • 31
  • 55
3

Use composition instead of inheritance. See: Composition over inheritance (wikipedia).

Your forms must inherit from Form or from another form which inherits from Form. Your user controls must inherit from UserControl or from another user control which inherits from UserControl. You cannot change this and therefore you cannot supply your own base class for both of them.

Instead, implement your validation logic in a separate class, and use it inside your forms and user controls.

public partial class MyForm : Form
{
    private readonly MyValidation _validation;

    public Myform()
    {
        InitializeComponent();
        _validation = new MyValidation(errorProvider1);
    }

    //TODO: use _validation instead of inherited validation logic.
}

I cannot give you more details on how to implement this exactly, as I don't know what your validation is.

Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188
  • Thanks. The "Validation logic" I talked about is actually the visual aspect of the validation, i.e. showing the errors to the user using an `ErrorProvider` (see the edit in the question). Can I really extract the `ErrorProvider` logic to a separate class? – Michael Haddad Nov 28 '17 at 15:32
  • Usually you implement the `IDataErrorInfo` interface (and probably `INotifyPropertyChanged`) in your business classes, do the validation there and only place an `ErrorProvider` component on your forms which shows the errors to the user automatically and needs no logic in the forms. So, I don't know which kind of logic you need there. – Olivier Jacot-Descombes Nov 28 '17 at 15:49
  • The `ErrorProvider` is not a sealed class. So, you could derive your own error provider from it and add any additional logic required to it. – Olivier Jacot-Descombes Nov 28 '17 at 15:54
  • My English is not great, so I am sorry if I am or was unclear. For example, I have logic that determines the color of the control. I have an if statement: if there is an error, the background color of the control changes to red; if the error was fixed - the background color changes back to its original color. – Michael Haddad Nov 29 '17 at 16:03
2

First thing is first - I think Olivier's answer is a very good one, and I would advise you to use it.
However, while you can't change the base classes of Form and UserControl, there is still a way to write your method only once using an interface and an extension method - and this is what I wanted to show in my answer here.

The interface:

internal interface IValidatable
{
    ErrorProvider ErrorProvider {get;} 
}

The extension class:

internal static class IValidatableExtensions
{
    internal void ShowValidationResult(this IValidatable self, ValidationResult result)
    {
    // Here you can use self.ErrorProvider to show your results
    }
}

Now you can have your forms and user controls implement the IVilidatable interface, and in the validation class simply call the ShowValidationResult method.

Again, I would simply send the validation instance a reference to the errorProvider as Olivier has demonstrated in his answer, but this technique might be useful in other cases, so I thought I better add an answer myself.

Ehsan Sajjad
  • 61,834
  • 16
  • 105
  • 160
Zohar Peled
  • 79,642
  • 10
  • 69
  • 121