Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Brand New Validators - Templated Extension Thing

0.00/5 (No votes)
25 Nov 2004 1  
An extension to standard MFC DDX/DDV mechanism and a new way of data validation in WinAPI programs

Sample image

Introduction

Message boxes are evil. Definitely. They should be the last option considered when it comes to displaying error messages. Boxes are intrusive, they emit pathetic "Ding" sound, and they're somewhat frightening and disturbing. This is the first issue. The second problem was my annoyance with MFC's standard DDX/DDV mechanism. It's very inflexible and allows almost no customization. These were the primary reasons for me to come up with a better method of data validation and, obviously, reporting errors to the user. So here is the full story...

Part One - Base Classes

Well, frankly speaking, that was the first time I did some kind of planning and �drafted� on various pieces of paper my future class hierarchy and stuff. First off, I initially thought of this library as a fully template-based one. At the same time, I needed an interface for all my validator classes. As you will see in the following section, this kind of separation of base classes to templated and non-templated ones is required to implement a container of validators, so here are the classes:

//

// General Validator interface.

//

struct IValidator
{
    //

    // Virtual destructor        

    virtual ~IValidator(void)
    {
    }

    //

    // Validation function. Returns true if validation

    // succeeded

    virtual bool Validate(void) = 0;    
};

Pretty simple, isn't it? IValidator exposes the only method - that is pure virtual function Validate().

//

// Typed validator. Type denotes the actual type of

// the value being validated.

//

template <class Type> struct ITypedValidator : public IValidator
{
    protected:
        typedef typename Type ValueType;
        ValueType m_tValue;
};

ITypedValidator is just an extension to the IValidator interface which introduces a ValueType - essentially the type being validated. It also has m_tValue member variable, which holds the resulting value. Next comes the ValidatorBase class - it holds two message strings and the identifier of the control being validated.

//

// Validator Base. Base class for all validators.

//

class ValidatorBase
{
protected:
    // Control handle

    HWND m_hControl;
    // Error message title

    std::string m_strTitle;
    // Error message text

    std::string m_strText;
    
    //

    // Constructor        

    ValidatorBase(HWND hControl, const std::string& strTitle, 
                                 const std::string& strText) :
        m_hControl(hControl),
        m_strTitle(strTitle),
        m_strText(strText)
    {
    }

    //

    // Copy constructor

    ValidatorBase(const ValidatorBase& vb) :
        m_hControl(vb.m_hControl),
        m_strTitle(vb.m_strTitle),
        m_strText(vb.m_strText)
    {
    }

    //

    // Virtual destructor

    virtual ~ValidatorBase(void)
    {
    }

    //

    // Assignment operator

    const ValidatorBase& operator = (const ValidatorBase& vb)
    {
        m_hControl = vb.m_hControl;
        m_strTitle = vb.m_strTitle;
        m_strText = vb.m_strText;

        return *this;
    }
};

So far, so clear, isn't it?

Part Two - Policies

If you're really interested in advanced C++ techniques, you should definitely read Andrei Alexandrescu's "Modern C++ Design". This is an exceptional book, and it's really worth reading. Along with other topics, it covers Policies - basically, a method of configuring template-based classes. My motivation for using policies was a justified attempt to provide as much flexibility as possible (again, as opposed to MFC).

So currently, there are policies for retrieving text from controls, for loading resources, and for reporting errors to the user. By varying those policies (and writing new ones), you can create a validator, which, for instance, will load string resources from the XML file on a remote server, validate text from the control by exploiting the power of regular expressions, and report errors by writing to the system Event Log (are your errors really that serious?). As you can see, the possibilities are almost endless. By now, there are policies for retrieving text from controls in a usual WinAPI fashion, for loading string resources from executable modules, and for reporting errors to the user via Message Boxes (whoops...) and in a fancy .NET-style way.

Part Three - Validator Classes

So now it's time for a serious business. First, let's see a GenericTypeValidator class.

//

// Generic Type Validator

// Type - Type being validated

// TypeValidator - function, returning true if input

//                 string is a valid representative of the Type

// TypeConverter - conversion function, converts from _TCHAR* to Type

// ControlTextProviderPolicy - Control Text Provider Policy

// ErrorReportingPolicy - Error Reporting Policy

// ResourceLoaderPolicy - Resource Loader Policy

//

template <class Type, bool TypeValidator(const _TCHAR*), 
Type TypeConverter(const _TCHAR*),
class ControlTextProviderPolicy = ControlTextProvider, 
class ErrorReportingPolicy = MessageBoxErrorReporting, 
class ResourceLoaderPolicy = ResourceLoader>
class GenericTypeValidator : public ValidatorBase, 
    public ITypedValidator<Type>, public ControlTextProviderPolicy, 
    public ErrorReportingPolicy, public ResourceLoaderPolicy
{
protected:
    // If this class is a base class

    bool m_bBase;
public:
    //

    // Constructor        

    GenericTypeValidator(HWND hControl, const std::string& strTitle, 
                                       const std::string& strText) :
        ValidatorBase(hControl, strTitle, strText),
        m_bBase(false)
    {
    }

    //

    // Constructor

    GenericTypeValidator(HWND hControl, UINT nIDTitle, UINT nIDText) :
        ValidatorBase(hControl, LoadString(nIDTitle), LoadString(nIDText)),
        m_bBase(false)
    {
    }

    //

    // Copy constructor

    GenericTypeValidator(const GenericTypeValidator& gtv) :
        ValidatorBase(gtv),
        m_bBase(false)
    {
    }

    //

    // Virtual destructor

    virtual ~GenericTypeValidator(void)
    {
    }

    //

    // Assignment operator

    const GenericTypeValidator& operator = (const GenericTypeValidator& gtv)
    {
        ValidatorBase::operator = (gtv);

        m_bBase = gtv.m_bBase;
        
        return *this;
    }

    //

    // Validation function        

    virtual bool Validate(void)
    {
        std::string strText = GetControlText(m_hControl);

        if(!TypeValidator(strText.c_str()))
        {
            if(!m_bBase)
                ReportError(m_hControl, m_strTitle, m_strText);
            return false;
        } // if


        m_tValue = TypeConverter(strText.c_str());

        if(!m_bBase)
            ReportSuccess(m_hControl);

        return true;
    }
};

This validator basically checks whether text in the control is, that's to say, a valid representative of some certain type - be it a floating-point value, an integer value, or anything else.

Now, we have GenericRangeValidator.

//

// Generic Range Validator

// Type - Type being validated

// TypeValidator - function, returning true if input string

//                 is a valid representative of the Type

// TypeConverter - conversion function, converts from _TCHAR* to Type

// Less - returns true if left-hand value is less than right-hand

// Grater - returns true if left-hand value is greater than right-hand

// ControlTextProviderPolicy - Control Text Provider Policy

// ErrorReportingPolicy - Error Reporting Policy

// ResourceLoaderPolicy - Resource Loader Policy

//

template <class Type, bool TypeValidator(const _TCHAR*), 
Type TypeConverter(const _TCHAR*),
bool Less(const Type&, const Type&), bool Greater(const Type&, const Type&),
class ControlTextProviderPolicy = ControlTextProvider, 
class ErrorReportingPolicy = MessageBoxErrorReporting, 
class ResourceLoaderPolicy = ResourceLoader>
class GenericRangeValidator : public GenericTypeValidator<Type, 
    TypeValidator, TypeConverter, ControlTextProviderPolicy, 
    ErrorReportingPolicy, ResourceLoaderPolicy>
{
    // Lower Bound

    ValueType m_tLower;
    // Upper bound

    ValueType m_tUpper;
public:
    //

    // Constructor

    GenericRangeValidator(HWND hControl, const std::string& strTitle, 
             const std::string& strText, ValueType tLower, ValueType tUpper) :
             GenericTypeValidator<Type, TypeValidator, 
             TypeConverter, ControlTextProviderPolicy, 
             ErrorReportingPolicy>(hControl, strTitle, strText),
             m_tLower(tLower), m_tUpper(tUpper)
    {
        m_bBase = true;
    }

    //

    // Constructor

    GenericRangeValidator(HWND hControl, UINT nIDTitle, UINT nIDText, 
        ValueType tLower, ValueType tUpper) :
        GenericTypeValidator<Type, TypeValidator, TypeConverter, 
        ControlTextProviderPolicy, 
        ErrorReportingPolicy>(hControl, nIDTitle, nIDText),
        m_tLower(tLower), m_tUpper(tUpper)
    {
        m_bBase = true;
    }

    //

    // Copy constructor

    GenericRangeValidator(const GenericRangeValidator& grv) :
        GenericTypeValidator<Type, TypeValidator, TypeConverter, 
        ControlTextProviderPolicy, ErrorReportingPolicy>(grv),
        m_tLower(grv.m_tLower),
        m_tUpper(grv.m_tUpper)
    {
        m_bBase = true;
    }

    //

    // Virtual destructor

    virtual ~GenericRangeValidator(void)
    {
    }

    //

    // Assignment operator

    const GenericRangeValidator& operator = 
          (const GenericRangeValidator& grv)
    {
        GenericTypeValidator<Type, TypeValidator, TypeConverter, 
          ControlTextProviderPolicy, ErrorReportingPolicy>::operator = (grv);

        m_tLower = grv.m_tLower;
        m_tUpper = grv.m_tUpper;
        m_bBase = true;

        return *this;
    }

    //

    // Validation function        

    virtual bool Validate(void)
    {
        if(!GenericTypeValidator<Type, TypeValidator, TypeConverter, 
            ControlTextProviderPolicy, ErrorReportingPolicy>::Validate())
        {
            ReportError(m_hControl, m_strTitle, m_strText);
            return false;
        } // if


        if(Less(m_tValue, m_tLower) || Greater(m_tValue, m_tUpper))
        {
            ReportError(m_hControl, m_strTitle, m_strText);
            return false;
        } // if


        ReportSuccess(m_hControl);

        return true;
    }
};

It is derived from GenericTypeValidator and it checks if m_tValue fits into the specified range. This class can be parameterized, along with other types, with comparison functions. GenericComparisonValidator does almost the same thing as GenericRangeValidator does, but compares m_tValue with one and the only value, thus can be used for validation, for instance, of minimum and maximum values.

//

// Generic Comparison Validator

// Type - Type being validated

// TypeValidator - function, returning true

//                 if input string is a valid representative of the Type

// TypeConverter - conversion function, converts from _TCHAR* to Type

// Comparer - returns true if comparison is correct

// ControlTextProviderPolicy - Control Text Provider Policy

// ErrorReportingPolicy - Error Reporting Policy

// ResourceLoaderPolicy - Resource Loader Policy

//

template <class Type, bool TypeValidator(const _TCHAR*), 
Type TypeConverter(const _TCHAR*),
bool Comparer(const Type&, const Type&), 
class ControlTextProviderPolicy = ControlTextProvider, 
class ErrorReportingPolicy = MessageBoxErrorReporting, 
class ResourceLoaderPolicy = ResourceLoader>
class GenericComparisonValidator : public GenericTypeValidator<Type, 
    TypeValidator, TypeConverter, ControlTextProviderPolicy, 
    ErrorReportingPolicy, ResourceLoaderPolicy>
{
    // Base Value

    ValueType m_tBase;
public:
    //

    // Constructor

    GenericComparisonValidator(HWND hControl, const std::string& strTitle, 
        const std::string& strText, ValueType tBase) :
        GenericTypeValidator<Type, TypeValidator, TypeConverter, 
        ControlTextProviderPolicy, 
        ErrorReportingPolicy>(hControl, strTitle, strText),
        m_tBase(tBase)
    {
        m_bBase = true;
    }

    //

    // Constructor

    GenericComparisonValidator(HWND hControl, UINT nIDTitle, 
          UINT nIDText, ValueType tBase) :
          GenericTypeValidator<Type, TypeValidator, 
          TypeConverter, ControlTextProviderPolicy, 
          ErrorReportingPolicy>(hControl, nIDTitle, nIDText),
          m_tBase(tBase)
    {
        m_bBase = true;
    }

    //

    // Copy constructor

    GenericComparisonValidator(const GenericComparisonValidator& gcv) :
        GenericTypeValidator<Type, TypeValidator, TypeConverter, 
        ControlTextProviderPolicy, ErrorReportingPolicy>(gcv),
        m_tBase(gcv.m_tBase)
    {
        m_bBase = true;
    }

    //

    // Virtual destructor

    virtual ~GenericComparisonValidator(void)
    {
    }

    //

    // Assignment operator

    const GenericComparisonValidator& operator = 
          (const GenericComparisonValidator& gcv)
    {
        GenericTypeValidator<Type, TypeValidator, TypeConverter, 
           ControlTextProviderPolicy, 
           ErrorReportingPolicy>::operator = (gcv);

        m_tBase = true;

        return *this;
    }

    //

    // Validation function        

    virtual bool Validate(void)
    {
        if(!GenericTypeValidator<Type, TypeValidator, TypeConverter, 
            ControlTextProviderPolicy, ErrorReportingPolicy>::Validate())
        {
            ReportError(m_hControl, m_strTitle, m_strText);
            return false;
        } // if


        if(!Comparer(m_tBase, m_tValue))
        {
            ReportError(m_hControl, m_strTitle, m_strText);
            return false;
        } // if


        ReportSuccess(m_hControl);

        return true;
    }
};

GenericStringValidator is a special version (not a specialization) of a validator which handles strings.

//

// Generic String Validator

// Validator - returns true if comparison is correct

// ControlTextProviderPolicy - Control Text Provider Policy

// ErrorReportingPolicy - Error Reporting Policy

// ResourceLoaderPolicy - Resource Loader Policy

//

template <bool Validator(const std::string&), 
class ControlTextProviderPolicy = ControlTextProvider, 
class ErrorReportingPolicy = MessageBoxErrorReporting, 
class ResourceLoaderPolicy = ResourceLoader>
class GenericStringValidator : public ValidatorBase, 
    public ITypedValidator<std::string>, 
    public ControlTextProviderPolicy, 
    public ErrorReportingPolicy, 
    public ResourceLoaderPolicy
{
public:
    //

    // Constructor

    GenericStringValidator(HWND hControl, 
        const std::string& strTitle, const std::string& strText) :
        ValidatorBase(hControl, strTitle, strText)
    {
    }

    //

    // Constructor

    GenericStringValidator(HWND hControl, UINT nIDTitle, UINT nIDText) :
        ValidatorBase(hControl, LoadString(nIDTitle), LoadString(nIDText))
    {
    }

    //

    // Copy constructor

    GenericStringValidator(const GenericStringValidator& gsv) :
        ValidatorBase(gsv)
    {
    }

    //

    // Virtual destructor

    virtual ~GenericStringValidator(void)
    {
    }

    //

    // Assignment operator

    const GenericStringValidator& operator = (const GenericStringValidator& gsv)
    {
        ValidatorBase::operator = (gsv);

        return *this;
    }

    //

    // Validation function        

    virtual bool Validate(void)
    {
        m_tValue = GetControlText(m_hControl);

        if(!Validator(m_tValue))
        {
            ReportError(m_hControl, m_strTitle, m_strText);
            return false;
        } // if


        ReportSuccess(m_hControl);

        return true;
    }
};

GenericControlValidator is supposed to handle various controls by sending them proper messages.

//

// Generic Control Validator

// ControlValidator - function, returning true if the control is in valid state

// ErrorReportingPolicy - Error Reporting Policy

// ResourceLoaderPolicy - Resource Loader Policy

//    

template <bool ControlValidator(HWND), 
class ErrorReportingPolicy = MessageBoxErrorReporting, 
class ResourceLoaderPolicy = ResourceLoader>
class GenericControlValidator : public ValidatorBase, 
    public IValidator, public ErrorReportingPolicy, 
    public ResourceLoaderPolicy
{    
public:
    //

    // Constructor        

    GenericControlValidator(HWND hControl, 
        const std::string& strTitle, const std::string& strText) :
        ValidatorBase(hControl, strTitle, strText)
    {
    }

    //

    // Constructor

    GenericControlValidator(HWND hControl, UINT nIDTitle, UINT nIDText) :
        ValidatorBase(hControl, LoadString(nIDTitle), LoadString(nIDText))
    {
    }

    //

    // Copy constructor

    GenericControlValidator(const GenericControlValidator& gcv) :
        ValidatorBase(gcv.m_hControl, gcv.m_strTitle, gcv.m_strText)
    {
    }

    //

    // Virtual destructor

    virtual ~GenericControlValidator(void)
    {
    }

    //

    // Assignment operator

    const GenericControlValidator& operator = 
          (const GenericControlValidator& gcv)
    {
        ValidatorBase::operator = (gcv);

        return *this;
    }

    //

    // Validation function        

    virtual bool Validate(void)
    {
        if(!ControlValidator(m_hControl))
        {
            ReportError(m_hControl, m_strTitle, m_strText);
            return false;
        } // if


        ReportSuccess(m_hControl);

        return true;
    }
};

Part Four - Using 'Em

This particular thing is pretty straightforward. Suppose you have a dialog, the contents of which you're about to validate. Now you have a couple of options. You can either validate everything in a more or less usual way by validating everything in the OnOK handler or perform on-the-fly validation, that is in WM_KICKIDLE message handler. These two choices differ insignificantly, but it's better to use non-intrusive error reporting policy for the latter. All right, let's start coding. First, add validator.h to your project. Now add a Validator::ValidatorPool member variable to your dialog class. You could've added loads of specific validators, but it isn't a huge fun to invoke them by yourself. But anyway - if you want it�.. Now we have to add a few validators. That's how it's done:

//

// Initializing validators

m_vpValidators.AddValidator(IDC_EDIT1, 
    IValidatorPtr(new IntegerValidator(*GetDlgItem(IDC_EDIT1), "Integer Value", 
    "Please enter an integer value")), true);
m_vpValidators.AddValidator(IDC_EDIT2, 
    IValidatorPtr(new FloatValidator(*GetDlgItem(IDC_EDIT2), "Floating Point Value", 
    "Please enter a floating point value")), true);
m_vpValidators.AddValidator(IDC_EDIT3, 
    IValidatorPtr(new IntegerInclusiveRangeValidator(*GetDlgItem(IDC_EDIT3), 
    "Ranged Integer Value", 
    "Please enter an integer value ranging from 0 to 542", -1, 543)), true);
m_vpValidators.AddValidator(IDC_EDIT4, 
    IValidatorPtr(new NotEmptyStringValidator(*GetDlgItem(IDC_EDIT4), 
    "Non-Empty String", 
    "Please enter something...")), true);

And now it's time to choose (don't we have to choose all the time?). For the Validate-in-OnOK approach, override OnOK virtual function of your dialog class and add the following line:

// ...

if(!m_vpValidators.Validate())
    return;
// ...

And that's it. Of course, you can toggle some specific validators depending on some conditions (say, a Check Box was checked, or an item in a List Control selected - anything) to disable validation for those specific controls. Here's the second option. Modify your stdafx.h by adding this include statement:

#include <afxpriv.h>

In your dialog header class, add the following prototype:

afx_msg LRESULT OnKickIdle(WPARAM wParam, LPARAM lParam);

Add this entry to the message map:

ON_MESSAGE(WM_KICKIDLE, OnKickIdle)

And implement OnKickIdle this way:

LRESULT CValidatorsDlg::OnKickIdle(WPARAM wParam, LPARAM lParam)
{
    GetDlgItem(IDOK)->EnableWindow(m_vpValidators.Validate());

    return 0;
}

And, again, that's it.

Part Five - Stuff

We're almost finished. First off, I can't promise that this code will compile with all compilers. I wrote it using Visual C++ 7.1 (the one that comes with Microsoft Visual Studio .NET 2003) and there it works just fine. I'd be terribly grateful for any comments concerning portability and compatibility.

And just a few notes about the things I'd like to implement. First of all, regexps - primarily to validate emails, URLs and credit cards. Of course, there is ::PathIsURL API, but why do guys from Redmond consider everything that starts with http:// a valid URL? Dammit, even Pocket Word thinks that this was an URL! Second - a balloon tooltip error reporting policy. And, of course, all your suggestions. Thanks!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here