Introduction

Introduction

 This is the first topic Next topic

Introduction

 This is the first topic Next topic

Let us be honest with ourselves, 'Validation' of data is probably one of the most important aspects of

our application design and development - BUT - is also probably the most disliked aspect for most

of us guys !

If we know that it is important and we know it MUST be done, then what we need is an easy way to

apply sufficient validation checking to make our application easy and pleasant to use by our users.

At the simplest level 'Validation' is the process of looking at data and deciding whether the provided

data value is acceptable - if not we need to flag up a warning on screen in some way.

Now then, what do we mean by 'acceptable' ?

This can vary, from being the incorrect data 'Type' to being the correct data type but outside of provided

and specified 'boundaries'. Like a human 'Age' integer being 333 when most don't even make 100.

If the screen input is about applying for a driving licence then the limits may need to be 17 - 70 in the UK.

However, since a lot of input field data is in string format - that is the data 'Type' is 'String', then we have

some much more difficult validation to deal with.

This is where Regular Expressions come into their own - we can apply RegEx string 'patterns' which can

be matched against the input string. By doing validation this way we can in fact validate for virtually

anything we like.

So patterns for UK postcodes, eMails, IP addresses, Visa numbers, telephone formats etc., etc., etc. can

be found on the web and also easily applied and checked for.

What's it all about ? - summary :

Before we move on to another sub-section lets say the purpose of these eNotes is this :-

To allow us developers to improve our WPF / XAML forms to 'screen' for unacceptable data entry items.

When an unacceptable entry is made by the user an obvious screen warning and tool-tip explanation,

should be displayed, so the user can immediately rectify the problem.

So that the app can continue processing then we should provide suitable defaults to avoid both hold-ups

and consequential errors.

We need an approach which is easy to understand and also relatively easy to apply.

What this is NOT about :

The eNotes and conference session is NOT about errors in the back-end data source, or how to manage

the issues of avoiding getting invalid data in databases.

There are other approaches and techniques which can be used, and are available, for doing this.

Time to go and learn some Validation 'stuff'.

PHH - 28th. January 2016.


Support learning materials

Support learning materials

 Previous topic Next topic

Support learning materials

 Previous topic Next topic

web searching - Josh Smith is your man in many related topics to these eNotes.

=====================================================================================

http://www.codeproject.com/script/Articles/MemberArticles.aspx?amid=247684

======================================================================================================

http://www.codeproject.com/Members/Nick-Polyak

http://nickssoftwareblog.com/

Now other sources :

==================================================================================================================

=========================================================

==================================================================================================================================================

PHH - 28th. January 2016.


An 'alternative' way of doing many related 'things' !

An 'alternative' way of doing many related 'things' !

 Previous topic Next topic

An 'alternative' way of doing many related 'things' !

 Previous topic Next topic

https://www.devexpress.com/Products/NET/Controls/WPF/

===============================================================================================================================

With DevExpress WPF Controls, you'll deliver high-performance line-of-business applications that fully address the needs of your
enterprise - apps that emulate Microsoft Office 2013 and Windows 8, while maintaining backward compatibility with previous versions of
the Windows operating system. Whether you need to create an Outlook-inspired UI or need to touch-enable your app for Microsoft Surface,
the DevExpress WPF Subscription will help you take your business solutions forward so you can deliver new, immersive experiences without
abandoning the WPF platform and your existing code investments.

=====================================================================================================

=============================================================================================================================================

=====================================================================================================================================

https://www.devexpress.com/Support/Start/

PHH - 4th. March 2016.


Chapter 0 - Validation explained

Chapter 0 - Validation explained

Chapter 0 - Validation explained

 Previous topic Next topic

'Validation' is one of those very important things we "love and hate", at the same time.

In this 'love hate relationship' it seems we also have 'errors and error handling' ;-0)

Both validation and error handling seem to get in the way of a quick application development cycle.

Maybe its because back in the old days validation and handling errors was just basically ignored by

many early programmers.

However, all programmers / developers of any level of standing are aware of the phrases like "pearls in, pearls out",

"crap in, crap out" and "garbage in, garbage out", etc. etc..

Simply translated and interpreted, we got out of our programs as good as we put in.

Therefore, we MUST make sure that all input data is exactly correct, on more than one level.

Lets put this another way - at any time during the execution of an application, data stored in memory and in

back-end storage MUST be correct and valid. If not then we are likely to generate errors, and many sorts of

error as well.

Processing incorrect and invalid data can only lead to grief ;-0((

Validation is the process of applying coded rules to our data, to catch (or trap) existing values which are for

some reason NOT correct. Values can be incorrect, or 'invalid' for more than one reason.

Validation is often required at 'boundaries' or physically 'interfaces'. Like at the GUI screen, or the business

logic / database interface.

The tighter we can make any validation rules the less likely we are to generate errors at run time.

However, we must make sure we do NOT exclude genuine correct and valid data being entered because

we have overlooked some logical conditions, or the likes. Do not allow any of your input screens to get

into a deadly embrace as far as the user is concerned.

It is generally recognised that it is best to trap and handle invalid data as soon as possible - sort it out

immediately.

Obviously, the handling of data and the validation process does involve .NET errors, and error handling - BUT -

we will only deal in specific 'validation type' errors.

http://blogs.msdn.com/b/bethmassi/archive/2008/06/27/displaying-data-validation-messages-in-wpf.aspx

http://blog.magnusmontin.net/2013/08/26/data-validation-in-wpf/

PHH - 26th. January 2016.


Levels of Validation

Levels of Validation

Levels of Validation

 Previous topic Next topic

We can think of Validation at screen input time, as coming in different levels.

One obvious level, is whether the screen value typed in is seen by the WPF UI form as the correct

data type.

For example if we need an Age value to be an integer and the user types a character string, like 'hello'.

Another level is when a data type like a DateTime has the correct format but an error in the detail. Here we

can see that if '13' is typed for the month part of the input, then although the general format is acceptable,

to actual 'sub-value' is not - months can only be 1 through 12.

The next level is built around 'ranges' of values, numerics and dates - min/max, high / low, start / finish, etc..

At yet another level of validation, we may or may not want spaces to be allowed in a string of characters

typed in. And we may or may not wish the field entry to be left 'empty' or blank.

And then there is a final level, one where the string input has to match in some complex fashion a provided

matching pattern using Regular Expressions.

I don't think may developer guys go so far as to check whether names input are actually real names ;-0)

With string inputs probably many can't be predicted at any practical or sensible level. For example, when

we are expecting a nationality to be typed in do we instead provide a long list of possible ones, from which

they have to 'drop list' select, or do we allow free typing ?

The way I see it, there is a limit to what we can possible check for - BUT - we can exclude 'stuff' which will

give us and our application 'grief' in the future.

So lets take a practical approach to all this stuff on Validation ;-0)

Lets move on ..........

PHH - 26th. January 2016.


Involving data types

Involving data types

Involving data types

 Previous topic Next topic

When is a number not a number ? :

If we have an 'open' text input box, or UI element for input, and we are expecting a numeric value,

then it is often possible to type in 'alpha' characters.

So we could have 'hello' when indeed we wish to have ' 13 ' or some other but similar integer value.

Since we do not have the 'masking' facilities of older input technologies we have to take the approach

that we let the user enter (type) what they wish, and then inform them if they are incorrect with any

key stroke entered.

If we do not add any functionality our selves then we get a basic default comparison and warning.

A quick answer :

We could give a quick and easy answer by saying that a number is an item typed in which has no

other characters than the 0 through 9 numeric characters (digits) and a possible point (or dot) and

possibly a comma too.

So this would cover most numeric entries for most business applications - scientific and engineering

apps would require a bit more work and understanding.

However, things are a bit more complicated when we try to use our own VRs - validation rules.

If we apply our own VR (or many) then we have also turned off the default rule, so we lose what that

has been doing for us.

Not yet resolved :-

A textbox input element bound to an integer public property and the default VR, does not allow me to enter a point or dot.

Strange as I can enter " ! " and " # " but not a comma either.

When is a string not a string ? :

Well, this is an easier question is some respects and more complex and complicated in others ;-0) seems like this

is the modern world for you.

A 'string' is a contiguous bunch (or group) of characters - where characters are all those type-able and not.

Most user input string are relative simple from this respect, as many are just made up from Alpha or Numeric characters.

Postcode (UK) strings are just alphanumeric with a space in the middle some place.

First names, last names, company names, countries and nationalities are simply alphabetic characters with the possibility

in some of these to have a dash ' - ' or a space " ".

So in the image shown below we have a Firstname input UI element which contains a string, however,

the string we see is no way like what we expect a person's first name to be, whatever their nationality or creed.

Study these details :-

=============================================================================

The validation rule that is set 'on' at the moment is checking length, and you can see we have exceeded the

total length limit of 23.

However, as it stand nothing is being done about the invalid characters.

Wouldn't it be nice if we could have all unsuitable name type characters removed on input, so it looks as if

they have been ignored ?

Well, this is where we are moving towards what used to be called 'Masking' of our input.

In the next sub-section we will move towards 'masking' or certainly better control of spurious inputs.

We will apply these ideas to numeric input as well as named strings, but in a way which is really

the other way around.

See you in the next section ..............

PHH - 26th. January & 3rd. March 2016.


Moving towards - 'Masking' our inputs

Moving towards - 'Masking' our inputs

Moving towards - 'Masking' our inputs

 Previous topic Next topic

String 'word / name' input data :

Lets start with a simpler situation than many, when it comes to validating and checking the data

values typed in by the user.

Since a string is just a bunch of any characters, then the value input is NOT invalid as such, just plain

wrong by our way of looking at things.

This means that for a string value the incorrect string value gets right through to the 'set' clause of the

data type class of which our public property is a member.

Let us view this 'set' section in the following image :-

=====================================================================================

Notice that all we need is one extra line at position 340 to do as we require and 'clean' up

the string characters.

Obviously, we now need to check out this STATIC method called "AllowOnlyNameChars".

========================================================================================================================

The above is a nice use of the STATIC members - used a bit like Functions of old days programming.

Notice that line 549 is a nice 'clean' and smart way to remove the detected unwanted characters.

This one line replace the string handling method I wrote myself and took ages to test and get right ;-0(

*** Do NOT re-invent the wheel !!! ***

Here is some information about the STATIC method from the Regex class in .NET :-

======================================================================================================================================

So if I set the update source trigger to 'LostFocus' for testing purposes, we can see any spurious characters, check below :-

==========================================================================================================================

And we can see the following once I have typed in some 'crap' :-

=======================================================================

But once I change the focus with a tab or a mouse click then we see a cleaned up entry like that below :-

=============================================================

By now you may be aware that using Regular Expressions and this string input 'situation' we can do a

lot more that just exclude non-alphabetic characters.

BUT - remember, we need to walk before we can run ;-0)

Lets move on to see and learn some more interesting things.

See you in the next sub-section ...............

PHH - 4th. March 2016.


Built around business logic

Built around business logic

Built around business logic

 Previous topic Next topic

Okay guys, it all okay to see that we have the correct string input so that it can be correctly

converted to a Type and that we therefore get the right type out at the other end, so to speak.

But, we also have the common scenario in which we have valid type data - BUT - it does not suit

our business logic, or commercial situation.

Quite often we need to limit the range of numerical and date and time entries.

So we can build into a Validation Rule (VR) some logic which allows us to apply a range limit.

Either the minimum and maximum length of a string, or the lowest and highest value of a numeric

or date value.

It is at this point that we can get ourselves into a bit of a mess, if we are not careful.

We need a method which is easy to understand and relatively easy to apply.

Since I consider the above situation as very similar to applying a 'filter' to our input data, I like to

apply the VR conditions at the UI element end of things.

I like to associate a particular .NET coded VR with a certain input attribute on a UI element.

Here shown below is my coded rule pointed to on line 87 - this adds the specified rule to the collection (or list)

of rules which are to be applied to this input text :-

====================================================================================================================

Notice above that VR class / type can have a couple of specified parameters as well.

The above VR specification is for the 'Age' property, however, below we are looking at the 'SpeakerId' property :-

=====================================================================================================================

Note that for this one (above) we have no extra VR added to the collection, so the default is likely to be used.

==============================================================================================================

==============================================================================================================

You may wonder why I have split up the conditional logic into a shell and a core.

It is quite simple really since code in the 'shell' depends on whether the UI element (object) is based

on a single item or a data 'Row' within a DataGrid.

By making a 'shell' and a 'core' I can use the same core logic for the two scenarios.

Having one lot of code and reusing it. This way we have only one place to edit and maintain the logic.

PHH - 26th. January 2016.


Types of Validation

Types of Validation

Types of Validation

 Previous topic Next topic

PHH - 26th. January 2016.


Input of Data - at the GUI

Input of Data - at the GUI

Input of Data - at the GUI

 Previous topic Next topic

PHH - 26th. January 2016.


Saving of Data - to Database

Saving of Data - to Database

Saving of Data - to Database

 Previous topic Next topic

PHH - 26th. January 2016.


Types of data

Types of data

Types of data

 Previous topic Next topic

================================

===============================================================

========================================================================================

========================================================================================

PHH - 26th. January 2016.


String

String

String

 Previous topic Next topic

PHH - 26th. January 2016.


Numeric

Numeric

Numeric

 Previous topic Next topic

PHH - 26th. January 2016.


Logic / Boolean

Logic / Boolean

Logic / Boolean

 Previous topic Next topic

PHH - 26th. January 2016.


DateTime

DateTime

DateTime

 Previous topic Next topic

PHH - 26th. January 2016.


Default values

Default values

Default values

 Previous topic Next topic

PHH - 26th. January 2016.


Chapter 1 - WPF and Validation

Chapter 1 - WPF and Validation

Chapter 1 - WPF and Validation

 Previous topic Next topic

========================================================================================

PHH - 26th. January 2016.


Example one - basic inputs

Example one - basic inputs

Example one - basic inputs

 Previous topic Next topic

PHH - 26th. January 2016.


Converters - Values back and forth

Converters - Values back and forth

Converters - Values back and forth

 Previous topic Next topic

*** Note and Think - something extra !? ***

Many folk think of a 'Converter' as just some code which is applied to the input stream on the UI element

on a WPF form.

Something which is applied before the 'converted' input is sent on to the receiving object to be processed.

However, if in the converter code we also apply some sort of a RegEx matching and 'filtering' code, then we

can also use the converter to clean-up string text typed in the form by the user.

In fact we could even have converts which are effectively 'straight through' apart from the 'cleaning' code section.

We need to do some exploration of this approach, so that for example "Hello-123!"£$%^&*()- Willie" as string

input data is converted to a string of "Hello Willie". Regular Expression matching is very good at this and we

can do the task in a couple of lines.

In fact we already have some code which would do this for us, we just need to call it from our converter to be -

meaning I have not as yet coded and applied a suitable converter.

Here are the STATIC members we could apply :-

========================================================================================================================

We have developed the above code in an earlier part of Chapter ' 0 ' called "Moving towards - 'Masking' our inputs".

Here is how we could clean-up an input string to only contain suitable 'name' type characters :-

===================================================================================

Then the resulting string could be pass 'through' by the converter code.

I will work on this and report later on.

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

Lets now continue .............

======================================================================================

We are only going to look at and study the 'Value Converter' situation.

For normal guys 'Type' converters are a bit of a 'bridge too far' ;-0)

Type :

https://msdn.microsoft.com/en-us/library/ee126043%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396

Value :

http://wpftutorial.net/ValueConverters.html

Since we are dealing with validation of data values, the value converters will suit us just fine.

=======================================================================

==================================================================================================================================================

================================================================================================================================

PHH - 26th. January 2016.


our Sample One - cleaning input strings

our Sample One - cleaning input strings

our Sample One - cleaning input strings

 Previous topic Next topic

Well guys we have a work application which does as we require of it ;-0)

The following image shows a 'Firstname' input UI element which does not accept (and display) any

character which is not an alpha character we would expect to be in a name, or word.

Here is what I see, even when after 'Richard' I type other non-alpha key strokes :-

========================================================================

The sort of characters I have entered (typed) but not seen in the TextBox are seen in the next

image when I do NOT have the same Converter code activated on the 'Lastname' field - check this out :-

============================================================================

=======================================================================

==========================================================

=========================================

=============================================================================================================================

============================================================================================================================

===========================================================

==================================================================================================================

========================================================================

==============================================================================

==========================================================================================================

====================================================================================================

============================================================================================================

PHH - 6th March 2016.


our Sample Two

our Sample Two

our Sample Two

 Previous topic Next topic

PHH - 6th March 2016.


our Sample Three

our Sample Three

our Sample Three

 Previous topic Next topic

PHH - 6th March 2016.


Accessing your .NET classes from XAML script

Accessing your .NET classes from XAML script

Accessing your .NET classes from XAML script

 Previous topic Next topic

One things that confuses new guys to XAML scripting for WPF forms, is the way to get hold of (access) our ,NET

coded class objects from within the XAML section of the design surface.

The answer is pretty simple once we know how ;-0) As in so many things in the modern programming / coding world.

We need to add suitable lines to out .NET namespaces in the Window (or UserControl) header section.

Below we see a case where we have three such settings - lines 6 through 8 - check this out :-

===================================================================================================================

We can now use 'local', 'local2', and 'local3' in our scripting.

Here is the example where we access our class 'String2NameText' to make a Resource :-

=========================================================================

And now we need to see how to use this Resource in the body of the XAML script - check below :-

============================================================================================================================

Remember the alternative syntax for using a Static Resource is as follows :-

===========================================================================================================

And to prove that we have the right 'local' settings, here are a couple of images which show the necessary header part

of our .NET files - you will find these namespaces referred to in local3 and local2 of the header section in the XAML script :-

============================================================================

And from 'MyConverters' to 'MyClasses' :-

=============================================================================

So when it comes to using Validation Rules and Converters we really do need to get our settings

right in the header lines - otherwise we won't get a compilation ;-0((

PHH - 6th March 2016.


XAML Resources - getting to grips

XAML Resources - getting to grips

XAML Resources - getting to grips

 Previous topic Next topic

=====================================================

=================================================================================================

==============================================================

============================================================================================================

PHH - 26th. January 2016.


Chapter 2 - Rules of Validation

Chapter 2 - Rules of Validation

Chapter 2 - Rules of Validation

 Previous topic Next topic

General remark and observation about data validation at a Form level :

It is very tempting to wish to have code and script for a data input Form which both detects individual TextBox input

errors (or invalid conditions) as well as NOT allowing the user to close the Form before the data issues have been

cleared up by the users actions.

As noble as this sounds it may NOT be a very good idea, it may at times cause a "deadly embrace".

Suppose the user is asked to supply his PostCode, and our RegExe rules are deficient and won't allow his/her genuine

postcode to be accepted. We can't expect the user to remove their genuine postcode - or we may even have the Form

demanding a genuine and correct PostCode.

There are a host of similar situations to the one mentioned above - SO - the answer is to trap the invalid conditions on

the Form, and then warn the user to re-examine the input data, and eventually allow them to continue if they wish to do so.

I suggest that all developers code in a way that they handle inputs after screen / Form closing so that invalid entries can't

cause major / minor disasters.

Its not an easy issue to get right first time, so some adjustments will need to be made to your approach.

Just don't get things so tight that common data input causes some sort of a 'lock-up' or 'deadly embrace'.

I have given at least two examples of how to check-out a Form for any and all invalid data entries. We just need to

decide what is invalid - and that's not an easy task in itself.

==========================================================================================

=========================================================================================================================

PHH - 26th. January 2016.


Sample - at a single GUI element level

Sample - at a single GUI element level

Sample - at a single GUI element level

 Previous topic Next topic

Okay you guys, we have some good success to follow through, in an example I have just written

and tested. It seems fine ;-0)

And it seems to do just what we need to get us going in a 'serious way' - meaning that bit more than demo

stuff with simple types etc., etc..

I have included the full working code and script in the relevant sub-folder of these eNotes, but on top of that

I will provide the German (Cologne 2016) conference guys with a solution file for Visual Studio 2015.

At the moment the example is working in C#, but I will create a Vulcan version for XBase folks.

Have a look at the following image of this example running :-

==========================================================================================

Each of the seven UI elements (seen in colour 'Bisque') is a TextBox, and three of these have 'incorrect' input data.

Incorrect as seen by the 'rules' that the developer guy wishes to use, or apply.

Here is the simple 'Main Window' which is a standard WPF form, not using an MVVM framework, and even with

some code behind :-

It is the 'Speaker' screen / form which is created and coded in an MVVM style. By this I mean I have

used the correct View, ViewModel, and data binding in WPF and XAML, but without actually using a framework

like Prism or MVVMlight.

I have tried to keep my code as simple and as straightforward as possible, without making my sample trivial or

misleading.

Hopefully guys reading and studying this example can then apply many / most ideas to their existing apps.

A General Discussion of the App Features :

Firstly, each of the seven TextBox inputs are checked in some fashion or another. Some by default rules and others

by rules I have either made up or copied from good sources elsewhere.

The validation rules (VRs) that I have added tend to be more complex, and involve data 'endpoints' like Max

and Min and not just type related checks.

Standard DateTime default rules are generally pretty good, and comprehensive, but don't have limits included.

As part of the general validation of a UI element, some sort of visual sign, or indication' is displayed when errors

are detected. This can be varied to suit the user.

Secondly, I have created and used a few VR's based on Regular Expressions, and so the PostCode input is

checked by a complex RegExe for UK validity. The VR is only for the UK, but others can be easily found

and applied in a similar manner.

Once we have created a new VR class, we have to apply it to the relevant TextBox in the XAML script. All this

coding in NOT done in the code-behind, and so is MVVM compliant.

Finally, there is an approach which can be easily applied, which checks the whole form to see whether there

are any outstanding errors, or invalid data entries. All TextBox elements are found and examined for validity.

So the image shown below does caution the user, even though the red indicators are well visible and obvious.

Check this out :-

=================================================================================================

And conversely, we can if we wish find and display that all is well to continue - see below :-

==================================================================================================================

At this point it would be most helpful for us to see and study the structure of this app as seen in the SE pane.

Here is what the SE (solution explorer) pane has to show :-

=================================================================

Note that in our specific Classes folder we have a class for the complex data type "Speaker", and

also a few (bunch) for VRs - in the ValidationRules file under the 'Classes' folder.

We will look at the VRs in some detail soon, as well as how these are applied in our code and script.

First, lets check out the extreme case where we have all seven TextBoxes with errors :-

================================================================================================

Note that an error is simply defined as an input element where the 'IsValid' attribute / property is 'True'.

Meaning 'yes' it/they are invalid.

As you shall see in good time, we even have versions of VR's for sting input which handle Upper and

Lower cases respectively, and we will examine later how to develop these further to cover stuff like

telephone numbers, and email addresses, etc..

Lets now move on ................

How do we apply a VR in our script ? :

Since the few default Validation Rules are quite primitive (apart from that for 'DateTime') we are best to first see

how one of our own VRs can be applied (or assigned) in the XAML script for the TextBox.

Note that if we do not add our own then we just get the default, without typing any extra script.

Personally, I suggest that we always apply our own VR - they can be must helpful even for pretty common

string Type inputs. Remember RegExes can help us make some amazing string searches and comparisons. The

one for British postcodes is just one powerful example.

Lets now look at some XAML script - check the following image :-

[ .. please be aware that we may be able to find some reduced syntax for lines 98-102 .. ]

=======================================================================================================================

Although the above syntax seems a bit long-winded, we may find a better way to do this, once we have been

able to study the above script which is simple enough in itself.

Line 100 above is where we assign or attach a validation rule of our own making.

We are making an association with the VR called "GeneralStringRule".

The example shown above see us also providing some parameter values for the class properties of Min, Max,

AllowEmpty, and AllowSpaces.

Line 98 shows us that we have a data binding with a ViewModel puplic property of "InSpeaker.FirstName".

Time to move on a pace or two ..........

What does a coded VR look like ? :

Remember that we are putting a bunch of separate VR classes together in one code file :-

======================================================

From the eight classes available above, we are exploring the second to last in the list,

that called 'GeneralStringRule'.

Lets see the 'MyClasses' code file when it is mainly collapsed :-

===================================================================

And now we need to look in detail at code within the "GeneralStringRule" class.

Once again, lets keep some collapsed sections so that we can get a feel for the overall structure :-

==========================================================================================================

And now lets expand a couple of regions, first the Properties :-

========================================================================================

All the properties are pretty standard and straightforward stuff - see above.

And now the crucial and most important part of the VR class :-

==============================================================================================================================

The above code is easy enough for you guys to read it through and study all the details.

Notice that the simple addition of property options to allow spaces and empty values, is a very

flexible addition to this general string rule.

Max and Min are more obvious, but also most useful.

We can easily change the constraints to allow one, two, or more separate spaces, to allow for "Willie Moore" to be

entered as a name. Or indeed "Benjamin David Philip Hepburn".

Moving on, lets see a working example of a 'RegExe' VR - one that uses Regular Expression text searching.

Here is the "UpperCaseOnlyStringRule" from our above list. Check out the details :-

=====================================================================================================

Notice in the above image that the general structure is similar to the earlier VR, but the important section

from 111 through 118 is based on a Regular Expression text search.

It won't surprise you to find out that the Lower Case version is very similar indeed.

Here is the important part :-

====================================================================================================

I will now leave it to you to combine some of these ideas and make one class which allows validation

of both upper and lower case strings which is 'switched' by a boolean property named 'IsAllUpper'.

The nice thing about having a whole bunch of VR classes is that we can easily copy the set to each

and every Project we create. Once a new class is designed, created, and then tried and tested, we have it

for good.

The VR can then be applied to any TextBox element anywhere - either the body of the WPF form or

within a data template we are using for List and Combo boxes. More on this later.

Lets move on and see some fun .............

How do we Find and Check all TextBox UI elements ? :

Okay the folks, back to looking at this sort of stuff below - what is actually happening ?

How do we get the information ? Check below to start us on our way of discovery :-

================================

On our data input screen - that called 'Speakers' - we have a group of TextBox UI elements which are all

contained within a Grid as seen in the following image - note the name of the Grid is "myGrid" (line 11 & 131) :-

==============================================================================================================================

Basically, we will write some code behind (YES! - I said 'Code Behind') which will take a UI element and find all

of the other elements within this, as in a tree structure.

We can discuss the 'code behind' issue later, as it does not screw us with regard to MVVM and Unit testing.

If we go to our code behind file we can effectively create an improved version of a Window, one which has check

validation facilities, but does not contain Business Logic or data manipulation.

Here below is the code behind file for 'SpeakerView' - lines 14 through 40 are the most important to study :-

================================================================================================================================

Line 17 above allows us to find the children of the 'Grid' on our screen / form.

[ There are ways to get Deep and Shallow search results - keep this in mind. ]

All we need do is enumerate through the items found, check and discard any which are not TextBox types,

Then we use the STATIC method 'Validation.GetHasError()' to see if the supplied parameter (a TextBox object)

has an error (or in other words, is valid).

This may be new to you, doing stuff like this with UI elements, but it is easy and is also effective.

"Off the top of my head", we need to be aware that for data templates for List controls and DataGrid controls,

we may need to use a 'Deep' search for TextBox elements, and may even need to use the Logical Tree

rather than the Visual Tree.

Anyway, for now we can see that this approach is very effective for many sorts of data input screen.

So lets use it ;-0)

[ If you are clever, you may be able to find a way to move the above check to the ViewModel. Look to ]

[ Command, RelayCommand and similar, written in standard WPF - see Chapter 3. ]

We need a discussion ......................

MVVM 'pattern of development' - 'Unit Testing' - clear 'Code Behind' :

At this point it is probably a good idea for us to talk about the now widely acclaimed, and applied, MVVM

'pattern' of application development.

Many guys get mixed up between applying an MVVM approach and using a framework for MVVM.

In fact the development 'pattern' of MVVM can be applied with standard WPF and .NET "out of the box".

I do this quite regularly, because I like the data binding and also separation that Views and ViewModels give

me in my application.

Certainly for smallish apps we can apply MVVM without real need for a framework.

We also need to realise that "inversion of control" is something in itself, and although it often comes along

with MVVM in examples, it is not part of it - just another modern approach to app development.

Unless we get all our business logic code in one ViewModel class then we can't Unit Test it. So often we

hear it said that nothing at all - no code - should be in the code behind a View. We also hear it said that

if there are 'Name' attributes in the XAML then we are doing something wrong.

Generally the above statements are correct - BUT - if in fact any code behind is NOT business logic related,

but instead adds some functionality to the View, then these comments can be ignored, and Unit Testing

can still be done 100%.

This is indeed the case above when we added a Button and a click event to have a View wide check made on

all TextBox UI elements. No business logic, just a GUI check / test to report back some very specific View

details.

So the fact that we have named the Grid "MyGrid" does not matter. Neither does it matter that we have a

click event being handled - its not business logic related.

However, one complexity I feel is always worth it when doing MVVM, and that is passing the data objects

into the ViewModel as parameter(s) and then binding these (or their public properties) to the UI elements

in the XAML script.

This is exactly what I have done in the simple sample above - revisit the code and script to check this

aspect out - below is the calling code part [ .. lines 30 & 31 in particular .. ] :-

===================================================================================================

And then within the ViewModel we see the input parameter used as on line 64.

However, we do need to also provide an 'empty' constructor for reasons we will not discuss here.

===================================================================

Lets move on ............

I explained a lot of all this stuff in detail in my presentations at Cologne, Germany in 2014 and 2015. I suggest

you find my eNotes and material from those conference events.

Lets move on a step or two ..............

Testing Out our new VRs (Validation Rules) :

As the following image will demonstrate it is easy to make a script change which allows us to test out

the various VRs we create.

You will notice below that we simply comment out a working existing one, and the insert our new trial

form of a VR.

It is important to checkout newly made VR's in a thorough and rigorous manner.

Oh! obviously we need the bound public property to match the Type that the VR is written for.

Below is a TextBox bound to property 'LastName' in a Speaker object, this is a string type :-

==================================================================================================================

Once you have done your test and are happy that 100% functionality exists, you can clean up the XAML

script, and mark the new VR class as fully tested in a comment within the VR.

PHH - 27th. January & 8th. February 2016.


working script and code

working script and code

working script and code

 Previous topic Next topic

================ CLASSES Data object =========================================================

using System;

using System.ComponentModel;   //----- INotifyPropertyChanged .....

namespace MyClasses

{

//---------------------------------------------------------

public class Speaker : INotifyPropertyChanged

{

//----- fields -----

private int _speakerId;

private string _firstName;

private string _lastName;

private Boolean _isNew;

private DateTime _firstSessionDate;

private int _age;

private string _summary;

private string _summary2;

//----- events -----

public event PropertyChangedEventHandler PropertyChanged;

//----- properties -----

public int SpeakerId

{

get { return _speakerId; }

set

{

_speakerId = value;

NotifyPropertyChanged("SpeakerId");

Summary = ToString1();

}

}

public string FirstName

{

get { return _firstName; }

set

{

_firstName = value;

NotifyPropertyChanged("FirstName");

Summary = ToString1();

}

}

public string LastName

{

get { return _lastName; }

set

{

_lastName = value;

NotifyPropertyChanged("LastName");

Summary = ToString1();

//--- throws an add error when 3rd of three !? ---

//--- see other comment line 86 ish ---

}

}

public string Summary

{

get { return _summary; }

set { _summary = value; NotifyPropertyChanged("Summary"); }

}

public string Summary2

{

get { return _summary2; }

set { _summary2 = value; NotifyPropertyChanged("Summary2"); }

}

public Boolean IsNew

{

get { return _isNew; }

set { _isNew = value; NotifyPropertyChanged("IsNew"); Summary2 = ToString2(); }

}

public DateTime FirstSessionDate

{

get { return _firstSessionDate; }

set { _firstSessionDate = value; NotifyPropertyChanged("FirstSessionDate"); Summary2 = ToString2(); }

}

public int Age

{

get { return _age; }

set { _age = value; NotifyPropertyChanged("Age");        Summary2 = ToString2();        }

}

//-------------------------------------------------

//----- constructors -----

public Speaker()

{

//--- if use Property assignments we get an odd error ---

    //----- why, why, why !? -----

this._summary = "";

this._summary2 = "";

this._lastName = "";

this._firstName = "";

this._isNew = false;

this._age = 1;

this._speakerId = -1;

this._firstSessionDate = new DateTime(1900, 01, 01);

}

//----- methods -----

public void NotifyPropertyChanged(string propertyName)

{

if (PropertyChanged != null)

{

PropertyChanged(this, new PropertyChangedEventArgs(propertyName));

}

}

override public string ToString()

{

if (SpeakerId > 0)

{ return SpeakerId.ToString() + " >> " + FirstName.Trim() + " " + LastName.Trim(); }

else

{ return " >> " + FirstName.Trim() + " " + LastName.Trim(); }

}

public string ToString1()

{

if (SpeakerId > 0)

{ return SpeakerId.ToString() + " >> " + FirstName.Trim() + " " + LastName.Trim(); }

else

{ return " >> " + FirstName.Trim() + " " + LastName.Trim(); }

}

public string ToString2()

{

return Age.ToString() + " >> " + IsNew.ToString() + " " + FirstSessionDate.ToLongDateString();

}

}

//---------------------------------------------------------

}

==================== CLASSES Validation ==============================================

using System;

using System.Globalization;

using System.Text.RegularExpressions;

using System.Windows.Controls;

namespace MyClasses

{

//---------------------------------------------------------

public class AgeRangeRule : ValidationRule

{

private int _min;

private int _max;

public AgeRangeRule() { }

public int Min

{

get { return _min; }

set { _min = value; }

}

public int Max

{

get { return _max; }

set { _max = value; }

}

public override ValidationResult Validate(object value, CultureInfo cultureInfo)

{

int age = 0;

try

{

if (((string)value).Length > 0)

age = Int32.Parse((String)value);

}

catch (Exception e)

{

return new ValidationResult(false, "Illegal characters or " + e.Message);

}

if ((age < Min) || (age > Max))

{

return new ValidationResult(false,

  "Please enter an age in the range: " + Min + " - " + Max + ".");

}

else

{

return new ValidationResult(true, null);

}

}

}

//==========================================================================================

public class LettersOnlyStringRule : ValidationRule

{

public LettersOnlyStringRule()

{

}

public override ValidationResult Validate(object value, CultureInfo cultureInfo)

{

string General = "";

try

{

if (((string)value).Length > 0)

{

General = value.ToString();

}

}

catch (Exception e)

{

return new ValidationResult(false, "Something wrong or " + e.Message);

}

/* Verifying that the General string contains only letters */

if (!Regex.IsMatch(General, "^[a-zA-Z]+$" ))

{

return new ValidationResult(false, "ONLY letters! (a-z, A-Z).");

}

else

{

return new ValidationResult(true, null);

}

}

}

public class UpperCaseOnlyStringRule : ValidationRule

{

public UpperCaseOnlyStringRule()

{

}

public override ValidationResult Validate(object value, CultureInfo cultureInfo)

{

string General = "";

try

{

if (((string)value).Length > 0)

{

General = value.ToString();

}

}

catch (Exception e)

{

return new ValidationResult(false, "Something wrong or " + e.Message);

}

/* Verifying that the General string contains only Upper Case letters */

if (!Regex.IsMatch(General, "^[A-Z]+$"))

{

return new ValidationResult(false, "ONLY Upper Case! (A-Z).");

}

else

{

return new ValidationResult(true, null);

}

}

}

public class LowerCaseOnlyStringRule : ValidationRule

{

public LowerCaseOnlyStringRule()

{

}

public override ValidationResult Validate(object value, CultureInfo cultureInfo)

{

string General = "";

try

{

if (((string)value).Length > 0)

{

General = value.ToString();

}

}

catch (Exception e)

{

return new ValidationResult(false, "Something wrong or " + e.Message);

}

/* Verifying that the General string contains only Lower Case letters */

if (!Regex.IsMatch(General, "^[a-z]+$" ))

{

return new ValidationResult(false, "ONLY Lower Case! (a-z).");

}

else

{

return new ValidationResult(true, null);

}

}

}

public class EMailStringRule : ValidationRule

{

public EMailStringRule()

{

}

public override ValidationResult Validate(object value, CultureInfo cultureInfo)

{

string General = "";

try

{

if (((string)value).Length > 0)

{

General = value.ToString();

}

}

catch (Exception e)

{

return new ValidationResult(false, "Something wrong or " + e.Message);

}

/* Verifying that the General string contains only EMail characters */

//----- not yet right - causes errors !? -----

if (!Regex.IsMatch(General, "(\\W+\\.)*\\W+@(\\W+\\.)+[A-Za-Z]+" ))

{

return new ValidationResult(false, "eMail structure ONLY !");

}

else

{

return new ValidationResult(true, null);

}

}

}

public class UKpostCodeStringRule : ValidationRule

{

public UKpostCodeStringRule()

{

}

public override ValidationResult Validate(object value, CultureInfo cultureInfo)

{

string General = "";

try

{

if (((string)value).Length > 0)

{

General = value.ToString();

}

}

catch (Exception e)

{

return new ValidationResult(false, "Something wrong or " + e.Message);

}

//----- 'OR' stands for 'Once Removed' - meaning PHH sourced it from elsewhere -----

//----- here is Ben Forta's [poor] effort -----

//----- [A-Z]{1,2}\d[A-Z\d]? \d[ABD-HJLNP-UW-Z]{2} -----

//----- cText := e"[A-Z]{1,2}\\d[A-Z\\d]? \\d[ABD-HJLNP-UW-Z]{2}" -----

//----- cText = "[A-Z]{1,2}\\d[A-Z\\d]? \\d[ABD-HJLNP-UW-Z]{2}";

/* Verifying that the General string contains a valid Post Code. */

string cText = "";

cText = "\\b([A-PR-UWYZ][A-HK-Y]?[0-9]?[0-9]? {1,2}[0-9][ABD-HJLN-UW-Z]{2}|GIR 0AA)\\b";

if (!Regex.IsMatch(General, cText))

{

return new ValidationResult(false, "Not a valid Post Code!");

}

else

{

return new ValidationResult(true, null);

}

}

}

//==========================================================================================

public class GeneralStringRule : ValidationRule

{

private int _min;

private int _max;

private Boolean _allowEmpty;

private Boolean _allowSpaces;

public GeneralStringRule()

{

Min = 0;

Max = 1000;

}

//----- properties -----

public int Min

{ get { return _min; } set { _min = value; } }

public int Max

{ get { return _max; } set { _max = value; } }

public Boolean AllowEmpty

{

get { return _allowEmpty; }

set { _allowEmpty = value; }

}

public Boolean AllowSpaces

{

get { return _allowSpaces; }

set { _allowSpaces = value; }

}

public override ValidationResult Validate(object value, CultureInfo cultureInfo)

{

string General = "";

try

{

if (((string)value).Length > 0)

{

General = value.ToString();

}

else

{

if (((string)value).Length == 0 && !this.AllowEmpty)

{

return new ValidationResult(false, "String can't be empty : ");

//return new ValidationResult(true, null);

}

else

{

General = value.ToString();

}

}

}

catch (Exception e)

{

return new ValidationResult(false, "Something wrong or " + e.Message);

}

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

if (!this.AllowSpaces && General.Contains(" "))

{

return new ValidationResult(false, "String contains space characters : ");

}

if ((General.Length < Min) || (General.Length > Max))

{

return new ValidationResult(false, "String length NOT in the range: " + Min + " - " + Max + ".");

}

else

{

return new ValidationResult(true, null);

}

}

}

//==========================================================================================

public class StringToIntValidationRule : ValidationRule

{

public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)

{

int i;

if (int.TryParse(value.ToString(), out i))

return new ValidationResult(true, null);

return new ValidationResult(false, "Please enter for me a valid integer value.");

}

}

//---------------------------------------------------------

}

===================== VIEWMODEL =======================================================

using System;

using System.ComponentModel;   //----- INotifyPropertyChanged .....

using MyClasses;

namespace MVVM_ValidationCologne2016.Views

{

//===============================================================================

public class SpeakerViewModel : INotifyPropertyChanged

{

//----- properties -----

private Speaker _inSpeaker;

private int _age;

private string _postCode;

public int Age

{

get { return _age; }

set

{

_age = value;

NotifyPropertyChanged("Age");

}

}

public string PostCode

{

get { return _postCode; }

set

{

_postCode = value;

NotifyPropertyChanged("PostCode");

}

}

public Speaker InSpeaker

{

get { return _inSpeaker; }

set

{

_inSpeaker = value;

NotifyPropertyChanged("InSpeaker");

}

}

//----- events -----

public event PropertyChangedEventHandler PropertyChanged;

//----- constructors -----

public SpeakerViewModel()

{

InSpeaker = new Speaker();

InSpeaker.LastName = "";

InSpeaker.FirstName = "";

InSpeaker.IsNew = false;

InSpeaker.Age = 1;

InSpeaker.SpeakerId = -1;

InSpeaker.FirstSessionDate = new DateTime(1900, 01, 01);

InSpeaker.Summary = InSpeaker.ToString1();

InSpeaker.Summary2 = InSpeaker.ToString2();

}

public SpeakerViewModel(Speaker oSpk)

{

InSpeaker = oSpk;

InSpeaker.Age = 30;

/* Set default age */

}

//----- methods -----

public void NotifyPropertyChanged(string propertyName)

{

if (PropertyChanged != null)

{

PropertyChanged(this, new PropertyChangedEventArgs(propertyName));

}

}

}

}

================== VIEW ===============================================================

<Window x:Class="MVVM_ValidationCologne2016.Views.SpeakerView"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

        xmlns:local="clr-namespace:MVVM_ValidationCologne2016.Views"

        xmlns:local2="clr-namespace:MyClasses"

        mc:Ignorable="d"

        Title="Validation - Cologne two :" Height="350" Width="540" Background="MintCream">

    <Grid Name="myGrid">

        <Grid.Resources>

            <ControlTemplate x:Key="MyCTtry">

                <StackPanel>

                    <!-- Placeholder for the TextBox itself -->

                    <AdornedElementPlaceholder x:Name="textBox"/>

                    <TextBlock Text="{Binding [0].ErrorContent}" Foreground="Red"/>

                </StackPanel>

            </ControlTemplate>

            <!--==========================================================-->

            <Style TargetType="TextBox">

                <Setter Property="HorizontalAlignment" Value="Left" />

                <Setter Property="VerticalAlignment" Value="Top" />

                <Setter Property="Height" Value="24" />

                <Setter Property="Width" Value="160" />

                <Setter Property="Foreground" Value="DarkBlue" />

                <Setter Property="Background" Value="Bisque" />

                <Setter Property="TextWrapping" Value="Wrap" />

                <Style.Triggers>

                    <Trigger Property="Validation.HasError" Value="true">

                        <Setter Property="ToolTip"

                        Value="{Binding RelativeSource={x:Static RelativeSource.Self},

                        Path=(Validation.Errors)[0].ErrorContent}"/>

                    </Trigger>

                </Style.Triggers>

            </Style>

            <!--==========================================================-->

            <Style TargetType="Label">

                <Setter Property="HorizontalAlignment" Value="Left" />

                <Setter Property="VerticalAlignment" Value="Top" />

            </Style>

            <!--==========================================================-->

            <Style TargetType="TextBlock">

                <Setter Property="HorizontalAlignment" Value="Left" />

                <Setter Property="VerticalAlignment" Value="Top" />

                <Setter Property="Height" Value="24" />

                <Setter Property="Width" Value="193" />

                <Setter Property="Background" Value="Coral" />

            </Style>

        </Grid.Resources>

        <!--==========================================================-->

        <TextBox Name="myID" Margin="130,108,2,137"

                    Text="{Binding InSpeaker.SpeakerId, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"

                    Validation.ErrorTemplate="{StaticResource ResourceKey=MyCTtry}"/>

        <TextBox Name="myAge" Margin="130,166,2,79"

                    Text="{Binding InSpeaker.Age, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"

            Validation.ErrorTemplate="{StaticResource ResourceKey=MyCTtry}"   />

        <TextBox Name="myIsNew" Margin="130,206,2,39"

                    Text="{Binding InSpeaker.IsNew, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"

                    Validation.ErrorTemplate="{StaticResource ResourceKey=MyCTtry}"/>

        <TextBox Name="mySessionDate" Margin="130,246,2,-1"

                    Text="{Binding InSpeaker.FirstSessionDate, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"

                    Validation.ErrorTemplate="{StaticResource ResourceKey=MyCTtry}"/>

        <TextBlock Margin="302,108,-203,137" Text="{Binding InSpeaker.Summary}" />

        <TextBlock Margin="302,246,-203,-1" Text="{Binding InSpeaker.Summary2}" />

        <Label Content="Firstname :" Margin="10,25,215,218" />

        <Label Content="Lastname :"  Margin="10,66,216,177" />

        <Label Content="SpeakerId :" Margin="10,108,214,135" />

        <Label Content="Age :" Margin="10,164,245,79" />

        <Label Content="IsNew :" Margin="10,204,234,39" />

        <Label Content="FirstSessionDate :" Margin="10,246,180,-3" />

        <Label Content="Summary :" Margin="302,78,-76,165" />

        <Label Content="Summary2 :" Margin="302,218,-82,25" />

        <Label Content="PostCode - UK :" HorizontalAlignment="Left" Margin="302,0,0,0" VerticalAlignment="Top"/>

        <!--==========================================================-->

        <TextBox Name="myPostCode" Margin="302,25,0,0" Width="193"

                 Validation.ErrorTemplate="{StaticResource ResourceKey=MyCTtry}" >

            <TextBox.Text>

                <Binding Path="PostCode" UpdateSourceTrigger="PropertyChanged" >

                    <Binding.ValidationRules>

                        <local2:UKpostCodeStringRule />

                    </Binding.ValidationRules>

                </Binding>

            </TextBox.Text>

        </TextBox>

        <TextBox Name="myFirst" Margin="130,25,2,220"

                 Validation.ErrorTemplate="{StaticResource ResourceKey=MyCTtry}" >

            <TextBox.Text>

                <Binding Path="InSpeaker.FirstName" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay">

                    <Binding.ValidationRules>

                        <local2:GeneralStringRule Min="2" Max="23" AllowEmpty="True" AllowSpaces="False"/>

                    </Binding.ValidationRules>

                </Binding>

            </TextBox.Text>

        </TextBox>

        <TextBox Name="myLast" Margin="130,66,2,179"

                 Validation.ErrorTemplate="{StaticResource ResourceKey=MyCTtry}" >

            <TextBox.Text>

                <Binding Path="InSpeaker.LastName"

                         UpdateSourceTrigger="PropertyChanged"

                         Mode="TwoWay">

                    <Binding.ValidationRules>

                        <local2:LettersOnlyStringRule />

                        <!-- Use for testing other VRS -->

                        <!--<local2:UKpostCodeStringRule />-->

                        <!--<local2:GeneralStringRule Min="2" Max="23" AllowEmpty="False" AllowSpaces="False"/>-->

                        <!--<local2:UpperCaseOnlyStringRule />-->

                        <!--<local2:LowerCaseOnlyStringRule />-->

                        <!--<local2:EMailStringRule />-->

                    </Binding.ValidationRules>

                </Binding>

            </TextBox.Text>

        </TextBox>

        <!--==========================================================-->

        <Button x:Name="ValidCheck" Content="Form Check - Valid" HorizontalAlignment="Left"

                Margin="375,190,0,0" VerticalAlignment="Top" Width="120" Background="LightGreen"

                Click="ValidCheck_Click"/>

        <!--==========================================================-->

    </Grid>

</Window>

==================== VIEW code behind ========================================================

using System.Windows;

using System.Collections;

using System.Windows.Controls;

namespace MVVM_ValidationCologne2016.Views

{

public partial class SpeakerView : Window

{

public SpeakerView()

{

InitializeComponent();

}

//=======================================================================

public void DoCheckValidGen()   //--- from Vulcan sample ---

{

IEnumerable oSCIN;

oSCIN = LogicalTreeHelper.GetChildren((FrameworkElement)this.myGrid);

IEnumerator oIE = oSCIN.GetEnumerator();

string cText = "";

string cLine = "";

string cType = "";

string cInValid = "";

int count = 0;

while (oIE.MoveNext())

{

cLine = oIE.Current.ToString();

if (cLine.Contains(".TextBox:"))

{

cType = oIE.GetType().ToString();

TextBox oTB = (TextBox)oIE.Current;

if(Validation.GetHasError(oTB))

{

cInValid = Validation.GetHasError(oTB).ToString();

cText += oTB.Name + "[" + cInValid + "]" + " " + "\n";

count += 1;

}

}

}

if (count > 0)

            {

MessageBox.Show(cText, "Check Invalid :");

MessageBox.Show("It seems that input data needs editing before the form is closed!","CAUTION - Errors? :");

}

else

{

MessageBox.Show("It would appear that all data values are valid ones.","SAFE to Continue! :");

}

}

private void ValidCheck_Click(object sender, RoutedEventArgs e)

{

DoCheckValidGen();

}

}

}

=========================================================================================

========================== MAINWINDOW ===================================================

<Window x:Class="MVVM_ValidationCologne2016.MainWindow"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

        xmlns:local="clr-namespace:MVVM_ValidationCologne2016"

        mc:Ignorable="d"

        Title="MainWindow - WPF Validation :" Height="260" Width="400" Background="Bisque">

    <Grid>

        <Button x:Name="button" Content="Speaker" HorizontalAlignment="Left"

                Margin="20,16,0,0" VerticalAlignment="Top" Width="75"

                Background="Yellow" Click="Speaker_button_Click" />

        <Button x:Name="button1" Content="Session" HorizontalAlignment="Left"

                Margin="20,60,0,0" VerticalAlignment="Top" Width="75"

                Background="BurlyWood" Click="Session_button_Click"/>

        <TextBlock HorizontalAlignment="Left" Margin="109,16,0,0"

                   TextWrapping="Wrap"  Padding="5"

                   Text="Simple sample of validating data input from two view screens, Speakers and Sessions."

                   FontSize="16" Foreground="Purple" Background="MintCream"

                   VerticalAlignment="Top" Height="138" Width="173"

                   x:Name="myCommTBLK"/>

    </Grid>

</Window>

<!-- <Button Content="Command" HorizontalAlignment="Left" Margin="207,139,0,0"

                VerticalAlignment="Top" Width="75"

                Command="{Binding MyTestCommand}"/> -->

======================= MAINWINDOW ====================================================

using System;

using System.Windows;

using MVVM_ValidationCologne2016.Views;

using MyClasses;

using System.Windows.Input;

namespace MVVM_ValidationCologne2016

{

public partial class MainWindow : Window

{

public MainWindow()

{

InitializeComponent();

}

private void Speaker_button_Click(object sender, RoutedEventArgs e)

{

try

{

Speaker TestSpeaker = new MyClasses.Speaker();

TestSpeaker.FirstName = "Richard";

TestSpeaker.LastName = "Coatman";

TestSpeaker.SpeakerId = 666;

TestSpeaker.IsNew = false;

TestSpeaker.Age = 75;

TestSpeaker.FirstSessionDate = new DateTime(2011, 10, 22);

TestSpeaker.Summary = TestSpeaker.ToString1();

TestSpeaker.Summary2 = TestSpeaker.ToString2();

SpeakerViewModel mySpeakerVM = new SpeakerViewModel(TestSpeaker);

SpeakerView mySpeaker = new SpeakerView();

mySpeaker.DataContext = mySpeakerVM;

mySpeaker.ShowDialog();

}

catch (SystemException oEX)

{

MessageBox.Show(oEX.ToString(),"My Exception :");

}

}

private void Session_button_Click(object sender, RoutedEventArgs e)

{

SessionView mySession = new SessionView();

mySession.ShowDialog();

}

}

}

=========================================================================================

<Application x:Class="MVVM_ValidationCologne2016.App"

             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

             xmlns:local="clr-namespace:MVVM_ValidationCologne2016"

             StartupUri="MainWindow.xaml">

    <Application.Resources>

    </Application.Resources>

</Application>

=========================================================================================

using System;

using System.Collections.Generic;

using System.Configuration;

using System.Data;

using System.Linq;

using System.Threading.Tasks;

using System.Windows;

namespace MVVM_ValidationCologne2016

{

/// <summary>

/// Interaction logic for App.xaml

/// </summary>

public partial class App : Application

{

}

}

=========================================================================================

PHH - 8th. February 2016.


Creating a 'ValidationRule'

Creating a 'ValidationRule'

Creating a 'ValidationRule'

 Previous topic Next topic

*** Remember *** it is easy to make and define validation rules.

VRs can be created and used in different ways, simply because of 'history' - new ways came along

for new and different reasons. I like some of the older ways of doing things - BUT - we must not

allow ourselves to get confused by try to use code and script from other guy's samples, when in

fact we are mixing up two or three different approaches at once ;-0(

========================================================================

Okay guys, lets start simple and then work up to greater things.

We can start with the sample which comes in the MSDN web help eNotes.

Here is an image of the VR (validation rule - ValidationRule) in action :-

========================================================================

Just to be clear about things, it is the right hand side (RHS) textbox which we are

looking at and dealing with - the cursor is hovering over the UI element.

Here below, we try entering a text string in place of integer digits :-

============================================================

And of course the tooltip detail is still available, as we have the appropriate XAML script in place :-

============================================================================================

If we wish we could turn off the Tooltip display, or the red text which is overlaid - or indeed both, and

revert back to the red rectangular outline for invalid entries. The choice is ours.

And of course we also have an error thrown when we have a blank entry, as seen below :-

===============================================

I suppose we could set-up our code so as to allow 'empty' of 'blank' input values and have our screen

input code return a 'null'.

I think I prefer a value, and my way is often to supply a ' -1 ' value for yet unspecified numeric values.

Sometimes we can enter a decimal point and throw an error, but not always, it depends on what forms of the

supporting code, script, and validation classes we are using.

Here is what we get on testing with a point :-

===========================================================

The above is all very good, easy to understand and also easy enough to change slightly to suit our

business logic needs.

So what does the .NET code look like which causes all this to happen ? And what to we need to add

to the XAML script to apply our new validation rule ??

A close look at the 'ValidationRule' (VR) details :

Check out carefully all of the following code sections :-

===============================================================================================================

Okay folks, the above image shows code which will be useful to many cases of integer data input.

Because it has a limit set for Max and Min we can adjust its application / flexibility at time of XAML scripting.

Here is what the XAML script looks like in my solution :-

================================================================================================

In fact I have just tried removing certain script lines to find out if the code/script still works.

Here is an improvement with the same performance :-

================================================================================================

Be careful in removing certain script attributes as they may be needed at other times, in case where

you have not tested in your quick UI input tests.

But pushing this a bit further we have the following :-

===============================================================================================

One thing I do encourage you to do, and that is thorough testing of your code/script at the form input level.

Once you get used to what is required then you can become a LOT faster in your design and creation.

Lets move on ..............

Please note that the use of Min and Max makes this VR very flexible. And by setting Min to a value of

0 or 1 we can control whether we allow zero, or only positive values.

Check out the following changes to see how easy it is to extend the use of our VR :-

============================================================================================

Just be careful that you don't get Min and Max the wrong way around, value wise!

Summary so far :

We seem to have a single VR (validation rule) which can be applied to many different UI elements

which have their Text attribute bound to an integer public property.

We can apply the lower and upper limits at the point of use within the XAML script, the VR code

itself remains fixed, the same.

The .NET code is in the ViewModel file and NOT the code behind - which is the MVVM pattern

way of doing things.

All VERY handy indeed ;-0)

Okay then folks, I do admit that we may have to work on the XAML syntax a bit, so that we can

reduce the size of the text typed, and make it look simpler - but first things first, lets get it to

work satisfactorily before we 'tart things up' ;-0)

Your homework task is for you to write a VR for the Type 'double', so that we can have a decimal

value indicated with the usual decimal point.

Moving on - Making a 'VR' for Text Strings :

The issues, errors, and problems related to string type data input are a bit different to those of integers.

However, we can handle them in a very similar manner to the way we coded the VR for Integers.

Here below is an image of a general VR for a string Type, later we can change and improve it :-

[ .. for now ignore the collapsed lines 83 - 88 as if they don't exist .. ]

=========================================================================================================

=========================================================================================================================

============================================================

=====================================================

=====================================================

==========================================================

====================================================

========================================================================================

======================================================

What about if we wish to be able to leave a blank or empty string field entry ?

=======================================================================================================

=============================================================================================================

======================================================

==========================================================================================================================

=========================================================================================================

PHH - 28th. January 2016.


MSDN details and reference material

MSDN details and reference material

MSDN details and reference material

 Previous topic Next topic

================================================================================

===========================================================

PHH - 28th. January 2016.


working code and script

working code and script

working code and script

 Previous topic Next topic

PHH - 28th. January 2016.


Applying many 'ValidationRules'

Applying many 'ValidationRules'

Applying many 'ValidationRules'

 Previous topic Next topic

PHH - 28th. January 2016.


Applying VRs to collection item public properties

Applying VRs to collection item public properties

Applying VRs to collection item public properties

 Previous topic Next topic

PHH - 28th. January 2016.


Throwing exceptions

Throwing exceptions

Throwing exceptions

 Previous topic Next topic

PHH - 27th. January 2016.


Checking a whole form for any invalid entries

Checking a whole form for any invalid entries

Checking a whole form for any invalid entries

 Previous topic Next topic

PHH - 28th. January 2016.


Data error information

Data error information

Data error information

 Previous topic Next topic

PHH - 27th. January 2016.


at a whole Form / View level

at a whole Form / View level

at a whole Form / View level

 Previous topic Next topic

For most of us developer guys, we write new apps where we would NOT wish a View, or screen

form, to close and disappear without all data entries and edits being totally valid.

We would wish to move on in the app, to the new View ONLY when there are no errors in the

UI element data values. Data errors of all sorts must be gotten rid of.

Here is an earlier example of a View (form) with four outstanding data items :-

========================================================================================

Ideally we need a 'Close' button (or similar) which is only active (or available for use) when all

the errors are put to rights.

Since the View and ViewModel go 'hand in hand', in a one to one pairing, then we need a

ViewModel facility which does this sort of checking for the whole form / view.

We now need to move on from our earlier 'simple' look at data binding and validation of the public

properties bound, to a binding of a ViewModel to a View - as it should be when MVVM is applied

in the correct manner.

PHH - 27th. January 2016.


Example one

Example one

Example one

 Previous topic Next topic

PHH - 27th. January 2016.


Interfaces available

Interfaces available

Interfaces available

 Previous topic Next topic

PHH - 27th. January 2016.


ExceptionValidationRule

ExceptionValidationRule

ExceptionValidationRule

 Previous topic Next topic

Current Situation - Short Summary :

Well guys, before we get into full details of how to make some of the available Interfaces work for

us and our applications, as far as Validation is concerned, we need to know that some of these that I have explored

and experimented with, don't seem to work with the proper way we ought to provide our (data) Models to

ViewModel.

At the moment I can only get the contents of this sub-folder to work with my ViewModel if I make each

property of my complex business object a public property of the VM itself, and do assignments on instantiation.

I am not happy with this situation, and I can't believe that it is a real problem / issue, there must be a way, surely !?

It is just that samples in good articles from the web, as well as colleague Nick's MVVM example, do things the simple

way, not the true data binding way.

What makes matters worse (for me) is that the simple approach covered earlier does work well with business complex

data objects, as it is the TextBox which has 'stuff' attached for validation, and its not done in code in the VM as the

other interface stuff does.

For completeness I will show you what I have found out about throwing exceptions and also the next sub-section

or two when we see "...DataError..." related interfaces in action.

However, we do have at least one way (the earlier one) which will do all we need, so all is not lost.

Lets get going with this new topic ...........

============================================================================================

Let us look at some working functionality.

Below is an image which shows public properties displayed for a 'Registration' business object.

A Registration object is created and populated just before being passed as a parameter into the ViewModel.

However, be aware that the data values displayed are in fact from the public properties of the VM and not the passed parameter :-

================================================================================================================================================================

Since we have a very simple set-up at the moment, lets see if any of the TextBox inputs

can trigger an invalid response, and hence the default red rectangular 'alarm' display.

We will use a numeric field as the string ones are virtually impossible to trigger before we

add our own VR conditions :-

================================================

Yep! - above we see that we can't have text string in the numeric input box.

And below we have a test for a Boolean input to a TextBox, even though we will most

probably use a 'CheckBox' in real apps we construct.

===============================================

And the look of the first three text input boxes after emptying and trying various things

to try and force an invalid alarm display - no joy as can be seen :-

================================================

Now then guys. We would like a bit more than just a red box display.

How about a Tooltip as we have in the earlier section / chapter ?

Check this out below :-

===================================================

It works but a did find some slight 'sticky' inconsistency - see below :-

[ .. even when the 't' was deleted I still got this message .. ]

==========================================================

After a bit of a fiddle around it did clear to this below :-

======================================================

So then folks, lets remind ourselves what is required in the XAML script to allow the Tooltip to be displayed.

Checkout the highlighted detail of the Resources 'Style' for any TextBox in the 'Grid' :-

=================================================================================================

The above script is just as in the earlier Sample we studied.

Remember that the Style definition goes in the Resources section of the 'Grid'.

So now lets see the script for the two Boolean and one Double input box, where the default VR seems to work just fine / okay :-

============================================================================================================================================

Lets move on a step or two ..............

See in the following image that we have a different and 'tighter' VR being applied :-

============================================================

Yes, this time we have 'Max / Min' limits.

Here is how we apply the exception rule in our script :-

[ .. line 47 is the crucial (important) one .. ]

=============================================================================================================

In the above image I have left lines 53 - 57 in this different format, to show you how a standard TextBox looks when

the same syntax is applied.

Now we need to look at the code itself - the code in the 'ViewModel' which throws the exception and provides also

a suitable invalid explanation / comment :-

==============================================================================================================

Notice in the above image that it is the Public property of the VM which is being tested.

Here is some other code which supports the property and its data value 'transfer' after parameter input.

===============================================================================================

And now the actual 'value' transfer itself :-

===========================================================================================

It may be more obvious to you if we rearrange the assignment lines as follows :-

=========================================================================================

Notice in lines 55 - 62 where the properties of the complex business object provide their values

as assignments to the public properties of the VM - note this is NOT my style and I think it is

silly - BUT - it works at a Form level.

Lets now move on to see a string input with a new validation constraint, check below :-

=============================================================

Yes, we can't be empty and we can't be too long either.

Here is the 'long' test below :-

==================================================

And here now is the extra script in lines 39 - 41 that are required :-

============================================================================================================

And the code lines required to test the specified conditions are as below :-

=============================================================================================================

Okay then guys, having done these validation checks we ought to be able to create more of our own,

as well as applying RegExe constraints and checking too.

In the next sub-section we will continue with this same project of this app, and look at the validation

'OnDataErrors' way of doing things - YES - we can mix the two approaches in the same View and VM.

However, before we do that we need to look at the 'child' section from here, where we see how to use the

Form push button called 'Do Invalid Check' to run a Relay Command that sees if there are any TextBoxes

on the Form Grid which have their 'IsValid' attribute set to False.

Guys trying to get to grips with MVVM should find this most helpful - I know I did ;-0)

See you in the child part .................

PHH - 9th & 10th. February 2016.


Button : Form 'Invalid' test - RelayCommand

Button : Form 'Invalid' test - RelayCommand

Button : Form 'Invalid' test - RelayCommand

 Previous topic Next topic

Okay guys, lets add a new button to our Registration View form, and not use a click event.

We will place all the coding stuff required in the ViewModel code as good MVVM pattern practice

suggests we should.

The only small dodgy thing we will allow ourselves is to Name the Grid object in the XAML markup, and

then use this in the code of the VM.

Lets look at the code behind to see that at least we have ticked the box for NOT adding code there :-

=======================================================================

Now we need to see the Project in action - here is a useful view of things happening :-

====================================================================================================

The above image shows that it is early stages of adding the functionality of a test validation Button - where

the whole Form will be tested for IsValid condition of all TextBoxes.

At this point (seen above) we have the RelayCommand code in place and the button click causes a response.

Also at this point of testing the body of the action method is rather slim, or non-existent - see below :-

==========================================================================================================

You will notice above that we are using a FrameworkElement object from the View, we are actually using the

'Grid' from the View.

Lets update this action method slightly and show this more clearly - check this out :-

=========================================================================================================================

And here is the XAML related proof :-

===========================================================================================

The way we pass the 'FrameworkElement' to the 'ViewModel' is easy and shown in detail below :-

[ .. line 56 through 59 in particular .. ]

=============================================================================================================================

Code line 61 is where we loosely couple the 'ViewModel' to the View, having previously made bindings in the XAML script.

Moving on a step or two ...............

Lets see what we get once we have added the code to find and test all the TextBox elements.

Note we are just using the same code as we did previously, for a Form-wide test for Invalid TextBoxes.

Have a look at this :-

=====================================================================================

Well, it appears to be right, as there are two items on the Form and also in the list.

However, because we have not Named our XAML tagged elements, they can't be displayed in this simple listing.

But then does it matter ? We have on-screen visual clues and we will not display this list in the final button

action method operation.

The following is what we will see - or something like it :-

===============================================================================================

We had better test to see what we get when there are no indicated validation errors - see below :-

=========================================================================================

Yes, just as we had hoped, and most likely expected ;-0)

And here is one more :-

===================================================================================================================================

Lets move on ........................

Adding the Code and Script for the 'action method' to be called upon :

Okay then folks, lets start at the XAML script end of things for using a button with 'RelayCommand' to

action our checking method to see if all TextBoxes contain valid entries (or data).

Remember that we are to use no click event or an event handler as such.

We need to point our 'Command' attribute at the RelayCommand shown on line 85 below :-

==========================================================================================================

Line 85 is the only part of the View which links back to the ViewModel and its members - methods, properties and events.

To prove it look back at the top of this sub-section to see the code behind - hardly any at all.

In the next image I have gather together all of the relevant members added to the ViewModel to action the new button.

Check this carefully :-

===================================================================================

We also need the next image to see where and how the 'RelayCommand' is created - check carefully :-

============================================================================================================

This is just part of one of the Constructors for the ViewModel - here is the full overview :-

============================================================================================

So if our XAML script in the Command attribute kicks off the RelayCommand to calling the 'CheckInvalid'

action method, what does that look like ?

Here is that required part of the RC system, it is the 'CheckInvalid' method :-

==========================================================

This then calls my own designed method hence the name "MyCheckInvalid", and this is what takes as a parameter

the FrameworkElement object belonging to the View - study the detail :-

=========================================================================================================

We will see how the FrameworkElement has been passed into our ViewModel a little latter. For now we

can study fully the complete 'MyCheckInvalid' method.

The details of this action method are just the same as they were previously, for sample one, so they

should be largely familiar to you - check this carefully :-

===================================================================================================================================

We tested this method reasonably well in the sample one section of these eNotes.

Lets now see how we pass the 'Grid' FrameworkElement into the ViewModel at the point of launching

the Registration View.

Remember the Main Windows just has regular non-MVVM code and event handling - here is the button click handler :-

================================================================================================================================

Lines 56 through 61 in the above image are those most crucial (important) to the 'pass-through' of the FrameworkElement.

On line 57 I had to know the name of the element in order to code things this way. This is probably the weakness of me

doing things this way, but it is quite acceptable I feel - no business logic in the code behind so Unit Tests can be done okay.

Moving on a pace, lets see some necessary code needed to hold everything together.

=========================================================

And that's just about it !

The full code and script is supplied in the 'working script and code' section of this sub-section, so

go and have a look there.

I will also make available the working Project for guys at the German conference in Cologne in April 2016.

Finally, even though this example runs, it does need to have a lot of little ends tidied up, and extra bits

added to make it "User Proof".

On top of this, we also have some validation done by the 'Data Error' approach covered in the next sub-section.

So I suggest you go there and see another way of doing things for WPF 'Validation'.

Exception throwing and Data Error stuff can happily co-exist in the same WPF Form.

See you in the next sub-section .................

PHH - 10th. February 2016.


IDataErrorInfo

IDataErrorInfo

IDataErrorInfo

 Previous topic Next topic

Okay then folks, yet another Interface to handle Validation and/or data errors.

We will look at what needs to be done to use the 'IDataErroInfo' interface, and how it can detect invalid data

values for us.

Here is the start of our ViewModel :-

============================================================================================================================

Note however that the 'using System.ComponentModel' in our file headings allows us to simplify the code - see below :-

===========================================================================================================

And as soon as we add the new interface to our list on line 9, we have in C# a click option which adds the necessary

stubs for the Interface - here is what I got added :-

=======================================================================

PHH - 27th. January & 9th. February 2016.


INotifyDataErrorInfo

INotifyDataErrorInfo

INotifyDataErrorInfo

 Previous topic Next topic

PHH - 27th. January 2016.


IService

IService

IService

 Previous topic Next topic

PHH - 27th. January 2016.


working script and code

working script and code

working script and code

 Previous topic Next topic

====================================================================================

using System;

using System.Windows;

using MVVM_ValidationCologne2016.Views;

using MyClasses;

using System.Windows.Input;

namespace MVVM_ValidationCologne2016

{

public partial class MainWindow : Window

{

public MainWindow()

{

InitializeComponent();

}

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

private void Speaker_button_Click(object sender, RoutedEventArgs e)

{

try

{

Speaker TestSpeaker = new MyClasses.Speaker();

TestSpeaker.FirstName = "Richard";

TestSpeaker.LastName = "Coatman";

TestSpeaker.SpeakerId = 666;

TestSpeaker.IsNew = false;

TestSpeaker.Age = 75;

TestSpeaker.FirstSessionDate = new DateTime(2011, 10, 22);

TestSpeaker.Summary = TestSpeaker.ToString1();

TestSpeaker.Summary2 = TestSpeaker.ToString2();

SpeakerViewModel mySpeakerVM = new SpeakerViewModel(TestSpeaker);

SpeakerView mySpeaker = new SpeakerView();

mySpeaker.DataContext = mySpeakerVM;

mySpeaker.ShowDialog();

}

catch (SystemException oEX)

{

MessageBox.Show(oEX.ToString(),"My Exception :");

}

}

          //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

private void Registration_button_Click(object sender, RoutedEventArgs e)

{

try

{

Registration oReg = new Registration();

oReg.Firstname = "Jimmy";

oReg.Lastname = "Johnson";

oReg.Country = "Wales";

oReg.Company = "BEST consultancy";

oReg.HasPaid = true;

oReg.IsNewbie = new Nullable<Boolean>();

oReg.RegCost = 123.45;

oReg.IsSpeaker = false;

//-----------------------------------------------------------------------

RegistrationView myRegistration = new RegistrationView();

         FrameworkElement oFE = myRegistration.myGridReg;         //--- care of case sensitivity ---

//----- needed for RelayCommand and searching all TextBoxes -----

RegistrationViewModel myRegistrationVM = new RegistrationViewModel(oReg, oFE);

myRegistration.DataContext = myRegistrationVM;

myRegistration.ShowDialog();

}

catch (SystemException oEX)

{

MessageBox.Show(oEX.ToString(),"My Exception :");

}

}

}

}

====================================================================================

<Window x:Class="MVVM_ValidationCologne2016.MainWindow"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

        xmlns:local="clr-namespace:MVVM_ValidationCologne2016"

        mc:Ignorable="d"

        Title="MainWindow - WPF Validation :" Height="260" Width="400" Background="Bisque">

    <Grid>

        <Button x:Name="button" Content="Speaker" HorizontalAlignment="Left"

                Margin="20,16,0,0" VerticalAlignment="Top" Width="75"

                Background="Yellow" Click="Speaker_button_Click" />

        <Button x:Name="button1" Content="Registration" HorizontalAlignment="Left"

                Margin="20,60,0,0" VerticalAlignment="Top" Width="75"

                Background="BurlyWood" Click="Registration_button_Click"/>

        <TextBlock HorizontalAlignment="Left" Margin="109,16,0,0"

                   TextWrapping="Wrap"  Padding="5"

                   Text="Simple sample of validating data input from two view screens, Speakers and Registrations."

                   FontSize="16" Foreground="Purple" Background="MintCream"

                   VerticalAlignment="Top" Height="138" Width="202"

                   x:Name="myCommTBLK"/>

    </Grid>

</Window>

====================================================================================

using System.Windows;

namespace MVVM_ValidationCologne2016.Views

{

/// <summary>

/// Interaction logic for RegistrationView.xaml

/// </summary>

public partial class RegistrationView : Window

{

public RegistrationView()

{

InitializeComponent();

}

}

}

====================================================================================

<Window x:Class="MVVM_ValidationCologne2016.Views.RegistrationView"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

        xmlns:local="clr-namespace:MVVM_ValidationCologne2016.Views"

        mc:Ignorable="d"

        Title="Registration View - Cologne 2016 :" Height="400" Width="540" Background="LightGray">

    <Grid Name="myGridReg">

        <Grid.Resources>

            <!--==========================================================-->

            <Style TargetType="TextBox">

                <Setter Property="HorizontalAlignment" Value="Left" />

                <Setter Property="VerticalAlignment" Value="Top" />

                <Setter Property="Height" Value="24" />

                <Setter Property="Width" Value="160" />

                <Setter Property="Foreground" Value="DarkBlue" />

                <Setter Property="Background" Value="Bisque" />

                <Setter Property="TextWrapping" Value="Wrap" />

                <Style.Triggers>

                    <Trigger Property="Validation.HasError" Value="true">

                        <Setter Property="ToolTip"

                            Value="{Binding RelativeSource={x:Static RelativeSource.Self},

                            Path=(Validation.Errors)[0].ErrorContent}"/>

                    </Trigger>

                </Style.Triggers>

            </Style>

            <!--==========================================================-->

            <Style TargetType="Label">

                <Setter Property="HorizontalAlignment" Value="Left" />

                <Setter Property="VerticalAlignment" Value="Top" />

            </Style>

            <!--==========================================================-->

        </Grid.Resources>

        <TextBox Margin="104,19,0,0" >

            <TextBox.Text>

                <Binding Path="Firstname" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay">

                    <Binding.ValidationRules>

                        <ExceptionValidationRule />

                    </Binding.ValidationRules>

                </Binding>

            </TextBox.Text>

        </TextBox>

        <TextBox Margin="104,61,0,0" Text="{Binding Path=Lastname,

                    UpdateSourceTrigger=PropertyChanged,

                    Mode=TwoWay, ValidatesOnDataErrors=True}" />

        <TextBox Margin="104,102,0,0" Text="{Binding Path=Country,

                    UpdateSourceTrigger=PropertyChanged,

                    Mode=TwoWay, ValidatesOnDataErrors=True}" />

        <TextBox Margin="104,143,0,0" Text="{Binding Path=Company, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />

        <TextBox Margin="104,186,0,0" Text="{Binding HasPaid, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />

        <TextBox Margin="104,228,0,0" Text="{Binding IsNewbie, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />

        <TextBox Margin="104,269,0,0" >

            <TextBox.Text>

                <Binding Path="RegCost" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">

                    <Binding.ValidationRules>

                        <ExceptionValidationRule />

                    </Binding.ValidationRules>

                </Binding>

            </TextBox.Text>

        </TextBox>

        <TextBox Margin="104,310,0,-15" >

            <TextBox.Text>

                <Binding Path="IsSpeaker" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged" />

            </TextBox.Text>

        </TextBox>

        <Label Content="Firstname : " Margin="16,15,0,0" />

        <Label Content="Lastname : " Margin="16,57,0,0" />

        <Label Content="Country : " Margin="16,100,0,0" />

        <Label Content="Company : " Margin="16,141,0,0" />

        <Label Content="HasPaid : " Margin="16,182,0,0" />

        <Label Content="IsNewbie : " Margin="16,224,0,0" />

        <Label Content="RegCost : " Margin="16,267,0,0" />

        <Label Content="IsSpeaker : " Margin="17,310,0,0" />

        <Button Content="Do Invalid Check" HorizontalAlignment="Left"

                Margin="308,19,0,0" VerticalAlignment="Top" Width="119" Height="24"

                Command="{Binding Path=DoInvalidCheckCommand}"/>

    </Grid>

</Window>

====================================================================================

using System;

using System.ComponentModel;   //----- INotifyPropertyChanged .....

using MyClasses;

using System.Windows;

using System.Windows.Input;

using System.Windows.Controls;

using System.Collections;

namespace MVVM_ValidationCologne2016.Views

{

//===============================================================================

public class RegistrationViewModel : INotifyPropertyChanged, IDataErrorInfo

{

//----- fields -----

private Registration _inRegistration;

private int _registrationId;

private string _firstname;

private string _lastname;

private string _country;

private string _company;

private Boolean _hasPaid;

private Nullable<Boolean> _isNewbie;

private double _regCost;

private Boolean _isSpeaker;

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

private ICommand _doInvalidCheckCommand;

public ICommand DoInvalidCheckCommand

{

get

{

return _doInvalidCheckCommand;

}

set

{

_doInvalidCheckCommand = value;

}

}

private FrameworkElement _oFEVM;

public FrameworkElement oFEVM

{

get

{

return _oFEVM;

}

set

{

_oFEVM = value;

}

}

public void CheckInvalid(object obj)

{

MyCheckInvalid(oFEVM);

}

private string MyCheckInvalid(FrameworkElement oFE)

{

//MessageBox.Show(".. doing the checks .."+oFE.Name.ToString(),"Invalid Check :");

IEnumerable oSCIN;

oSCIN = LogicalTreeHelper.GetChildren(oFE);

IEnumerator oIE = oSCIN.GetEnumerator();

string cText = "";

string cLine = "";

string cType = "";

string cInValid = "";

int count = 0;

while (oIE.MoveNext())

{

cLine = oIE.Current.ToString();

if (cLine.Contains(".TextBox:"))

{

cType = oIE.GetType().ToString();

TextBox oTB = (TextBox)oIE.Current;

if (Validation.GetHasError(oTB))

{

cInValid = Validation.GetHasError(oTB).ToString();

cText += oTB.Name + "[" + cInValid + "]" + " " + "\n";

count += 1;

}

}

}

if (count > 0)

{

MessageBox.Show(cText, "Check Invalid :");

MessageBox.Show("It seems that input data needs editing before the form is closed!", "CAUTION - Errors? :");

}

else

{

MessageBox.Show("It would appear that all data values are valid ones.", "SAFE to Continue! :");

}

return " .. do something .. ";

}

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

//----- events -----

public event PropertyChangedEventHandler PropertyChanged;

//----- constructors -----

public RegistrationViewModel()

{

oFEVM = new FrameworkElement();  //--- empty - unspecified ---

DoInvalidCheckCommand = new RelayCommand(new Action<object>(CheckInvalid));

//------------------------------------------------------------------------

InRegistration = new Registration();

InRegistration.Firstname = "...";

InRegistration.Lastname = "...";

InRegistration.Country = "...";

InRegistration.Company = "...";

InRegistration.HasPaid = false;

InRegistration.IsNewbie = new Nullable<Boolean>();

InRegistration.RegCost = 0.00;

InRegistration.IsSpeaker = false;

}

public RegistrationViewModel(Registration oReg, FrameworkElement oFE)

{

oFEVM = oFE;

DoInvalidCheckCommand = new RelayCommand(new Action<object>(CheckInvalid));

//------------------------------------------------------------------------

InRegistration = oReg;

//---------------------

InRegistration.Firstname = oReg.Firstname;

InRegistration.Lastname = oReg.Lastname;

InRegistration.Country = oReg.Country;

InRegistration.Company = oReg.Company;

InRegistration.HasPaid = oReg.HasPaid;

InRegistration.IsNewbie = oReg.IsNewbie;

InRegistration.RegCost = oReg.RegCost;

InRegistration.IsSpeaker = oReg.IsSpeaker;

//----------------------------------------

Firstname = oReg.Firstname;

Lastname = oReg.Lastname;

Country = oReg.Country;

Company = oReg.Company;

HasPaid = oReg.HasPaid;

IsNewbie = oReg.IsNewbie;

RegCost = oReg.RegCost;

IsSpeaker = oReg.IsSpeaker;

}

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

//----- properties -----

public Registration InRegistration

{

get { return _inRegistration; }

set        { _inRegistration = value; NotifyPropertyChanged("InRegistration");        }

}

public int RegistrationId

{

get { return _registrationId; }

set { _registrationId = value; NotifyPropertyChanged("RegistrationId"); }

}

public string Firstname

{

get { return _firstname; }

set {

if ( ((string)value).Length < 2 || ((string)value).Length > 19)

{

throw new ArgumentException("number of characters between 2 and 19");

}

_firstname = value; NotifyPropertyChanged("Firstname"); }

}

public string Lastname

{

get { return _lastname; }

set { _lastname = value; NotifyPropertyChanged("Lastname"); }

}

public string Company

{

get { return _company; }

set { _company = value; NotifyPropertyChanged("Company"); }

}

public string Country

{

get { return _country; }

set { _country = value; NotifyPropertyChanged("Country"); }

}

public Boolean HasPaid

{

get { return _hasPaid; }

set { _hasPaid = value; NotifyPropertyChanged("HasPaid"); }

}

public Nullable<Boolean> IsNewbie

{

get { return _isNewbie; }

set { _isNewbie = value; NotifyPropertyChanged("IsNewbie"); }

}

public double RegCost

{

get { return _regCost; }

set {

if (value < 39.50 || value > 299.25)

{

throw new ArgumentException("cost must be between 39.50 and 299.25");

}

_regCost = value; NotifyPropertyChanged("RegCost"); }

}

public Boolean IsSpeaker

{

get { return _isSpeaker; }

set { _isSpeaker = value; NotifyPropertyChanged("IsSpeaker"); }

}

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

public string Error

{

get

{

return null;

}

}

public string this[string columnName]

{

get

{

switch (columnName)

{

case "Lastname":

if (this.Lastname.Length < 3 || this.Lastname.Length > 23)

return "The string must be between 3 and 23";

break;

case "Country":

if (this.Country.Length < 5 || this.Country.Length > 35)

return "The Country name must be between 5 and 35";

break;

/*case "InRegistration.Lastname":

if (this.InRegistration.Lastname.Length < 7 || this.InRegistration.Lastname.Length > 26)

return "The string must be between 7 and 26";

break; */

}

return string.Empty;

}

}

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

//----- methods -----

public void NotifyPropertyChanged(string propertyName)

{

if (PropertyChanged != null)

{

PropertyChanged(this, new PropertyChangedEventArgs(propertyName));

}

}

}

}

====================================================================================

using System;

using System.Globalization;

using System.Text.RegularExpressions;

using System.Windows.Controls;

namespace MyClasses

{

public class AgeRangeRule : ValidationRule

{

private int _min;

private int _max;

public AgeRangeRule() { }

public int Min

{

get { return _min; }

set { _min = value; }

}

public int Max

{

get { return _max; }

set { _max = value; }

}

public override ValidationResult Validate(object value, CultureInfo cultureInfo)

{

int age = 0;

try

{

if (((string)value).Length > 0)

age = Int32.Parse((String)value);

}

catch (Exception e)

{

return new ValidationResult(false, "Illegal characters or " + e.Message);

}

if ((age < Min) || (age > Max))

{

return new ValidationResult(false,

  "Please enter an age in the range: " + Min + " - " + Max + ".");

}

else

{

return new ValidationResult(true, null);

}

}

}

//==========================================================================================

public class LettersOnlyStringRule : ValidationRule

{

public LettersOnlyStringRule()

{

}

public override ValidationResult Validate(object value, CultureInfo cultureInfo)

{

string General = "";

try

{

if (((string)value).Length > 0)

{

General = value.ToString();

}

}

catch (Exception e)

{

return new ValidationResult(false, "Something wrong or " + e.Message);

}

/* Verifying that the General string contains only letters */

if (!Regex.IsMatch(General, "^[a-zA-Z]+$" ))

{

return new ValidationResult(false, "ONLY letters! (a-z, A-Z).");

}

else

{

return new ValidationResult(true, null);

}

}

}

public class UpperCaseOnlyStringRule : ValidationRule

{

public UpperCaseOnlyStringRule()

{

}

public override ValidationResult Validate(object value, CultureInfo cultureInfo)

{

string General = "";

try

{

if (((string)value).Length > 0)

{

General = value.ToString();

}

}

catch (Exception e)

{

return new ValidationResult(false, "Something wrong or " + e.Message);

}

/* Verifying that the General string contains only Upper Case letters */

if (!Regex.IsMatch(General, "^[A-Z]+$"))

{

return new ValidationResult(false, "ONLY Upper Case! (A-Z).");

}

else

{

return new ValidationResult(true, null);

}

}

}

public class LowerCaseOnlyStringRule : ValidationRule

{

public LowerCaseOnlyStringRule()

{

}

public override ValidationResult Validate(object value, CultureInfo cultureInfo)

{

string General = "";

try

{

if (((string)value).Length > 0)

{

General = value.ToString();

}

}

catch (Exception e)

{

return new ValidationResult(false, "Something wrong or " + e.Message);

}

/* Verifying that the General string contains only Lower Case letters */

if (!Regex.IsMatch(General, "^[a-z]+$" ))

{

return new ValidationResult(false, "ONLY Lower Case! (a-z).");

}

else

{

return new ValidationResult(true, null);

}

}

}

public class EMailStringRule : ValidationRule

{

public EMailStringRule()

{

}

public override ValidationResult Validate(object value, CultureInfo cultureInfo)

{

string General = "";

try

{

if (((string)value).Length > 0)

{

General = value.ToString();

}

}

catch (Exception e)

{

return new ValidationResult(false, "Something wrong or " + e.Message);

}

/* Verifying that the General string contains only EMail characters */

//----- not yet right - causes errors !? -----

if (!Regex.IsMatch(General, "(\\W+\\.)*\\W+@(\\W+\\.)+[A-Za-Z]+" ))

{

return new ValidationResult(false, "eMail structure ONLY !");

}

else

{

return new ValidationResult(true, null);

}

}

}

public class UKpostCodeStringRule : ValidationRule

{

public UKpostCodeStringRule()

{

}

public override ValidationResult Validate(object value, CultureInfo cultureInfo)

{

string General = "";

try

{

if (((string)value).Length > 0)

{

General = value.ToString();

}

}

catch (Exception e)

{

return new ValidationResult(false, "Something wrong or " + e.Message);

}

//----- 'OR' stands for 'Once Removed' - meaning PHH sourced it from elsewhere -----

//----- here is Ben Forta's [poor] effort -----

//----- [A-Z]{1,2}\d[A-Z\d]? \d[ABD-HJLNP-UW-Z]{2} -----

//----- cText := e"[A-Z]{1,2}\\d[A-Z\\d]? \\d[ABD-HJLNP-UW-Z]{2}" -----

//----- cText = "[A-Z]{1,2}\\d[A-Z\\d]? \\d[ABD-HJLNP-UW-Z]{2}";

/* Verifying that the General string contains a valid Post Code. */

string cText = "";

cText = "\\b([A-PR-UWYZ][A-HK-Y]?[0-9]?[0-9]? {1,2}[0-9][ABD-HJLN-UW-Z]{2}|GIR 0AA)\\b";

if (!Regex.IsMatch(General, cText))

{

return new ValidationResult(false, "Not a valid Post Code!");

}

else

{

return new ValidationResult(true, null);

}

}

}

//==========================================================================================

public class GeneralStringRule : ValidationRule

{

private int _min;

private int _max;

private Boolean _allowEmpty;

private Boolean _allowSpaces;

//----- constructor -----

public GeneralStringRule()

{

Min = 0;

Max = 1000;

}

//----- properties -----

public int Min

{ get { return _min; } set { _min = value; } }

public int Max

{ get { return _max; } set { _max = value; } }

public Boolean AllowEmpty

{ get { return _allowEmpty; } set { _allowEmpty = value; } }

public Boolean AllowSpaces

{ get { return _allowSpaces; } set { _allowSpaces = value; }}

public override ValidationResult Validate(object value, CultureInfo cultureInfo)

{

string General = "";

try

{

if (((string)value).Length > 0)

{

General = value.ToString();

}

else

{

if (((string)value).Length == 0 && !this.AllowEmpty)

{

return new ValidationResult(false, "String can't be empty : ");

//return new ValidationResult(true, null);

}

else

{

General = value.ToString();

}

}

}

catch (Exception e)

{

return new ValidationResult(false, "Something wrong or " + e.Message);

}

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

if (!this.AllowSpaces && General.Contains(" "))

{

return new ValidationResult(false, "String contains space characters : ");

}

if ((General.Length < Min) || (General.Length > Max))

{

return new ValidationResult(false, "String length NOT in the range: " + Min + " - " + Max + ".");

}

else

{

return new ValidationResult(true, null);

}

}

}

//==========================================================================================

public class StringToIntValidationRule : ValidationRule

{

public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)

{

int i;

if (int.TryParse(value.ToString(), out i))

return new ValidationResult(true, null);

return new ValidationResult(false, "Please enter for me a valid integer value.");

}

}

}

====================================================================================

using System.Windows.Input;

using System;

using System.Windows;

namespace MyClasses

{

//=======================================================================================

public class RelayCommand : ICommand

{   //----- from C# Corner -----

private Action<object> execute;

private Func<object, bool> canExecute;

public event EventHandler CanExecuteChanged

{

add { CommandManager.RequerySuggested += value; }

remove { CommandManager.RequerySuggested -= value; }

}

public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)

{

this.execute = execute;

this.canExecute = canExecute;

}

public bool CanExecute(object parameter)

{

return this.canExecute == null || this.canExecute(parameter);

}

public void Execute(object parameter)

{

this.execute(parameter);

}

}

//======================================================================================

}

====================================================================================

using System;

using System.ComponentModel;   //----- INotifyPropertyChanged .....

namespace MyClasses

{

public class Registration : INotifyPropertyChanged

{

//----- fields -----

private int _registrationId;

private string _firstname;

private string _lastname;

private string _country;

private string _company;

private Boolean _hasPaid;

private Nullable<Boolean> _isNewbie;

private double _regCost;

private Boolean _isSpeaker;

//----- events -----

public event PropertyChangedEventHandler PropertyChanged;

//----- properties -----

public int RegistrationId

{

get { return _registrationId; }

set { _registrationId = value; NotifyPropertyChanged("RegistrationId"); }

}

public string Firstname

{

get { return _firstname; }

set { _firstname = value; NotifyPropertyChanged("Firstname"); }

}

public string Lastname

{

get { return _lastname; }

set { _lastname = value; NotifyPropertyChanged("Lastname"); }

}

public string Country

{

get { return _country; }

set { _country = value; NotifyPropertyChanged("Country"); }

}

public string Company

{

get { return _company; }

set { _company = value; NotifyPropertyChanged("Company"); }

}

public Boolean HasPaid

{

get { return _hasPaid; }

set { _hasPaid = value; NotifyPropertyChanged("HasPaid"); }

}

public Nullable<Boolean> IsNewbie

{

get { return _isNewbie; }

set { _isNewbie = value; NotifyPropertyChanged("IsNewbie"); }

}

public double RegCost

{

get { return _regCost; }

set {

_regCost = value;

NotifyPropertyChanged("RegCost");

}

}

public Boolean IsSpeaker

{

get { return _isSpeaker; }

set { _isSpeaker = value; NotifyPropertyChanged("IsSpeaker"); }

}

//----- constructors -----

public Registration()

{

this.Firstname = "...";

this.Lastname = "...";

this.Country = "...";

this.Company = "...";

this.HasPaid = false;

this.IsNewbie = new Nullable<Boolean>();

this.RegCost = 0.00;

this.IsSpeaker = false;

}

public Registration(string cF, string cL, string cCT, string cCP,

Boolean lHP, Nullable<Boolean> lNB, double nC, Boolean lSP)

{

this.Firstname = cF;

this.Lastname = cL;

this.Country = cCT;

this.Company = cCP;

this.HasPaid = lHP;

this.IsNewbie = lNB;

this.RegCost = nC;

this.IsSpeaker = lSP;

}

//----- methods -----

public void NotifyPropertyChanged(string propertyName)

{

if (PropertyChanged != null)

{

PropertyChanged(this, new PropertyChangedEventArgs(propertyName));

}

}

override public string ToString()

{

if (RegistrationId > 0)

{ return RegistrationId.ToString() + " >> " + Lastname.Trim(); }

else

{ return " >> " + Lastname.Trim(); }

}

}

}

====================================================================================

using System;

using System.Collections.Generic;

using System.Configuration;

using System.Data;

using System.Linq;

using System.Threading.Tasks;

using System.Windows;

namespace MVVM_ValidationCologne2016

{

/// <summary>

/// Interaction logic for App.xaml

/// </summary>

public partial class App : Application

{

}

}

====================================================================================

<Application x:Class="MVVM_ValidationCologne2016.App"

             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

             xmlns:local="clr-namespace:MVVM_ValidationCologne2016"

             StartupUri="MainWindow.xaml">

    <Application.Resources>

    </Application.Resources>

</Application>

====================================================================================

====================================================================================

====================================================================================

====================================================================================

====================================================================================

PHH - 10th. February 2016.


Chapter 3 - using 'Commands' in WPF/XAML

Chapter 3 - using 'Commands' in WPF/XAML

Chapter 3 - using 'Commands' in WPF/XAML

 Previous topic Next topic

=======================================================================================================================

The above text refers to the live link shown below :-

http://msdn.microsoft.com/en-us/magazine/dn169081.aspx

We all need to know this about Mr 'MVVMlight' himself :-

=============================================================================================================================

'GalaSoft' is Laurent Bugnion if I am not mistaken.

I will leave you to investigate further.

==============================================================================================================

And live link versions are here :-

http://mvvmlight.codeplex.com/

http://bindableapplicationb.codeplex.com/

http://appbarutils.codeplex.com/

http://www.codeproject.com/Articles/28959/Introduction-to-Attached-Behaviors-in-WPF

http://blogs.msdn.com/b/johngossman/archive/2008/05/07/the-attached-behavior-pattern.aspx

PHH - 26th. January 2016.


Standard XAML 'Command'

Standard XAML 'Command'

Standard XAML 'Command'

 Previous topic Next topic

Well guys, when we are dealing with 'standard' WPF stuff, we have a 'Command' attribute on

just some of the UI elements we select from our Toolbox.

The question is which ones - which tools have a 'Command' attribute ?

Let us start with the simple PushButton :-

==========================================================================================================================

And when we search the Properties we see the following :-

===============================================

And now the main details :-

=============================================================================================================================================================================

Notice in the above image that we have four properties which contain the word 'Command' at the start of the name.

Sometimes the invoked Command will need a parameter and in those cases that it does we can then bind

to the attribute/property called 'CommandParameter'. We will see how to do this later.

The 'button base' Click event will cause the Command system to be used or triggered. We can programmatically

ignore the event and the handler for 'Click'.

Other UI elements :

Okay then folks, which other UI elements have by default a 'Command' attribute / property, and which do not?

Well, we may expect that UI elements similar to push button will have, and CheckBox is built upon 'button base'.

So lets examine the Help Viewer 2.2 details :-

==============================================

=========================================================================================================================================================================

Yep! this looks pretty much as the previous one did for PushButton.

So its no surprise to see this for RadioButton, even though I feel that RB has had its day when it

comes to WPF and data binding - there are just other ways to do stuff which is MUCH better than

the RadioButton way.

Check this out :-

==============================================

============================================================================================================================================================================

The point of putting Command related properties on the ButtonBase UI elements is that they have few

obvious events which are likely to be required in this way.

However, other elements like TextBox and ListBox etc., have such a bunch of different, and commonly used,

events that it is no good associating the Click event with the 'Command'.

Lets checkout the TextBox and TextBlock classes :-

==============================================

====================================================================================================================================================================

===============================================

===========================================================================================================================================================================

What is a Command ? - Answer by Laurent Bugnion (the MVVMlight man himself) :

https://msdn.microsoft.com/en-us/magazine/dn237302.aspx

=================================================================================================

PHH - 27th. January 2016.


'RelayCommand' in standard WPF code, C#

'RelayCommand' in standard WPF code, C#

'RelayCommand' in standard WPF code, C#

 Previous topic Next topic

Most guys first hear of 'RelayCommand' as part of the MVVMlight framework, or similar.

However, we can easily code our own version of 'RelayCommand' in standard WPF and XAML

application 'stuff'.

We just need to know why we use it, and how to set it up with the 'Command' attribute of some

of the UI elements, or tools.

Also, we can easily apply the standard RelayCommand we create with a standard coding approach

to the MVVM pattern of development - YES, even without a framework.

As we look first at the code and the simplest way to get it to work for us, do please remember that

we will eventually place all the code in the ViewModel which supports the View. None of the command

and action method stuff will go in the code behind.

Here below is a standard Command attribute in use on a push Button UI element - no extra frameworks :-

=============================================================================================================

Now we need to have a link to a very useful article on using RelayCommands with standard WPF code :-

http://www.c-sharpcorner.com/UploadFile/20c06b/icommand-and-relaycommand-in-wpf/

===================================================================================================

In the following part of this section we will use the 'RelayCommand' class as defined by Bruno in his article.

We will also develop sample apps which have no input data parameters to the command, as well as some which

take a simple input, and finally one which takes a bound complex object as a parameter.

Here it (the RC class) is after I typed it into my VS 2015 shell :-

========================================================================================================

Please do NOT worry if you think you can't understand it - we will gradually get to know it and what it can do for us.

As we gradually go though the versions of a working solution, from simple and working to better design and a correct

development pattern like MVVM, we will see how all the various little parts fall into place.

Here is my first trial with just me inserting working code into a standard WPF Main Window project.

Check out the app structure carefully :-

====================================================================================================================================================================

All we are doing above is using the Command attribute with our bound RelayCommand to replace the

button Click event.

So when we click the 'Run Script' button we see the results displayed in the blue TextBlock.

Here is the button done in the normal event way :-

===============================================================================================

And now here it is when we by-pass the event system and use the Command property (attribute) instead :-

=================================================================================================================

Now that I have gotten myself going with new Command stuff, I will try to explain what you need to

do to make yourself have success as well.

It is really much easier than it first looks.

Okay then folks, lets see an image of what we are about to achieve - check below :-

=============================================================================

Yes, we have a standard 'MainWindow' of the WPF type. This is not written in the MVVM way of doing

things (with Views and ViewModels), but the three sub-windows are.

So the code behind the 'GetProcessView' is minimal indeed, as shown in the following image :-

=================================================================

We have three sets of Views and ViewModels to accompany our need to handle Get-Process,

Get-Printer, and Get-PSDrive, from our PowerShell command line (or simple scripts).

Here below is the early solution / project structure :-

===========================================================

And when we run the current application we see something like that shown below :-

[ .. when this was compiled and run only the 'Get-Process' button did much except launch the child form .. ]

=======================================================================================================================================================

However, the 'Run-Script' button did work, and used no click even to get the results. Instead the

Command attribute 'binding' to a RelayCommand was used.

Checkout below the XAML script binding to the Button Command attribute :-

=================================================================================================================

And the collapsed overall shape of the XAML script for the 'Get-Process' form is as shown below :-

===========================================================================================

And guys, we need to prove that as with good MVVM coding 'pattern' the code behind is extremely

minimal in detail - see the next image below :-

==================================================================

However, the code behind for the Main Window does show some code, and this is being done the

correct MVVM way.

Checkout in detail (most thoroughly) line 16 through 19 which launches the 'Get-Process' form :-

=============================================================================================

So now that we have a running form launch for Get-Process, I will now copy the approach to make

the other two forms work in a similar way.

Moving on .................

Need to Restructure 'slightly' ! :

So far guys we have gotten our early success while having the class we defined as 'RelayCommand' in the same

namespace as our first ViewModel.

See below for how it was coded :-

.............................................

.............................................

=================================================================================================================

We will be much better advised to make a new folder and into this new folder place code file which

contain the supporting classes for our project.

In the following image we see the newly changed structure of the SE pane :-

[ .. checkout in particular the 'MyClasses' stuff' .. ]

==========================================================

Now that we have the 'RelayCommand' class in our new folder and file, we need to make sure

we have the correct namespaces entered into the headers of our code files.

Here is the detail we find in the new 'MyClasses' file :-

=======================================================================================================

In the above image notice line 4 in particular.

Now let us see the ViewModel code, with the newly entered namespace on line 8.

All the VM's will need this 'using' line :-

========================================================================================

Okay then guys, I will now complete the third VM, that to find the PowerShell 'Drive's.

And although we need to do something soon about a more tidy results display / output, we

can at least see that the 'RelayCommand' part works OK as the following image shows :-

*** Remember *** it is the push button 'Run Script' which has its Command attribute bound

to the RelayCommand object.

Check this out :-

=================================================================================================================

Also remember that the TextBlock displaying the results is only seen as in the above image, once

the 'Run Script' button has been clicked.

So the click of the button does not fire our 'Click' event as such, but makes the Command 'run'.

All the three examples have been simple ones, and ones for which the 'action' method we create and use,

do not require input parameters.

Lets try and move on a little and try and supply such parameter input data.

Oh! before we do lets look at how best the 'PSDrive' results should display :-

Arrrhh ! - that is much better ;-0)

Moving on ..........................

Passing Parameters - to be used by the 'RelayCommand' system :

Okay guys, after some quick success above, we need to be able to do more than use the 'RelayCommand' system

as just a click of a button, meaning that no data is passed on and used by the 'action method'.

What we really would like to do next is change our script and code so that a data bound public property of

the 'ViewModel' is passed as a data parameter into our 'Action Method' - so it can be used in processing.

Lets take this one step at a time, and see how we can adjust what we already have working.

Checkout the following image, see the message box header and body text - this is for testing :-

==================================================

As the first step to using a parameter, I am (in line 18 below) assigning from XAML script a string object to the

attribute / property of the Button :-

==================================================================================================================================

Yes, as you can imagine this is not a very clever thing to do as we would be better to provide a binding to a public

property of the ViewModel, and thence change its value in code.

However, just for now, lets run with this string direct assignment, and change our attention to where we execute

the code after the button is clicked.

Once again the following is NOT the way to best do what we are trying to do, but we can learn in the process.

Checkout lines 32 and 33 below - try to work out what we should be doing instead :-

=============================================================================================================

Yes, we have added specific code into our general 'RelayCommand' class - and any developer guy should get the

feeling that this just 'feels wrong'.

Okay, below we can see that the message box does show, but then nothing else happens.

In order to get the following output, both the message box and the results displayed in the TextBlock,

we need to have line 32 shown below, uncommented - check this out :-

===================================================================================================

Okay, then to me folks this simply suggests that line 32 is necessary and makes things work as they should,

and line 33 is in the wrong place, and needs moving.

So lets change things around a bit and get our suitable results in a slightly different way.

Check this next image, see the message box header, as well as the message body :-

============================================================

First we clean up the 'Execute' method, leaving only the original line, number 32 :-

==================================================================================

And we then need to place the string object into the ViewModel body, where in fact all stuff should go, and reside.

For the moment lets just change the 'action' method - see lines 76 through 80 below :-

======================================================================================

In the above code note that we now have a header of "my Object param ...."

Here is a clearer look at the part which does the job for us :-

====================================================================================

Before we see what else we have done to make an object input to the RelayCommand system, lets see

what we are after by the end of the changes :-

==================================================================================================================

That is fine - BUT - where does the body of the message box now come from ?

See the new public property on line 31, this works with the private field on line 15 :-

=======================================================================================================

So what have we now done in our XAML script ?

Checkout line 18 which is the new replacement for line 19 :-

================================================================================================================

Yes, above we have bound the public property in the VM called 'RunScriptParam' to the attribute named 'CommandParameter'.

So this makes two bindings for the command system - check below :-

=====================================================================================================

So now let us study the final form of the code to correctly use a parameter with RelayCommand :-

[ .. lines 54, 83, and 86 in particular.. ]

=======================================================================================================

And to confirm, below we see the newly added string object which gets passed from the binding and into

the RelayCommand object where it is used and passed on further :-

First the specification of the action method :-

========================================================================================================

And now the string object :-

======================================================================================================

And finally, the result of this parameter 'pass-through' system is as seen below :-

===============================

Enough for now, go and look at the child section from here to see how we can now alter this slightly to

allow us to pass a complex business object and a parameter, and then use it.

See you in the next sub-section ......................

PHH - 27th. January & 5th. February 2016.


Command parameters - complex objects

Command parameters - complex objects

Command parameters - complex objects

 Previous topic Next topic

Okay then folks, lets have a quick summary of what we are trying to do, and why.

Short Summary :

We are using the standard XAML / WPF script and code facilities in .NET to have a click on a Button

UI element fire off an 'action' method in our 'ViewModel' code, and if we so require it to provide a

parameter for the action method. This object 'parameter' can be simple or complex, as long as

it is just one object.

The whole point of this is to make sure our code is included in the ViewModel and not in the

code behind the View. Consequently, both the action and its data will belong to the VM.

It appears that if we wish to react to more events than Clicks on 'ButtonBase' UI elements we

will need to use an MVVM framework. But more of that later.

By moving all business logic code from the code behind the View into the ViewModel, we can then

facilitate (make available) Unit Testing.

Top developer guys tell us we must do this, and that it is good practice.

So lets now see how we may pass a complex object into an action method using our RelayCommand

type and the appropriate code.

Lets see some success first, even though it is simple and possibly uninspiring :-

============================================================================

The above image shows a message box displaying the values of properties within a complex type - in

fact the Type is 'Speaker' - the values are just default ones so are unimpressive.

The message box gets displayed after the button called 'Run Script' is clicked - the Command system

is responsible for this and not the event handling system directly. The developers script/code does

not go near handling events.

Now then, lets see the code for instantiating a Speaker object and assigning it more suitable values.

Checkout the following image details :-

=======================================================================================================================================

===========================================================================================

=======================================================================================================

===================================================================================

==========================================

==============================================================================================================

==============================================================================================================

PHH - 5th. February 2016.


working script and code

working script and code

working script and code

 Previous topic Next topic

=================================================================================

<Window x:Class="PowerShell_fromWPF_02.a_Views.GetPSDriveView"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

        xmlns:local="clr-namespace:PowerShell_fromWPF_02.a_Views"

        mc:Ignorable="d"

        Title="GetPSDrive" Height="350" Width="620" Background="LightGreen">

    <Grid>

        <TextBox Background="Thistle" Width="592" Height="65"

                 Margin="10,22,0,0" AcceptsReturn="True"

                 TextWrapping="Wrap" Text="{Binding Path=ScriptText, UpdateSourceTrigger=PropertyChanged}"

                 HorizontalAlignment="Left" VerticalAlignment="Top"/>

        <Button Content="Run Script" Height="21" Width="85" HorizontalAlignment="Left"

                Margin="10,92,0,0" VerticalAlignment="Top" Background="LemonChiffon"

                Command="{Binding Path=RunScriptCommand}"

                CommandParameter="{Binding Path=RunScriptParam}"/>

        <TextBlock HorizontalAlignment="Left" Height="176" Margin="10,133,0,0"

                   TextWrapping="Wrap" Background="Yellow"

                   Text="{Binding Path=Results, UpdateSourceTrigger=PropertyChanged}" />

    </Grid>

</Window>

=================================================================================

using System.Management.Automation.Runspaces;

using System.Collections.ObjectModel;

using System.Management.Automation;

using System.Text;

using System.ComponentModel;

using System.Windows.Input;

using System;

using PowerShell_fromWPF_02.c_MyClasses;

using System.Windows;

namespace PowerShell_fromWPF_02.b_ViewModels

{

public partial class GetPSDriveViewModel : INotifyPropertyChanged

{

private string _runScriptParam = "..nothing..";

private string _scriptText = "Get-PSDrive";

private string _results;

private ICommand _runScriptCommand;

public ICommand RunScriptCommand

{

get

{

return _runScriptCommand;

}

set

{

_runScriptCommand = value;

}

}

public string RunScriptParam

{

get { return _runScriptParam; }

set { _runScriptParam = value; NotifyPropertyChanged("RunScriptParam"); }

}

public string ScriptText

{

get { return _scriptText; }

set { _scriptText = value; NotifyPropertyChanged("ScriptText"); }

}

public string Results

{

get { return _results; }

set { _results = value; NotifyPropertyChanged("Results"); }

}

//----- events -----

public event PropertyChangedEventHandler PropertyChanged;

public GetPSDriveViewModel()

{

RunScriptCommand = new RelayCommand(new Action<object>(my_ClickReaction));

}

//----- methods -----

public void NotifyPropertyChanged(string propertyName)

{

if (PropertyChanged != null)

{

PropertyChanged(this, new PropertyChangedEventArgs(propertyName));

}

}

private string RunScript(string scriptText)

{

Runspace runspace = RunspaceFactory.CreateRunspace();

runspace.Open();

Pipeline pipeline = runspace.CreatePipeline();

pipeline.Commands.AddScript(scriptText);

pipeline.Commands.Add("Out-String");

Collection<PSObject> results = pipeline.Invoke();

runspace.Close();

StringBuilder stringBuilder = new StringBuilder();

foreach (PSObject obj in results)

{ stringBuilder.AppendLine(obj.ToString()); }

return stringBuilder.ToString();

}

public void my_ClickReaction(object obj)

{

Results = RunScript(ScriptText);

MessageBox.Show(obj.ToString(), "my Object param ....");

}

}

}

=================================================================================

<Window x:Class="PowerShell_fromWPF_02.MainWindow"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

        xmlns:local="clr-namespace:PowerShell_fromWPF_02"

        mc:Ignorable="d"

        Title="PowerShell from WPF and RelayCommands :" Height="350" Width="525" Background="Bisque">

    <Grid>

        <Grid.Resources>

            <Style TargetType="Button">

                <Setter Property="Width" Value="100" />

                <Setter Property="HorizontalAlignment" Value="Left" />

                <Setter Property="VerticalAlignment" Value="Top" />

            </Style>

        </Grid.Resources>

        <Button x:Name="pbProcess" Content="Get-Process" Margin="21,22,0,0" Click="pbProcess_Click"/>

        <Button x:Name="pbPrinter" Content="Get-Printer" Margin="21,63,0,0" Click="pbPrinter_Click"/>

        <Button x:Name="pbPSDrive" Content="Get-PSDrive" Margin="21,103,0,0" Click="pbPSDrive_Click"/>

    </Grid>

</Window>

=================================================================================

using System.Windows;

using PowerShell_fromWPF_02.b_ViewModels;

using PowerShell_fromWPF_02.a_Views;

namespace PowerShell_fromWPF_02

{

public partial class MainWindow : Window

{

public MainWindow()

{

InitializeComponent();

}

private void pbProcess_Click(object sender, RoutedEventArgs e)

{

GetProcessView ourGPrView = new GetProcessView();

GetProcessViewModel ourGPrViewModel = new GetProcessViewModel();

ourGPrView.DataContext = ourGPrViewModel;

ourGPrView.Show();

}

private void pbPrinter_Click(object sender, RoutedEventArgs e)

{

GetPrinterView ourGPtView = new GetPrinterView();

GetPrinterViewModel ourGPtViewModel = new GetPrinterViewModel();

ourGPtView.DataContext = ourGPtViewModel;

ourGPtView.Show();

}

private void pbPSDrive_Click(object sender, RoutedEventArgs e)

{

GetPSDriveView ourGPSDView = new GetPSDriveView();

GetPSDriveViewModel ourGPSDViewModel = new GetPSDriveViewModel();

ourGPSDView.DataContext = ourGPSDViewModel;

ourGPSDView.Show();

}

}

}

=================================================================================

using System.Windows.Input;

using System;

using System.Windows;

namespace PowerShell_fromWPF_02.c_MyClasses

{

//=======================================================================================

public class RelayCommand : ICommand

{   //----- from C# Corner -----

private Action<object> execute;

private Func<object, bool> canExecute;

public event EventHandler CanExecuteChanged

{

add { CommandManager.RequerySuggested += value; }

remove { CommandManager.RequerySuggested -= value; }

}

public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)

{

this.execute = execute;

this.canExecute = canExecute;

}

public bool CanExecute(object parameter)

{

return this.canExecute == null || this.canExecute(parameter);

}

public void Execute(object parameter)

{

this.execute(parameter);

}

}

//======================================================================================

}

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

=================================================================================

using System;

using System.ComponentModel;   //----- INotifyPropertyChanged .....

namespace PowerShell_fromWPF_02.c_MyClasses

{

//---------------------------------------------------------

public class Speaker : INotifyPropertyChanged

{

//----- fields -----

private int _speakerId;

private string _firstName;

private string _lastName;

private Boolean _isNew;

private DateTime _firstSessionDate;

private int _age;

private string _summary;

private string _summary2;

//----- events -----

public event PropertyChangedEventHandler PropertyChanged;

//----- properties -----

public int SpeakerId

{

get { return _speakerId; }

set

{

_speakerId = value;

NotifyPropertyChanged("SpeakerId");

Summary = this.ToString();

}

}

public string FirstName

{

get { return _firstName; }

set

{

_firstName = value;

NotifyPropertyChanged("FirstName");

Summary = this.ToString();

}

}

public string LastName

{

get { return _lastName; }

set

{

_lastName = value;

NotifyPropertyChanged("LastName");

//Summary = this.ToString();

//--- throws an error when 3rd of three !? ---

}

}

public string Summary

{

get { return _summary; }

set { _summary = value; NotifyPropertyChanged("Summary"); }

}

public string Summary2

{

get { return _summary2; }

set { _summary2 = value; NotifyPropertyChanged("Summary2"); }

}

public Boolean IsNew

{

get { return _isNew; }

set { _isNew = value; NotifyPropertyChanged("IsNew"); Summary2 = ToString2(); }

}

public DateTime FirstSessionDate

{

get { return _firstSessionDate; }

set { _firstSessionDate = value; NotifyPropertyChanged("FirstSessionDate"); Summary2 = ToString2(); }

}

public int Age

{

get { return _age; }

set

{

_age = value;

NotifyPropertyChanged("Age");

Summary2 = ToString2();

}

}

//-------------------------------------------------

//----- constructors -----

public Speaker()

{

this.LastName = "";

this.FirstName = "";

this.IsNew = false;

this.Age = 1;

this.SpeakerId = -1;

this.FirstSessionDate = new DateTime(1900, 01, 01);

this.Summary = this.ToString();

this.Summary2 = this.ToString2();

}

//----- methods -----

public void NotifyPropertyChanged(string propertyName)

{

if (PropertyChanged != null)

{

PropertyChanged(this, new PropertyChangedEventArgs(propertyName));

}

}

override public string ToString()

{

if (SpeakerId > 0)

{ return SpeakerId.ToString() + " >> " + FirstName.Trim() + " " + LastName.Trim(); }

else

{ return " >> " + FirstName.Trim() + " " + LastName.Trim(); }

}

public string ToString2()

{

return Age.ToString() + " >> " + IsNew.ToString() + " " + FirstSessionDate.ToLongDateString();

}

}

//---------------------------------------------------------

}

=================================================================================

<Window x:Class="PowerShell_fromWPF_02.a_Views.GetProcessView"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

        xmlns:local="clr-namespace:PowerShell_fromWPF_02.a_Views"

        mc:Ignorable="d"

        Title="GetProcess" Height="350" Width="620" Background="Cornsilk">

    <Grid>

        <TextBox Background="Thistle" Width="592" Height="65"

                 Margin="10,22,0,0" AcceptsReturn="True"

                 TextWrapping="Wrap" Text="{Binding Path=ScriptText, UpdateSourceTrigger=PropertyChanged}"

                 HorizontalAlignment="Left" VerticalAlignment="Top"/>

        <Button x:Name="button" Content="Run Script" Height="21"

                Margin="10,92,0,0" VerticalAlignment="Top" Background="LightBlue"

                Command="{Binding Path=RunScriptCommand}" HorizontalAlignment="Left" Width="85"

                CommandParameter="{Binding Path=MySpeaker}"/>

        <TextBlock HorizontalAlignment="Left" Height="176" Margin="10,133,0,0"

                   TextWrapping="Wrap" Background="LightBlue"

                   Text="{Binding Path=Results, UpdateSourceTrigger=PropertyChanged}" />

    </Grid>

</Window>

=================================================================================

using System.Windows;

using System.Management.Automation.Runspaces;

using System.Collections.ObjectModel;

using System.Management.Automation;

using System.Text;

using System.ComponentModel;

using System.Windows.Input;

using System;

using PowerShell_fromWPF_02.c_MyClasses;

namespace PowerShell_fromWPF_02.b_ViewModels

{

public partial class GetProcessViewModel : INotifyPropertyChanged

{

private Speaker _mySpeaker;

private string _scriptText = "Get-Process";

private string _results;

private ICommand _runScriptCommand;

public ICommand RunScriptCommand

{

get

{

return _runScriptCommand;

}

set

{

_runScriptCommand = value;

}

}

public Speaker MySpeaker

{

get { return _mySpeaker; }

set { _mySpeaker = value; NotifyPropertyChanged("MySpeaker"); }

}

public string ScriptText

{

get { return _scriptText; }

set { _scriptText = value; NotifyPropertyChanged("ScriptText"); }

}

public string Results

{

get { return _results; }

set { _results = value; NotifyPropertyChanged("Results"); }

}

//----- events -----

public event PropertyChangedEventHandler PropertyChanged;

//----- constructor -----

public GetProcessViewModel()

{

RunScriptCommand = new RelayCommand(new Action<object>(my_ClickReaction));

MySpeaker = new Speaker();

MySpeaker.Age = 39;

MySpeaker.FirstName = "Willie";

MySpeaker.LastName = "Moore";

MySpeaker.SpeakerId = 999;

MySpeaker.FirstSessionDate = new DateTime(2002,10,22);

        }

//----- methods -----

public void NotifyPropertyChanged(string propertyName)

{

if (PropertyChanged != null)

{

PropertyChanged(this, new PropertyChangedEventArgs(propertyName));

}

}

private string RunScript(string scriptText)

{

Runspace runspace = RunspaceFactory.CreateRunspace();

runspace.Open();

Pipeline pipeline = runspace.CreatePipeline();

pipeline.Commands.AddScript(scriptText);

pipeline.Commands.Add("Out-String");

Collection<PSObject> results = pipeline.Invoke();

runspace.Close();

StringBuilder stringBuilder = new StringBuilder();

foreach (PSObject obj in results)

{ stringBuilder.AppendLine(obj.ToString()); }

return stringBuilder.ToString();

}

public void my_ClickReaction(object obj)

{

//--- not local must be class variable ---

Results = RunScript(ScriptText);

string MyComplexObj = "";

MyComplexObj += ((Speaker)obj).Age.ToString()+"\n";

MyComplexObj += ((Speaker)obj).FirstName.ToString() + "\n";

MyComplexObj += ((Speaker)obj).LastName.ToString() + "\n";

MyComplexObj += ((Speaker)obj).FirstSessionDate.ToString() + "\n";

MyComplexObj += ((Speaker)obj).SpeakerId.ToString() + "\n";

MessageBox.Show(MyComplexObj, "my Speaker obj. :");

}

}

}

=================================================================================

using System.Windows;

using PowerShell_fromWPF_02.b_ViewModels;

using PowerShell_fromWPF_02.a_Views;

namespace PowerShell_fromWPF_02

{

public partial class MainWindow : Window

{

public MainWindow()

{

InitializeComponent();

}

private void pbProcess_Click(object sender, RoutedEventArgs e)

{

GetProcessView ourGPrView = new GetProcessView();

GetProcessViewModel ourGPrViewModel = new GetProcessViewModel();

ourGPrView.DataContext = ourGPrViewModel;

ourGPrView.Show();

}

private void pbPrinter_Click(object sender, RoutedEventArgs e)

{

GetPrinterView ourGPtView = new GetPrinterView();

GetPrinterViewModel ourGPtViewModel = new GetPrinterViewModel();

ourGPtView.DataContext = ourGPtViewModel;

ourGPtView.Show();

}

private void pbPSDrive_Click(object sender, RoutedEventArgs e)

{

GetPSDriveView ourGPSDView = new GetPSDriveView();

GetPSDriveViewModel ourGPSDViewModel = new GetPSDriveViewModel();

ourGPSDView.DataContext = ourGPSDViewModel;

ourGPSDView.Show();

}

}

}

=================================================================================

=================================================================================

=================================================================================

=================================================================================

PHH - 5th. February 2016.


Alternative approach - Expression Blend SDK

Alternative approach - Expression Blend SDK

Alternative approach - Expression Blend SDK

 Previous topic Next topic

There is another way of handling the events from the UI elements, other than Relay Commands.

We can use what is built into the SDK for Expression Blend.

Here is some of Josh Smith's script from his 'Lookless' sample we look at in Appendix 'B' :-

========================================================================================================================================

PHH - 26th. February 2016.


Vulcan.NET, 'RelayCommand' standard WPF

Vulcan.NET, 'RelayCommand' standard WPF

Vulcan.NET, 'RelayCommand' standard WPF

 Previous topic Next topic

PHH - 4th. February 2016.


Commands and 'EventToCommand'

Commands and 'EventToCommand'

Commands and 'EventToCommand'

 Previous topic Next topic

https://msdn.microsoft.com/en-us/magazine/dn237302.aspx

=========================================================================================================

======================================================================================================================

==========================================================================================================================

PHH - 6th. February 2016.


'RelayCommand' - the 'MVVMlight' framework, C#

'RelayCommand' - the 'MVVMlight' framework, C#

'RelayCommand' - the 'MVVMlight' framework, C#

 Previous topic Next topic

http://www.mvvmlight.net/doc/

=======================================================================================

==========================================================================

Laurent Bugion - Mr MVVMlight himself - has this to say regarding 'RelayCommand' and MVVM

frameworks.

https://msdn.microsoft.com/en-us/magazine/dn237302.aspx

===================================================================================================================

==========================================================

================================================================================================================

=====================================================================================================================

=======================================================================================================================

============================================================================================================

PHH - 6th. February 2016.


working script and code

working script and code

working script and code

 Previous topic Next topic

===================================================================================

===================================================================================

===================================================================================

===================================================================================

===================================================================================

http://www.codeproject.com/Tips/665546/Passing-Command-Parameter-for-Buttons-within-an

PHH - 4th. February 2016.


'EventToCommand' - MVVMlight - L.Bugnion

'EventToCommand' - MVVMlight - L.Bugnion

'EventToCommand' - MVVMlight - L.Bugnion

 Previous topic Next topic

Laurent Bugnion is known as Mr MVVMlight as he created it for GalaSoft - he is also a knowledgeable chap indeed.

Here is a link to one of his many useful web articles :-

https://msdn.microsoft.com/en-us/magazine/dn237302.aspx

Another useful article from LB is seen here :-

http://www.galasoft.ch/mvvm/

It (the first of these) starts like this below :-

==============================================================================================================

And, here is the live linked pointed to by Laurent (Bugnion) above :-

http://msdn.microsoft.com/en-us/magazine/dn169081.aspx

He continues by saying :-

===================================================================================================================

PHH - 7th. February 2016.


Handling 'Events' the 'Expression Blend' SDK way

Handling 'Events' the 'Expression Blend' SDK way

Handling 'Events' the 'Expression Blend' SDK way

 Previous topic Next topic

Previously, we have discussed the need, when applying the MVVM pattern, to have a way of

handling events raised on the UI elements but with their method 'handling' code in the ViewModel,

and certainly not in the code behind.

Well, there is another way to do this other than the Relay Command way, and it uses the SDK which

is provided with / for 'Expression Blend'.

Lets see how we may script out XAML to use the alternative way of 'eventing' - check details below :-

===============================================================================

Note that in the above image we need to access some extra Namespaces - and ' d ', ' i ' and ' ei ' may be

seen defined in the heading section of our Window - check them out :-

===================================================================================================

For your information the Namespaces in the Control's .NET code is as follows :-

============================================================================================

The above code is the very start of our newly define 'Complete Custom Control' - remember it is not

a customised control, but one defined from scratch (the very beginning).

Lets now study the required References section which accompanies the working application :-

==============================================

And if we get all that right we can use the new SDK way of making our delete buttons

below do their work - here are a bunch of delete buttons coded in this way :-

==============================================================

The message box just tells me that my tests are correct and we are just about to have the

LastName data entry in the first CCC (complete custom control) removed, or overwritten with

nothing.

Here is the result of continuing with an 'OK' click :-

============================================================

Obviously, the 'Lastname' field in the second CCC on the form, is left alone and it still has a value.

Here is the .NET code for the action methods pointed to in the line 73 delegate process :-

======================================================================================================

Above are four very simple action methods and notice that they are very conveniently, and sensibly,

held within the Contact class itself.

We just have to make sure that our binding on line 74 points things at the right place.

Later we will look at alternative ways of setting the binding, as this is implicitly related to the way we

couple the VM to the View and also what and how we pass data objects into the VM at form launching

time.

For the moment check out the way I have gotten things to work for my exploration of the topic. I am sure

we can improve on this a little later on.

===========================================================================================

At least all of our coding is out of the code behind, and that we are applying MVVM and data-binding

as I have shown it in the past in earlier Cologne conferences.

We can explore further a better way to do this, not a lot different, it will just feel smarter and smoother ;-0)

PHH - 1st. March 2016.


Chapter 4 - using 'Regular Expressions'

Chapter 4 - using 'Regular Expressions'

Chapter 4 - using 'Regular Expressions'

 Previous topic Next topic

Regular Expressions are powerful 'things'.

They are flexible and easy enough to apply in our .NET code.

They operate on a text string and can do just about anything - yes, anything at all.

The only problem I have with them is that they are so different in syntax, and close to

mathematical formulation, that I forget the way to read them, never mind write them.

This means we need a good reference book and somewhere to turn for help when we need it.

And be careful, web searches and even books can have incorrect expressions published in them.

Its not the first time that I have found expressions which do NOT do the UK Post Code fully, and

correctly.

http://regexlib.com/DisplayPatterns.aspx?cattabindex=5&categoryId=6&AspxAutoDetectCookieSupport=1

============================================================================================================

================================================================================

Spaces - containing and NOT containing.

Post Codes

Telephone and fax numbers - mobiles

Web sites and email numbers.

Book Ben Forta (in 10 minutes).

No special characters #~=+!"£$%^&*() etc..

one of a limited set of letters W,X,Y,Z

two of a similar limited set as above.

PHH - 26th. January 2016.


"RegExes" to the rescue

"RegExes" to the rescue

"RegExes" to the rescue

 Previous topic Next topic

Okay then guys, we can write a few .NET code lines to make some useful, if simple,

validation rules which check out our data in the TextBox input UI elements, but we will need

quite a few more complex VRs for most business applications.

Before we go to the complexity of email addresses, telephone numbers, UK post codes and more,

lets look a rule to simply only allow alphabetic characters. No spaces or special characters.

First lets us see how we can add this Validation Rule to the existing 'LastName' input box :-

==========================================================================

Yes, as you can see we are getting a small handful of VRs from which to choose.

Let us now see this new VR is use with our LastName GUI input :-

===================================================

As long as we type in characters (as seen above) we get no errors thrown.

But what about an included space character ? - check below :-

================================================================

The message is too long - we can shorten this for the next time we get errors.

First lets look at the new VR code we have used :-

[ .. only three extra code lines from 80 to 84 .. ]

========================================================================================================

Line 82 above shows just how easy it is in .NET to apply or test a Regular Expression (regexe).

For most of us we need to be VERY careful as to where we get our expressions to use for testing, we

probably need a reliable source which tells us what they will do.

It is probably easy enough for some of us to see that the expression ^[a-zA-Z]+$ means only the characters

allowed are lower case a through z and upper case A through Z.

We can do more with case in a moment, first lets do some more testing :-

===================================================

Above we have just entered a first space character as we type, so its at the end of the text string.

What about introducing a first space somewhere within the existing input string - check below :-

===================================================

Yes, it all seems to work great. So lets see the final and tidied XAML script :-

================================================================================================

I am still sure we can improve and clean the above syntax - more on that later.

So lets see how the RegExe copes with some other special characters.

===================================================

Your homework exercise is to write a VR which allows ONLY special characters, including spaces.

Lets move on ....................

See you in the next sub-section ;-0)

PHH - 26th. January 2016.


Lower and Upper case letters

Lower and Upper case letters

Lower and Upper case letters

 Previous topic Next topic

Lower and Upper case letters :

Well guys, once we get the hang of creating a RegExe validation rule then it all gets a bit quicker

and easier to write others.

Here is a couple of VRs which I wrote in only a minute or two, once I had cracked the 'Letters ONLY'

rule in the previous section.

If ^[a-zA-Z]+$ means all letters then ^[A-Z]+$ means upper case and ^[a-z]+$ means lower case.

Here is the script being changed - on the 'LastName' testing UI element :-

==========================================================================================

And when we compile and run the changed test app we see the following :-

===================================================

And when corrected and typing continued we see this :-

======================================================

Obviously, the lower case rule works in a very similar way.

I will leave it up to you to test it out ;-0)

See you in the next sub-section.

PHH - 29th. January 2016.


Post Codes - UK

Post Codes - UK

Post Codes - UK

 Previous topic Next topic

Hey guys! Once we get into using Regular Expressions we can do a great deal with validating text string

data value inputs. There are a host of cases which can be covered using RegExe use.

The folks in the UK have a powerful Postal code system which is great to use from a practical point of view,

BUT, is a real pain to make sure we don't have contact data which incorporates wrong PostCodes.

Please be aware that although the internet and web searches often help us out with our problems, there

are a good few RegExe offerings for UK postcodes which are not very good.

It is difficult to find a freely available 'perfect' RegExe, however, I have a one which I use which proves to be

pretty good generally.

The one Ben Forta offers in his book does not recognise that my home postcode is genuine ;-0((

Lets talk this through as far as Validation Rules are concerned.

Starting simply, I have this in my Namespace for my VRs :-

And here is the start of my VR file containing this Namespace :-

=====================================================================

Even at the moment, without trying very hard, I have a bunch of different VRs, as you

can see in the next image :-

======================================================================

I have to confess I have not named my postcode VR as well as I may have liked. I would prefer to call

it 'UKPostCodeValidationRule' - but for now lets just run with what is shown below :-

[ .. this will be cleaned up and simplified later .. ]

================================================================================================================

Yes, its line 53 which makes it all work correctly (or 95% correctly) when it comes to the code running. Then it is

just like any / many RegExe VRs in other respects.

We will clean up and tidy this code soon.

First lets look at the important part - where we can allow an empty entry. Most often we need this 'get-out' for

our UK postcodes - not just because they may be unknown at point of entry, but because the code to hand is

apparently incorrect. There is no simple way of knowing how to correct a postcode which is not accepted by the

VR being applied.

Lets check the necessary code to allow for empty PostCode entry :-

=========================================================================

Lets move on a step or two ..........

Now we need to see how the VRs are placed in the collection of VRs for the data Row in the DataGrid :-

============================================================================================================

In the above image line 79 shows us how to use an input parameter with our RegExe rule.

And now shown below we see the VR and RegExe in action :-

=====================================================================================================================================

Remember that the icon shows us the Row in question, ignore the highlighted row this just indicates we have move off focus as far

as our line being edited is concerned.

Lets return to the line and change the postcode :-

====================================================================================================================

Yes, I guessed and removed the 'V' and all seems well - see both above and below :-

=============================================================================================================

Okay - we seem to be getting somewhere. Now lets turn our attention to cleaning up the VR and correctly

naming it.

Study the following code carefully and thoroughly :-

=============================================================================================================================

The active code for this version is provided in the child section form here - "Working code".

Time to move on, see you in the next sub-section ........................

PHH - 29th. January & 24th. February 2016.

Addendum :

Alternative 'escaped' sequence .........

=============================================================================================================================

Vulcan is the other way around.

====================================================================================================================

Not sure if this is as good a pattern as the C# one above this !


Working code

Working code

Working code

 Previous topic Next topic

//=====================================================================

public class UKPostCodeValidationRule : ValidationRule

{

private Boolean _allowEmpty = true;

public Boolean AllowEmpty

{

get { return _allowEmpty; }

set

{

if (_allowEmpty == value) return;

_allowEmpty = value;

}

}

public override ValidationResult Validate(object value, CultureInfo cultureInfo)

{

string General = "";

try

{

Course course = (value as BindingGroup).Items[0] as Course;

//---------------------------------------------------------

if (course.PostCode.Length > 0)

{  General = course.PostCode; }

if (course.PostCode.Trim() == "" && this.AllowEmpty)

{  return new ValidationResult(true, null); }

}

catch (Exception e)

{        return new ValidationResult(false, "Something wrong or " + e.Message); }

string cText = "";

cText = "\\b([A-PR-UWYZ][A-HK-Y]?[0-9]?[0-9]? {1,2}[0-9][ABD-HJLN-UW-Z]{2}|GIR 0AA)\\b";

if (!Regex.IsMatch(General, cText))

{  return new ValidationResult(false, "Not a valid Post Code!"); }

else

{  return new ValidationResult(true, null);        }

}

}

========================================================================================================

PHH - 25th. February 2016.


ZIP codes

ZIP codes

ZIP codes

 Previous topic Next topic

PHH - 29th. January 2016.


Telephone numbers

Telephone numbers

Telephone numbers

 Previous topic Next topic

https://www.aa-asterisk.org.uk/index.php/Regular_Expressions_for_Validating_and_Formatting_GB_Telephone_Numbers

===========================================================================================================================================

=========================================================================================================================================

"^\(?(?:(?:0(?:0|11)\)?[\s-]?\(?|\+)44\)?[\s-]?\(?(?:0\)?[\s-]?\(?)?|0)(?:\d{2}\)?[\s-]?\d{4}[\s-]?\d{4}|\d{3}\)?[\s-]?\d{3}[\s-]?\d{3,4}|\d{4}\)?[\s-]?(?:\d{5}|\d{3}[\s-]?\d{3})|\d{5}\)?[\s-]?\d{4,5}|8(?:00[\s-]?11[\s-]?11|45[\s-]?46[\s-]?4\d))(?:(?:[\s-]?(?:x|ext\.?\s?|\#)\d+)?)$"

================================================================================================================================================================

==============================================================================================================

===============================================================================================================

==================================================================================================================

PHH - 29th. January & 25th. February 2016.


Date 'patterns'

Date 'patterns'

Date 'patterns'

 Previous topic Next topic

Thanks to Josh Smith at :- http://www.codeproject.com/Articles/15610/Regex-Validation-in-WPF

===========================================================================================================================================

===================================================

http://stackoverflow.com/questions/4896632/uk-date-regular-expression/5070463

(0?[1-9]|[12][0-9]|3[01])[- /.](0?[1-9]|1[012])[- /.](19|20)\d\d

^(?:^(?:(?:(?:(?:(?:0?[13578]|1[02])/31)|(?:(?:0?[13-9]|1[0-2])/(?:29|30)))/(?:1[6-9]|[2-9]\d)\d{2})|(?:0?2/29/(?:(?:(?:1[6-9]|[2-9]\d)(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))|(?:(?:0?[1-9])|(?:1[0-2]))/(?:0?[1-9]|1\d|2[0-8])/(?:(?:1[6-9]|[2-9]\d)\d{2}))$)$

CustomValidator :

protected void dateFormatValidator_ServerValidate(object source, ServerValidateEventArgs args) { if (args.Value.Length == 6) { var month = args.Value.Substring(0, 2); var day = args.Value.Substring(2, 2); var year = args.Value.Substring(4, 2); DateTime dummyValue; args.IsValid = DateTime.TryParse(month + "/" + day + "/" + year, out dummyValue); } else { args.IsValid = false; } }

PHH - 26th. February 2016.


eMail address boxes

eMail address boxes

eMail address boxes

 Previous topic Next topic

http://regexlib.com/(A(CAg_Bth78XI7Ry2C7vo_2HR3yuG9GuP1YeCHLd1AH53pIpI-z7JHENTvKnhDjJJhOkjyka4kah-CTZaupEY3MAVWa6qYC256houEBaNXoG01))/UserPatterns.aspx?authorId=10d43491-1297-481f-ae66-db9f2263575c

======================================================================================

Okay guys, lets discuss case and email addresses.

http://email.about.com/od/emailbehindthescenes/f/email_case_sens.htm

Question: Are Email Addresses Case Sensitive?

Answer: Yes, Email Addresses Are Case Sensitive.

Every email address has three parts.

What comes before the '@' (the so-called "local mailbox part"),

the '@' itself

and what follows the '@' (the domain name).

The answer to the question whether email addresses are case sensitive—whether it matters if you

type ReCipiENt@eXaMPle.cOm or RECIPIENT@EXAMPLE.COM

or recipient@example.com—has to do with these elements of an email address.

The domain name part of an email address is case insensitive (i.e. case does not matter).

The local mailbox part, however, is case sensitive. The email address ReCipiENt@eXaMPle.cOm is

indeed different from recipient@example.com (but it is the same as ReCipiENt@example.com).

However, Case Typically Does Not Matter

Since the case sensitivity of email addresses can create a lot of confusion, interoperability problems and widespread

headaches, it would be foolish to require email addresses to be typed with the correct case.

Hardly any email service or ISP does enforce case sensitive email addresses, returning messages whose

recipient's email address was not typed correctly (in all upper case, for example).

This means that it does not typically matter what case you type an email address in when you send a message

(If the recipient did give you an email address with distinct case, preserve it, however.)

and you should always only use lower case characters when creating a new email address to rule out any confusion.

*** Hope that helps !! ***

Lets move on ...................

=========================================================================================

https://msdn.microsoft.com/en-us/library/01escwtf(v=vs.110).aspx

==================================================================================================

=====================================================================================================================================

checkString = @"^(?("")("".+?(?<!\\)""@)|(([0-9a-z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9a-z])@))"

checkString += @"(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-z][-\w]*[0-9a-z]*\.)+[a-z0-9][\-a-z0-9]{0,22}[a-z0-9]))$"

===============================================================================================================================================

==================================================================================================================

==========================================================================================================================

====================================================================================================================================

In the above RegExe 'pattern' the [0-9a-z] means any character which is numeric and zero to nine, or any lower case character a through z.

However, the upgraded sub-pattern of [0-9a-zA-Z] although it will work okay, can also be represented as [\w], where the slash lower case 'w'

means any character or digit and any case as well as an underscore.

At the back of Ben Forta's book it says :-

"any alpha-numeric character, digit, or underscore"

*** CARE *** ' \W ' is the opposite of ' \w ' meaning it is the other sub-set of possible characters where the whole set is ' \w ' plus ' \W '

=============================================================================================================================================================

==================================================================================================================================================

============================================================================================================================================

==========================================================================================================================================================

==================================================================================================================================================

PHH - 29th. January & 25th. February 2016.


working code

working code

working code

 Previous topic Next topic

===========================================================================================

public class emailAddressValidationRule : ValidationRule

{

private Boolean _allowEmpty = true;

public Boolean AllowEmpty

{

get { return _allowEmpty; }

set

{

if (_allowEmpty == value) return;

_allowEmpty = value;

}

}

public override ValidationResult Validate(object value, CultureInfo cultureInfo)

{

string General = "";

try

{

if (((string)value).Length > 0)

{

General = value.ToString();

}

//---------------------------------------------------------

if (General.Trim() == "" && this.AllowEmpty)

{ return new ValidationResult(true, null); }

}

catch (Exception e)

{ return new ValidationResult(false, "Something wrong or " + e.Message); }

string cText = "";

//----- below only allows lower case characters -----

//cText = @"^(?("")("".+?(?<!\\)""@)|(([0-9a-z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9a-z])@))" +

//@"(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-z][-\w]*[0-9a-z]*\.)+[a-z0-9][\-a-z0-9]{0,22}[a-z0-9]))$";

//----- next, below allows both lower and upper case characters! -----

cText = @"^(?("")("".+?(?<!\\)""@)|(([\w]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[\w])@))" +

@"(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([\w][-\w]*[\w]*\.)+[\w][\-\w]{0,22}[\w]))$";

if (!Regex.IsMatch(General, cText))

{ return new ValidationResult(false, "Not a valid eMail address !"); }

else

{ return new ValidationResult(true, null); }

}

}

============================================================================================================================

PHH - 25th. February 2016.


Web sites

Web sites

Web sites

 Previous topic Next topic

PHH - 29th. January 2016.


More than one word

More than one word

More than one word

 Previous topic Next topic

PHH - 29th. January 2016.


'N' numbers of words

'N' numbers of words

'N' numbers of words

 Previous topic Next topic

PHH - 29th. January 2016.


General RegExe rule - expression as parameter

General RegExe rule - expression as parameter

General RegExe rule - expression as parameter

 Previous topic Next topic

PHH - 29th. January 2016.


Italian Identity 'numbers' - "Fiscal Code"

Italian Identity 'numbers' - "Fiscal Code"

Italian Identity 'numbers' - "Fiscal Code"

 Previous topic Next topic

=============================================================

^([A-Za-z]{6}[0-9lmnpqrstuvLMNPQRSTUV]{2}[abcdehlmprstABCDEHLMPRST]{1}[0-9lmnpqrstuvLMNPQRSTUV]{2}[A-Za-z]{1}[0-9lmnpqrstuvLMNPQRSTUV]{3}[A-Za-z]{1})|([0-9]{11})$

Looks like a 16 character code, or an eleven digit number.

*** Thanks to Massimo Bighelli for his interest and guidance on this topic. ***

http://regexlib.com/(A(CAg_Bth78XI7Ry2C7vo_2HR3yuG9GuP1YeCHLd1AH53pIpI-z7JHENTvKnhDjJJhOkjyka4kah-CTZaupEY3MAVWa6qYC256houEBaNXoG01))/UserPatterns.aspx?authorId=10d43491-1297-481f-ae66-db9f2263575c

If we ignore lower case (case can be dealt with in a different way) we can simplify the pattern as follows :-

^([A-Z]{6}[0-9LMNPQRSTUV]{2}[ABCDEHLMPRST]{1}[0-9LMNPQRSTUV]{2}[A-Z]{1}[0-9LMNPQRSTUV]{3}[A-Z]{1})|([0-9]{11})$

Note above that the ^ and the $ are to indicate it must be matched from the first to the last character.

Lets break this up to explain what is being done :-

[A-Z]{6} >> six upper case letters (alphabetic).

[0-9LMNPQRSTUV]{2} >> two characters either a digit or an upper case alpha character in the specified group, L-V with a few omissions.

[ABCDEHLMPRST]{1} >> one alpha character in the specified group A-T with a few omissions.

[0-9LMNPQRSTUV]{2} >> two characters of digit or upper case of the specified group L- V with a few omissions.

[A-Z]{1} >> one upper case alpha character.

[0-9LMNPQRSTUV]{3} >> three characters of digit or upper case alphas from the specified group L-V with a few omissions.

[A-Z]{1} >> one upper case alpha character.

----------------- OR -----------------

[0-9]{11} >> eleven digits.

However, Massimo said "it's structured as shown below" - explanation :

3 char for the Surname and

3 char for the Name, following some rules and managing all types of exceptions (shorter names or surname...)

2 num for the year born

1 char for the month born (alphabetical table jumping ambiguous letters)

2 num for the day born (+40 for females)

4 char for fiscal ID of the city of birth (if born outside Italy, the country)

1 char checksum (algorithm)

So for Wolfgang's example :- "RDMWFG63C04A952D"

then 'RDM' stands for Riedmann,

and 'WFG' for Wolfgang,

and he was born in 1963 (a young chap !)

then 'C' means he is a March guy like myself,

and 04 for the 4th. of March (sorry just missed your birthday)

for the rest I have not yet gotten the information ........... ;-0)

============================================================================

Thanks to Wolfgang Riedmann we have an interesting challenge of making a suitable validating and type

of input mask which would vastly improve just the basic TextBox string input of an Italian Identity 'number'.

I say number but it is really a combination of letters and numbers, and possibly upper case characters as well.

Here in the next image is the test app sample in action, note the upper case letters, these have been accepted

and converted to upper case from the lower case input.

When we get to the seventh character (the lower case 'g') then the validation rule checker 'complains'.

Check out the details of the tooltip prompt etc. :-

==================================================================

And when we enter a full correct identity we see the following :-

===========================================

But adding just one more character (letter) gives us a validation issue as seen below :-

=================================================

The full identity is 16 characters long and Wolfgang sent me the following 'masking' / 'formatting' pattern :-

!!!!!!99!99!999! (where ! is an Alphabetic character)

Here is his correct personal number - its not confidential he says :-

e.g. RDMWFG63C04A952D

Now then, here is my version of the required 'RegExe' to check out and match the whole 16 character 'pattern' :-

^[A-Z]{6}[0-9]{2}[A-Z][0-9]{2}[A-Z][0-9]{3}[A-Z]$

and better still, here is a slightly simpler version - where [0-9] is replaced by \d :-

^[A-Z]{6}\d{2}[A-Z]\d{2}[A-Z]\d{3}[A-Z]$

I am not an expert on Regular Expressions by many a mile, I am in fact hardly a user - BUT - we can get reasonably

far by just studying the first few short chapters of Ben Forta's little book - see the Introduction section for details

of his very useful little book.

Going further - sorting out a visual problem :It is at the hear

We seem to have been doing well, so what is the problem ?

Well, the issue as I see it is that all the time we type a new identity (from a blank input UI element) we have a warning

on screen and in Tooltip about the entry NOT being valid.

Here is a good example, all characters are correct and we are just waiting for one more to be entered, a 'D'.

===============================================================

What we would like to see is that as long as as the user enters correct and meaningful characters, which fits the

overall pattern of the Italian Identity, we should not get a warning - AND - as soon as the user types a character

which is outside of the accepted pattern, we get a validation warning.

So the above image should not be showing a red warning, as all the so far 'entered' characters are correct (well valid).

I have a plan and it works nicely, BUT, there may be an easier, and smarter, way to do the same sort of thing ;-0)

Explaining my plan :

I realise that to know if a 16 character string is an Italian identity, or not, we need to check a pattern like the one

shown below - here, in my code the 'general' variable is actually the input string in the UI element :-

=========================================================================================

The line 490 method call to a suitable checking method is seen below - its saves lines of code later :-

====================================================================================================================

What we need to take into account is that in real time we enter an ID entity, one character at a time. So in effect we

need to be trying to match the input string with a different pattern specification as we go along, and enter more characters.

Luckily, we can make it simpler than having to have a different pattern for each length of input string - so we do not need 16

different patterns to checkout.

Here is the pattern we can use for the first six field position in the string data input :-

===================================================================================================

So lets see what we can enter and still keep away from the validation warnings :-

==========================================

All characters above are correct for the full pattern - they have just not been completed as a 'group'.

Next we have the full 16 set of characters - all correct, but not necessarily meaningful :-

==========================================

But here now is 'one character too far' !!! - checkout below :-

===================================================

Or even a real and correct ID entity with an 'extra' :-

==================================================

This is all good fun and we can learn a lot in doing it - BUT - we do not necessarily know

when we have a full and complete identity. We maybe need to think about what we wish to have,

and what we wish to achieve.

=================================================

=====================================================================================

Now apply this to the Italian Identity :

=========================================================================================================

=============================================================================================

========================================

=================================================================

============================================

=======================================================

========================================

=================================================================

PHH - 6th. & 7th. & 11th. March 2016.


Wikipedia - basic background and details

Wikipedia - basic background and details

Wikipedia - basic background and details

 Previous topic Next topic

https://en.wikipedia.org/wiki/Italian_fiscal_code_card

=================================

=========================================================================================================================================

===============================================================================================================================================

==========================================================================================================================================

=======================================================================================================================================

==========================================================================================================================================

====================================================================================================================================================

===================================================================================================================================

====================================================================================

=========================================================================================================================

========================================================================================================================================

Il 12/03/2016 14:29, Philip Hepburn ha scritto:

Hi Massimo,

Yes, what you have sent us in your last post fits with what I ahve just found in a search of the web for 'Italian Fiscal Code'.

The attached image shows a RegEx pattern - do not panic, it is not as complex and as difficult as it first looks.

nice !

I first simplified it by removing the lower case inclusions - we can handle lower case later in the Regex call as an intput parameter option to ignore case in the patern matching.

Then I split up the parts (or sections).

You did not say that there was an all difit alternative of 11 digits !?

The 11 digits are not an alternative, it is a different thing.

16-character personal fiscal code are assigned to the birth, and will never change.

The 11 digits are the VATcode, and is assigned to legal entities subject to the VAT system. It is assigned to professionals and companies at the time of registration at the Chamber of Commerce. It is not a personal code.

If you close the position at the Chamber of Commerce, and after a while 'will open another, you will be assigned a new VATcode.

Again, the 11th digit is a checksum, so you can check the correctness of the VATcode.

No chance to generate it, is awarded by the chamber of commerce.

If you are interested I will send also the function to check the VATcode

What is good with this detail of the middle parts of the Fiscal Code, is that it lists the possible special characters - for instance those possible for months of the year.

I will now go and look in Wikipedia, but don't forget to send me the checksum details.

So now I will be able to update my existing validation code to incorporate the fuller RegEx pattern shown in the images.

Digita il testo o l'indirizzo di un sito web oppure traduci un documento.

Well, what really happens when you have to provide your "personal fiscal code", or that of your child for enrollment in the pool, you do not remember it, and of course, you left the card at home.

But if the secretary is in front of a computer and use software like mine, she is capable to generate the fiscal code from name, date of birth, city of birth, and you have to remember only the last character, to confirm that the generated code is correct (ie no one of the exceptions mentioned by Wolfgang occurred)

Now go searching for the check function

(long time passed from VO era...)

Regards

Here is.

The same function to check or generate the fiscal code, depends on the parameters received.

The check part is this:

FOR i=2 TO 14 STEP 2 // estrazione e conteggio per caratteri pari

car:=SubStr(codfisc,i,1)

totale:=totale+IIF(Val(car)=0 .AND. car<>'0',Asc(car)-65,Val(car))

NEXT

FOR i=1 TO 15 STEP 2 // estrazione e conteggio per caratteri dispari

car:=SubStr(codfisc,i,1)

posizione:=IIF(Val(car)=0 .AND. car<>'0',Asc(car)-64,Val(car)+1)

totale:=totale+Val(SubStr(tabdispari,posizione*2-1,2))

NEXT

resto:=totale % 26

controllo:=IIF(CHR(resto+65)=Right(codfisc,1),.t.,.f.)

IF chksum // se solo controllo checksum ho finito

RETURN controllo

ENDIF

If you are interested in the whole function, tell me, and I will translate my comments.

If the first 3 char are digits, jump to CtrlPIVA() ie function to check VATcode

FUNCTION ctrlcf(codfisc,ok_vuoto,cognome,nome,datanascita,sesso,nomefile,ordine,comune,codgenerato)

*===============================================================================

*

* CTRLCF ( <cCodfiscale>, <lOk_vuoto>,

* [ <cCognome>, <cNome>, <dDatanascita>, <cSesso> ,;

* <cFileComuni>, <nIndice>, <cComune> ],

* [ @<cCodgenerato>] ) --> lRisultato

*

* Descrizione

* Controlla la validitÃ del codice fiscale passato verificandone l'ultima

* cifra di controllo.

* Opzionalmente puÃ² generare le prime 11 cifre del codice che Ã¨ in grado

* di ricostruirsi sulla base dei primi 4 parametri anagrafici opzionali.

* In questo caso, se Ã¨ passato anche l'ultimo parametro per indirizzo,

* vengono aggiornati in questo i primi 11 caratteri generati.

* Se il sesso Ã¨ 'D'=ditta, forza controllo partita IVA con ok_vuoto = .t.

* Se viene passato anche il nome del file dei COMUNI d'ITALIA (che deve

* essere giÃ aperto e deve avere nel campo LOCAL i nomi dei comuni),

* puÃ² generare il codice completo, e restituirlo nel <cCodGenerato>.

* PuÃ² essere passato anche l'indice per la ricerca, in caso contrario

* la ricerca sul file COMUNI d'ITALIA Ã¨ sequenziale sul nome COMUNE.

* Il controllo puÃ² venir saltato se nel parametro ok_vuoto viene passato .t.,

* che significa <lRisultato> = .t. se <cCodFiscale> Ã¨ vuoto.

* Se le prime 3 cifre del codice sono numeriche, chiama la funzione di

* controllo partita IVA (ctrlpiva) e ne ritorna il risultato logico.

*

* Argomenti

* <cCodfiscale> : stringa, codice fiscale persona fisica

* <lOk_vuoto> : logico, determina se controllare il codice vuoto

* <cCognome> : stringa, cognome

* <cNome> : stringa, nome

* <dDatanascita> : data, data di nascita

* <cSesso> : stringa, sesso, in uno dei caratteri M,m,F,f,D,d

* <cFileComuni> : stringa, nome del file COMUNI d'ITALIA

* <nIndice> : numero, indice per seek sui COMUNI

* <cComune> : stringa, nome del COMUNE d'ITALIA

* @<cCodgenerato> : stringa, contiene i primi 11 caratteri generati dalla

* funzione, per verifica di quanto passato

*

* Ritorno

* lRisultato : .T. se il codice fiscale passato Ã¨ corretto o se Ã¨

* vuoto con <lOk_vuoto> = .t.

* .F. se il codice fiscale passato Ã¨ errato

*

* Esempi

* generato=''

* ritorno=CONTROLCF('BGHMSM58L16B157T','BIGHELLI','MASSIMO',CTOD('16/07/58'),;

* 'M',@generato)

* IF .NOT. ritorno

* @ 1,1 SAY 'Codice fiscale errato!'

* ENDIF

*

*===============================================================================

LOCAL tabdispari := '0100050709131517192102041820110306081214161022252423'

LOCAL car := ''

LOCAL i AS SHORT // contatore per for-next

LOCAL controllo AS LOGIC // valore logico restituito dalla routine

LOCAL voca := ''

LOCAL cons := ''

LOCAL tabmesi := 'ABCDEHLMPRST'

LOCAL chksum := .f. // se .t. esegue solo controllo checksum

LOCAL completo := .f. // se .t. esegue generazione completa dai COMUNI

LOCAL oldarea // salvataggio area corrente se apro file COMUNI

LOCAL oldordine // salvataggio indice attivo sui COMUNI

LOCAL resto

LOCAL totale := 0

LOCAL posizione

IF Empty(codfisc) .AND. ok_vuoto

RETURN .t.

ENDIF

IF (IsDigit(Left(codfisc,1)).AND.IsDigit(SubStr(codfisc,2,1)).AND.;

IsDigit(SubStr(codfisc,3,1)) )

IF ValType(sesso)='U'

RETURN ctrlpiva(Left(codfisc,11),.t.)

ELSEIF ValType(sesso)='C'

IF sesso $ 'Dd'

RETURN ctrlpiva(Left(codfisc,11),.t.)

ENDIF

ENDIF

ENDIF

// se non dispongo dei 16 caratteri e non devo generarlo completo...

IF At(' ',codfisc)<>0 .AND. ValType(nomefile) # 'C'

RETURN .f.

ENDIF

// se gli unici parametri sono codice fiscale e ok_vuoto, controllo solo chksum

IF ValType(cognome)='U'

chksum := .t.

// se non devo solo controllare il chksum, controllo di avere i 4 parametri

// per la generazione dei primi 11 caratteri, e che siano del tipo giusto

ELSEIF ValType(datanascita) # 'D' .OR. Empty(datanascita) .OR.;

ValType(sesso) # 'C' .OR. !(sesso $ 'MmFf') .OR.;

ValType(cognome) # 'C' .OR. Empty(cognome) .OR.;

ValType(nome) # 'C' .OR. Empty(nome)

RETURN .f.

// se ho ricevuto anche il nome del file COMUNI d'Italia, imposto il flag

// di generazione completa

ELSEIF ValType(nomefile) = 'C'

codgenerato := ''

completo := .t.

chksum := .f.

ELSEIF PCount()=8

codgenerato := ''

chksum := .f.

ENDIF

// +------------------------------------------------------------+

// Â¦ controllo di correttezza sul checksum (sempre eseguito) Â¦

// +------------------------------------------------------------+

FOR i=2 TO 14 STEP 2 // estrazione e conteggio per caratteri pari

car:=SubStr(codfisc,i,1)

totale:=totale+IIF(Val(car)=0 .AND. car<>'0',Asc(car)-65,Val(car))

NEXT

FOR i=1 TO 15 STEP 2 // estrazione e conteggio per caratteri dispari

car:=SubStr(codfisc,i,1)

posizione:=IIF(Val(car)=0 .AND. car<>'0',Asc(car)-64,Val(car)+1)

totale:=totale+Val(SubStr(tabdispari,posizione*2-1,2))

NEXT

resto:=totale % 26

controllo:=IIF(CHR(resto+65)=Right(codfisc,1),.t.,.f.)

IF chksum // se solo controllo checksum ho finito

RETURN controllo

ENDIF

// +----------------------------------------------------------+

// Â¦ controllo di correttezza sui primi 3 dati anagrafici Â¦

// Â¦ cognome, nome, data nascita. Â¦

// +----------------------------------------------------------+

// -------------------------------------------elaborazione cognome

FOR i=1 TO Len(cognome)

IF SubStr(cognome,i,1) $ 'AEIUOaeiuo' // se Ã¨ una vocale...

voca:=voca+SubStr(cognome,i,1) // aggiorno vocali

ELSEIF SubStr(cognome,i,1)<>' ' // se non Ã¨ vocale e non Ã¨ blank

cons:=cons+SubStr(cognome,i,1) // aggiorno consonanti

ENDIF // tutto il resto non Ã¨ da considerare

NEXT

codgenerato:=Left(cons+voca+'X',3)

// ------------------------------------------elaborazione nome

voca:=''

cons:=''

FOR i=1 TO Len(nome)

IF SubStr(nome,i,1) $ 'AEIUOaeiuo' // se Ã¨ una vocale...

voca:=voca+SubStr(nome,i,1) // aggiorno vocali

ELSEIF SubStr(nome,i,1)<>' ' // se non Ã¨ vocale e non Ã¨ blank

cons:=cons+SubStr(nome,i,1) // aggiorno consonanti

ENDIF // tutto il resto non Ã¨ da considerare

NEXT

IF Len(cons) >= 4 // se ci sono almeno 4 consonanti

codgenerato:=codgenerato+Left(cons,1)+SubStr(cons,3,2)

ELSE // altrimenti come per cognome

codgenerato:=codgenerato+Left(cons+voca+'X',3)

ENDIF

// ----------------------------------------elaborazione anno, mese, giorno

codgenerato:=codgenerato+Right(Str(Year(datanascita)),2)

codgenerato:=codgenerato+SubStr(tabmesi,Month(datanascita),1)

codgenerato:=codgenerato+Right('00'+AllTrim(Str(Day(datanascita)+IF(sesso $ 'mM',0,40),2)),2)

// ----------------------------------------elaborazione codice catastale

IF completo

//schermoM=messaggio(,{'^Controllo codice fiscale'})

oldarea := SELECT()

DBSELECTAREA(nomefile)

IF ValType(ordine) = 'N'

oldordine := (nomefile)->(INDEXORD())

(nomefile)->(DBSETORDER(ordine))

(nomefile)->(DBSEEK(comune))

(nomefile)->(DBSETORDER(oldordine))

ELSE

DBLOCATE({||comune = (nomefile)->LOCALI})

ENDIF

IF (nomefile)->(FOUND())

codgenerato:=codgenerato+(nomefile)->codcf

// eseguo creazione del chksum su codgenerato

totale:=0

FOR i=2 TO 14 STEP 2 // estrazione e conteggio per caratteri pari

car:=SubStr(codgenerato,i,1)

totale:=totale+IIF(Val(car)=0 .AND. car<>'0',Asc(car)-65,Val(car))

NEXT

FOR i=1 TO 15 STEP 2 // estrazione e conteggio per caratteri dispari

car:=SubStr(codgenerato,i,1)

posizione:=IIF(Val(car)=0 .AND. car<>'0',Asc(car)-64,Val(car)+1)

totale:=totale+Val(SubStr(tabdispari,posizione*2-1,2))

NEXT

resto:=totale % 26

codgenerato:=codgenerato+CHR(resto+65)

ENDIF

DBSELECTAREA(oldarea)

//chiudifin(@schermoM)

ENDIF

// ----------------------------------------confronto

IF codgenerato<>Left(codfisc,Len(codgenerato)) .OR. !controllo

RETURN .f.

ENDIF

RETURN .t.

Massimo Bighelli

ARCA Sistemi S.r.l.

And this to check "Partita IVA" ie VATcode

... and somewhere in VS vb.net I have the function to check IBAN (for Italy)

FUNCTION ctrlpiva(partiva AS STRING, ok_vuoto AS LOGIC) AS LOGIC // controllo partita IVA

*===============================================================================

*

* CTRLPIVA ( <cPartitaIVA>, [<lOk_vuoto>] ) --> lRisultato

*

* Descrizione

* Controlla la validitÃ della partita IVA passata verificandone l'ultima

* cifra di controllo.

* Opzionalmente puÃ² ritornare .t. nel caso possa essere accettata una

* partita IVA vuota ed lOk_vuoto = .t. passando una partita IVA vuota.

* In caso contrario viene eseguito il controllo, controllando prima che

* tutte le 11 cifre di cui Ã¨ composta siano effettivamente delle cifre,

* e quindi procedendo al calcolo del cheksum.

*

* Nota

* Questa funzione viene automaticamente invocata anche dalla funzione di

* controllo codice fiscale, quando le prime 3 cifre del codice fiscale da

* controllare sono numeriche.

*

* Argomenti

* <cPartitaIVA> : stringa delle cifre della partita IVA da controllare

* <lOk_vuoto> : logico, determina se controllare la partita IVA vuota

*

* Ritorno

* lRisultato : .T. se la partita IVA passata Ã¨ corretto o se Ã¨ vuota

* con <lOk_vuoto> = .t.

* .F. se la partita IVA passata Ã¨ errata

*

* Esempi

*

* wpiva = nomefile->piva

* ritorno=CTRLPIVA(wpiva,.t.) // accetta anche una partita IVA vuota

* IF .NOT. ritorno // (ma non piena ed errata)

* @ 1,1 SAY 'Partita iva errata!'

* ENDIF

*

* Vedi anche: CTRLCF

*

*===============================================================================

LOCAL totdispari := 0

LOCAL totpari := 0

LOCAL controllo := ''

LOCAL i,temp

IF ValType(ok_vuoto) = 'U'

ok_vuoto := .f.

ENDIF

IF Empty(partiva) .AND. ok_vuoto

RETURN .t.

ENDIF

FOR i = 1 UPTO 11

IF !IsDigit(SubStr3(partiva,i,1))

RETURN .f.

ENDIF

NEXT

FOR i = 1 TO 10 STEP 2

totdispari += Val(SubStr(partiva,i,1))

NEXT

FOR i = 2 TO 11 STEP 2

temp := (Val(SubStr(partiva,i,1))*2)

IF temp >= 10

totpari += (temp-10+1)

ELSE

totpari += temp

ENDIF

NEXT

controllo := INT( 10 - ( (totdispari + totpari) % 10 ) )

IF (controllo % 10) = Val(Right(partiva,1))

RETURN .t.

ENDIF

RETURN .f.

Massimo Bighelli

ARCA Sistemi S.r.l.

PHH - 13th. March 2016.


A more comprhensive approach to 'IFC'

A more comprhensive approach to 'IFC'

A more comprhensive approach to 'IFC'

 Previous topic Next topic

Okay guys, we are going to look at a much more comprehensive approach to validation of the Italian

Fiscal Code - taking into account what the Wikipedia details told us about general 'pattern' and also

a checksum in the last and 16th. position.

We can validate an input pattern as it is entered, but also when we see we have a 16 character input,

with correct formatting, then carry out a checksum calculation and comparison with the 16th character

typed in by the user,

What we will NOT do :

We are not going to tray and generate a correct Fiscal Code from personal data, like name and where

born, and data of birth.

We will just use codes input by the user, and from a database.

What we WILL do :

We will use the rules found in Wikipedia to take an input full 16 character code and generate its checksum

value - then compare this with the value input, of the 16 character.

Depending on the outcome of the comparison we will affect the validation result accordingly.

Obviously, in the VR code (validation rule) we only try the checksum comparison once the input string reaches

a total length of 16 with any spaces removed / ignored.

So we will be using a Converter, a VR using RegEx as well as number crunching and data look-up tables.

I think this is a good example to study for new guys coming to Validation and WPF.

We will then apply the VR on a TextBox UI element within a data template for a ListBox control.

Lets start on the design and coding ..............

=============================================================

Yes, we see above that I am keeping my S.E. (solution explorer) tidy by structuring the content

of it - make sure you create folders for your smaller code files - keep them all small !

We need to discuss the use of Dictionaries, as we need to use an approach which is effectively

'loop-up tables', we we know one value and need to find out its 'twinned' value. Look-up table use

is particularly useful and applicable when there is not much, or any, pattern to the correspondence.

In the table shown below (4 sets of paired columns) we have the most irregular of the three tables.

Look out below for the irregularity of the 'values' as a sequence across the whole range, where

I, J, K, and L, are a good example, and 9, A, B, and C another :-

Making the Look-Up Tables - creating Dictionaries :

=================================================================================

With an irregular sequence, or series of keys, the easiest way to create the look-up table in

memory is to make a Dictionary and simply hard code it key for value, right through the sequence :-

========================================================================

Just make sure that above you carefully and visually check the details once the typing is done.

Now the next data grid / look-up table is seen below, and it has more sequential structure to it :-

========================================================

Because it has structure we can code it into memory with some 'For' loops - check below :-

===================================================================================

Make sure above that you get all of your boundary values correct, this is where you are likely to go wrong.

Both the digits and the alphas form an unbroken series, but because the overlap we need to have two

loop structures.

Now lets see the third look-up grid, and this is simply one key to value correspondence.

Yes, about as simple as they come :-0) Check this out below :-

===============================================================================================

So the corresponding code is easy enough to follow and understand :-

=======================================================================================

Okay then folks, we have above the three code sections to make each of the three Dictionaries we need

for the process of finding a Checksum and therefore check character.

Before we go any further into the calculation process, we need to look at a class definition which will allow

us an easy way of carrying out the lookup identifications and calculations - it is the 'IFC' class :-

Designing and Coding the 'IFC' Class :

'IFC' for Italian Fiscal Code - this is a very useful class to have for the checksum creation process :-

======================================================================================

I have left one property un-collapsed or expanded, so that we can see the property changed notification

code line.

I like to make all my data classes responsive to property changes, simply because we never know when

somewhere down-the-line we need to carry out data binding in the GUI surface.

Creating (or Calculating) a Checksum (or Check Character) :

In order to verify that the checksum / check character is a valid one, and hence likely that the code

provided is 'valid', we need to kick-off the process of making the check value from the first 15 characters

in a way similar to that shown below :-

========================================================================

Notice above that before we call on the 'FindCheckSum' function (or STATIC method), we have

first removed any space characters and converted everything to upper case.

Only if we have a resultant string which is 16 characters in length do we perform the checksum calculation.

There is simply no point in doing the creation process if the string is shorter or longer than 16 characters.

Finding the Checksum / Check character :

Okay then folks, to start the process we first need the three Dictionaries made, ready for use.

Then, from the IFC supplied, we need to provide this string to the instantiation (constructor) of the IFC class.

Once we have the IFC instantiated object we can then pass it into both the STATIC methods, called

'SplitFC2oddsAndEvens' and then 'TotalUpOddsEvens'.

Check out the following details thorough and carefully :-

=========================================================================================================================

Okay then, the above makes good sense, so what do our function methods on lines 102 and 103 do for us ?

Splitting up the 16 Character Code into two Strings (odds and evens):

The first 'function' method does the splitting up of the original character string. We need odds and evens and also

the specified last check character for later comparison - study this in detail :-

============================================================================================================================================================

Above we have made the required three strings.

Now we need to apply the Dictionary data as a look-up process before we can the sum the resultant values.

Summing the Strings and Finding the Remainder (Modulus) :

The following code carries out the look-up on both the odd and evens Dictionaries, and then sums the

resultant values as it continues the look-up.

=====================================================================================================

Notice that we need two loop structures, one for Odds and one for Evens.

Lines 140 - 43 do the final totalling as well as the finding of modulo 26 - the remainder after dividing by 26.

Then quite simply we make one last and individual look-up and that is in the Dictionary for 'Remainders',

and this gives us the check character which we assign to the appropriate property in out IFC object.

Lets move on ..............

To find the check character and then compare it with that supplied we need to go to the next subsection.

See you there .................

PHH - 13th. & 14th. March 2016.


the 'Checksum' - finding, comparing, and testing

the 'Checksum' - finding, comparing, and testing

the 'Checksum' - finding, comparing, and testing

 Previous topic Next topic

The Italian 'Codice Fiscale' (Fiscal Code) has an in-built check character as the last and 16th character.

This check character (is actually a checksum) is there to reduce the number of errors in copying, transferring,

and using the 'CF' or 'FC'.

A sum is made of numbers representing each of the first 15 positional characters, and then the modulus found

when this sum is divided by 26. The remainder - modulo 26 - is then between 0 and 25, and can be turned into

an alphabetic character for storage as the 16th character in the code.

Lets see the code I am using to do this ................ it is coded for clarity and simplicity, rather than performance.

Comparing Checksums - Calculated and Supplied (Provided) :

Lets us work backwards, and first see how the remainder value is calculated, and the 16th character generated :-

=======================================================================

So it would seem that we have to make two sums in some way or another - one for odd code characters, and one

for the even numbered code characters. More on this below.

Line 142 shown above uses this remainder to access a Dictionary as we would a look-up table, and return an alpha

character corresponding to the numeric remainder. We will see how the three required Dictionaries are defined later on.

First lets examine the next short piece of code, it takes a specified code and splits it into two strings - one for the odd numbered

code characters and one for the even numbered ones. The 16th character is excluded, as this is the check character.

*** WARNING *** Be aware that if we have the code characters in an array then the odd numbered ones actually have array

indexes which are even. Simply because the first array element (number 1) is array element indexed zero ( ' 0 ' ). And therefore

the second character is element indexed one ( ' 1 ' ).

Below the code lines 102 and 103 do the splitting up and then the totalling of the values got after the table 'look-up' process :-

==============================================================================================================================

Line 105 is based on the fact that the specified code has a 16th character and we are seeing if this is as it should be - we calculate

the remainder and check character for ourselves, then do the simple comparison.

If these are equal then there is a VERY high chance that the full code string is correct and valid.

Lets move on to see how the rest of my .NET code works ................

Testing out our New 'Functions' :

Yes, our STATIC methods are like functions in the old programming world.

Before we examine in detail how the functions (STATIC members) are coded and work, lets see how we

may call the topmost level of STATIC member.

Below in line 20 we are calling the 'CreateCheckSum' method, with an input which is the code string to be verified :-

====================================================================================================

The result of calling the method is an upper case check character.

Here below are a couple of screen shots of it in action - note the check character is 'Z' :-

=================================================================================

And now a second screen shot, where I have changed the specified check character to 'X' :-

========================================================================================================

And so we get the resultant output shown next in my images :-

=================================================================================

However, with the check character put back at 'z' and the rest of the code string typed as lower case, we

get the following output at runtime :-

==================================================================================================================================

So case does not matter in this verifying validation phase of our checking.

However, if we reverse (swap over) a couple of the characters, we see a negative checking message box display :-

=============================================================================

The negative comparison is because the calculated check character is now 'V' and not 'X' as specified.

=====================================================================================

Okay guys, time to move on - I will leave it to you to do lots more tests and trials.

See you in the next sub-section ..............

PHH - 14th. March 2016.


working code and script

working code and script

working code and script

 Previous topic Next topic

=======================================================================================

public class ItalianIdentityStringRule : ValidationRule

{

public ItalianIdentityStringRule()

{

}

public override ValidationResult Validate(object value, CultureInfo cultureInfo)

{

string General = "";

try

{

if (((string)value).Length > 0)

{

General = value.ToString();

}

}

catch (Exception e)

{

return new ValidationResult(false, "Something wrong or " + e.Message);

}

/* Verifying that the General string contains a valid 'Italian ID'. */

string cText = "";

//cText = "^[A-Z]{6}\\d{2}[A-Z]\\d{2}[A-Z]\\d{3}[A-Z]$";

cText = @"^[A-Z]{6}\d{2}[A-Z]\d{2}[A-Z]\d{3}[A-Z]$";

//--- use the 'Ignore.Case' with a Converter to force Upper case ! ---

if (!Regex.IsMatch(General, cText, RegexOptions.IgnoreCase))

{

return new ValidationResult(false, "Not a valid Italian ID number !");

}

else

{

return new ValidationResult(true, null);

}

}

}

=======================================================================================

public class ItalianIdentityStringRule2 : ValidationRule

{

public ItalianIdentityStringRule2()

{

}

public ValidationResult DoRegexTest(string input, string pattern)

{

if (!Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase))

{

return new ValidationResult(false, "WRONG - Italian ID number is '!!!!!!99!99!999!'");

}

else

{

return new ValidationResult(true, null);

}

}

public override ValidationResult Validate(object value, CultureInfo cultureInfo)

{

#region [set-up]

string General = "";

int len = 0;

try

{

len = ((string)value).Length;

if (len > 0)

{

General = value.ToString();

}

}

catch (Exception e)

{

return new ValidationResult(false, "Something wrong or " + e.Message);

}

/* Verifying that the General string contains a valid 'Italian ID'. */

#endregion ... set-up ...

string pattern = "";

//--- use the 'Ignore.Case' with a Converter to force Upper case ! ---

if (len <= 6)

{

pattern = @"^[A-Z]{1,6}$";

return DoRegexTest(General, pattern);

}

//----------------------------------------------------------------

if (len > 6 && len <= 8)

{

pattern = @"^[A-Z]{6}\d{1,2}$";

return DoRegexTest(General, pattern);

}

//----------------------------------------------------------------

if (len == 9)

{

pattern = @"^[A-Z]{6}\d{2}[A-Z]$";

return DoRegexTest(General, pattern);

}

//----------------------------------------------------------------

if (len > 9 && len <= 11)

{

pattern = @"^[A-Z]{6}\d{2}[A-Z]\d{1,2}$";

return DoRegexTest(General, pattern);

}

//----------------------------------------------------------------

if (len == 12)

{

pattern = @"^[A-Z]{6}\d{2}[A-Z]\d{2}[A-Z]$";

return DoRegexTest(General, pattern);

}

//----------------------------------------------------------------

if (len > 12 && len < 16)

{

pattern = @"^[A-Z]{6}\d{2}[A-Z]\d{2}[A-Z]\d{1,3}$";

return DoRegexTest(General, pattern);

}

//----------------------------------------------------------------

if (len == 16)

{

pattern = @"^[A-Z]{6}\d{2}[A-Z]\d{2}[A-Z]\d{3}[A-Z]$";

return DoRegexTest(General, pattern);

}

//----------------------------------------------------------------

if (len < 1 || len > 16)

{

pattern = @"^[A-Z]{6}\d{2}[A-Z]\d{2}[A-Z]\d{3}[A-Z]$";

return DoRegexTest(General, pattern);

}

return new ValidationResult(false, "Something odd is up !? ... ");

}

}

//==========================================================================================

=======================================================================================

        <TextBox Name="myPostCode" Margin="302,25,0,0" Width="193"

                 Validation.ErrorTemplate="{StaticResource ResourceKey=MyCTtry}" >

            <TextBox.Text>

                <Binding Path="PostCode"

                         UpdateSourceTrigger="PropertyChanged"

                         Mode="TwoWay"

                         Converter="{StaticResource String2Upper}" ValidatesOnDataErrors="True">

                    <Binding.ValidationRules>

                        <!--<local2:UKpostCodeStringRule />  -->

                        <local2:ItalianIdentityStringRule2 />

                    </Binding.ValidationRules>

                </Binding>

            </TextBox.Text>

        </TextBox>

=======================================================================================

 <!--<Label Content="PostCode - UK :" HorizontalAlignment="Left" Margin="302,0,0,0" VerticalAlignment="Top"/>-->

        <Label Content="Italian - ID :" HorizontalAlignment="Left" Margin="302,0,0,0" VerticalAlignment="Top"/>

        <!--==========================================================-->

=======================================================================================

            <local3:String2NameText x:Key="Clean2Name" />

            <local3:String2UpperCaseText x:Key="String2Upper" />

        </Grid.Resources>

        <!--==========================================================-->

=======================================================================================

<Window x:Class="MVVM_ValidationCologne2016.Views.SpeakerView"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

        xmlns:local="clr-namespace:MVVM_ValidationCologne2016.Views"

        xmlns:local2="clr-namespace:MyClasses"

        xmlns:local3="clr-namespace:MyConverters"

        mc:Ignorable="d"

        Title="Validation - Cologne two :" Height="350" Width="540" Background="MintCream">

=======================================================================================

PHH - 7th. March 2016.


working code and script (2) - 'IFC'

working code and script (2) - 'IFC'

working code and script (2) - 'IFC'

 Previous topic Next topic

===================================================================================

using System.Collections.Generic;

using System.ComponentModel;

using System.Windows;

namespace CheckSumIFC

{

public class MyCheckSumIFC

{

//--- fields ---

string cInString = "";

//--- constructors ---

public MyCheckSumIFC() { }

public MyCheckSumIFC(object oInString)

{

this.cInString = (string)oInString;

}

//----- STATIC methods -----

public static Dictionary<string, int> MakeOddDict()

{

//--- [ key, value ]---

Dictionary<string, int> oddChars = new Dictionary<string, int>();

oddChars.Add("0", 1);

oddChars.Add("1", 0);

oddChars.Add("2", 5);

oddChars.Add("3", 7);

oddChars.Add("4", 9);

oddChars.Add("5", 13);

oddChars.Add("6", 15);

oddChars.Add("7", 17);

oddChars.Add("8", 19);

oddChars.Add("9", 21);

oddChars.Add("A", 1);

oddChars.Add("B", 0);

oddChars.Add("C", 5);

oddChars.Add("D", 7);

oddChars.Add("E", 9);

oddChars.Add("F", 13);

oddChars.Add("G", 15);

oddChars.Add("H", 17);

oddChars.Add("I", 19);

oddChars.Add("J", 21);

oddChars.Add("K", 2);

oddChars.Add("L", 4);

oddChars.Add("M", 18);

oddChars.Add("N", 20);

oddChars.Add("O", 11);

oddChars.Add("P", 3);

oddChars.Add("Q", 6);

oddChars.Add("R", 8);

oddChars.Add("S", 12);

oddChars.Add("T", 14);

oddChars.Add("U", 16);

oddChars.Add("V", 10);

oddChars.Add("W", 22);

oddChars.Add("X", 25);

oddChars.Add("Y", 24);

oddChars.Add("Z", 23);

return oddChars;

}

public static Dictionary<string, int> MakeEvenDict()

{

//--- [ key, value ]---

Dictionary<string, int> evenChars = new Dictionary<string, int>();

//--- [ Digits ] 48 - 57  is 0 to 9 -----

for (int i = 0; i <= 9; i++)

{

string newEntry = new string((char)(48 + i), 1);

evenChars.Add(newEntry, i);

}

//----- [Alpha characters] -----

for (int i = 0; i < 65; i++)

{

evenChars.Add((new string((char)(65 + i), 1)).ToString(), i);

}

return evenChars;

}

public static Dictionary<int, string> MakeRemainderDict()

{

//--- [ key, value ]---

Dictionary<int, string> Remainder = new Dictionary<int, string>();

for (int i = 0; i < 65; i++)

{

Remainder.Add(i, (new string((char)(65 + i), 1)).ToString());

}

return Remainder;

}

public static string FindCheckSum(string instring)

{

string cReturn = "";

Dictionary<string, int> DictOdds = MakeOddDict();

Dictionary<string, int> DictEvens = MakeEvenDict();

Dictionary<int, string> DictRemainders = MakeRemainderDict();

IFC myIFC = new IFC(instring);

MyCheckSumIFC.SplitFC2oddsAndEvens(myIFC);

MyCheckSumIFC.TotalUpOddsEvens(myIFC, DictOdds, DictEvens, DictRemainders);

if (myIFC.CalcRemString == myIFC.SpecRemString)

{

MessageBox.Show("Calculated and specified CheckSum values equal !!!", "Correct Checksum :");

}

else {

MessageBox.Show("Calculated and specified CheckSum values NO MATCH ???", "WRONG Checksum :");

}

return cReturn = myIFC.CalcRemString;

}

public static void TotalUpOddsEvens(IFC inIFC, Dictionary<string, int> DictO,

Dictionary<string, int> DictE, Dictionary<int, string> DictR)

{

inIFC.TotalEvens = 0;

inIFC.TotalOdds = 0;

string cChar;

int nChar;

int GrandTotal = 0;

int Remainder = 0;

int len = inIFC.EvenString.Length;

for (int i = 0; i < len; i++)

{

cChar = inIFC.EvenString[i].ToString();

nChar = DictE[cChar];

inIFC.TotalEvens += nChar;

            }

len = inIFC.OddString.Length;

for (int i = 0; i < len; i++)

{

cChar = inIFC.OddString[i].ToString();

nChar = DictO[cChar];

inIFC.TotalOdds += nChar;

            }

GrandTotal = inIFC.TotalEvens + inIFC.TotalOdds;

Remainder = GrandTotal % 26;

inIFC.CalcRemString = DictR[Remainder];

}

public static void SplitFC2oddsAndEvens(IFC inIFC)

{

string ToBeSplit = inIFC.FullString;

int len = ToBeSplit.Length;

string[] myChars = new string[ToBeSplit.Length];

for (int i = 0; i < len; i++)

{

myChars[i] = ToBeSplit[i].ToString();

}

inIFC.EvenString = myChars[1] + myChars[3] + myChars[5] + myChars[7] + myChars[9] + myChars[11] + myChars[13];

inIFC.OddString = myChars[0] + myChars[2] + myChars[4] + myChars[6] + myChars[8] + myChars[10] + myChars[12] + myChars[14];

inIFC.SpecRemString = myChars[15];

}

public class IFC : INotifyPropertyChanged

{

//--- fields ---

private string _fullString;

private string _oddString;

private string _evenString;

private string _calcRemString;

private string _specRemString;

private int _totalOdds;

private int _totalEvens;

//--- constructors ---

public IFC() { }

public IFC(string cFull)

{

this.FullString = cFull;

}

//--- properties ---

public int TotalOdds

{

get { return _totalOdds; }

set { _totalOdds = value; NotifyPropertyChanged("TotalOdds"); }

}

public int TotalEvens

{

get { return _totalEvens; }

set { _totalEvens = value; NotifyPropertyChanged("TotalEvens"); }

}

public string FullString

{

get { return _fullString; }

set { _fullString = value; NotifyPropertyChanged("FullString"); }

}

public string OddString

{

get { return _oddString; }

set { _oddString = value; NotifyPropertyChanged("OddString"); }

}

public string EvenString

{

get { return _evenString; }

set { _evenString = value; NotifyPropertyChanged("EvenString"); }

}

public string CalcRemString

{

get { return _calcRemString; }

set { _calcRemString = value; NotifyPropertyChanged("CalcRemString"); }

}

public string SpecRemString

{

get { return _specRemString; }

set { _specRemString = value; NotifyPropertyChanged("SpecRemString"); }

}

//----- events -----

public event PropertyChangedEventHandler PropertyChanged;

//--- methods ---

public void NotifyPropertyChanged(string propertyName)

{

if (PropertyChanged != null)

{

PropertyChanged(this, new PropertyChangedEventArgs(propertyName));

}

}

}

public static string CreateCheckSum(object oInString)

{

string cReturn = "";

string cInString = ((string)oInString).ToUpper();

cInString = cInString.Replace(" ", "");

if (!(cInString.Length == 16))

{

return cReturn;

}

cReturn = MyCheckSumIFC.FindCheckSum(cInString);

return cReturn;

}

}

}

===================================================================================

public MainWindow()

{

InitializeComponent();

string testString = MyCheckSumIFC.CreateCheckSum("MRTMTT25D09F205Z");

//string testString = MyCheckSumIFC.CreateCheckSum("mrtmtt25d09f205z");

//string testString = MyCheckSumIFC.CreateCheckSum("MTRMTT25D09F205Z");

MessageBox.Show(testString,"My 'testString' IFC :");

//===================================================================

===================================================================================

===================================================================================

===================================================================================

===================================================================================

PHH - 14th. March 2016.


Massimo's data entry Forms for ICF (Codice Fiscale)

Massimo's data entry Forms for ICF (Codice Fiscale)

 Previous topic Next topic

Massimo's data entry Forms for ICF (Codice Fiscale)

 Previous topic Next topic

===================================================================================================================================================

====================================================================================================================================================

=====================================================================================================================================================================================

PHH - 14th. March 2016.


Working script and code

Working script and code

Working script and code

 Previous topic Next topic

=============================================================================

public class LettersOnlyStringRule : ValidationRule

{

public LettersOnlyStringRule()

{

}

public override ValidationResult Validate(object value, CultureInfo cultureInfo)

{

string General = "";

try

{

if (((string)value).Length > 0)

{

General = value.ToString();

}

}

catch (Exception e)

{

return new ValidationResult(false, "Something wrong or " + e.Message);

}

/* Verifying that the General string contains only letters */

if ( !Regex.IsMatch(General, @"^[a-zA-Z]+$") )

{

return new ValidationResult(false, "ONLY letters! (a-z, A-Z)." );

}

else

{

return new ValidationResult(true, null);

}

}

}

=============================================================================

public class UpperCaseOnlyStringRule : ValidationRule

{

public UpperCaseOnlyStringRule()

{

}

public override ValidationResult Validate(object value, CultureInfo cultureInfo)

{

string General = "";

try

{

if (((string)value).Length > 0)

{

General = value.ToString();

}

}

catch (Exception e)

{

return new ValidationResult(false, "Something wrong or " + e.Message);

}

/* Verifying that the General string contains only Upper Case letters */

if ( !Regex.IsMatch(General, @"^[A-Z]+$") )

{

return new ValidationResult(false, "ONLY Upper Case! (A-Z)." );

}

else

{

return new ValidationResult(true, null);

}

}

}

=============================================================================

public class LowerCaseOnlyStringRule : ValidationRule

{

public LowerCaseOnlyStringRule()

{

}

public override ValidationResult Validate(object value, CultureInfo cultureInfo)

{

string General = "";

try

{

if (((string)value).Length > 0)

{

General = value.ToString();

}

}

catch (Exception e)

{

return new ValidationResult(false, "Something wrong or " + e.Message);

}

/* Verifying that the General string contains only Lower Case letters */

if ( !Regex.IsMatch(General, @"^[A-Z]+$") )

{

return new ValidationResult(false, "ONLY Lower Case! (a-z)." );

}

else

{

return new ValidationResult(true, null);

}

}

}

=============================================================================

=============================================================================

=============================================================================

=============================================================================

=============================================================================

=============================================================================

=============================================================================

=============================================================================

=============================================================================

=============================================================================

=============================================================================

PHH - 29th. January 2016.


Chapter 5 - Vulcan.NET syntax

Chapter 5 - Vulcan.NET syntax

Chapter 5 - Vulcan.NET syntax

 Previous topic Next topic

Well guys, it has been a while in the making (or constructing) but the Vulcan.NET version of the

application to demonstrate Validation with WPF and XAML is done, and working well.

Here are a couple of screen shots to motivate us before we study the fine detail :-

And another view with two lists :-

Yes, it is as good as it looks. if not even better ;-0)

The short video shows a lot more since it is in real time, and shows the validation warnings etc.

In the following sub-sections I will endeavour to show and explain all that is happening in the Vulcan.NET

code, and show the syntax which is working for me.

The XAML stuff stays the same for C# and VN apps, as long as the general approach stays the same.

*** NOTE *** Do NOT miss out the subsection of 'Regular Expressions', this is your "God send" and can

help you in so many ways, without a need to learn lots of RegExe 'stuff'.

PHH - 7th. April 2016.


Some Tips and Tricks

Some Tips and Tricks

Some Tips and Tricks

 Previous topic Next topic

TextBlock :

Use the TextBlock UI element for Read Only data displays. This can't be edited.

TextBox :

Use the TextBox UI element for data items / fields which need to be changed, input, or edited.

Yes, TextBoxes can be disabled as far as editing is concerned, but that is NOT the point ! - keep it simple.

Solution Explorer (pane) :

I can't emphasize enough the benefit of structuring your SE pane, right from the start of the design and app

creation process.

Here is the collapsed view of what I have as a final working solution / project :-

==========================================================

Use more folders than fewer, as they aid a plan description :-

====================================

And only add the class code files when you are down to single files for a class.

Below is the data folder I have in my working application :-

==============================

PHH - 7th. April 2016.


XAML

XAML

XAML

 Previous topic Next topic

Since we are likely to get quite a few lines of XAML script when we define templates, styles, and our

general data binding, here is a good tip for using the collapse of the script sections.

Although good MVVM practice says we should not use the 'Name' attribute, this is only as an indication

to us that we are not using it elsewhere in a way NOT suitable to the MVVM approach, like in events, or back

in our calling .NET code.

However, if we place 'Name' early (or first) in our attributes we can see the named detail in the collapsed

script lines - her is a single example :-

=====================================================================================================================================

The above tells me that the collapsed section is for the 'eMail' public property on the data Model.

If we now look at a whole bunch of collapsed UI element sections we see the potential benefit - check below :-

=================================================================================================================

The above text editor display gives us a nice overall picture of what we are doing.

So we can quickly local the telephone sub-section, starting on line 99, then expand it if we wish to do some work on it.

PHH - 7th. April 2016.


Main Window

Main Window

Main Window

 Previous topic Next topic

For this app I have designed and constructed a main Window which is not along the

basic MVVM pattern of approach to design.

So it has a few event handlers in code behind, for the three push button controls.

Here is the SE for 'Window1' :-

================================================

And the XAML design editor surface looks like this below :-

=====================================================

The in-focus item is the 'ContentControl' which is needed to display the subsequent 'UserControl' objects.

Check this and the rest of the XAML script in the following image :-

========================================================================================================

Basically, we are to use the 'ContentControl' (with UCs - UserControls) as a "picture in picture" facility that

we see on our modern TV screens.

We may even call this 'view in view'.

See more on this under the sub-section 'User Controls' - next.

PHH - 7th. April 2016.


User Controls

User Controls

User Controls

 Previous topic Next topic

For us to use a UserControl on an open and existing Window object, we need a UI element

called a 'ContentControl' ('CC').

This 'CC' can take as its content any framework element or combinations thereof.

We will use it to hold a 'UC', and be able to use code to swap in and out one of two different UCs

that we design and specify.

Here is the 'CC' in the designer :-

And so we need to create and use a UC (UserControl) - check below for an example :-

=================================================================================================

The UC is like another Window - but whereas we can't nest 'Window' objects, we can place a UC in the

nested structure of the Window - via the CC.

Here is how it looks in the design surface :-

====================================================

And here is what the XAML script looks like which creates the CC which will 'house' the UC.

House meaning 'contain' - as in engineering the term 'housing'.

===================================================================================================

Okay then guys, how do we switch in and out the required UC for the CC ?

Well, as expected, it is quite easy to do in code - see below on line 40 :-

===========================================================================================================================

This approach to using UCs in CCs can be very useful and flexible. It was seen by me in WinRT 'metro' apps of a few

years back, when it was best to keep one page (effectively a Window) on screen and use UCs as new windows.

There is no reason why we can have multiple areas of a Window as CCs and place multiple UCs on the Window at

any one time.

If you like to get messy, there is no reason why you can't ahve UCs on a UC and so on 'ad infinitum' !!!

*** Remember *** Keep things simple at all times. Usually, complex and complicated solutions come

from the developer not think through the design and business problem correctly or fully, or both.

Good designers, and good developers make this look easy, simple, and less complicated.

PHH - 7th. April 2016.


Data Templates

Data Templates

Data Templates

 Previous topic Next topic

*** Rule ONE *** If you are working with list UI elements then 'DataTemplates' are a MUST.

To a guy new to XAML (newbie) it seems like a big task, or chore, to add stuff into the 'Resources' section however,

it actually makes life much easier once we 'embrace' templates for our data.

The same data can be shown using different templates, within the same View (or form) if required.

"One data object, many UI views of it."

The 'DataTemplate' is something which 'overlays' the data presented to the View via the ViewModel.

I now LOVE using DataTemplates, and more recently how to apply validation and converters to parts of them.

Here is a collapsed view of two different templates in the Resources section of the XAML script :-

========================================================================================================

Both of the above templates are for the same data object class. In this example a collection of 'Person' items.

If we now expand the lines of one of these templates ('MyTestTemplate3') we see the following details :-

===================================================================================================================

And now if we examine in detail a single 'TextBox' definition we see the following :-

[ .. remember, TextBoxes are for editing, TextBlocks for reading .. ]

============================================================================================================

This is what it looks like when run and displayed on screen :-

===============================================

Please note that a data template can be used without the need to apply a Converter or a Validation Rule.

So below here is a R/O 'DataTemplate' built around 'TextBlocks' :-

===================================================================================================================

And in operation this app shows the following - yes! - no photograph / image :-

[ .. the above was out of choice .. ]

====================================

Note that I like to use DatePicker UI elements in place of TextBoxes for dates.

The control does a lot of 'masking' of incorrect data for us. It will not allow us to complete an entry with

an impossible day or month.

However, restricting years and business logic data limits is up to us and our Validation Rule (VR).

DataTemplate do not need to use all of the data object properties, and indeed we could use one property

several times if we wished.

For more advanced guys you need to know that one data template could display properties from two objects,

if the data items collections were combined in the correct manner. However, the same basic rules apply.

If you can do the above design and creation as a developer, then you can do just about anything.

Lets move on ...............

PHH - 7th. April 2016.


Validation Rules

Validation Rules

Validation Rules

 Previous topic Next topic

Okay then guys, lets remember that if we use TextBoxes then the string entry data usually needs to be checked,

and verified / validated in some way or another.

We will not bother to consider the case when the 'IsReadOnly' attribute is set to 'True' - effectively making it

a TextBlock.

Let us now study the following XAML script where we have a pair of VRs being applied :-

================================================================================================================

Yes, folks, it is possible to have more than one VR applied. This allows us then to make less logically complex

rules for validation purposes.

In the above example we are checking out the two ends of the date range allowed, in two separate VR classes.

Notice too that the VRs have an input parameter, some have many, and some have none at all.

So now we need to look at, and study, the code for a typical VR, and we can consider the VR for maximum

date limit as a typical VR.

Check out these details :-

[ .. the compressed format first, to see the overall shape of things .. ]

======================================================================================================================

So obviously, we need to study the 'Validate' virtual method in much more detail - the rest of the code is just

supporting stuff :-

==========================================================================================================================

As you can see above the code is pretty general and straightforward stuff, you may like to change a few of the

values however.

Lets now see this VR in action - check image details below :-

=====================================================

Which checks out with script line 85 a couple of images above.

Remember that when designing VRs we need to keep the logic as simple as possible, to avoid issues

with testing, and problems being hidden for a long time before they rear their ugly heads at special

conditions in the data being used.

One very good way to simplify the logic code is to use Regular Expressions, since the required conditions, as such,

are often built into the RegExes, and most RegExe patterns can be searched and found on the web.

Lets now look at the virtual method part of the VR to check for 'letters' only in a string. By 'letters' we mean

that the sting is just upper or lower cased characters, no other characters at all.

Check this out :-

===============================================================================================================

Basically, the line 101 above does all the work - we need to look in more detail at RegExes in the next sub-section.

This is another 'thing' to get our heads around, and convince ourselves that it is a good idea - I am glad I did ;-0)

My reasons for saying this will become apparent as you read the next section.

See you there ..................

PHH - 7th. April 2016.


Regular Expressions

Regular Expressions

Regular Expressions

 Previous topic Next topic

The simple truth is this - without using Regular Expressions I could not of made this application.

At the least I would have had to create and apply some VERY complex conditions and logic, as well as

the fact that the finished app would probably have not been so good in the details it handled and validated.

I make no apologies to you guys for dipping into the world of RegExes, even tough I am quite a novice in the field.

Here is a RegExe way of doing what could be done in more complex code in .NET.

=============================================================================================================

However, we can some get another exaple of a simple enough idea of checking a UK telephone number, and

realising that the approach of using .NET code and conditions etc., is crazy and doing a Google on the web to

find a suitable RegExe pattern is so much easier and produces a more robust and flexible solution.

All we have to do is find a pattern and then test it out thoroughly to see it does as we require.

Here is the Vulcan syntax for the 'RegExe' checking a phone number :-

==========================================================================================================================================

I am not sure where I got this RegEx pattern but here is an alternative :-

http://regexlib.com/UserPatterns.aspx?authorId=d95177b0-6014-4e73-a959-73f1663ae814

===========================================================================================

And searching some more It seems as though I may have found (again) the first one shown above.

http://stackoverflow.com/questions/1464887/asp-regular-expression-for-uk-telephone-format-in-vb-net

==============================================================================================================

Moving on a pace or two .....................

We all need to make a 'mind set' change so that we are pleasantly predisposed to reaching out for a RegEx solution,

whenever we have a VR to construct.

It is my personal conviction (opinion) that code based VRs are a lot less flexible, less powerful, and more difficult

to get logically correct than when we use a suitable RegExe pattern.

There are lots of suitable RegExe patterns out there, more than one suitable web site, and plenty of test sample

to use to prove or disprove the workings of what we apply.

You do NOT need to be a guru or any sort of an expert with RegExes - just need to know enough to survive. This

is where Ben Forta's book comes in.

Go get yourself a copy ;-0)

PHH - 7th. & 13th. April 2016.


Applying the VRs

Applying the VRs

Applying the VRs

 Previous topic Next topic

When applying the Validation Rules to a text box UI element, or indeed another like DatePicker, we use

the same syntax and script structure.

In a TextBox we bind data to the 'Text' attribute, and for a DatePicker to the 'SelectedDate' attribute. The

script is similar for both.

Lets first look at a whole tag (tagged?) section for applying VRs to a TextBox :-

===============================================================================================================

So basically, the binding script section required by any attribute is similar to the following lines :-

===============================================================================================================

Between the tags for VRs we simply list the single or multiple VRs we wish to apply, and specify any parameters as well.

If we now look at the DatePicker tag section we see lines 82 to 87 below which are similar to that we saw in the TextBox :-

[ .. only the VRs are different .. ]

====================================================================================================================

So basically we select the XAML tag attribute for binding and then add the binding lines as seen above, making

sure we pick the right VRs to apply to the collection on the UI element.

Here below is how we set the important 'Codice Fiscale' binding on the relevant TextBox, and the related VR :-

============================================================================================================

Note above that we have a Converter as well as a VR - see line

Just make sure that the correct namespaces are referred to in the XAML header part - like this :-

===========================================================================

This simply allows access to the correct and relevant classes for Convert and VR.

PHH - 7th. April 2016.


Converters (value)

Converters (value)

 Previous topic Next topic

Converters (value)

 Previous topic Next topic

Well guys, welcome to the world of 'Converters' for WPF code and XAML script.

Many of us have used WPF successfully without bothering at all, only only a little, with Converters.

However, if we are to use Validation, and data binding correctly / fully, then we need to get to grips with creating

and using our own Converter classes.

In WPF we usually do our data entry and editing via the TextBox control.

One BIG benefit of using a Converter is that we can also design it to act like a 'masking' class.

I will explain :-

We tend to think of Converters as classes that turn one type of string input into another that is suitable for Types other

than a String object - and back again. So '03-03-1967' could be turned into a DateTime object and other DateTime objects

could be represented in a string similar to that just shown.

However, we can now consider other forms of 'conversion' which in another way of thinking we can consider as 'masking'.

The simplest of these could be simply to turn all character entries into upper case.

But then going a further we could turn the entered string into another string which has no special characters in it - you know - the

absence of spaces and dashes and starts and underscores etc., etc..

To see more on this topic see the child section from here.

Lets now study the following image, in which we have a very simple converter called "String2UpperCaseText". This is the one we

talked of above.

Apart from line 30 the class passes text back and forth, in and out, as it were :-

==========================================================================================================

So you could easily write yourself an alternative converter class which does the conversion to lower case.

If you were really 'stupid' you could convert to upper case one way and lower case the other - can't imagine why however !?

Lets us now look at a converter which takes a string input by the user, and then removes any and all characters which

we would not expect to see in a 'Name' string. It sorts of 'cleans' the input string - check below :-

============================================================================================================

The simple converter structure passes strings back and forth, but ant input characters in the TextBox input string

are first passed through a STATIC method called from line 16 - this applies a 'masking' type behaviour, and filters

out any unsuitable input characters.

*** Note *** When we use more specific UI elements, or controls, we often see that they seem to incorporate some

form of converter or validator, within the control itself. For example the DatePicker control does not allow us to

successfully enter anything but digits and yet the basic input element is a TextBox. And then if we try to enter

days and months that are not Valid they too are lost or stripped out of the entry.

Actually, we have far more control over data entry than we at first thought - AND - it is relatively easy to apply.

As an exercise, try to design a write a 'Converter' which only allows odd numbers to be entered, or even one which

only allows prime numbers as entries - that may be more tricky as the test / check will have to be done once the

TextBox has lost focus, and not as the property changes. You need to think about this ;-0) Think of 23 and what

would happen as the '2' is entered in we were checking each character being typed in.

Good Luck !

Addendum :

Don't forget that if we have a relatively small set of input possibilities then we may well be advised to provide some

sort of 'enum' and have a drop list from which to choose / select.

PHH - 7th. April 2016.


Masking

Masking

Masking

 Previous topic Next topic

Although what we are about to see and discuss is not quite 'masking' as we have previously known it, we do have a

reasonably easy to apply way of cleaning up, and filtering out, string input data so that it then fits and suits what we

wish to have provided, and effectively remove what we don't want entered.

We can apply STATIC filtering methods (or cleaning methods) to any string related to a Converter we are creating and using.

In the image shown below we have a converter accepting and returning a string - BUT - on line 14 we pass the accepted

string through a 'cleaning' or 'masking' member - check it out :-

====================================================================================================================================

And now we need to study the 'InputMasking' class with some of its STATIC members :-

==========================================================================================

The above STATIC member is used on line 14 of the converter.

As you can easily imagine, we can change, alter, and adapt this approach to mask, or filter-out quite

a variety of input characters.

Remember, if we remove certain input characters then it is if they were never entered in the first place ;-0)

Lets now checkout the STATIC method which allows only Digit characters. In the image shown below you

will see that I have changed over to using Regular Expressions to carry out the checking procedure, and not

the more unwieldy code approach :-

===========================================================================================

Any character seen on line 36 above is excluded from the input string.

Please be aware that there are other (likely simpler) ways to express the digits only condition using RegExes.

The reason I have left things as above, is that we can make many similar but different exclusion strings this way.

If you read through Ben Forta's little book you will learn about " \d and \D " - I will leave it to you to try them for yourself.

=======================================================================================

Okay then, this looks good, and there are many other things you can do while applying 'masking' or 'filtering'

STATIC methods to an input string.

Keep these ideas on the top of your head for later, or another time.

Lets move on ...........................

PHH - 7th. April 2016.


Codice Fiscale

Codice Fiscale

Codice Fiscale

Previous topic Next topic

And now to the VERY important part, the details of the Italian 'Codice Fiscale' (ICF).

There is a good explanation of 'ICF' in Wikipedia - here are two examples :-

================================================================================================================

And here is what Wikipedia has to say :-

==================================================================================================================================================

==========================================================================================================================================

I will leave it up to you about finding and reading more Wikipedia information.

Lets move on .............

PHH - 7th. April 2016.


CheckSums

CheckSums

CheckSums

 Previous topic Next topic

Although the following at first looks complicated, it is in fact quite straightforward as coding goes.

All we have to do is follow the rules as laid out in the Wikipedia information.

============================================================================================

Quite conveniently there is a check sum digit as the last and 16th. character of the code.

If we use the previous 15 characters (the first 15) and then calculate the check sum from these, we can compare the result

with the 16th character, if they match then we should be a-okay if the Italian guys have done their maths correctly ;-0)

Here is part of what is published in Wikipedia :-

==============================================================================================================================

[ .. there are two other tables in Wikipedia but we won't show then here .. ]

*** WARNING *** first a word of warning - the text above talks of odd and even characters - BUT - that is not to get mixed up

with indexes when we use C# and zero based indexes and Vulcan when the index is ' 1 ' based. We need to double check

before we commit ourselves to lots of code generation.

Lets now tackle this from a 'top down' point of view - lets start at the end of the problem where we have a

Check Sum class with many STATIC methods to support what needs doing.

We can below see this class and the collapsed STATIC members which are required to support each other.

Have a general study and overall examination of the following members :-

=========================================================================================================================

And now we need to see how to use and apply these, starting with the last one, on line 195,

which takes a Codice Fiscale and compares the supplied 16 character with the calculated check character.

Study the following :-

=====================================================================================

Yep! it makes sense, and we only need to take line 209 to see which subsequent STATIC method

we need to follow through.

Here is the 'CreateCheckSum' STATIC member :-

======================================================================================

Above we see that from a supplied Codice Fiscale we find and return the calculated Check Sum.

Obviously we need to now study the 'FindCheckSum' method (member).

Here it is to work through :-

==================================================================================================

Lines 115, 116, and 118 are those that do the business (work).

The member on 115 splits the fiscal code into odd and even character groups, and then line 116 takes these

lists of odds and evens and using the three 'look-up' tables (we here call them dictionaries) we find the totals.

Finally above, line 118 calculates the remainder single character string.

Lets follow through the 'TotalUpOddsEvens' method (line 116) - we find the characters one by one, and the

from the dictionary fins a look-up value for the addition process :-

[ .. beware the zero based index ! .. ]

======================================================================================================================

Line 175 finds the grand total of lookup values for odds and evens, and then line 176 the modulo 26, or the remainder when

we divide by 26.

A final lookup for this remainder gives us a check character to compare with the supplied character at position 16 of the

initial string.

Now we need to study the more task oriented STATIC methods which support this process.

First the splitting up of the input characters - see below :-

=====================================================================================================================================================

And now we need to see how we convert the three Wikipedia value (look-up) tables into code and a .NET Dictionary.

First the simple 'Remainder' STATIC method :-

====================================================================================

And now the more complex 'Evens' loo-up' Dictionary :-

==================================================================================

And finally, the 'Odds' Dictionary which has no fixed pattern, and so we have to simply transfer

the pairs of values from a visual table into a Dictionary - no easy way, we just need to grind out some code typing

to get the job done ;-0((

Here is what I have :-

=======================================================================

So that's it - we are done.

We can now compare the supplied 16th character with a one calculated by us and our code, to see if they match.

Any pairs of characters which do NOT match indicate to us that the supplied Fiscal Code is invalid.

Our Validation rule need to do this check sum process and see it the match is True or False, and affect the

resulting Validation object accordingly.

PHH - 7th. April 2016.


Generator - on the web

Generator - on the web

Generator - on the web

 Previous topic Next topic

You may like to see and use (try) the 'Generator' supplied on the web.

Here is the live link / URL :-

http://zip-codes.nonsolocap.it/codice-fiscale/

You may see something like the following (after entering similar information) :-

=====================================================================================================

This code generated by me using the tool, is included in the data within the Vulcan.NET solution I provide

with the conference material.

I suggest you find this generating tool on the web and give it a go - then add another Person item to the

collection in the STATIC method of the Person class.

Good Luck, ...................

PHH - 7th. April 2016.


Specific Validation Rule

Specific Validation Rule

Specific Validation Rule

 Previous topic Next topic

*** Note *** The following method / approach will not work for the UK postal code.

If the UK postal code had a fixed sequence format then it would be possible, but it does NOT ;-0((

Looks to me as if someone missed a trick when it was designed - very human, very useful, but a data

and system's man's nightmare !!!

==================================================================================

Okay then guys, this small sub-section is probably the most complex to get your heads around ;-0)

I say this simply because it is made up from a number of parts, and each of those is complex in its own way too.

I have designed and created this Validation Rule myself - its all my doing, and my fault if it is not totally correct.

The overriding idea behind the design is so that as we type in (enter) a new and suitable ICF we do not get red warnings

when the partly complete code is in fact correct, but just not fully entered.

At the same time I wanted the red warnings if in fact what had already been entered was NOT valid in its own right.

By this I mean that the fixed and 'linear / sequential' code format lends to us doing possible checks as each character

is typed in by the user.

I think from what I have researched and read (and what Wolfgang posted) that the code is in the form of "!!!!!!99!99!99!999!"

This means that we can test in different ways depending on the length of the input string.

Below the collapsed code image indicates the string length conditions :-

[ .. note only one of these is ever TRUE .. ]

==============================================================

By now seeing below the whole extent of the VR we can study lines 432 through 435 to see the RegExe

'pattern' which can be used for any string length up to 6 :-

========================================================================================================================

Note above, in lines 456 - 462, we see that the full code format can be tested, as well as the correctness (or other) of the

check sum included as the 16 character.

We test the checksum correctness first and then the full format - this seems to work even though there may be other

possible approaches.

Okay then guys, you get the general idea, of testing with different 'RegEx' patterns as we build the string.

We now need to check-out the method we have used to do the Regular Expression testing. This method shown below

will provide suitable validation error messages (or nothing) depending on the string input data - study carefully :-

======================================================================================================================================================

And now you may like to revisit the code that is used to validate any input string of length 10 or 11 characters in length.

It should have the format !!!!!!99!9 or !!!!!!99!99 - check below :-

=================================================================

And finally a look at just the lines which check the completed entry of 16 characters.

We could if we wish only use this test, and ignore the previous red warnings for shorter strings.

===============================================================================================================

Final comment :

The full above code approach seems really nice when in action by the user.

To me it is worth the effort ;-)

Lets move on ......................

PHH - 7th. April 2016.


Data

Data

Data

 Previous topic Next topic

When it comes to data and the MVVM approach things get a little more personal, or individual.

Personally, I like to have my data classes separate, and then a simple 'Model' which uses my data classes,

usually to simply present the data object(s) to the ViewModel.

Most often data presented to the Model is in the format of a collection of complex business items (objects).

For example, a collection of 'Person' objects, as seen in the details discussed below.

Here is the S.E. pane showing part of the structure of my Vulcan 'Validation' solution :-

=================================

Lets see what I have available in my data folders, first the 'myData.prg' file.

Here is a class (collapsed) I called 'ICF' for "Italian Codice Fiscale" :-

=========================================================================================

And if we start to expand some of the collapsed regions we see the following :-

======================================================================================

The main parts (regions) show one or more private fields, the event and method to handle property

change notification, and a sample Property definition which uses the notification method.

Here is the fully expanded sample Property definition :-

====================================================================

And now the fully expanded member and definition for the event handling :-

[ .. remember, that if you are doing data binding (a lot) then all your classes for data should have these included .. ]

========================================================================

========================================================================================================

And now a full listing of the fields, which have corresponding Property definitions :-

===================================================

The ICF class makes it quite easy to code the Check Sum members required for final entry checking.

Lets move on ...............

And now my 'Person' data class :

We need a 'Person' class as each Person object will have a property which is an 'ICF' object. One that

needs both validation and the check sum comparison being made.

Here below is a general outline of the 'Person' class :-

=====================================================================================================

Once again you will notice the private fields and public properties, as well as the notification of property changes.

We have an extra bit of 'bling' with the image of the person being handled / provided.

And on line 146 we see the start of a STATIC method to provide a collection of sample Person objects. We will see the

use of this in the Model we will define a little later.

Usually, it is good to have a fully define Constructor - useful in the STATIC method - see below :-

================================================================================================

Lets do something interesting as a little change to pure data processing etc., lets see how we make a image object from a JPG

or PNG file.

Check this out thoroughly :-

[ .. line 136 picks up the image file .. ]

=================================================================================================================================

I like to place my image files in a solution folder as seen below :-

======================================

In order to use these files we need to provide the syntax shown on line 136 in the above code.

Be warned, placing files in other folders can be tricky to get correct - even though the idea is a simple enough one.

Also, if we wish to use the files in the solution folder it is best to set the properties of each image file as seen below :-

[ .. the important bit is the 'Build Action' set to Resource .. ]

==================================================================================================================

Lets move on a pace or two ...............

We need to have a quick look at the full set of private fields in our Person class - see below :-

=======================================================

For each of the above we have a related public property.

Here is the fully expanded view of the property for the ICF :-

========================================================================

Yes, above is just like all the other public properties I define in my data classes to be used in Data Binding.

Code like above they always seem to work.

Now lets move on to data items and property values - our sample collection ..................

Sample Data - Items in a Collection :

Okay then guys, this is not rocket science, it just takes a little time and patience.

Below we see the collection being instantiated and two of the items also being instantiated and added to the collection.

Check the following details carefully :-

===================================================================================================================================================================

If you wish you can add more of your own Person objects.

Now we need to move to the next section to see how we use the STATIC method above in our Model.

See you there ...................

PHH - 7th. & 13th. April 2016.


Models

Models

Models

 Previous topic Next topic

Okay guys - a Models in MVVM terminology (or parlance) is actually some sort of a data object.

So a Model class is a data class.

Lets now see below what my SE pane looks like for Models in the Vulcan MPF Validation solution :-

====================================

Yes, although we will have three Views and three accompanying ViewModels, we only have one Model.

This is NOT wrong, we are just going to use the same Model for all three of our View / ViewModel pairings.

Lets see what our very simple Model looks like - check below :-

========================================================================================

Our above Model class has one empty Constructor and a STATIC method to supply some data.

In one sense the STATIC method is just a "pass-me-through" member, as it actually returns a collection

from using the STATIC method we saw previously, in the Person class definition.

So now we can move on to see how we use the Model to instantiate the ViewModel, and then couple to the View.

Remember, there are a few different (and correct) ways of applying data and a Model to the MVVM 'pattern' of

development.

Here below is a pretty standard way of doing things :-

=====================================================================================

And if we get things right we should see the following on-screen display :-

Yes, I know, with a simple example like this it seems crazy to define and use a Model class and

object, when we could have used the STATIC method directly from the Person class.

However, remember that we are trying to design and use a standard coding approach, and one which

stays the same when we get more complex, and need to pass in to the VM constructor a group, or

bunch, of collections and complex objects etc..

For these more complex situations we would make the Model more complex, and in the Constructor

of the VM assign the various complex properties to a variety of public properties in the VM - these would

then be used directly for binding purposes.

Its a GOOD idea to develop and use one standard way to doing (or applying) the MVVM pattern of development.

Lets move on .................................

PHH - 7th. April 2016.


ViewModels

ViewModels

ViewModels

 Previous topic Next topic

Every View (or GUI form) should have at least one 'ViewModel'. Yes, it could have more, no reason why not.

The VM ('ViewModel') should be kept as simple as possible and be coded so that it has public properties for

everything that will be 'data bound' to attributes in the XAML script.

The VM should be the 'hand' that fits the 'glove' of the 'View'.

People get their issues and problems from making the VM and V just too complex, and then also cutting corners in

what I suggest they do with script, code, and in particularly the notification or property changes in the data classes.

I follow my own advice (from previous eNotes published) and get things to work.

This year's eNotes for Cologne have been based firmly on the material I presented in my sessions last year - Cologne 2015.

I had to change nothing that I said, and the new stuff just worked as required - data binding, MVVM stuff, the lot.

I suggest you do the same.

Here is what I have at this end for my ViewModels :-

==========================================

Yes, we have a 'VM' to go with each of the three Views.

The following VM structure is simple, but also similar to the others that I create and use.

Public properties based on private fields, and with event based on notification of property changes.

Usually (always?) my VM's have a constructor which takes a data object as a parameter, and this is then

assigned to one or more of the VM's public properties.

Check out these details :-

==================================================================================================

*** Remember *** do NOT try and cut corners and save typing to do it your way - this is how you break

Data Binding ;-0((

Make sure that the 'Person' class handles property change notification correctly.

Lets move on ................

PHH - 7th. April 2016.


Views

Views

Views

 Previous topic Next topic

Okay then guys, every good ViewModel has been created to fit perfectly (hand in glove) with a well

designed and scripted 'View'.

So the functionality of a GUI form depends on the ViewModel and how it is coded, as well as the data Model

passed into the VM as a parameter.

Here are the three Views I have in my current demo Validation application.

Notice that two of the three are UC's (UserControls) and one is a Window UI element :-

=============================================

So a View is a Window or a UserControl.

Here is a sample of mine which is a UC :-

[ .. note that it is very similar in detail to Windows we have seen - with Grid and Grid.Resources etc. .. ]

=======================================================================================================

And now we can see the similarity with the following 'Window' element script :-

========================================================================================================================

*** Remember *** UC's can be assigned to ContentControls on a Window (or other). Windows can't be nested, but UCs can

be placed on a Window as long as we have a receiving container of type ContentControl.

Think of this as "View in View" like the TV has 'Picture in Picture'.

Lets move on ................

PHH - 7th. April 2016.


Displaying - MVVM style of approach

Displaying - MVVM style of approach

Displaying - MVVM style of approach

 Previous topic Next topic

In case you did not know it, the move to the MVVM pattern of development is actually a 'revolution' in

coding term on w Windows operating system.

Because Windows grew from small foundations, built mainly upon a GUI and visual approach, we had

Windows and its applications built firmly upon 'Events' and mostly graphical events.

So we all became 'event mad' and structured our application solutions around the Form and its related events.

All very good (in one sense) and we all did a lot of it, and even got to love what we did.

Now then, along comes a few guys some years back, who said our business applications are all about data. And

we should therefore be primarily be focusing our attention on data objects and collections of data objects.

The consequence of placing are business data objects are the foundation (or basis) of our code solutions, is that

we need to progress from our data towards the final UI element we still call a 'Form'. In the new terminology we

call this a 'View'. Simply because it lets us see our data objects.

If our data objects exist and come well before we have an instantiated 'View', then we need a way to bring our data

and the View (form) together. This is where Data Binding and the use of the ViewModel (as well as Model) come

into play.

We therefore need to reduce the amount of GUI events we use when in fact we are interested in data and changes of

properties, which is in fact part of our data. Business data objects (complex) are made up from a group, or bunch,

of properties.

So if we wish to track changes in our data values we need events based on property change notification and not GUI events.

Data binding completes the new approach in as much as it allows us to see (unchanged) the data values deep down

in our business objects, without passing values from one object to another and even assigning GUI element attributes

to business data values - all that stuff goes !

So we do this : we instantiate a Model using some data objects. We then instantiate a ViewModel using the Model as

input parameter - the constructor makes sure the Model is correct assigned to public properties within the VM.

A View is instantiated and then the VM is coupled (some call it loose coupling) to the View. Coupling is done via the

DataContext attribute / property on the View object.

Here is a typical example, from our current Vulcan.NET application, check it out carefully :-

===============================================================================================================================

Lines above of 23, 25 and 27 are the crucial ones which do the MVVM job.

Now we need to see how the empty constructor and any other are coded :-

=================================================================================================================================

Either way we have a public property of type ObservableCollection<Person> to act as an item for binding.

Here is another one (similar) which uses a UserControl as the 'View' :-

================================================================================================================================

You need to read and re-read the above details, as these are the VERY foundations of the modern way to structure and design

our code in our business applications.

Unfortunately, in many ways, we can't just rework and recode our old applications, because the whole approach

is different, I won't say "wrong".

I would suggest to any guy with legacy code that it is not worth the big effort to just turn it into .NET when so many

other things are left as legacy design and application 'style'.

Good Luck to all who have a bunch of legacy code.

Lets move on .................

PHH - 7th. April 2016.


Data Binding

Data Binding

Data Binding

 Previous topic Next topic

In fact folks this is the easy bit !

If we have set up the ViewModel correctly with a Model passed into the constructor as a parameter, and have this Model

assigned to a public property of the VM, then the XAML script part of the binding is easy indeed.

Lets look at how the script lines look for binding within a DataTemplate :-

====================================================================================================

And now on a 'DatePicker' UI element, within a template :-

=========================================================================================================

Sometimes we may have a 'TextBlock' on its own (outside of a 'DataTemplate') and then this is a

possible syntax :-

========================================================================================

Or even with a single tag and a 'TextBox' :-

=============================================================================================================

Note that above there is a slight different in syntax depending upon where we have to place the binding details.

However, none of this is rocket science ;-0)

In order to do the binding in the XAML script as seen above, we need our .NET code to couple correctly.

Here is a simple way in C# where a ViewModel (VM) is assigned to the DataContext property of the View object :-

================================================================

However, when we do 'MVVM pattern' in the full way we are more likely to see this below :-

====================================================================================

Note that the constructor input parameter on line 25 is a Model - just a data object.

Lets move on .............

PHH - 7th. April 2016.


working code and script

working code and script

working code and script

 Previous topic Next topic

=======================================================

Images are within the solution RAR file I supply.

=========================================================================================

// myCheckSumICF.prg

//----- PHH - 20th. March 2016. -----

#using System

#using System.Collections.Generic

#using System.Collections.ObjectModel   //----- ObservableCollection .....

#using System.ComponentModel   //----- INotifyPropertyChanged .....

//=========================================================================

#using VN_myData

begin namespace VN_myCheckSum

    PUBLIC CLASS MyCheckSumICF

//----- Italian Codice Fiscale - support members -----

        //----- fields -----

PUBLIC cInString AS STRING

//--- constructors ---

        CONSTRUCTOR()

SUPER()

        return

constructor(oInString as object)

SUPER()

SELF:cInString := (STRING)oInString

RETURN

//----- STATIC methods -----

PUBLIC Static Method MakeOddDict() AS Dictionary<STRING,INT>

//--- [ key, value ]---

local implied oddChars := Dictionary<STRING, INT>{}

oddChars:Add("0", 1)

oddChars:Add("1", 0)

oddChars:Add("2", 5)

oddChars:Add("3", 7)

oddChars:Add("4", 9)

oddChars:Add("5", 13)

oddChars:Add("6", 15)

oddChars:Add("7", 17)

oddChars:Add("8", 19)

oddChars:Add("9", 21)

oddChars:Add("A", 1)

oddChars:Add("B", 0)

oddChars:Add("C", 5)

oddChars:Add("D", 7)

oddChars:Add("E", 9)

oddChars:Add("F", 13)

oddChars:Add("G", 15)

oddChars:Add("H", 17)

oddChars:Add("I", 19)

oddChars:Add("J", 21)

oddChars:Add("K", 2)

oddChars:Add("L", 4)

oddChars:Add("M", 18)

oddChars:Add("N", 20)

oddChars:Add("O", 11)

oddChars:Add("P", 3)

oddChars:Add("Q", 6)

oddChars:Add("R", 8)

oddChars:Add("S", 12)

oddChars:Add("T", 14)

oddChars:Add("U", 16)

oddChars:Add("V", 10)

oddChars:Add("W", 22)

oddChars:Add("X", 25)

oddChars:Add("Y", 24)

oddChars:Add("Z", 23)

return oddChars

        //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

PUBLIC Static Method MakeEvenDict() AS Dictionary<STRING,INT>

//--- [ key, value ]---

local implied evenChars := Dictionary<STRING, INT>{}

            //--- [ Digits ] 48 - 57  is 0 to 9 -----

            LOCAL newEntry as STRING

            FOR local implied i := 0 upto 9

newEntry := string{(char)(48 + i), 1}

evenChars:Add(newEntry, i)

NEXT

//----- [Alpha characters] -----

            FOR local implied i := 0 upto 65

evenChars:Add( string{(char)(65 + i), 1}:ToString(), i )

NEXT

return evenChars

        //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

        PUBLIC STATIC METHOD MakeRemainderDict() AS Dictionary<INT, string>

            //--- [ key, value ]---

LOCAL IMPLIED Remainder := Dictionary<INT, STRING>{};

for LOCAL implied i := 0 upto 65

                Remainder:Add(i, STRING{(char)(65 + i), 1}:ToString() )

next

        RETURN Remainder

        //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

        PUBLIC STATIC METHOD FindCheckSum(instring AS STRING) AS STRING

            LOCAL IMPLIED cReturn := ""

            LOCAL DictOdds AS Dictionary<STRING, INT>

            DictOdds := MakeOddDict()

            LOCAL IMPLIED DictEvens := MakeEvenDict()

            local implied DictRemainders := MakeRemainderDict();

            LOCAL myICF := ICF{instring} as ICF

            //MessageBox.Show(myICF:FullString, "my full string")

            MyCheckSumICF.SplitFC2oddsAndEvens(myICF)

            MyCheckSumICF.TotalUpOddsEvens(myICF, DictOdds, DictEvens, DictRemainders);

        RETURN cReturn := myICF:CalcRemString

        //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

        PUBLIC STATIC METHOD SplitFC2oddsAndEvens(inICF AS VN_myData.ICF)  AS VOID

            LOCAL IMPLIED ToBeSplit := inICF:FullString

            LOCAL IMPLIED len := ToBeSplit:Length

            LOCAL myChars AS STRING[]

            myChars := STRING[]{len}

            //MessageBox.Show(ToBeSplit:ToString(),"my test 1")

            FOR local implied i := 0 upto len-1

               myChars[i+1] := ToBeSplit[i]:ToString()

               LOCAL cText := ToBeSplit[i]:ToString() as string

               //MessageBox.Show(cText, "my ith char :")

            NEXT

            //MessageBox.Show("loop done ...", "my ith char :")

            inICF:EvenString := myChars[2] + myChars[4] + myChars[6] + myChars[8] + myChars[10] + myChars[12] + myChars[14]

inICF:OddString := myChars[1] + myChars[3] + myChars[5] + myChars[7] + myChars[9] + myChars[11] + myChars[13] + myChars[15]

inICF:SpecRemString := myChars[16]

            //MessageBox.Show("loop assign done ...", "my ith char :")

            //MessageBox.Show(inICF:EvenString, "evens :")

            //MessageBox.Show(inICF:OddString, "odds :")

        RETURN

        //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

        PUBLIC STATIC METHOD TotalUpOddsEvens(inICF as ICF , DictO as Dictionary<string, int> , ;

DictE AS Dictionary<STRING, INT> , DictR AS Dictionary<INT, STRING> )  AS VOID

            inICF:TotalEvens := 0

inICF:TotalOdds := 0

local cChar as string

local nChar as int

local GrandTotal := 0 as int

local Remainder := 0 as int

LOCAL IMPLIED len := inICF:EvenString:Length

for LOCAL implied i := 0 upto len-1

cChar := inICF:EvenString[i]:ToString()

nChar := DictE[cChar]

inICF:TotalEvens += nChar

            next

len := inICF:OddString:Length

for LOCAL implied i := 0 upto len-1

cChar := inICF:OddString[i]:ToString()

nChar := DictO[cChar]

inICF:TotalOdds += nChar

            next

GrandTotal := inICF:TotalEvens + inICF:TotalOdds

Remainder := GrandTotal % 26

inICF:CalcRemString := DictR[Remainder]

        return

        //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

        public static method CreateCheckSum(oInString as object) as string

            local implied cReturn := ""

local cInString := ((string)oInString):ToUpper() as string

cInString := cInString:Replace(" ", "")

            //MessageBox.Show(cInString,"my instring")

            //MessageBox.Show(cInString:Length:ToString(),"my len instring")

if (!(cInString:Length == 16))

return cReturn

endif

cReturn := MyCheckSumICF.FindCheckSum(cInString)

            //MessageBox.Show(cReturn,"my return CCS")

        return cReturn

        //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

         public static method CompareCheckSums(oInString as object) as logic

            local implied lReturn := false

local cInString := ((string)oInString):ToUpper() as string

cInString := cInString:Replace(" ", "")

            //MessageBox.Show(cInString,"my instring")

            //MessageBox.Show(cInString:Length:ToString(),"my len instring")

if (!(cInString:Length == 16))

return lReturn

            endif

            local implied cReturn := ""

cReturn := cInString:Substring(15,1)

            LOCAL cCheck as string

            cCheck := MyCheckSumICF.CreateCheckSum(cInString)

            if (cReturn == cCheck)

                lReturn := true

            endif

            //MessageBox.Show(lReturn:ToString(),"my comparison :")

        return lReturn

        //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

END CLASS

//====================================================================================

 END NAMESPACE

=========================================================================================

//----- MyConverters.prg  March 22 nd. 2016. -----

#using System

#using System.Globalization

#using System.Text.RegularExpressions

#USING System.Windows.Data

//-----------------------------------

#using MyMasking

BEGIN NAMESPACE MyConverters

    PUBLIC CLASS String2NameText IMPLEMENTS IValueConverter

        public method Convert(value as object, targetType as Type, parameter as object, culture as CultureInfo) as object

    return InputMasking.AllowOnlyNameChars(value:ToString())

public method ConvertBack(value as object, targetType as Type, parameter as object, culture as CultureInfo) as object

//----- this is a 'pass through' member -----

return value

   END CLASS

   //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

   public class String2UpperCaseText implements IValueConverter

public method Convert(value as object ,targetType as Type , parameter as object, culture as CultureInfo) as object

if (value == null)

return ""

            endif

return (value:ToString()):ToUpper()

public method ConvertBack(value as object, targetType as Type, parameter as object, culture as CultureInfo) as object

//----- this is a 'pass through' member -----

RETURN value

END CLASS

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

END NAMESPACE

=========================================================================================

//----- myData.prg -----

//----- PHH - 20th. March 2016. -----

#using System

#using System.Collections.Generic

#using System.Collections.ObjectModel   //----- ObservableCollection .....

#using System.ComponentModel   //----- INotifyPropertyChanged .....

//=========================================================================

begin namespace VN_myData

    CLASS ICF IMPLEMENTS INotifyPropertyChanged

        //----- Italian Codice Fiscale -----

        //----- fields -----

        private _fullString as string

private _oddString as string

private _evenString as string

private _calcRemString as string

private  _specRemString as string

private _totalOdds as int

private _totalEvens as int

         //----- events -----

        EVENT PropertyChanged AS PropertyChangedEventHandler

        //----- constructors -----

        CONSTRUCTOR()

            SUPER()

        RETURN

        CONSTRUCTOR(cInput as string)

            SUPER()

            SELF:FullString := cInput

        RETURN

        //----- properties -----

        property FullString as string

            get

                return self:_fullString

            end get

            set

                self:_fullString := value

                self:NotifyPropertyChanged("FullString")

            end set

        END PROPERTY

        property OddString as string

            get

                return self:_oddString

            end get

            set

                self:_oddString := value

                self:NotifyPropertyChanged("OddString")

            end set

        END PROPERTY

        property EvenString as string

            get

                return self:_evenString

            end get

            set

                self:_evenString := value

                self:NotifyPropertyChanged("EvenString")

            end set

        END PROPERTY

        property CalcRemString as string

            get

                return self:_calcRemString

            end get

            set

                self:_calcRemString := value

                self:NotifyPropertyChanged("CalcRemString")

            end set

        END PROPERTY

        property SpecRemString as string

            get

                return self:_specRemString

            end get

            set

                self:_specRemString := value

                self:NotifyPropertyChanged("SpecRemString")

            end set

        END PROPERTY

        property totalOdds as int

            get

                return self:_totalOdds

            end get

            set

                self:_totalOdds := value

                self:NotifyPropertyChanged("totalOdds")

            end set

        END PROPERTY

        property totalEvens as int

            get

                return self:_totalEvens

            end get

            set

                self:_totalEvens := value

                self:NotifyPropertyChanged("totalEvens")

            end set

        END PROPERTY

        //----- methods -----

        method NotifyPropertyChanged(propertyName as string) as void

            if (self:PropertyChanged != null)

                self:PropertyChanged:Invoke(self, PropertyChangedEventArgs{propertyName})

            endif

        return

    END CLASS

//====================================================================================

//====================================================================================

END NAMESPACE // VN_myData

=========================================================================================

//----- myPersons.prg -----

//----- PHH - 23rd. March 2016. -----

#using System

#using System.IO

#using System.Collections.Generic

#using System.Collections.ObjectModel   //----- ObservableCollection .....

#using System.ComponentModel   //----- INotifyPropertyChanged .....

//=========================================================================

#using System.Windows.Media

#using System.Windows.Media.Imaging

begin namespace VN_myData

//====================================================================================

    class Person implements INotifyPropertyChanged

        //----- for Italian Codice Fiscale -----

        //----- fields -----

        private _firstname as string

private _lastname as string

private _company as string

        private _eMail as string

        private _telephone as string

        private  _firstContact as DateTime

        private _codiceFiscale as ICF

        private _faceImage as BitmapImage

        //----- events -----

        event PropertyChanged as PropertyChangedEventHandler

        //----- constructors -----

        constructor()

            super()

        return

        constructor(cF as string, cL as string)

            super()

            self:Firstname := cF

            self:Lastname := cL

        return

        constructor(cF as string, cL as string, cC as string, cE as string, cT as string, dtFirst as DateTime, oCF as ICF)

            super()

            self:Firstname := cF

            self:Lastname := cL

            self:Company := cC

            SELF:eMail := cE

            self:Telephone := cT

            self:FirstContact := dtFirst

            self:CodiceFiscale := oCF

        return

        //----- properties -----

        property Firstname as string

            get

                return self:_firstname

            end get

            set

                self:_firstname := value

                self:NotifyPropertyChanged("Firstname")

            end set

        END PROPERTY

        property Lastname as string

            get

                return self:_lastname

            end get

            set

                self:_lastname := value

                self:NotifyPropertyChanged("Lastname")

            end set

        END PROPERTY

        property Company as string

            get

                return self:_company

            end get

            set

                self:_company := value

                self:NotifyPropertyChanged("Company")

            end set

        END PROPERTY

        property eMail as string

            get

                return self:_eMail

            end get

            set

                self:_eMail := value

                self:NotifyPropertyChanged("eMail")

            end set

        END PROPERTY

        property Telephone as string

            get

                return self:_telephone

            end get

            set

                self:_telephone := value

                self:NotifyPropertyChanged("Telephone")

            end set

        END PROPERTY

        property FirstContact as DateTime

            get

                return self:_firstContact

            end get

            set

                self:_firstContact := value

                self:NotifyPropertyChanged("FirstContact")

            end set

        END PROPERTY

        property CodiceFiscale as ICF

            get

                return self:_codiceFiscale

            end get

            set

                self:_codiceFiscale := value

                self:NotifyPropertyChanged("CodiceFiscale")

            end set

        end property

        property FaceImage as BitmapImage

            get

                return self:_faceImage

            end get

            set

                self:_faceImage := value

                self:NotifyPropertyChanged("FaceImage")

            end set

        end property

        //----- methods -----

        method AddFaceImage(ImageFile as string) as void

            local oBI as BitmapImage

            oBI := BitmapImage{}

            oBI:BeginInit()

            local cFilePath := System.IO.Directory.GetCurrentDirectory() +"\"+ "..\e_Images\"+ImageFile as string

            if file.exists(cFilePath)

                oBI:UriSource := Uri{cFilePath, UriKind.RelativeOrAbsolute}

                oBI:EndInit()

                self:FaceImage := oBI

            end if

        return

        //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

        public static method GetPersons() as ObservableCollection<Person>

local MyPersons as ObservableCollection<Person>

            MyPersons := ObservableCollection<Person>{}

            local oP as Person

            oP := Person{"James","Dean","Moonshine Widgets","jim@somewhere.co.uk","01633 245057",DateTime{2002,05,13},ICF{"MRTMTT25D09F205Z"}}

            oP:AddFaceImage("images6.jpg")

            MyPersons:Add(oP)

            oP := Person{"Mary","Malone","Sunlight Prods","MM@sunprods.net","+44 (0) 2043 239057",DateTime{2005,05,25},ICF{"VRDGPP13R10B293P"}}

            oP:AddFaceImage("images1.jpg")

            MyPersons:Add(oP)

            oP := Person{"Willie","Wonker","Chocolate Bars","willie@wonkerbars.com","014336 321057",DateTime{2011,07,11},ICF{"VRDGPP13R10B29PP"}}

            oP:AddFaceImage("images5.jpg")

MyPersons:Add(oP)

            oP := Person{"Matteo","Moretti","Flat Shelves","mat@overthere.com","0123 56789",DateTime{2007,11,30},ICF{"MRTMTT25D09F205Z"}}

            oP:AddFaceImage("images2.png")

            MyPersons:Add(oP)

            oP := Person{"Annette","Baker","Round Tins","annette@bakingtins.co.uk","+44 1633 567891",DateTime{2007,11,30},ICF{"BKRNTT89E53H738D"}}

            oP:AddFaceImage("images3.png")

            MyPersons:Add(oP)

            oP := Person{"Samantha","Miller","Curve Corners","Sam@NiceOne.net","+44 (0)1434 563421",DateTime{2010,09,23},ICF{"MLLSNT82P65Z404U"}}

            oP:AddFaceImage("images4.jpg")

            MyPersons:Add(oP)

        return MyPersons

        //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

        method NotifyPropertyChanged(propertyName as string) as void

            if (self:PropertyChanged != null)

                self:PropertyChanged:Invoke(self, PropertyChangedEventArgs{propertyName})

            endif

        return

    end class

end namespace // VN_myData

=========================================================================================

//----- myMasking.prg - march 22nd. 2016. -----

#using System

#using System.Globalization

#using System.Text.RegularExpressions

#USING System.Windows.Data

BEGIN NAMESPACE MyMasking

PUBLIC CLASS InputMasking

    PUBLIC STATIC PROPERTY AllowOnlyNameCharsString AS STRING

        GET

            RETURN e"[0-9\"!£$%^&*?><#~,.@~'|/:;+=_()]"

        END GET

    END PROPERTY

    PUBLIC STATIC METHOD AllowOnlyNameChars(input AS STRING) AS STRING

//--- check for numerics and !"£$ stuff ---

//--- below masks the double quote " as well ---

LOCAL output AS STRING

            LOCAL pattern := InputMasking.AllowOnlyNameCharsString AS STRING

IF Regex.IsMatch(input, pattern)

LOCAL replacement := "" AS STRING

output := Regex.Replace(input, pattern, replacement)

RETURN output

ENDIF

RETURN input

    public static method AllowOnlyDigitsInNumerics(input as string) as string

    //--- check for non numerics ---

    //--- current below details need changing ! ---

    local output as string

    //local pattern := InputMasking.AllowOnlyNameCharsString;

    local pattern := e"[a-zA-Z\"!£$%^&*?><#~,.@~'|/:;=_()]" as string

    if Regex.IsMatch(input, pattern)

    local replacement := "" as string

    output := Regex.Replace(input, pattern, replacement)

    return output

    endif

    RETURN input

    public static method RemoveAllButNameChars(input as string) as string

local output := "" as string

local implied len := input:Length

if (input:Length < 1)

RETURN ""

        ENDIF

//--------------------------------

LOCAL NChar AS STRING

        FOR LOCAL i := 0 as int upto len-1

NChar := input:Substring(i, 1)

if Regex.IsMatch(NChar, InputMasking.AllowOnlyNameCharsString)

NChar := ""

endif

output += NChar

next

return output

END CLASS

END NAMESPACE

=========================================================================================

//----- myVRs.prg - March 2016. -----

#using System

#using System.Globalization

#using System.Text.RegularExpressions

#using System.Windows.Controls

//-----------------------------------

#using VN_myCheckSum

BEGIN NAMESPACE MyVRs

PUBLIC CLASS AgeRangeRule inherit ValidationRule

    //--- fields ---

    PRIVATE _min AS INT

    PRIVATE _max AS INT

    PUBLIC PROPERTY Min AS INT

        GET

            return _min

        END GET

        SET

            _min := value

        END SET

    END PROPERTY

    PUBLIC PROPERTY Max AS INT

        GET

            return _max

        END GET

        SET

            _max := value

        END SET

    END PROPERTY

    public method Validate(value as object, cultureInfo as CultureInfo) as ValidationResult

//--------------------------------------------

        LOCAL age := 0 as INT

        TRY

            IF ((STRING)value):Length > 0

                age := Int32.Parse((STRING)value)

            ENDIF

        CATCH oEX AS System.Exception

            RETURN ValidationResult{FALSE, "Illegal characters or " + oEX:Message}

        END TRY

        IF (age < Min) || (age > Max)

RETURN ValidationResult{FALSE, "Please enter an age in the range: " + Min:ToString() + " - " + Max:ToString() + "."}

ENDIF

            //---------------------------------------------

RETURN ValidationResult{TRUE, NULL}

END CLASS

//==========================================================================

PUBLIC CLASS ShellAgeRangeVR inherit ValidationRule

    //--- fields ---

    PRIVATE _min AS INT

    PRIVATE _max AS INT

    PUBLIC PROPERTY Min AS INT

        GET

            return _min

        END GET

        SET

            _min := value

        END SET

    END PROPERTY

    PUBLIC PROPERTY Max AS INT

        GET

            RETURN _max

        END GET

        SET

            _max := value

        END SET

    END PROPERTY

    PUBLIC METHOD Validate(value AS OBJECT, cultureInfo AS cultureInfo) AS ValidationResult

RETURN GeneralCoreVRs.CoreAgeRangeVR(value, Min, Max)

END CLASS

//==========================================================================

class LettersOnlyStringRule inherit System.Windows.Controls.ValidationRule

    // Methods

    virtual method Validate(value as Object, cultureInfo as System.Globalization.CultureInfo) as System.Windows.Controls.ValidationResult

        local input as string

        //

        input := ""

        try

            //

            if ( ((string)value):Length > 0 )

                //

                input := value:ToString()

            endif

        catch exception as System.Exception

            //

            return System.Windows.Controls.ValidationResult{false, String.Concat("Something wrong or ", exception:Message)}

        end try

        if (! System.Text.RegularExpressions.Regex.IsMatch(input, "^[a-zA-Z]+$"))

            //

            return System.Windows.Controls.ValidationResult{false, "ONLY letters! (a-z, A-Z)."}

        endif

        return System.Windows.Controls.ValidationResult{true, null}

end class

class LettersMainlyStringRule inherit System.Windows.Controls.ValidationRule

    // Methods

    virtual method Validate(value as Object, cultureInfo as System.Globalization.CultureInfo) as System.Windows.Controls.ValidationResult

        local input as string

        //

        input := ""

        try

            //

            if ( ((string)value):Length > 0 )

                //

                input := value:ToString()

            endif

        catch exception as System.Exception

            //

            return System.Windows.Controls.ValidationResult{false, String.Concat("Something wrong or ", exception:Message)}

        end try

        if (! System.Text.RegularExpressions.Regex.IsMatch(input, "^[a-zA-Z _]+$"))

            //

            return System.Windows.Controls.ValidationResult{false, "ONLY letters! (a-z, A-Z)."}

        endif

        return System.Windows.Controls.ValidationResult{true, null}

end class

class BritishTelephoneVR inherit System.Windows.Controls.ValidationRule

    // Methods

    virtual method Validate(value as Object, cultureInfo as System.Globalization.CultureInfo) as System.Windows.Controls.ValidationResult

        local input as string

        input := ""

        local cTele as string

        cTele := "^\(?(?:(?:0(?:0|11)\)?[\s-]?\(?|\+)44\)?[\s-]?\(?(?:0\)?[\s-]?"

        cTele += "\(?)?|0)(?:\d{2}\)?[\s-]?\d{4}[\s-]?\d{4}|\d{3}\)?[\s-]?\d{3}[\s-]?"

        cTele += "\d{3,4}|\d{4}\)?[\s-]?(?:\d{5}|\d{3}[\s-]?\d{3})|\d{5}\)?[\s-]?"

        cTele += "\d{4,5}|8(?:00[\s-]?11[\s-]?11|45[\s-]?46[\s-]?4\d))(?:(?:[\s-]?(?:x|ext\.?\s?|\#)\d+)?)$"

        try

            if ( ((string)value):Length > 0 )

                input := value:ToString()

            endif

            if (!Regex.IsMatch(input,cTele))

                return System.Windows.Controls.ValidationResult{false, ".. wrong details !? - like '+44 (0)1633-243057'.."}

            endif

        catch exception as System.Exception

            return System.Windows.Controls.ValidationResult{false, String.Concat("Something wrong or ", exception:Message)}

        end try

        return System.Windows.Controls.ValidationResult{true, null}

end class

class UpperCaseOnlyStringRule inherit System.Windows.Controls.ValidationRule

    // Methods

    virtual method Validate(value as Object, cultureInfo as System.Globalization.CultureInfo) as System.Windows.Controls.ValidationResult

        local input as string

        //

        input := ""

        try

            //

            if ( ((string)value):Length > 0)

                //

                input := value:ToString()

            endif

        catch exception as System.Exception

            //

            return System.Windows.Controls.ValidationResult{false, String.Concat("Something wrong or ", exception:Message)}

        end try

        if (! System.Text.RegularExpressions.Regex.IsMatch(input, "^[A-Z]+$"))

            //

            return System.Windows.Controls.ValidationResult{false, "ONLY Upper Case! (A-Z)."}

        endif

        return System.Windows.Controls.ValidationResult{true, null}

END CLASS

class Mask01ValidationRule inherit System.Windows.Controls.ValidationRule

    // Methods

    virtual method Validate(value as Object, cultureInfo as System.Globalization.CultureInfo) as System.Windows.Controls.ValidationResult

        local input as string

        //

        input := ""

        try

            //

            if (((string)value):Length > 0)

                //

                input := value:ToString()

            endif

        catch exception as System.Exception

            //

            return System.Windows.Controls.ValidationResult{false, String.Concat("Something wrong or ", exception:Message)}

        end try

        if (! System.Text.RegularExpressions.Regex.IsMatch(input, "^[A-Z]{3}\.[0-9]{3}$", System.Text.RegularExpressions.RegexOptions.IgnoreCase))

            //

            return System.Windows.Controls.ValidationResult{false, "Mask in format XXX.NNN - type the dot!"}

        endif

        return System.Windows.Controls.ValidationResult{true, null}

end class

class RegexGeneralValidationRule inherit System.Windows.Controls.ValidationRule

    // Fields

    private _errorPrompt as string

    private _myRegexOptions as string

    private _regexText as string

    // Methods

    virtual method Validate(value as Object, cultureInfo as System.Globalization.CultureInfo) as System.Windows.Controls.ValidationResult

        local input as string

        //

        input := ""

        try

            //

            if (((string)value):Length > 0)

                //

                input := value:ToString()

            endif

        catch exception as System.Exception

            //

            return System.Windows.Controls.ValidationResult{false, String.Concat("Something wrong or ", exception:Message)}

        end try

        if (! System.Text.RegularExpressions.Regex.IsMatch(input, "^[A-Z]{3}\.[0-9]{3}$", System.Text.RegularExpressions.RegexOptions.IgnoreCase))

            //

            return System.Windows.Controls.ValidationResult{false, "Mask in format XXX.NNN - type the dot!"}

        endif

        return System.Windows.Controls.ValidationResult{true, null}

    // Properties

    property ErrorPrompt as string

        Get

            //

            return self:_errorPrompt

        End Get

        Set

            //

            self:_errorPrompt := value

        End Set

    end property

    property MyRegexOptions as string

        Get

            //

            return self:_myRegexOptions

        End Get

        Set

            //

            self:_myRegexOptions := value

        End Set

    end property

    property RegexText as string

        Get

            //

            return self:_regexText

        End Get

        Set

            //

            self:_regexText := value

        End Set

    end property

end class

class LowerCaseOnlyStringRule inherit System.Windows.Controls.ValidationRule

    virtual method Validate(value as object, cultureInfo as System.Globalization.CultureInfo) ;

                                                    as System.Windows.Controls.ValidationResult

        local input as string

        input := ""

        try

            if (((string)value):Length > 0)

                input := value:ToString()

            endif

        catch exception as System.Exception

            return System.Windows.Controls.ValidationResult{false, String.Concat("Something wrong or ", exception:Message)}

        end try

        if (! System.Text.RegularExpressions.Regex.IsMatch(input, "^[a-z]+$"))

            return System.Windows.Controls.ValidationResult{false, "ONLY Lower Case! (a-z)."}

        endif

        return System.Windows.Controls.ValidationResult{true, null}

end class

class emailAddressValidationRule inherit System.Windows.Controls.ValidationRule

    // Fields

    private _allowEmpty := true as Logic

    // Methods

    virtual method Validate(value as Object, cultureInfo as System.Globalization.CultureInfo) as System.Windows.Controls.ValidationResult

        local input as string

        local pattern as string

        //

        input := ""

        try

            input := value:ToString()

            if ((input:Trim() == "") .and. self:AllowEmpty)

                //

                return System.Windows.Controls.ValidationResult{true, null}

            endif

            if  ((input:Trim() == "") .and. !self:AllowEmpty)

                return System.Windows.Controls.ValidationResult{false, "... can't be emplty ..."}

            endif

        catch exception as System.Exception

            //

            return System.Windows.Controls.ValidationResult{false, String.Concat("Something wrong or ", exception:Message)}

        end try

        pattern := ""

        pattern := "^(?("+chr(34)+")("+chr(34)+".+?(?<!\\)"+chr(34)+"@)|(([\w]((\.(?!\.))|[-!#\$%&"+chr(39)+"\*\+/=\?\^`\{\}\|~\w])*)(?<=[\w])@))(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([\w][-\w]*[\w]*\.)+[\w][\-\w]{0,22}[\w]))$"

        if (! System.Text.RegularExpressions.Regex.IsMatch(input, pattern))

            //

            return System.Windows.Controls.ValidationResult{false, "Not a valid eMail address !"}

        endif

        return System.Windows.Controls.ValidationResult{true, null}

    // Properties

    property AllowEmpty as Logic

        Get

            //

            return self:_allowEmpty

        End Get

        Set

            //

            if (self:_allowEmpty != value)

                //

                self:_allowEmpty := value

            endif

        End Set

    end property

end class

class UKpostCodeStringRule inherit System.Windows.Controls.ValidationRule

    // Methods

    virtual method Validate(value as Object, cultureInfo as System.Globalization.CultureInfo) as System.Windows.Controls.ValidationResult

        local input as string

        local pattern as string

        //

        input := ""

        try

            //

            if (((string)value):Length > 0)

                //

                input := value:ToString()

            endif

        catch exception as System.Exception

            //

            return System.Windows.Controls.ValidationResult{false, String.Concat("Something wrong or ", exception:Message)}

        end try

        pattern := ""

        pattern := "\b([A-PR-UWYZ][A-HK-Y]?[0-9]?[0-9]? {1,2}[0-9][ABD-HJLN-UW-Z]{2}|GIR 0AA)\b"

        if (! System.Text.RegularExpressions.Regex.IsMatch(input, pattern, System.Text.RegularExpressions.RegexOptions.IgnoreCase))

            //

            return System.Windows.Controls.ValidationResult{false, "Not a valid Post Code!"}

        endif

        return System.Windows.Controls.ValidationResult{true, null}

end class

class ItalianIdentityStringRule inherit System.Windows.Controls.ValidationRule

    // Methods

    virtual method Validate(value as Object, cultureInfo as System.Globalization.CultureInfo) as System.Windows.Controls.ValidationResult

        local input as string

        local pattern as string

        //

        input := ""

        try

            //

            if (((string)value):Length > 0)

                //

                input := value:ToString()

            endif

        catch exception as System.Exception

            //

            return System.Windows.Controls.ValidationResult{false, String.Concat("Something wrong or ", exception:Message)}

        end try

        pattern := ""

        pattern := "^[A-Z]{6}\d{2}[A-Z]\d{2}[A-Z]\d{3}[A-Z]$"

        if (! System.Text.RegularExpressions.Regex.IsMatch(input, pattern, System.Text.RegularExpressions.RegexOptions.IgnoreCase))

            //

            return System.Windows.Controls.ValidationResult{false, "Not a valid Italian ID number !"}

        endif

        return System.Windows.Controls.ValidationResult{true, null}

end class

class ItalianIdentityStringRule2 inherit System.Windows.Controls.ValidationRule

    method DoRegexTest(input as string, pattern as string) as System.Windows.Controls.ValidationResult

        if (! System.Text.RegularExpressions.Regex.IsMatch(input, pattern, System.Text.RegularExpressions.RegexOptions.IgnoreCase))

            return System.Windows.Controls.ValidationResult{false, "WRONG - Italian ID number is "+chr(39)+"!!!!!!99!99!999!"+chr(39)}

        endif

        if (input:Length != 16)

            return System.Windows.Controls.ValidationResult{false, "not yet completed - needs 16 chars."}

        endif

    return System.Windows.Controls.ValidationResult{true, null}

    virtual method Validate(value as object, cultureInfo as System.Globalization.CultureInfo) ;

                                                       as System.Windows.Controls.ValidationResult

        local input as string

        local length as Long

        local pattern as string

        input := ""

        length := 0

        try

            length := ((string)value):Length

            if (length > 0)

                input := value:ToString()

            endif

        catch exception as System.Exception

            return System.Windows.Controls.ValidationResult{false, String.Concat("Something wrong or ", exception:Message)}

        end try

        pattern := ""

        if (length <= 6)

            pattern := "^[A-Z]{1,6}$"

            return self:DoRegexTest(input, pattern)

        endif

        if ((length > 6) .and. (length <= 8))

            pattern := "^[A-Z]{6}\d{1,2}$"

            return self:DoRegexTest(input, pattern)

        endif

        if (length == 9)

            pattern := "^[A-Z]{6}\d{2}[A-Z]$"

            return self:DoRegexTest(input, pattern)

        endif

        if ((length > 9) .and. (length <= 11))

            pattern := "^[A-Z]{6}\d{2}[A-Z]\d{1,2}$"

            return self:DoRegexTest(input, pattern)

        endif

        if (length == 12)

            pattern := "^[A-Z]{6}\d{2}[A-Z]\d{2}[A-Z]$"

            return self:DoRegexTest(input, pattern)

        endif

        if ((length > 12) .and. (length < 0x10))

            pattern := "^[A-Z]{6}\d{2}[A-Z]\d{2}[A-Z]\d{1,3}$"

            return self:DoRegexTest(input, pattern)

        endif

        if (length == 16)

            if (MyCheckSumICF.CompareCheckSums(input) )

                pattern := "^[A-Z]{6}\d{2}[A-Z]\d{2}[A-Z]\d{3}[A-Z]$"

                return self:DoRegexTest(input, pattern)

            endif

            return System.Windows.Controls.ValidationResult{false, "check sums do not match !?"}

        endif

        if ((length < 1) .or. (length > 16))

            pattern := "^[A-Z]{6}\d{2}[A-Z]\d{2}[A-Z]\d{3}[A-Z]$"

            return self:DoRegexTest(input, pattern)

        endif

    return System.Windows.Controls.ValidationResult{false, "Something odd is up !? ... "}

end class

class GeneralStringRule inherit System.Windows.Controls.ValidationRule

    // Fields

    private _allowEmpty as Logic

    private _allowSpaces as Logic

    private _max as Long

    private _min as Long

    // Methods

        constructor();super()

        //

        self:Min := 0

        self:Max := 0x3e8

    virtual method Validate(value as Object, cultureInfo as System.Globalization.CultureInfo) as System.Windows.Controls.ValidationResult

        local str as string

        local length as Long

        local objArray1 as Object[]

        //

        str := ""

        length := ((string)value):Length

        try

            //

            if (length > 0)

                //

                str := value:ToString()

            else

                //

                if ((length == 0) .and. ! self:AllowEmpty)

                    //

                    return System.Windows.Controls.ValidationResult{false, "String can"+chr(39)+"t be empty : "}

                endif

                if ((length == 0) .and. self:AllowEmpty)

                    //

                    return System.Windows.Controls.ValidationResult{true, null}

                endif

                str := value:ToString()

            endif

        catch exception as System.Exception

            //

            return System.Windows.Controls.ValidationResult{false, String.Concat("Something wrong or ", exception:Message)}

        end try

        if (! self:AllowSpaces .and. str:Contains(" "))

            //

            return System.Windows.Controls.ValidationResult{false, "String contains space characters : "}

        endif

        if ((length < self:Min) .or. (length > self:Max))

            //

            objArray1 := <Object>{"String length NOT in the range: ", self:Min, " - ", self:Max, "."}

            return System.Windows.Controls.ValidationResult{false, String.Concat(objArray1)}

        endif

        return System.Windows.Controls.ValidationResult{true, null}

    // Properties

    property AllowEmpty as Logic

        Get

            //

            return self:_allowEmpty

        End Get

        Set

            //

            self:_allowEmpty := value

        End Set

    end property

    property AllowSpaces as Logic

        Get

            //

            return self:_allowSpaces

        End Get

        Set

            //

            self:_allowSpaces := value

        End Set

    end property

    property Max as Long

        Get

            //

            return self:_max

        End Get

        Set

            //

            self:_max := value

        End Set

    end property

    property Min as Long

        Get

            //

            return self:_min

        End Get

        Set

            //

            self:_min := value

        End Set

    end property

end class

class StringToIntValidationRule inherit System.Windows.Controls.ValidationRule

    // Methods

    virtual method Validate(value as Object, cultureInfo as System.Globalization.CultureInfo) as System.Windows.Controls.ValidationResult

        local num as Long

        //

        if (Int32.TryParse(value:ToString(), num))

            //

            return System.Windows.Controls.ValidationResult{true, null}

        endif

        return System.Windows.Controls.ValidationResult{false, "Please enter for me a valid integer value."}

END CLASS

class TestDateVR inherit System.Windows.Controls.ValidationRule

    // Fields

    private _maxDate as System.DateTime

    private _minDate as System.DateTime

    // Methods

        constructor();super()

        //

        self:MinDate := System.DateTime{0x76d, 1, 1}

        self:MaxDate := System.DateTime{0x778, 2, 2}

    virtual method Validate(value as Object, cultureInfo as System.Globalization.CultureInfo) as System.Windows.Controls.ValidationResult

        local time as System.DateTime

        //

        time := System.DateTime{0x799, 3, 3}

        try

            //

            time := (System.DateTime)value

            if ((time < self:MinDate) .or. (time > self:MaxDate))

                //

                return System.Windows.Controls.ValidationResult{false, "Date outside the specified bounds ... ? "}

            endif

        catch exception as System.Exception

            //

            return System.Windows.Controls.ValidationResult{false, String.Concat("Date not right !? ", exception:Message)}

        end try

        return System.Windows.Controls.ValidationResult{true, null}

    // Properties

    property MaxDate as System.DateTime

        Get

            //

            return self:_maxDate

        End Get

        Set

            //

            self:_maxDate := value

        End Set

    end property

    property MinDate as System.DateTime

        Get

            //

            return self:_minDate

        End Get

        Set

            //

            self:_minDate := value

        End Set

    end property

end class

class TestBDayDateVR inherit System.Windows.Controls.ValidationRule

    // Fields

    export BDay as System.DateTime

    // Methods

        constructor();super()

        local time as System.DateTime

        //

        time := System.DateTime{0x7c4, 4, 1}

    virtual method Validate(value as Object, cultureInfo as System.Globalization.CultureInfo) as System.Windows.Controls.ValidationResult

        local time as System.DateTime

        local time2 as System.DateTime

        //

        time := System.DateTime{0x79d, 3, 13}

        time2 := System.DateTime{0x7c4, 1, 4}

        try

            //

            time := (System.DateTime)value

            if (time == time2)

                //

                return System.Windows.Controls.ValidationResult{false, "Birthday - Many Happy Returns !!!"}

            endif

        catch exception as System.Exception

            //

            return System.Windows.Controls.ValidationResult{false, String.Concat("Date not right !? ", exception:Message)}

        end try

        return System.Windows.Controls.ValidationResult{true, null}

end class

class TestMaxDateVR inherit System.Windows.Controls.ValidationRule

    // Fields

    private _maxDate as System.DateTime

    // Methods

        constructor();super()

        //

        self:MaxDate := System.DateTime{0x778, 2, 2}

    virtual method Validate(value as Object, cultureInfo as System.Globalization.CultureInfo) as System.Windows.Controls.ValidationResult

        local time as System.DateTime

        //

        time := System.DateTime{0x799, 3, 3}

        try

            //

            time := (System.DateTime)value

            if (time > self:MaxDate)

                //

                return System.Windows.Controls.ValidationResult{false, String.Concat("Date too late ... latest is :  ", self:MaxDate:ToShortDateString())}

            endif

        catch exception as System.Exception

            //

            return System.Windows.Controls.ValidationResult{false, String.Concat("Date not right !? ", exception:Message)}

        end try

        return System.Windows.Controls.ValidationResult{true, null}

    // Properties

    property MaxDate as System.DateTime

        Get

            //

            return self:_maxDate

        End Get

        Set

            //

            self:_maxDate := value

        End Set

    end property

end class

class TestMinDateVR inherit System.Windows.Controls.ValidationRule

    // Fields

    private _minDate as System.DateTime

    // Methods

        constructor();super()

        //

        self:MinDate := System.DateTime{0x76d, 1, 1}

    virtual method Validate(value as Object, cultureInfo as System.Globalization.CultureInfo) as System.Windows.Controls.ValidationResult

        local time as System.DateTime

        //

        time := System.DateTime{0x79d, 3, 3}

        try

            //

            time := (System.DateTime)value

            if (time < self:MinDate)

                //

                return System.Windows.Controls.ValidationResult{false, String.Concat("Date too early ... earliest is :  ", self:MinDate:ToShortDateString())}

            endif

        catch exception as System.Exception

            //

            return System.Windows.Controls.ValidationResult{false, String.Concat("Date not right !? ", exception:Message)}

        end try

        return System.Windows.Controls.ValidationResult{true, null}

    // Properties

    property MinDate as System.DateTime

        Get

            //

            return self:_minDate

        End Get

        Set

            //

            self:_minDate := value

        End Set

    end property

end class

//==========================================================================

PUBLIC CLASS GeneralCoreVRs

    PUBLIC STATIC METHOD CoreGeneralStringVR(cInput AS STRING, nMin AS INT, nMax AS INT, ;

                            AllowSpaces as Boolean, AllowEmpty as Boolean) as ValidationResult

local implied General := ""

local implied nLen := ((STRING)cInput):Length

try

if nLen > 0

General := cInput:ToString()

            ELSE

if nLen == 0 && !AllowEmpty

return ValidationResult{false, "String can't be empty : "}

ENDIF

if nLen == 0 && AllowEmpty

return ValidationResult{true, null}

ENDIF

General := cInput:ToString()

endif

catch oEX as System.Exception

return ValidationResult{false, "String - something wrong or " + oEX:Message}

end try

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

if (!AllowSpaces && cInput:Contains(" "))

RETURN ValidationResult{FALSE, "String contains space characters : "}

        endif

if (cInput:Length < nMin || cInput:Length > nMax)

return ValidationResult{false,;

"String length should be between " + nMin:ToString() + " and " + nMax:ToString() + " in length."}

endif

return ValidationResult{true, null}

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

    PUBLIC STATIC METHOD CoreAgeRangeVR(value AS OBJECT, nMin AS INT, nMax AS INT) AS ValidationResult

LOCAL age := 0 AS INT

TRY

IF ((STRING)value):Length > 0

age := Int32.Parse((STRING)value)

ENDIF

CATCH oEX AS System.Exception

RETURN ValidationResult{FALSE, "Illegal characters or " + oEX:Message}

        END TRY

//--------------------------------------------

IF (age < nMin) || (age > nMax)

LOCAL IMPLIED cMessage := "Please enter an age in the range: " + nMin:ToString() + " - " + nMax:ToString() + "."

RETURN ValidationResult{FALSE, cMessage}

        ENDIF

RETURN ValidationResult{TRUE, NULL}

    //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

END CLASS

END NAMESPACE

=========================================================================================

<Window x:Class="VN_ICF_Cologne2016.TwoListsView"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:local2="clr-namespace:MyVRs"

    xmlns:local3="clr-namespace:MyConverters"

    Title="TwoListsView : Vulcan : Cologne 2016 :" Height="500"

    Width="620" Background="MintCream">

    <Grid>

        <Grid.Resources>

            <local3:String2NameText x:Key="Clean2Name" />

            <local3:String2UpperCaseText x:Key="String2Upper" />

            <!--==========================================================-->

            <Style TargetType="TextBox">

                <Setter Property="Background" Value="LightGray" />

                <Style.Triggers>

                    <Trigger Property="Validation.HasError" Value="true">

                        <Setter Property="ToolTip"

                        Value="{Binding RelativeSource={x:Static RelativeSource.Self},

                        Path=(Validation.Errors)[0].ErrorContent}"/>

                    </Trigger>

                    <!--========================================-->

                    <Trigger Property="IsMouseOver" Value="True">

                        <Setter Property="Cursor" Value="Hand" />

                    </Trigger>

                </Style.Triggers>

            </Style>

            <!--==========================================================-->

            <Style TargetType="DatePicker">

                <Style.Triggers>

                    <Trigger Property="Validation.HasError" Value="true">

                        <Setter Property="ToolTip"

                        Value="{Binding RelativeSource={x:Static RelativeSource.Self},

                        Path=(Validation.Errors)[0].ErrorContent}"/>

                    </Trigger>

                    <!--========================================-->

                    <!-- <Trigger Property="IsMouseOver" Value="True">

                        <Setter Property="Cursor" Value="Hand" />

                    </Trigger>-->

                </Style.Triggers>

            </Style>

            <!--==========================================================-->

            <ControlTemplate x:Key="MyCTtry">

                <StackPanel>

                    <!-- Placeholder for the TextBox itself -->

                    <AdornedElementPlaceholder x:Name="textBox"/>

                    <TextBlock Text="{Binding [0].ErrorContent}" Foreground="Red"/>

                </StackPanel>

            </ControlTemplate>

            <!--==========================================================-->

            <DataTemplate x:Key="myTestTemplate3">

                <Border BorderBrush="RoyalBlue" BorderThickness="2" Width="Auto">

                    <!-- use TextBox for editing / TextBlock for Read Only -->

                    <StackPanel Margin="5" Background="LightGray" Width="Auto">

                        <StackPanel Orientation="Horizontal">

                            <Border BorderBrush="DarkBlue" BorderThickness="1" Width="Auto">

                                <Image Name="imgFace" Source="{Binding Path=FaceImage}" Stretch="Fill" Height="70"

                                    Width="70" HorizontalAlignment="Left"  />

                            </Border>

                            <StackPanel Orientation="Vertical">

                                <TextBox Name="tbFirstname" FontSize="14" FontWeight="Bold" Foreground="Green">

                                    <TextBox.Text>

                                        <Binding Path = "Firstname" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">

                                            <Binding.ValidationRules>

                                                <local2:GeneralStringRule Min="2" Max="23" AllowEmpty="True" AllowSpaces="True"/>

                                            </Binding.ValidationRules>

                                        </Binding>

                                    </TextBox.Text>

                                </TextBox>

                                <!-- alternative XAML script syntax -->

                                <TextBox Name="tbLastname" FontSize="12" Foreground="DarkBlue" Background="Yellow">

                                     <TextBox.Text>

                                        <Binding Path = "Lastname" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">

                                            <Binding.ValidationRules>

                                                <local2:GeneralStringRule Min="2" Max="23" AllowEmpty="True" AllowSpaces="False"/>

                                            </Binding.ValidationRules>

                                        </Binding>

                                    </TextBox.Text>

                                </TextBox>

                                <TextBox Name="tbCompany" FontSize="12" Foreground="DarkBlue" >

                                    <TextBox.Text>

                                        <Binding Path = "Company" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">

                                            <Binding.ValidationRules>

                                                <local2:GeneralStringRule Min="3" Max="30" AllowEmpty="True" AllowSpaces="True"/>

                                                <local2:LettersMainlyStringRule/>

                                            </Binding.ValidationRules>

                                        </Binding>

                                    </TextBox.Text>

                                </TextBox>

                            </StackPanel>

                        </StackPanel>

                        <TextBox Name="tbTelephone" FontSize="12" Foreground="Red" Background="Beige">

                            <TextBox.Text>

                                <Binding Path="Telephone" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged" >

                                    <Binding.ValidationRules>

                                        <local2:BritishTelephoneVR />

                                    </Binding.ValidationRules>

                                </Binding>

                            </TextBox.Text>

                        </TextBox>

                        <TextBox Name="tbeMail" FontSize="12" Foreground="DarkBlue" >

                            <TextBox.Text>

                                <Binding Path="eMail" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged" >

                                    <Binding.ValidationRules>

                                        <local2:emailAddressValidationRule  />

                                    </Binding.ValidationRules>

                                </Binding>

                            </TextBox.Text>

                        </TextBox>

                        <DatePicker Name="dpFirstContact" FontSize="12" Foreground="DarkBlue" >

                            <DatePicker.SelectedDate>

                                <Binding Path="FirstContact" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">

                                    <Binding.ValidationRules>

                                        <local2:TestMinDateVR MinDate="1980,01,01" ValidationStep="ConvertedProposedValue"/>

                                        <local2:TestMaxDateVR MaxDate="2020,02,02" ValidationStep="ConvertedProposedValue"/>

                                    </Binding.ValidationRules>

                                </Binding>

                            </DatePicker.SelectedDate>

                        </DatePicker>

                        <TextBox Name="tbCodiceFiscale" FontSize="12" Foreground="DarkGreen" FontWeight="Bold"

                                 FontStyle="Italic">

                            <TextBox.Text>

                                <Binding Path="CodiceFiscale.FullString"

                            UpdateSourceTrigger="PropertyChanged"

                            Mode="TwoWay"

                            Converter="{StaticResource String2Upper}" >

                                    <Binding.ValidationRules>

                                        <local2:ItalianIdentityStringRule2 ValidationStep="ConvertedProposedValue" />

                                        <!--<local2:ItalianIdentityStringRule2 /> -->

                                    </Binding.ValidationRules>

                                </Binding>

                            </TextBox.Text>

                        </TextBox>

                    </StackPanel>

                </Border>

            </DataTemplate>

            <DataTemplate x:Key="myTestTemplate4">

                <!--===== no editing so no Validation or Conversion =====-->

                <Border BorderBrush="Red" BorderThickness="2" Width="Auto">

                    <StackPanel Margin="5" Background="LightGray" Width="130">

                        <TextBlock FontSize="12" Foreground="Blue" Text="{Binding Path=CodiceFiscale.FullString,Mode=TwoWay,

                                   UpdateSourceTrigger=PropertyChanged}" FontWeight="Bold" FontStyle="Italic"/>

                        <!--=============================================================================================-->

                        <StackPanel Orientation="Horizontal" Background="Yellow">

                            <TextBlock FontSize="12" FontWeight="Bold" Foreground="DarkBlue" Text="{Binding Path=Lastname,

                                       Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />

                            <TextBlock Text =" "/>

                            <TextBlock FontSize="14" Foreground="Red" Text="{Binding Firstname,Mode=TwoWay,

                                       UpdateSourceTrigger=PropertyChanged}" FontStyle="Italic"/>

                        </StackPanel>

                        <!--=============================================================================================-->

                        <TextBlock FontSize="12" Foreground="Green" Text="{Binding Path=Telephone,Mode=TwoWay,

                                   UpdateSourceTrigger=PropertyChanged}" Background="Beige"/>

                        <TextBlock FontSize="12" Foreground="DarkBlue" Text="{Binding Path=Company,Mode=TwoWay,

                                   UpdateSourceTrigger=PropertyChanged}"/>

                        <TextBlock FontSize="12" Foreground="DarkBlue" Text="{Binding Path=eMail,Mode=TwoWay,

                                   UpdateSourceTrigger=PropertyChanged}" FontWeight="Bold"/>

                    </StackPanel>

                </Border>

            </DataTemplate>

        </Grid.Resources>

        <ComboBox x:Name="comboBox" HorizontalAlignment="Left"

                  Width="Auto" Height="Auto" Margin="334,50,0,0"

                  VerticalAlignment="Top" ItemsSource="{Binding oPersons}"

                  HorizontalContentAlignment="Stretch"

                  ItemTemplate="{StaticResource myTestTemplate4}"

                  SelectedIndex="0"/>

        <ListBox x:Name="listBox" HorizontalAlignment="Left" Height="393" Margin="20,50,0,0"

                 VerticalAlignment="Top" Width="301" ItemsSource="{Binding oPersons}"

                 HorizontalContentAlignment="Stretch"

                 ItemTemplate="{StaticResource myTestTemplate3}"/>

        <Label x:Name="lEdit" Content="[ Can edit most fields below :- ]" HorizontalAlignment="Left"

               Margin="20,21,0,0" VerticalAlignment="Top" Width="Auto" FontSize="14"

               Foreground="Green"/>

        <Label x:Name="lReadOnly" Content="[ Can ONLY read the data fields below :- ]"

               HorizontalAlignment="Left" FontSize="14"

               Margin="322,21,0,0" VerticalAlignment="Top" Width="Auto"

               Foreground="Purple"/>

    </Grid>

</Window>

=========================================================================================

#using System

#using System.Windows.Controls

#using System.Windows

begin namespace VN_ICF_Cologne2016

    partial class TwoListsView inherit Window

    constructor()

        super()

        InitializeComponent()

    return

    end class

end namespace

=========================================================================================

<UserControl x:Class="VN_ICF_Cologne2016.ucMyComboBox"

             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

             xmlns:local2="clr-namespace:MyVRs"

             xmlns:local3="clr-namespace:MyConverters"

             mc:Ignorable="d"

             d:DesignHeight="300" d:DesignWidth="300" Background="Bisque" >

    <Grid>

        <Grid.Resources>

            <local3:String2NameText x:Key="Clean2Name" />

            <local3:String2UpperCaseText x:Key="String2Upper" />

            <ControlTemplate x:Key="MyCTtry">

                <StackPanel>

                    <!-- Placeholder for the TextBox itself -->

                    <AdornedElementPlaceholder x:Name="textBox"/>

                    <TextBlock Text="{Binding [0].ErrorContent}" Foreground="Red"/>

                </StackPanel>

            </ControlTemplate>

            <!--==========================================================-->

            <DataTemplate x:Key="myTestTemplate2">

                <!--===== items in list controls need a data template =====-->

                <StackPanel Margin="5" Background="LightGray" Width="160">

                    <TextBox FontSize="14" FontWeight="Bold" Foreground="Green">

                        <TextBox.Text>

                            <Binding Path = "Firstname" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">

                                <Binding.ValidationRules>

                                    <local2:GeneralStringRule Min="2" Max="23" AllowEmpty="True" AllowSpaces="True"/>

                                </Binding.ValidationRules>

                            </Binding>

                        </TextBox.Text>

                    </TextBox>

                    <!-- alternative XAML script syntax -->

                    <!--===== 'TextBox' can be edited so we need Validation and Converters =====-->

                    <TextBox Name="tbLastname" FontSize="12" Foreground="DarkBlue" >

                        <TextBox.Text>

                            <Binding Path = "Lastname" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">

                                <Binding.ValidationRules>

                                    <local2:GeneralStringRule Min="2" Max="23" AllowEmpty="True" AllowSpaces="False"/>

                                </Binding.ValidationRules>

                            </Binding>

                        </TextBox.Text>

                    </TextBox>

                    <TextBox Name="tbTelephone" FontSize="12" Foreground="Red"  Background="Beige">

                         <TextBox.Text>

                            <Binding Path="Telephone" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged" >

                                <Binding.ValidationRules>

                                    <local2:BritishTelephoneVR />

                                </Binding.ValidationRules>

                            </Binding>

                        </TextBox.Text>

                    </TextBox>

                    <TextBox Name="tbCompany" FontSize="12" Foreground="DarkBlue" >

                        <TextBox.Text>

                            <Binding Path = "Company" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">

                                <Binding.ValidationRules>

                                    <local2:GeneralStringRule Min="3" Max="30" AllowEmpty="True" AllowSpaces="True"/>

                                </Binding.ValidationRules>

                            </Binding>

                        </TextBox.Text>

                    </TextBox>

                    <TextBox Name="tbeMail" FontSize="12" Foreground="DarkBlue" >

                        <TextBox.Text>

                            <Binding Path="eMail" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged" >

                                <Binding.ValidationRules>

                                    <local2:emailAddressValidationRule  />

                                </Binding.ValidationRules>

                            </Binding>

                        </TextBox.Text>

                    </TextBox>

                    <DatePicker FontSize="12" Foreground="DarkBlue" >

                        <DatePicker.SelectedDate>

                            <Binding Path="FirstContact" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">

                                <Binding.ValidationRules>

                                    <local2:TestMinDateVR MinDate="1980,01,01" ValidationStep="ConvertedProposedValue"/>

                                    <local2:TestMaxDateVR MaxDate="2020,02,02" ValidationStep="ConvertedProposedValue"/>

                                </Binding.ValidationRules>

                            </Binding>

                        </DatePicker.SelectedDate>

                    </DatePicker>

                    <TextBox FontSize="12" Foreground="Blue"

                             FontWeight="Bold" FontStyle="Italic"

                             Validation.ErrorTemplate="{StaticResource ResourceKey=MyCTtry}">

                        <TextBox.Text>

                            <Binding Path="CodiceFiscale.FullString"

                            UpdateSourceTrigger="PropertyChanged"

                            Mode="TwoWay"

                            Converter="{StaticResource String2Upper}" >

                                <Binding.ValidationRules>

                                    <!--<local2:ItalianIdentityStringRule2 ValidationStep="ConvertedProposedValue" />-->

                                    <local2:ItalianIdentityStringRule2 />

                                </Binding.ValidationRules>

                            </Binding>

                        </TextBox.Text>

                    </TextBox>

                </StackPanel>

            </DataTemplate>

            <!--==========================================================-->

            <Style TargetType="TextBox">

                <Style.Triggers>

                    <Trigger Property="Validation.HasError" Value="true">

                        <Setter Property="ToolTip"

                        Value="{Binding RelativeSource={x:Static RelativeSource.Self},

                        Path=(Validation.Errors)[0].ErrorContent}"/>

                    </Trigger>

                    <!--========================================-->

                    <Trigger Property="IsMouseOver" Value="True">

                        <Setter Property="Cursor" Value="Hand" />

                    </Trigger>

                </Style.Triggers>

            </Style>

            <!--==========================================================-->

            <Style TargetType="DatePicker">

                <Style.Triggers>

                    <Trigger Property="Validation.HasError" Value="true">

                        <Setter Property="ToolTip"

                        Value="{Binding RelativeSource={x:Static RelativeSource.Self},

                        Path=(Validation.Errors)[0].ErrorContent}"/>

                    </Trigger>

                    <!--========================================-->

                   <!-- <Trigger Property="IsMouseOver" Value="True">

                        <Setter Property="Cursor" Value="Hand" />

                    </Trigger>-->

                </Style.Triggers>

            </Style>

            <!--==========================================================-->

        </Grid.Resources>

        <ComboBox x:Name="comboBox" HorizontalAlignment="Left" Margin="24,67,0,0"

                  VerticalAlignment="Top" Width="224"

                  ItemsSource="{Binding oPersons}" Height="Auto"

                  HorizontalContentAlignment="Stretch"

                  ItemTemplate="{StaticResource myTestTemplate2}"/>

        <TextBlock x:Name="textBlock" HorizontalAlignment="Left" Margin="25,22,0,0"

                   TextWrapping="Wrap" Text="User Control - as 'View in View'"

                   VerticalAlignment="Top" Width="184" Background="LightBlue" Height="20"/>

    </Grid>

</UserControl>

=========================================================================================

#using System

#USING System.Windows.Controls

begin namespace VN_ICF_Cologne2016

    partial class ucMyComboBox  inherit UserControl

    constructor()

       super()

       InitializeComponent()

    return

    end class

end namespace

=========================================================================================

<UserControl x:Class="VN_ICF_Cologne2016.ucMyListBox"

             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

             mc:Ignorable="d"

             d:DesignHeight="400" d:DesignWidth="400" Background="LightGreen">

    <Grid>

        <Grid.Resources>

            <DataTemplate x:Key="myTestTemplate">

                <Border BorderBrush="Chartreuse" BorderThickness="3" Width="Auto">

                    <StackPanel Orientation="Horizontal">

                        <Border BorderBrush="DarkBlue" BorderThickness="2" Width="Auto">

                            <StackPanel Margin="5" Background="LightGray" Width="130">

                                <!--===== 'TextBlock' no edit and so no Validation or conversion =====-->

                                <TextBlock FontSize="14" FontWeight="Bold" Foreground="Green">

                                    <TextBlock.Text>

                                        <Binding Path = "Firstname" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged"/>

                                    </TextBlock.Text>

                                </TextBlock>

                                <!-- alternative XAML script syntax -->

                                <TextBlock FontSize="12" Foreground="DarkBlue" Text="{Binding Path=Lastname}"/>

                                <TextBlock FontSize="12" Foreground="Red" Text="{Binding Path=Telephone}" Background="Beige"/>

                                <TextBlock FontSize="12" Foreground="DarkBlue" Text="{Binding Path=Company}"/>

                                <TextBlock FontSize="12" Foreground="DarkBlue" Text="{Binding Path=eMail}"/>

                                <TextBlock FontSize="12" Foreground="DarkBlue" Text="{Binding Path=FirstContact}"/>

                                <TextBlock FontSize="12" Foreground="Blue" Text="{Binding Path=CodiceFiscale.FullString}" FontWeight="Bold" FontStyle="Italic"/>

                            </StackPanel>

                        </Border>

                        <Border BorderBrush="DarkBlue" BorderThickness="2" Width="Auto">

                            <Image Source="{Binding Path=FaceImage}" Stretch="Fill" Height="130"

                                    Width="130" HorizontalAlignment="Right"  />

                        </Border>

                    </StackPanel>

                </Border>

            </DataTemplate>

        </Grid.Resources>

        <ListBox x:Name="listBox" HorizontalAlignment="Left" Height="300" Margin="10,23,0,0"

                 VerticalAlignment="Top" Width="320" ItemsSource ="{Binding oPersons}"

                 BorderThickness="2"

                 BorderBrush="Red"

                 HorizontalContentAlignment="Stretch"

                 ItemTemplate="{StaticResource myTestTemplate}"/>

    </Grid>

</UserControl>

=========================================================================================

#using System

#USING System.Windows.Controls

begin namespace VN_ICF_Cologne2016

    partial class ucMyListBox  inherit UserControl

    constructor()

        super()

        InitializeComponent()

    return

    end class

end namespace

=========================================================================================

//----- MyComboBoxViewModel.prg -----

//----- PHH - 7th April 2016. -----

#using System

#using System.Collections.Generic

#using System.Collections.ObjectModel   //----- ObservableCollection .....

#using System.IO

#using System.ComponentModel   //----- INotifyPropertyChanged .....

#using VN_myData

begin namespace VN_myViewModels

class MyComboBoxViewModel  implements INotifyPropertyChanged

    //----- fields -----

    private _oPersons as ObservableCollection<Person>

    //----- properties -----

    property oPersons as ObservableCollection<Person>

        get

            return self:_oPersons

        end get

        set

            self:_oPersons := value

            self:NotifyPropertyChanged("oPersons")

        end set

    end property

    //----- events -----

    event PropertyChanged as PropertyChangedEventHandler

    //----- constructors -----

    constructor()

        super()

        self:oPersons := ObservableCollection<Person>{}

    return

    constructor(oP as ObservableCollection<Person>)

        super()

        self:oPersons := oP

    return

    //----- methods -----

    method NotifyPropertyChanged(propertyName as string) as void

        if (self:PropertyChanged != null)

            self:PropertyChanged:Invoke(self, PropertyChangedEventArgs{propertyName})

        endif

    return

end class

end namespace // VN_myViewModels

=========================================================================================

//----- MyListBoxViewModel.prg

//----- PHH - 7th. April 2016. -----

#using System

#using System.Collections.Generic

#using System.Collections.ObjectModel   //----- ObservableCollection .....

#using System.IO

#using System.ComponentModel   //----- INotifyPropertyChanged .....

#using VN_myData

begin namespace VN_myViewModels

class MyListBoxViewModel  implements INotifyPropertyChanged

    //----- fields -----

    private _oPersons as ObservableCollection<Person>

    //----- properties -----

    property oPersons as ObservableCollection<Person>

        get

            return self:_oPersons

        end get

        set

            self:_oPersons := value

            self:NotifyPropertyChanged("oPersons")

        end set

    end property

    //----- events -----

    event PropertyChanged as PropertyChangedEventHandler

    //----- constructors -----

    constructor()

        super()

        self:oPersons := ObservableCollection<Person>{}

    return

    constructor(oP as ObservableCollection<Person>)

        super()

        self:oPersons := oP

    return

    //----- methods -----

    method NotifyPropertyChanged(propertyName as string) as void

        if (self:PropertyChanged != null)

            self:PropertyChanged:Invoke(self, PropertyChangedEventArgs{propertyName})

        endif

    return

end class

end namespace // VN_myViewModels

=========================================================================================

//----- MyTwoListsViewModel.prg -----

//----- PHH - 7th. April 2016. -----

#using System

#using System.Collections.Generic

#using System.Collections.ObjectModel   //----- ObservableCollection .....

#using System.IO

#using System.ComponentModel   //----- INotifyPropertyChanged .....

#using VN_myData

begin namespace VN_myViewModels

class MyTwoListsViewModel  implements INotifyPropertyChanged

    //----- fields -----

    private _oPersons as ObservableCollection<Person>

    //----- properties -----

    property oPersons as ObservableCollection<Person>

        get

            return self:_oPersons

        end get

        set

            self:_oPersons := value

            self:NotifyPropertyChanged("oPersons")

        end set

    end property

    //----- events -----

    event PropertyChanged as PropertyChangedEventHandler

    //----- constructors -----

    constructor()

        super()

        self:oPersons := ObservableCollection<Person>{}

    return

    constructor(oP as ObservableCollection<Person>)

        super()

        self:oPersons := oP

    return

    //----- methods -----

    method NotifyPropertyChanged(propertyName as string) as void

        if (self:PropertyChanged != null)

            self:PropertyChanged:Invoke(self, PropertyChangedEventArgs{propertyName})

        endif

    return

end class

end namespace // VN_myViewModels

=========================================================================================

 //----- MyPersonsModel.prg -----

//----- PHH - 7th. Aprl 2016. -----

#using System

#using System.IO

#using System.Collections.Generic

#using System.Collections.ObjectModel   //----- ObservableCollection .....

#using System.ComponentModel   //----- INotifyPropertyChanged .....

//=========================================================================

begin namespace VN_myData

class MyPersonsModel

    //----- constructors -----

    constructor()

        super()

    return

    static method Data as ObservableCollection<Person>

        Local implied oD := Person.GetPersons()

    return oD

end class

end namespace

=========================================================================================

<Application x:Class="VN_ICF_Cologne2016.App"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    StartupUri="Window1.xaml">

    <Application.Resources>

    </Application.Resources>

</Application>

=========================================================================================

#using System

#using System.Collections.Generic

#using System.Configuration

#using System.Data

#using System.Linq

#using System.Windows

BEGIN NAMESPACE VN_ICF_Cologne2016

   /// <summary>

   /// Interaction logic for App.xaml

   /// </summary>

   PARTIAL CLASS App INHERIT Application

   END CLASS

END NAMESPACE

=========================================================================================

<Window x:Class="VN_ICF_Cologne2016.Window1"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:local="clr-namespace:VN_ICF_Cologne2016"

    Title="Vulcan : Codice Fiscale : Cologne 2016 :"

    Height="500" Width="400" Background ="Beige" >

    <Grid>

        <Button x:Name="pbTwoLists" Content="Codice Fiscale - View" HorizontalAlignment="Left"

                Margin="25,28,0,0" VerticalAlignment="Top" Width="196" Height="30"

                Background="LightGreen" Foreground="OrangeRed" FontWeight="Bold" FontSize="18"

                Click="pbTwoLists_Click">

        </Button>

        <ContentControl Name="ccMyContentCont"  Content=".. stuff will appear ..."

                        Margin="10,141,10,10"/>

        <Button x:Name="pbListBox" Content="UC - 1" HorizontalAlignment="Left"

                Margin="277,28,0,0" VerticalAlignment="Top" Width="75" Click="pbListBox_Click"/>

        <Button x:Name="pbComboBox" Content="UC - 2" HorizontalAlignment="Left"

                Margin="277,77,0,0" VerticalAlignment="Top" Width="75" Click="pbComboBox_Click"/>

    </Grid>

</Window>

=========================================================================================

#using System

#using System.Collections.Generic

#using System.Windows

#using System.Collections.ObjectModel   //----- ObservableCollection .....

//------------------------------------

#using VN_myData

#using VN_myCheckSum

#using VN_myViewModels

begin namespace VN_ICF_Cologne2016

partial class Window1 inherit Window

   constructor()

      super()

      InitializeComponent()

    return

    private method pbTwoLists_Click(sender as system.Object, e as System.Windows.RoutedEventArgs) as System.VOID

        //----- we want an application 'scope' for our data collection -----

        //----- view -----

        local oMy2ListsView as TwoListsView

        oMy2ListsView := TwoListsView{}

        //----- viewmodel -----

        local implied oVM := MyTwoListsViewModel{MyPersonsModel.Data()}

        //----- couple -----

        oMy2ListsView:DataContext := oVM

        oMy2ListsView:Show()

    return

    private method pbListBox_Click(sender as system.Object, e as System.Windows.RoutedEventArgs) as System.VOID

        //----- we want an application 'scope' for our data collection -----

        //----- view -----

        local ucTestLB as ucMyListBox

        ucTestLB := ucMyListBox{}

        //----- viewmodel -----

        local implied oVM := MyListBoxViewModel{MyPersonsModel.Data()}

        //----- couple -----

        ucTestLB:DataContext := oVM

        //----- place in container -----

        self:ccMyContentCont:Content := ucTestLB

    return

    private method pbComboBox_Click(sender as system.Object, e as System.Windows.RoutedEventArgs) as System.VOID

        //----- we want an application 'scope' for our data collection -----

        local ucTestCB as ucMyComboBox

        ucTestCB := ucMyComboBox{}

        //----- viewmodel -----

        local implied oVM := MyComboBoxViewModel{MyPersonsModel.Data()}

        //----- couple -----

        ucTestCB:DataContext := oVM

        //----- place in container -----

        self:ccMyContentCont:Content := ucTestCB

    return

end class

end namespace

=========================================================================================

=========================================================================================

PHH - 7th. April 2016.


Chapter 6 - Asynchronus validation

Chapter 6 - Asynchronus validation

Chapter 6 - Asynchronus validation

 Previous topic Next topic

PHH - 27th. January 2016.


Chapter 7 - 'List', 'DataGrid', and Data Templates

Chapter 7 - 'List', 'DataGrid', and Data Templates

Chapter 7 - 'List', 'DataGrid', and Data Templates

 Previous topic Next topic

Okay guys, it seems difficult enough to get our heads around the Validation process when

we have a bunch of controls on a WPF form, but what about when we have lists of complex

data items, and the properties of these list items have one or two which are list of other

complex data items !?

As long as we take this one step at a time we should get there in the end.

Lets now see from Cologne material for last year (2015) such a situation - check below :-

In the above image we see 'Frank' being edited, and this is a string property within the items

contained in a combobox.

However, do notice that within Frank's list item there is another combobox, these items may

or may not be editable by design, but if there are set to 'Write' and not 'R/O' then we will need

to validate and handle errors etc.. Just as we have done previously in simpler examples.

Lets move on and have a look at the next image which is relevant to this sub-section :-

[ .. look below for the data item which is being edited, and the List item which is in focus .. ]

In the above image we see that a whole bunch of columns seem to be available for editing, and one (Lastname) is in the process

of being changed by the keyboard input.

We need to have validation rules applied to each column, possibly with the exception of Types for logic and dates where the control

itself may provide sufficient validation.

Before we look at dates, booleans, and numerics, we need to deal with the other basic and common columns - string types but

with various VR conditions.

Enough for now, lets go and see what we can do with list in ListBox controls.

See you in the next sub-section ...............

PHH - 10th. February 2016.


'ListBox' and Validation

'ListBox' and Validation

'ListBox' and Validation

 Previous topic Next topic

Okay then folks, lets try and explore Validation within ListBox controls, where we

have a set of complex data items, and the properties of these data items are both displayed

and editable (write as opposed to r/o).

Here below is the sort of situation about which I am speaking :-

[ .. the string data being edited currently has a string value of 'Ireland' .. ]

In the above image we can see just over two list items - each one being a 'Speaker' object.

Although we have yet to code and script our own Validation sample, we can examine this 'ListBox'

and its related 'DataTemplate' to see what is being done.

Here is the 'ListBox' definition itself :-

==============================================================================

It really is only lines 58 and 59 which are related to data content and how the data looks.

Line 58 binds to a collection and line 59 assigns the template for the data, or how it will look

when displayed in more basic UI elements.

Lets see how this 'DataTemplate' looks, right now :-

==================================================================================================================================================

As you can see above, we have data being bound to four TextBox UI elements, on lines 12,13,17, and 22.

From our previous work we know how to apply VRs (validation rules) to TextBox controls.

Its about time that I made a NEW sample based on what we have seen from last year's material, and add to

it the validation parts we require. And the Invalid check fo rthe whole Form. Fingers crossed as 'here we go' .............

............. Hello again !

Yes, I have been away and done it ! Coded a suitable sample to handle 'ListBox' data of a complex business type.

It is probably best to show you in the following image - check it out thoroughly :-

[ .. we also have an extra property, that of 'Age' - as its useful for testing purposes .. ]

====================================================================================

Above we can see five collection items being displayed in a ListBox UI framework element.

As a test I have edited the original 'Age' numerical values with text characters to generate an error in

each of the five list items - check the red borders.

The 'Do Validation Check' button uses a 'RelayCommand' and code now placed in the ViewModel, to find

out all of the 'TextBox' elements in the deep (full) visual tree, then checks to see if they are invalid.

This works whether the errors are visible on screen, or hidden out of sight.

The applied VRs (validation rules) are similar to ones we used previously, although I have 'improved'

one or two to remove small logical mistakes by me ;-0)

If and when you right your own VRs, make sure you fully test then out - rigorous testing !!!

Here is one of the Integer bound values, showing a default validation rule messgae output :-

=============================================================

And once we associate our own VR to the 'Known as' field, or FirstName property, via the attribute

and property of the particular TextBox we can then see the following display at editing :-

==============================================

However, in our own defined VR called 'GeneralStringRule' we can allow spaces to be present, as

seen in the next image :-

====================================================================================================================

The above setting for the input parameters (property values), allows us to have an empty input string,

as well as spaces somewhere within the text string.

If we need to do cleverer things with spaces and groups of characters, say to make three words a valid input,

then we are well advised to go to using Regular Expressions.

Here is our cleaned up and tested VR code from earlier sections :-

================================================================================================================================

Lets move on a step or two ............

Short Summary so far :

We have not as yet done anything that different to how we attached VRs to TextBox UI elements in sample

one previously.

We have associated a coded VR class to the TextBoxes used in the DataTemplate to describe in XAML how to

draw and display the properties of the listed data items.

If we like we can associate one VR with each and every DataTemplate text box.

Specialised controls like CheckBox, DatePicker, and Calendar, tend to have sufficient validation built in for most

or their uses. But if needs be we could add further constraints if we so wished. For now, we have better and more

pressing things to do.

What's the bigger issue then ?

Well, finding all the UI elements in the full visual tree is probably the hardest and newest thing to do.

If we can find all the UI elements of the Window, or Grid, then we can filter out the TextBox elements,

or indeed just the Invalid ones.

Once we know if we have no errors, or a few invalid entries somewhere, then we can display relevant screen

information to warn and/or advise users on a course of action.

Lets see how we can search the visual tree ...........

Finding all UI elements in a Grid or Window :

You may or may not know that all UI elements on a Window or UserControl are nested in a Tree type structure.

Each sub-element is contained in a parent element, and so we talk of children - or child elements. This tree

structure can go quite deep once we get to Styles and templates and the likes.

We never know how deep our tree structure is until we do our own search (in real time) - at runtime.

Here is some recursive code I found from :-

https://social.msdn.microsoft.com/Forums/en-US/2a282f92-f533-42fb-acf7-7cfdfbd7ab3e/visualtreehelpergetchildren-does-not-consider-datetenplates-in-silverlight?forum=silverlightbugs

We may have to thank :- "Jawahar Suresh Babu" for this :-

=========================================================================================================

The above code finds all of the 'TextBox' elements which are invalid.

However, a small variation on this lets us see all of the elements, which is useful to see and know,

even if it is just once.

Lets start by seeing (filtering) only the 'TextBox' objects in the tree - study the following :-

===============================================================

And now we can see that main part of the search code, even if we don't see that part of it which

displays in a MessageBox the 'oSB' (or StringBuilder) details :-

===================================================================================================

As a homework exercise I will leave you to write some code to find out how many UI elements there are in total,

and also to print this in some sort of an organised list display.

Good Luck !

Lets move on ....................

See you in the child section where we look at a more controlled way leave the form, depending on data errors, or not.

PHH - 11th. February 2016.


A more controlled way of leaving the Form

A more controlled way of leaving the Form

A more controlled way of leaving the Form

 Previous topic Next topic

Well guys, now that we can search and traverse the Visual Tree, we can make conditional

tests in a variety of ways, and possibly we can therefore make a better job of leaving and

closing a data input form.

Lets see an improved form layout which allows us to find and display a bunch of different sets of

UI elements existing within the visual tree.

Study the image below where we can select to list and view all of the UI elements, only those valid,

only those invalid, just the TextBoxes, and all but the TextBoxes.

Notice that we still need to add a Close button - check below :-

===============================================================================================

We can improve the above form in a few ways - we need to display just which set has be found

and counted, as well as another button for closing being added.

Here is my next attempt at improvement :-

=============================================================================================================

Yep! - a lot better, we can now see which set of elements is in the RHS list box.

And the Close button at least moves towards the controlled form exit that we would wish for.

Here is what we see when we click 'Close' with one error existing within the form :-

=======================================================================

And after we close the warning 'box' we see this :-

=================================================

At the moment the screen does not close, or allow for user over-ride, but I only wished to demonstrate

to you what is possible, and NOT do all the hard work for you ;-0)

Here is my current simple code :-

=========================================================================

And obviously I had to make some changes to the original 'MyCheckInvalid' method - see below :-

===================================================================================================================================================

Before we leave this section, I would like to suggest to you (or even remind you) that it is probably

NOT a good idea to make things too tight - do lock users into a form without a way for them to select

that they wish to close it even when they know that an error has been detected by the code and

program logic.

I also suggest that your code tries to handle any and all data input values, including those that are invalid.

Try to get your screen / Form logic to help users get most or all inputs correct - BUT - lets not assume that

at form close the data is 100% every time.

Time to move on ......................

See you in the next sub-section where we look at drop list, or ComboBox UI elements / controls.

We may even do extra work in making a sample which has drop list as a single property of anothe complex

business object / class.

See you there ................

PHH - 13th. & 14th.February 2016.


working script and code

working script and code

working script and code

 Previous topic Next topic

===============================================================================

<Window x:Class="MVVM_ValidationCologne2016.Views.SpeakersListView"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

        xmlns:local="clr-namespace:MVVM_ValidationCologne2016.Views"

        xmlns:local2="clr-namespace:MyClasses"

        mc:Ignorable="d"

        Title="Validate Speakers in List : Cologne 2016 :" Height="460" Width="650" Background="LightBlue">

    <Grid Name="myGridSpeakersList">

        <Grid.Resources>

            <ControlTemplate x:Key="MyCTtry">

                <StackPanel>

                    <!-- Placeholder for the TextBox itself -->

                    <AdornedElementPlaceholder x:Name="textBox"/>

                    <TextBlock Text="{Binding [0].ErrorContent}" Foreground="Red"/>

                </StackPanel>

            </ControlTemplate>

            <!--==========================================================-->

            <DataTemplate x:Key="myValidTemp01">

                <Border BorderBrush="RoyalBlue" BorderThickness="2" Width="200">

                    <StackPanel Orientation="Vertical">

                        <TextBox Text="{Binding LastName}" FontWeight="Bold" FontSize="12" Foreground="DarkBlue"/>

                        <TextBox Text="{Binding oReg.Country}" FontStyle="Italic" Foreground="Red"/>

                        <StackPanel Orientation="Horizontal">

                            <TextBlock Text="Known as : " Foreground="DarkBlue" />

                            <TextBox Foreground="DarkBlue" FontFamily="Century Schoolbook">

                                <TextBox.Text>

                                    <Binding Path="FirstName" UpdateSourceTrigger="PropertyChanged" >

                                        <Binding.ValidationRules>

                                            <local2:GeneralStringRule Min="2" Max="23" AllowEmpty="True" AllowSpaces="True"/>

                                        </Binding.ValidationRules>

                                    </Binding>

                                </TextBox.Text>

                            </TextBox>

                        </StackPanel>

                        <StackPanel Orientation="Horizontal">

                            <TextBlock Text="Age : " Foreground="DarkBlue" />

                            <TextBox Text="{Binding Age, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Foreground="DarkBlue" />

                        </StackPanel>

                        <StackPanel Orientation="Horizontal">

                            <TextBlock Text="Company : " Foreground="DarkBlue" />

                            <TextBox Text="{Binding oReg.Company}" FontFamily="Century Schoolbook" Foreground="DarkBlue" />

                        </StackPanel>

                    </StackPanel>

                </Border>

            </DataTemplate>

            <!--==========================================================-->

            <Style TargetType="TextBox">

                <Setter Property="HorizontalAlignment" Value="Left" />

                <Setter Property="VerticalAlignment" Value="Top" />

                <Setter Property="Height" Value="24" />

                <Setter Property="Width" Value="160" />

                <Setter Property="Foreground" Value="DarkBlue" />

                <Setter Property="Background" Value="Bisque" />

                <Setter Property="TextWrapping" Value="Wrap" />

                <Style.Triggers>

                    <Trigger Property="Validation.HasError" Value="true">

                        <Setter Property="ToolTip"

                        Value="{Binding RelativeSource={x:Static RelativeSource.Self},

                        Path=(Validation.Errors)[0].ErrorContent}"/>

                    </Trigger>

                </Style.Triggers>

            </Style>

            <!--==========================================================-->

            <Style TargetType="Button" >

                <Setter Property="HorizontalAlignment" Value="Left" />

                <Setter Property="VerticalAlignment" Value="Top" />

                <Setter Property="Width" Value="75" />

                <Setter Property="Height" Value="22"/>

                <Setter Property="Background" Value="LightGreen" />

            </Style>

            <!--==========================================================-->

            <Style TargetType="TextBlock" >

                <Setter Property="HorizontalAlignment" Value="Left" />

                <Setter Property="VerticalAlignment" Value="Top" />

                <Setter Property="TextWrapping" Value="Wrap" />

            </Style>

        </Grid.Resources>

        <ListBox HorizontalAlignment="Left" Margin="10,10,0,10" Width="223"

                 ItemsSource="{Binding aList}"

                 ItemTemplate="{StaticResource myValidTemp01}"/>

        <ListBox Margin="267,139,10,10"

                 ItemsSource="{Binding oOC, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />

        <Button Content="Do Validation Check" Margin="269,110,0,0"

                Width="134" Background="LightSalmon"

                Command="{Binding Path=DoInvalidCheckCommand}"/>

        <Button Content="All" Margin="269,10,0,0" Command="{Binding Path=FindAllCommand}"/>

        <Button Content="Valid" Margin="269,34,0,0" Command="{Binding Path=FindValidCommand}"/>

        <Button Content="TBs" Margin="269,59,0,0" Command="{Binding Path=FindTBsCommand}"/>

        <Button Content="NonTBS" Margin="269,85,0,0" Command="{Binding Path=FindNonTBsCommand}"/>

        <TextBlock Height="20" Margin="514,110,0,0" Width="38"

                   Text="{Binding ElementCount}" />

        <Label Content="Element count : " HorizontalAlignment="Left" Margin="418,107,0,0" VerticalAlignment="Top"/>

        <TextBlock Height="44" Margin="378,10,0,0" Width="196"

                   Text="{Binding ElementSetName}"

                   FontFamily="centuryschoolbook" FontSize="18"/>

        <Button Content="Close" Margin="574,110,0,0"

                Width="58" Background="Red" Foreground="Yellow"

                Command="{Binding Path=CloseFormCommand}"/>

    </Grid>

</Window>

===============================================================================

using System.Windows;

namespace MVVM_ValidationCologne2016.Views

{

/// <summary>

/// Interaction logic for SpeakersListView.xaml

/// </summary>

public partial class SpeakersListView : Window

{

public SpeakersListView()

{

InitializeComponent();

}

}

}

===============================================================================

using System;

using System.ComponentModel;   //----- INotifyPropertyChanged .....

using MyClasses;

using System.Collections.Generic;

using System.Windows;

using System.Collections;

using System.Windows.Controls;

using System.Windows.Input;

using System.Windows.Media;

using System.Text;

using System.Collections.ObjectModel;

namespace MVVM_ValidationCologne2016.Views

{

//===============================================================================

public class SpeakersListViewModel : INotifyPropertyChanged

{

//----- fields -----

private List<Speaker> _aList;

private List<FrameworkElement> _aListTBs = new List<FrameworkElement>();

private ObservableCollection<string> _oOC = new ObservableCollection<string>();

private int _elementCount = 0;

private string _elementSetName = "";

StringBuilder oSB = new StringBuilder();

Window oParent;

//----- properties -----

public ObservableCollection<string> oOC

{

get { return _oOC; }

set

{

_oOC = value;

NotifyPropertyChanged("oOC");

}

}

public List<Speaker> aList

{

get { return _aList; }

set

{

_aList = value;

NotifyPropertyChanged("aList");

}

}

public List<FrameworkElement> aListTBs

{

get { return _aListTBs; }

set

{

_aListTBs = value;

NotifyPropertyChanged("aListTBs");

}

}

public int ElementCount

{

get { return _elementCount; }

set

{

_elementCount = value;

NotifyPropertyChanged("ElementCount");

}

}

public string ElementSetName

{

get { return _elementSetName; }

set

{

_elementSetName = value;

NotifyPropertyChanged("ElementSetName");

}

}

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

private ICommand _doInvalidCheckCommand;

public ICommand DoInvalidCheckCommand

{

get

{

return _doInvalidCheckCommand;

}

set

{

_doInvalidCheckCommand = value;

}

}

private ICommand _findAllCommand;

public ICommand FindAllCommand

{

get

{

return _findAllCommand;

}

set

{

_findAllCommand = value;

}

}

private ICommand _findValidCommand;

public ICommand FindValidCommand

{

get

{

return _findValidCommand;

}

set

{

_findValidCommand = value;

}

}

private ICommand _findTBsCommand;

public ICommand FindTBsCommand

{

get

{

return _findTBsCommand;

}

set

{

_findTBsCommand = value;

}

}

private ICommand _findNonTBsCommand;

public ICommand FindNonTBsCommand

{

get

{

return _findNonTBsCommand;

}

set

{

_findNonTBsCommand = value;

}

}

private ICommand _closeFormCommand;

public ICommand CloseFormCommand

{

get

{

return _closeFormCommand;

}

set

{

_closeFormCommand = value;

}

}

private FrameworkElement _oFEVM;

public FrameworkElement oFEVM

{

get

{

return _oFEVM;

}

set

{

_oFEVM = value;

}

}

//----- methods -----

public void MyCloseForm(object obj)

{

string myResult = MyCheckInvalid(oFEVM);

ElementSetName = ">> Any Invalid <<";

MessageBox.Show(myResult,"My Result :");

if (myResult == "OK")

{ this.oParent.Close();        }

//----- provide option to over-ride ! -----

        }

public void FindAll(object obj)

{

oOC.Clear();

aListTBs.Clear();

GetAllElementsInVT(1, oFEVM);

ElementCount = oOC.Count;

ElementSetName = ">> All <<";

}

public void FindTBs(object obj)

{

oOC.Clear();

aListTBs.Clear();

SearchForTBs(1, oFEVM);

ElementCount = oOC.Count;

ElementSetName = ">> TBs <<";

}

public void FindNonTBs(object obj)

{

oOC.Clear();

aListTBs.Clear();

SearchForNonTBs(1, oFEVM);

ElementCount = oOC.Count;

ElementSetName = ">> Non TBS <<";

}

public void FindAllValid(object obj)

{

oOC.Clear();

aListTBs.Clear();

SearchForAllValid(1, oFEVM);

ElementCount = oOC.Count;

ElementSetName = ">> All Valid <<";

}

public void SearchForAllValid(int depth, DependencyObject obj)

{

//----- reject all but TextBoxes -----

if ( !Validation.GetHasError(obj) )

{

this.oOC.Add(obj.ToString());

}

for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)

{

SearchForAllValid(depth + 1, VisualTreeHelper.GetChild(obj, i));

}

}

public void SearchForNonTBs(int depth, DependencyObject obj)

{

//----- reject all but TextBoxes -----

if (!obj.ToString().Contains(".TextBox:"))

{

this.oOC.Add(obj.ToString());

}

for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)

{

SearchForNonTBs(depth + 1, VisualTreeHelper.GetChild(obj, i));

}

}

public void SearchForTBs(int depth, DependencyObject obj)

{

//----- reject all but TextBoxes -----

if (obj.ToString().Contains(".TextBox:"))

{

this.oOC.Add(obj.ToString());

}

for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)

{

SearchForTBs(depth + 1, VisualTreeHelper.GetChild(obj, i));

}

}

public void PrintVisualTree(int depth, DependencyObject obj)

{

//----- reject all but TextBoxes -----

if (obj.ToString().Contains(".TextBox:"))

{

this.oSB.Append("\n" + obj);

this.oOC.Add(obj.ToString());

}

for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)

{

PrintVisualTree(depth + 1, VisualTreeHelper.GetChild(obj, i));

}

}

public void GetAllElementsInVT(int depth, DependencyObject obj)

{

//----- include all -----

this.oOC.Add(obj.ToString());

for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)

{

GetAllElementsInVT(depth + 1, VisualTreeHelper.GetChild(obj, i));

}

}

public void GetAllTextBoxesInVT(int depth, DependencyObject obj)

{

//----- reject all but TextBoxes -----

if (obj.ToString().Contains(".TextBox:"))

{

aListTBs.Add((FrameworkElement)obj);

}

for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)

{

GetAllTextBoxesInVT(depth + 1, VisualTreeHelper.GetChild(obj, i));

}

}

public void CheckInvalid(object obj)

{

string myResult = MyCheckInvalid(oFEVM);

ElementCount = oOC.Count;

ElementSetName = ">> Any Invalid <<";

}

private string MyCheckInvalid(FrameworkElement oFE)

{

oOC.Clear();

aListTBs.Clear();

GetAllTextBoxesInVT(1, oFE);

string cText = "";

string cInValid = "";

int count = 0;

foreach(FrameworkElement item in aListTBs)

{

if (Validation.GetHasError(item))

{

cInValid = Validation.GetHasError(item).ToString();

//cText += item.Name + "[" + cInValid + "]" + " " + "\n";

//----- below is better ! -----

cText += item.ToString() + "[" + cInValid + "]" + " " + "\n";

count += 1;

this.oOC.Add(((FrameworkElement)item).ToString());

}

}

ElementCount = oOC.Count;

if (count > 0)

{

MessageBox.Show("It seems that input data needs editing before the form is closed!", "CAUTION - Errors? :");

return count.ToString();

}

else

{

MessageBox.Show("It would appear that all data values are valid ones.", "SAFE to Continue! :");

return "OK";

}

}

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

//----- events -----

public event PropertyChangedEventHandler PropertyChanged;

//----- constructors -----

public SpeakersListViewModel()

{

aList = new List<Speaker>();

oFEVM = new FrameworkElement();

oParent = new Window();

List<Speaker> oSpkLst = new List<Speaker>();

        }

public SpeakersListViewModel(List<Speaker> oSpkLst, FrameworkElement oFE, Window oP)

{

this.oParent = oP;

oFEVM = oFE;

DoInvalidCheckCommand = new RelayCommand(new Action<object>(CheckInvalid));

FindAllCommand = new RelayCommand(new Action<object>(FindAll));

FindTBsCommand = new RelayCommand(new Action<object>(FindTBs));

FindNonTBsCommand = new RelayCommand(new Action<object>(FindNonTBs));

FindValidCommand = new RelayCommand(new Action<object>(FindAllValid));

CloseFormCommand = new RelayCommand(new Action<object>(MyCloseForm));

//------------------------------------------------------------------------

aList = oSpkLst;

}

//----- methods -----

public void NotifyPropertyChanged(string propertyName)

{

if (PropertyChanged != null)

{

PropertyChanged(this, new PropertyChangedEventArgs(propertyName));

}

}

}

}

===============================================================================

===============================================================================

PHH - 14th. February 2016.


'Combox' and Validation

'Combox' and Validation

'Combox' and Validation

 Previous topic Next topic

PHH - 11th. February 2016.


'DataGrid' and Validation (by Cell)

'DataGrid' and Validation (by Cell)

 Previous topic Next topic

'DataGrid' and Validation (by Cell)

 Previous topic Next topic

Be aware that the DataGrid is a complex and complicated GUI control.

We can do a lot with it - BUT - we also need to know a lot of stuff to be able to get it to work for us.

Often we see developer guys reaching for the DataGrid (DG) UI element when in fact they could just

as easily use a simpler approach, and one more in-line with their knowledge and skills.

PLEASE be well aware of this when doing your Form designs.

=======================================================================

Well folks, in some ways this topic is a difficult one, but in other ways we can get Validation to

work in DataGrids as well as previously in simpler controls.

Here is a useful URL / live link :-

http://blogs.u2u.be/diederik/post/2009/09/30/Validation-in-a-WPF-DataGrid.aspx

DataGrids are to display data collections - that is lists of data items with the same data type, and

hence structure of the data.

When we go further we will see that collections can have items which themselves have collections

as a property - 'lists within list' we often describe this as. More on this latter.

The truth of the matter is that most often developer guys try to do too much within the DataGrid UI

element. They try to make their Forms too clever.

If we try and build into the DataGrid too much functionality, and/or make it quite complex, then the

harder it is for us to get all aspect of data input and business logic working as required.

We can easily confuse aspects of business logic with other aspects of data validation. We must learn

to separate the two things, and get our GUI design correct.

There are some basic tricks and tips which make it much easier to apply both business logic as well

as data input validation.

When trying to use the MVVM pattern, remember that keeping things as simple as possible helps a

great deal in fast and accurate development.

Don't put too much in one View and hence (and also) one ViewModel.

A lot of this stuff will unfold as we go along.

Getting a Start :

Okay guys, in this sub-section we will look at a typical DataGrid in use with data which does not have

public properties which are themselves collections.

So the collections will be of level depth '1' (one) like the sample we see below for Speakers - the list is

a collection of Speaker objects.

Lets see a small working sample - check this out below :-

====================================================================================

Before I got it to looks as cool as it does above, I did manage to get the validation to work on each

column that I applied a VR (validation rule) to. Here below is a sample test :-

[ .. the Tooltip message is generated from my own VR code .. ]

=====================================================================

Note that the error tooltip is provided on a row basis - here below is a helpful image to

prove what I say - we may have expected it to be on the 'Cell' itself - but it isn't :-

===============================================================================

Here is another incorrect variation of integer input :-

=====================================================================================================

You are probably most interested to see what the XAML script is like which allows us to display the

invalid message string - here below is what I came up with after reading a web article by Diederik Krols :-

================================================================================================

Notice above that the target type is a 'DataGridRow' and the script looks similar to that we used previously

for TextBox UI elements in our working sample.

Lets keep on going ..................

As well as the Integer VR rule we can now see the general string rule we developed previously :-

=================================================================================================

And we can have strings too long as well - see below and early test in the development phase :-

=================================================

You may be interested to see the XAML script of how a VR is associated with a DaraGrid (DG) data column.

Here is the details for the association fir 'FirstName' as seen in action in the last image :-

=========================================================================================================================================

And just for another view of things, here is the script got the 'Age' property which is of Type integer :-

===============================================================================================

Both of the above are indeed VERY similar to the script 'structure' and syntax we used for TextBoxes in

our earlier sample apps.

In fact it was those earlier samples that allowed me to copy and adapt the script to create the above working

script samples. Simply replace 'TextBox' with 'DataGridTextColumn' and possibly a couple more very small

changes or differences. But I did manage to get it to work without prior knowledge.

Compare the following with what we have just done :-

==========================================================================================

Before we get into discussions about how VR are used by the DG element, lets have a brief look at the

the collapsed view of the DataGrid script - study carefully :-

=================================================================================================================

We now need to have a short general discussion about rows in DGs, and how Validation seems to be applied

to them, and obviously their data cells ..............

How Validation works in DataGrids :

If we use the DataGrid control (UI element) from the Visual Studio toolbox, and also use it pretty much 'as is', then

when we have an invalid data entry in one cell in a particular row, we can't move to any other cell anywhere, in order to

carry out an edit. Yes we can move the mouse pointer and highlight another row (see below) but we can't start another

edit process in any of the other cells - we are forced to provide a data entry for the cell being 'flagged' before we can

move on to any other.

Personally, I am not sure that I like this, and I have started to look to see if this behaviour can be over-ridden by me,

however, I doubt it will be possible.

Check out these images to explain what we have just discussed above :-

===================================================================================

And now this one :-

====================================================================================

The message in the tooltip is row bound, as it would seem that we can only have one invalid

cell in any row. This also facilitates the situation when the row and/or DataGrid control is partly

collapsed or hidden.

Presumably, there is a property of the DG control which can inform us as to whether there are

any invalid cells (or should we say one cell) within the whole grid. We nee to explore the

situation

Lets move on a pace, a step or two ................

How the 'DataGrid' control works for Editing :

Just a thought, what happens if the data initially filling the DG has some data values which are

invalid according to the VRs we are associating with the data columns ?

Maybe the data when originally entered was not invalid, and has become so in time as things change.

Lets change the data population STATIC method - check below in lines 156 & 165 :-

====================================================================================================================================

Lets try out a few experiments - here is the opening Form after I have added a couple of zero values to the Age of two Speakers.

====================================================================================

Yes, it would appear that we can edit any cell being shown, even though we have two values

of zero in our Age column.

Lets try and edit one of these :-

====================================================================================

As long as we don't change the cell content then we don't get the 'flagged' invalid marker appearing.

We can move on to other rows etc. :-

====================================================================================

But once we delete and insert a zero character we get the invalid flagging :-

====================================================================================

However, there is other behaviour of the DG control which comes to our rescue (if required).

If we press the escape key while editing any cell, it has its value returned to the original value

before the cell edit started, even if in this case it was ' 0 ' (zero) and invalid :-

===================================================================================

Lets try this for 'Frank' (my good friend) :-

================================

And when we press the escape key we get this below :-

================================

Lets move on a little ................

Adding a New Row and Validation :

If we have set the attribute value to True as in line 29 below, we can add rows into the DataGrid.

Other lines numbered 33 - 35 also affect the behaviour of our DG when in use - check carefully below :-

Since we have our test DG set to be able to add rows, lets try it out for real :-

===================================================================================

So what are the values provided for the new and 'blank' row. Remember that in our case for this

example, the row is a record, or an item which is a complex business object, of type 'Speaker'.

If we look at the 'empty' Constructor for the Speaker class then we see the following :-

=======================================================================================

And so it is obvious from the hard code and the DG visual image, that the new row is a default 'Speaker'

item object.

So here is our newly edited data row :-

===================================================================================

And now to be thorough in our research, we need to try to delete an existing row - lets try Nick :-

===================================================================================

After highlighting the row we just press the 'Delete' key (or other ways) - this is what I get :-

====================================================================================

It may be my lack of experience with DG controls but I can't seem to easily find a way of

restoring the deleted row. Answers on a postcard PLEASE !

Time to move on ...............

We need to get clear in our minds as to what happens when we try to close a Form with a

DG control on it and there is still a cell showing invalid data !?

Can we close the form ? If we do close it will the data value revert to its previous one, as by

using the escape key ?

Lets go and visit another sub-section to see what we can learn there ;-0)

See you in a short while .................

PHH - 11th.& 15th. February 2016.


working script and code

working script and code

working script and code

 Previous topic Next topic

===================================================================================

using System.Windows;

namespace MVVM_ValidationCologne2016.b_Views

{

/// <summary>

/// Interaction logic for DataGridValidation.xaml

/// </summary>

public partial class DataGridValidation : Window

{

public DataGridValidation()

{

InitializeComponent();

}

}

}

===================================================================================

<Window x:Class="MVVM_ValidationCologne2016.b_Views.DataGridValidation"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

        xmlns:local="clr-namespace:MVVM_ValidationCologne2016.b_Views"

        xmlns:local2="clr-namespace:MyClasses"

        mc:Ignorable="d"

        Title="DataGridValidation - Cologne 2016 :" Height="400" Width="500">

    <Grid>

        <Grid.Resources>

            <!--==========================================================-->

            <Style TargetType="DataGridRow" >

                <Style.Triggers>

                    <Trigger Property="Validation.HasError" Value="true">

                        <Setter Property="ToolTip"

                        Value="{Binding RelativeSource={x:Static RelativeSource.Self},

                        Path=(Validation.Errors)[0].ErrorContent}"/>

                    </Trigger>

                </Style.Triggers>

            </Style>

            <!--==========================================================-->

        </Grid.Resources>

        <!--==================================================================================-->

        <DataGrid

                Margin="10,10,10,29"

                Name="myDataGrid" AutoGenerateColumns="False"

                ItemsSource="{Binding aList,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"

                CanUserAddRows="True"

                Background="AliceBlue"

                AlternatingRowBackground="BlanchedAlmond"

                AlternationCount="2"

                CanUserReorderColumns="True"

                CanUserResizeColumns="True"

                CanUserDeleteRows="True" >

            <DataGrid.Columns>

                <DataGridTextColumn Header="Firstname :" Width="*" >

                    <DataGridTextColumn.Binding>

                        <Binding Path="FirstName">

                            <Binding.ValidationRules>

                                <local2:GeneralStringRule Min="2" Max="23" AllowEmpty="True" AllowSpaces="True"/>

                            </Binding.ValidationRules>

                        </Binding>

                    </DataGridTextColumn.Binding>

                </DataGridTextColumn>

                <!--=============================================================-->

                <DataGridTextColumn Header="Lastname :" Width="*" >

                    <DataGridTextColumn.Binding>

                        <Binding Path="LastName">

                            <Binding.ValidationRules>

                                <local2:GeneralStringRule Min="2" Max="23" AllowEmpty="False" AllowSpaces="False"/>

                            </Binding.ValidationRules>

                        </Binding>

                    </DataGridTextColumn.Binding>

                </DataGridTextColumn>

                <!--=============================================================-->

                <DataGridTextColumn Header="Country :" Width="*" >

                    <DataGridTextColumn.Binding>

                        <Binding Path="oReg.Country">

                            <Binding.ValidationRules>

                                <local2:GeneralStringRule AllowEmpty="False" AllowSpaces="True"/>

                            </Binding.ValidationRules>

                        </Binding>

                    </DataGridTextColumn.Binding>

                </DataGridTextColumn>

                <!--=============================================================-->

                <DataGridTextColumn Header="Company :" Width="*" >

                    <DataGridTextColumn.Binding>

                        <Binding Path="oReg.Company">

                            <Binding.ValidationRules>

                                <local2:GeneralStringRule AllowEmpty="False" AllowSpaces="True"/>

                            </Binding.ValidationRules>

                        </Binding>

                    </DataGridTextColumn.Binding>

                </DataGridTextColumn>

                <!--=============================================================-->

                <DataGridTextColumn Header="Age :" Width="*" >

                    <DataGridTextColumn.Binding>

                        <Binding Path="Age">

                            <Binding.ValidationRules>

                                <local2:AgeRangeRule Max="70" Min="16"/>

                            </Binding.ValidationRules>

                        </Binding>

                    </DataGridTextColumn.Binding>

                </DataGridTextColumn>

                <!--<DataGridTemplateColumn Header="Registration"

                                    Width="100"

                                    CanUserResize="True"

                                    CellTemplate="{StaticResource ResourceKey=ourFirstDTcombo}"/> -->

                <!--=============================================================-->

            </DataGrid.Columns>

        </DataGrid>

        <Button Content="Validation Check" HorizontalAlignment="Left" Foreground="Yellow"

                Margin="10,0,0,4" VerticalAlignment="Bottom" Width="110" Background="Crimson"/>

        <Button Content="Close" HorizontalAlignment="Right"

                Margin="0,0,10,4" VerticalAlignment="Bottom" Width="75" Background="LightGreen"/>

    </Grid>

</Window>

===================================================================================

private void pbMyDataGrid_Click(object sender, RoutedEventArgs e)

{

List<Speaker> aList = new List<Speaker>();

aList = Speaker.PopulateSpeakers();

DataGridValidation oDGV = new DataGridValidation();

FrameworkElement oFE = oDGV.myDataGrid;   //--- care of case sensitivity ---

DataGridViewModel oDGVM = new DataGridViewModel(aList, oFE, oDGV);

oDGV.DataContext = oDGVM;

oDGV.ShowDialog();

}

===================================================================================

using System;

using System.ComponentModel;   //----- INotifyPropertyChanged .....

using MyClasses;

using System.Collections.Generic;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Input;

using System.Windows.Media;

using System.Text;

using System.Collections.ObjectModel;

namespace MVVM_ValidationCologne2016.Views

{

//===============================================================================

public class DataGridViewModel : INotifyPropertyChanged, IDataErrorInfo

{

//----- << there are some unused sections below >>-----

//----- << as this is a work in progress file >>-----

//----- fields -----

private List<Speaker> _aList;

private List<FrameworkElement> _aListTBs = new List<FrameworkElement>();

private ObservableCollection<string> _oOC = new ObservableCollection<string>();

private int _elementCount = 0;

private string _elementSetName = "";

StringBuilder oSB = new StringBuilder();

Window oParent;

//----- properties -----

public ObservableCollection<string> oOC

{

get { return _oOC; }

set

{

_oOC = value;

NotifyPropertyChanged("oOC");

}

}

public List<Speaker> aList

{

get { return _aList; }

set

{

_aList = value;

NotifyPropertyChanged("aList");

}

}

public List<FrameworkElement> aListTBs

{

get { return _aListTBs; }

set

{

_aListTBs = value;

NotifyPropertyChanged("aListTBs");

}

}

public int ElementCount

{

get { return _elementCount; }

set

{

_elementCount = value;

NotifyPropertyChanged("ElementCount");

}

}

public string ElementSetName

{

get { return _elementSetName; }

set

{

_elementSetName = value;

NotifyPropertyChanged("ElementSetName");

}

}

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

private ICommand _doInvalidCheckCommand;

public ICommand DoInvalidCheckCommand

{

get

{

return _doInvalidCheckCommand;

}

set

{

_doInvalidCheckCommand = value;

}

}

private ICommand _findAllCommand;

public ICommand FindAllCommand

{

get

{

return _findAllCommand;

}

set

{

_findAllCommand = value;

}

}

private ICommand _findValidCommand;

public ICommand FindValidCommand

{

get

{

return _findValidCommand;

}

set

{

_findValidCommand = value;

}

}

private ICommand _findTBsCommand;

public ICommand FindTBsCommand

{

get

{

return _findTBsCommand;

}

set

{

_findTBsCommand = value;

}

}

private ICommand _findNonTBsCommand;

public ICommand FindNonTBsCommand

{

get

{

return _findNonTBsCommand;

}

set

{

_findNonTBsCommand = value;

}

}

private ICommand _closeFormCommand;

public ICommand CloseFormCommand

{

get

{

return _closeFormCommand;

}

set

{

_closeFormCommand = value;

}

}

private FrameworkElement _oFEVM;

public FrameworkElement oFEVM

{

get

{

return _oFEVM;

}

set

{

_oFEVM = value;

}

}

public string Error

{

get

{

throw new NotImplementedException();

}

}

public string this[string columnName]

{

get

{

throw new NotImplementedException();

}

}

//----- methods -----

public void MyCloseForm(object obj)

{

string myResult = MyCheckInvalid(oFEVM);

ElementSetName = ">> Any Invalid <<";

MessageBox.Show(myResult, "My Result :");

if (myResult == "OK")

{ this.oParent.Close(); }

//----- provide option to over-ride ! -----

}

public void FindAll(object obj)

{

oOC.Clear();

aListTBs.Clear();

GetAllElementsInVT(1, oFEVM);

ElementCount = oOC.Count;

ElementSetName = ">> All <<";

}

public void FindTBs(object obj)

{

oOC.Clear();

aListTBs.Clear();

SearchForTBs(1, oFEVM);

ElementCount = oOC.Count;

ElementSetName = ">> TBs <<";

}

public void FindNonTBs(object obj)

{

oOC.Clear();

aListTBs.Clear();

SearchForNonTBs(1, oFEVM);

ElementCount = oOC.Count;

ElementSetName = ">> Non TBS <<";

}

public void FindAllValid(object obj)

{

oOC.Clear();

aListTBs.Clear();

SearchForAllValid(1, oFEVM);

ElementCount = oOC.Count;

ElementSetName = ">> All Valid <<";

}

public void SearchForAllValid(int depth, DependencyObject obj)

{

//----- reject all but TextBoxes -----

if (!Validation.GetHasError(obj))

{

this.oOC.Add(obj.ToString());

}

for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)

{

SearchForAllValid(depth + 1, VisualTreeHelper.GetChild(obj, i));

}

}

public void SearchForNonTBs(int depth, DependencyObject obj)

{

//----- reject all but TextBoxes -----

if (!obj.ToString().Contains(".TextBox:"))

{

this.oOC.Add(obj.ToString());

}

for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)

{

SearchForNonTBs(depth + 1, VisualTreeHelper.GetChild(obj, i));

}

}

public void SearchForTBs(int depth, DependencyObject obj)

{

//----- reject all but TextBoxes -----

if (obj.ToString().Contains(".TextBox:"))

{

this.oOC.Add(obj.ToString());

}

for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)

{

SearchForTBs(depth + 1, VisualTreeHelper.GetChild(obj, i));

}

}

public void PrintVisualTree(int depth, DependencyObject obj)

{

//----- reject all but TextBoxes -----

if (obj.ToString().Contains(".TextBox:"))

{

this.oSB.Append("\n" + obj);

this.oOC.Add(obj.ToString());

}

for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)

{

PrintVisualTree(depth + 1, VisualTreeHelper.GetChild(obj, i));

}

}

public void GetAllElementsInVT(int depth, DependencyObject obj)

{

//----- include all -----

this.oOC.Add(obj.ToString());

for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)

{

GetAllElementsInVT(depth + 1, VisualTreeHelper.GetChild(obj, i));

}

}

public void GetAllTextBoxesInVT(int depth, DependencyObject obj)

{

//----- reject all but TextBoxes -----

if (obj.ToString().Contains(".TextBox:"))

{

aListTBs.Add((FrameworkElement)obj);

}

for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)

{

GetAllTextBoxesInVT(depth + 1, VisualTreeHelper.GetChild(obj, i));

}

}

public void CheckInvalid(object obj)

{

string myResult = MyCheckInvalid(oFEVM);

ElementCount = oOC.Count;

ElementSetName = ">> Any Invalid <<";

}

private string MyCheckInvalid(FrameworkElement oFE)

{

oOC.Clear();

aListTBs.Clear();

GetAllTextBoxesInVT(1, oFE);

string cText = "";

string cInValid = "";

int count = 0;

foreach (FrameworkElement item in aListTBs)

{

if (Validation.GetHasError(item))

{

cInValid = Validation.GetHasError(item).ToString();

//cText += item.Name + "[" + cInValid + "]" + " " + "\n";

//----- below is better ! -----

cText += item.ToString() + "[" + cInValid + "]" + " " + "\n";

count += 1;

this.oOC.Add(((FrameworkElement)item).ToString());

}

}

ElementCount = oOC.Count;

if (count > 0)

{

MessageBox.Show("It seems that input data needs editing before the form is closed!", "CAUTION - Errors? :");

return count.ToString();

}

else

{

MessageBox.Show("It would appear that all data values are valid ones.", "SAFE to Continue! :");

return "OK";

}

}

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

//----- events -----

public event PropertyChangedEventHandler PropertyChanged;

//----- constructors -----

public DataGridViewModel()

{

aList = new List<Speaker>();

oFEVM = new FrameworkElement();

oParent = new Window();

List<Speaker> oSpkLst = new List<Speaker>();

}

public DataGridViewModel(List<Speaker> oSpkLst, FrameworkElement oFE, Window oP)

{

this.oParent = oP;

oFEVM = oFE;

aList = oSpkLst;

}

//----- methods -----

public void NotifyPropertyChanged(string propertyName)

{

if (PropertyChanged != null)

{

PropertyChanged(this, new PropertyChangedEventArgs(propertyName));

}

}

}

}

===================================================================================

===================================================================================

===================================================================================

===================================================================================

===================================================================================

===================================================================================

===================================================================================

===================================================================================

PHH - 14th. February 2016.


'DataGrid' and Validation (by Row)

'DataGrid' and Validation (by Row)

 Previous topic Next topic

'DataGrid' and Validation (by Row)

 Previous topic Next topic

Lets start this sub-section with a visit to the "Help Viewer 2.2" information / knowledge base. I use it

a lot and think others guys are missing out ;-)

Do a search on "DataGrid class" - I got this :-

============================================================

Notice that it is right up to date as we have framework versions 4.6 & 4.5 mentioned.

Lets look at the hierarchy of inheritance - notice the two 'element' entries - UI and Framework :-

================================================================

There is a lot of information in the section found by searching, here is just a snip, but with an EXCELLENT hot link :-

===============================================================================================================================

If we follow the hot link we find some very useful information here below is just the start of it :-

============================================================================================================================

Once the documentation points us at the sample it provides, it is easy to copy and adapt the script and C# code to give

us a working example on which we can build - more on that later.

For the present, lets see what I got once I had entered the code and run the application - study this below :-

================================================================================================

Yes, above we have a DataGrid with four collection items.

The items are each a 'Course' object - and the class 'Course' is is relatively simple "Complex Business Data Class".

So we are seeing four complex business data objects.

If we edit the ID cell in the first row, and enter a character string in place of digits, we get a validation error as shown.

Please note the warning (exclamation) icon at the LHS (left hand side) of the first row - check this out below :-

=======================================================================================================================

The error message shown above is actually from a VR (validation rule) provided in the example from Microsoft.

By studying and copying this VR we can easily then write our own.

Lets now see what we get when we edit the 2nd. row and remove the Course name, leaving a blank in the input box.

Checkout the next image to see what happens :-

============================================================================================

Obviously, it is time for us to move on and into the next sub-section.

There we will see how to improve the working MS sample and to improve our skills and knowledge as

we have a go at writing our own VR's and a few other bits and bobs (pieces).

See you in the next sub-section ...................

PHH - 15th. February 2016.


Adding our own Validation Rules

Adding our own Validation Rules

Adding our own Validation Rules

 Previous topic Next topic

As you will know by now, I do like VRs and think that applying them, or associating them with text

input UI elements, is a good and nice way to design validation into our WPF forms.

Personally, I like to keep the validation code for one public property, separate from code for other properties.

Here in the section, where we apply a 'Row Wide' validation, we can still keep all of our code rules separate

from one and other.

Here is a simple enough VR where the code has been copied and adapted from the one provided in the sample

from Microsoft - check it thoroughly :-

==========================================================================================

Obviously, line 173 is the crucial one as it gives us a 'handle' on the data item we require.

We will see quite a few lines similar to lines 173 & 174 above as we develop more VRs for other properties.

Lets now try out this VR by providing a value too large for the limits set, and then moving the cursor into the following row :-

=====================================================================================================================================

Yep! - the error message is indeed the one we wrote in our rule.

Lets now try and go too low in value - see below for the result (no message as the mouse is not hovering over the warning icon) :-

[ .. also check the XAML script which is below the Form, see the two lines of validation rules .. ]

=====================================================================================================

I don't like the above sample VR name, 'Course' is too general, and "Dates" is far better I feel - it matches what

it does. probably 'CompareDates' would have been even better.

And now here is my improvement in the script section of the application - we now have four Validation Rules :-

=======================================================================================================

So lets now try out at runtime the new VR for 'Course Name' - checkout below :-

=========================================================================================================================================

[ .. remember we need to move off the row being edited before we get Row Validation Errors showing .. ]

Well, yes, it would appear from the above image that we can't have single character string for 'Name'.

Look at the following image when we provide a second character - now no errors showing :-

====================================================================================================

Lets move on to another VR we made.

In line numbered 51 shown above in script, you see I created a new VR called 'StartDate'.

Here it is in action - we compare TODAY with the starting date of the course. Note that this is quite arbitrary and just a simple

condition made up by myself so that I could make a VR and check it out - full testing etc. :-

=============================================================================================================================

Okay then folks, enough of all that testing, and error generation, lets try and add a new row completely.

Here is a newly typed in row of Course data which seems to comply with all the rules - see below and study :-

========================================================================================================

Okay guys, time to move on a little further.

Lets look at the script involved with the 'flagging' of the cell in focus when certain validation is 'violated'.

This style below is to make the TextBox red with a gold text, and an appropriate Tooltip. We saw script like

this earlier in the chapters (number 2 ?).

Study this carefully :-

=====================================================================================================================================

The LHS (left hand side) warning icon comes from other XAML script we will see later.

Now lets see what message we get when we type unacceptable characters in the date field (or cell) :-

=============================================================================================================================

And to check that the other date column does the same here is a further test :-

=====================================================================================================

So it looks like we get this red/gold cell display when the VR rule we write can't make any sense of the input

characters when a specified Type is expected.

The part of the script which selects to use this style is seen in the following image :-

==========================================================================================================

Obviously, it is line 47 which makes the association.

If we wish we can make a second style and apply that instead, just to feel in control of this stuff ;-0)

Nothing like feeling in control !

Here is some fun :-

===================================================================================================================

The column for 'ID' has now a different association for the error style display.

Here is where the style is 'applied' - see line 53 :-

===========================================================================================================

And now we just need to study the changed and adapted script for 'my' new error style :-

==========================================================================================================

Try making your own, for practise and also good fun ;-0)

Note below that we still get the first 'red' style in the other columns 3,4 and 5 as we have not as yet changed the script :-

===================================================================================================================

Enough of having fun.

Moving on ................

Let us now see how we generate the LHS warning icon shown below :-

===========

This is also done in XAML script and most of us can at least make sense of the sections of script, even

if we can't totally understand it :-

============================================================================================

So the template for the 'ErrorTemplate' is in fact a ControlTemplate. Don't worry too much if you do know

a lot about templates.

You need to remember that there is a LOT of stuff built into the DataGrid control, and it is not a simple control

either.

Let us just say this is the way we can decide, and over-write, what is shown when we get a Row Error

(validation type thereof) thrown.

New ones (VRs) done by myself :

Hey ! guys, it does not take much knowledge or skill to take a working VR from a good sample app and

use it as a starter for creating our own.

This is exactly what I did with the Microsoft sample - improved things and made a wider set of VRs to

suit my needs.

Here below is my version of the 'StartDate' VR - just for trial, exploring and experimentation I have decided

to disallow any input start dates before the current date and time - so they can't be in the past. Study this :-

============================================================================================

And moving on to something different, lets see how we can limit the Course title to between two hard

coded values (here 2 and 21) - later we will see how to use properties we can then use as parameters,

and assign at script application time.

Check these following details carefully :-

===============================================================================================

Note that each Validation Rule returns a 'ValidationResult' object - simple as it is.

Example VR changed to suit and improve our solution :

The following image shows us what used to be 'CourseValidationRule' in the Microsoft sample.

NOT a very descriptive name I have to say - AND - I am surprised with Microsoft !

I think it is a better title to call it 'DatesValidationRule', as it compares two dates, the only relevant

dates for the course applet - check this out :-

=======================================================================================

This VR serves as a good example to us - it shows that we need to take time with naming our members

and objects in our samples and solutions, otherwise we are stuck with a poor named item for the whole

rest of the development and maintenance time. Not ideal.

The names of our VRs should reflect exactly what they do.

So for my Postcode VR for me being resident in the UK, the title is better as "UKPostCodeStringRule" - see below :-

==================================================================================================================

And the above is better than my first attempt shown below :-

===================================================================================================================

As I do more with validation rules I am becoming convinced to include 'VR' in all names / titles at the RH end of the name.

So I think in future I will name this VR as "UKPostCodeStringVR" - and adding 'Shell' or 'Core' if I make a two tier set

of VRs.

See later and other sub-sections of "shells and cores." This is the 'wrapper' approach discussed in later sections.

Lets move on, see you in the next sub-section ...............

PHH - 15th. & 21st. February 2016.


All the validation in one rule !?

All the validation in one rule !?

 Previous topic Next topic

All the validation in one rule !?

 Previous topic Next topic

Lets look at another possibility for using and applying VRs (validation rules).

What if instead of providing a bunch of VRs we create and associate one 'Super' validation rule?

Look at this image to see what I mean - this is the XAML end of things :-

===================================================================================================================

In the above image we see script line 111 which refers to the only one VR is the collection. I have commented out

the others while I do trials and tests.

Here is the full script section :-

====================================================================================================================

Notice in our one 'super' trail VR we still have a parameter being used - 'AllowEmpty' is the property.

Now lets look at the new 'super' coded VR - check below :-

=====================================================================================================================

As you can see above we are applying conditional tests one after another.

PHH - 22nd. February 2016.


Making and Using Parameters with 'DG' VRs

Making and Using Parameters with 'DG' VRs

Making and Using Parameters with 'DG' VRs

 Previous topic Next topic

Yes, folks, when we apply Validation Rules (VRs) to rows within our DataGrid control we can also use parameters

in the script line we use to assign (or apply) the VR.

Because a VR class has defined properties does NOT say that we need to use them as parameters, we could just

let the defaults be used. However, it can prove very flexible as an approach - we will see a good example or two in

the following sub-section.

Usually, on one row we set a bunch (number) of VRs - this covers a handful of public properties on the complex data

type represented in the row.

Lets make a start and see how we can either allow or disallow empty entries for the PostCode column (field) :-

========================================================================================================================

We do not get an error warning because in the above image our script is providing a 'True' value for the "AllowEmpty" property

of the VR class called 'PostCodeValidationRule'.

And once we get to use parameters and scripting values, we may even use quite a few in one call.

Here below is me providing four parameter inputs for a general string VR - check it out carefully :-

=========================================================================================================================

Another thing to realise is that we can use some, but not all, of the parameters available, its up to us.

Just make sure that you write / design each VR so it has a usable default value for each declared public property.

Lets do a test where we edit the last row of the test data, and remove the PostCode cell content, we then try to move

off the two being edited, and we see that no error icon appears at the LHS of the data row. So the empty string

value has been accepted by our VR.

Study these details below :-

=======================================================================================================================

Just before we leave this small topic, lets focus for a moment on the 'ValidationStep' we have seen so

often in the MS sample applet.

What is a validation step ? and what are the available choices or inputs ?

Lets use intellisense to show us what is possible - checkout below :-

=======================================================================================================================

Well, it feels that 'UpdatedValue' is likely to be the best for us generally, but we need to find out more

on this subject as we do more with validation.

This can be our homework for the weekend.

Lets move on, see you in the next sub-section ...............

PHH - 17th. February & 21st. 2016.


Making our own improvements

Making our own improvements

Making our own improvements

 Previous topic Next topic

================================================================================================================================================

===================================================================================

===================================================================================================================

=====================================================================================================================================

==========================================================================================

=====================================================================================================

Testing and Multiple Validation Errors per Row :

PHH - 16th. February 2016.


working code and script

working code and script

working code and script

 Previous topic Next topic

Here is what is required in the References section :-

================================================

==================================== < Example as out of the Help Viewer notes > ==================================

using System;

using System.Collections.ObjectModel;

using System.ComponentModel;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Data;

namespace DataGridValidation

{

public partial class MainWindow : Window

{

public MainWindow()

{

InitializeComponent();

dataGrid1.InitializingNewItem += (sender, e) =>

{

Course newCourse = e.NewItem as Course;

newCourse.StartDate = newCourse.EndDate = DateTime.Today;

};

}

}

public class Courses : ObservableCollection<Course>

{

public Courses()

{

this.Add(new Course

{

Name = "Learning WPF",

Id = 1001,

StartDate = new DateTime(2010, 1, 11),

EndDate = new DateTime(2010, 1, 22)

});

this.Add(new Course

{

Name = "Learning Silverlight",

Id = 1002,

StartDate = new DateTime(2010, 1, 25),

EndDate = new DateTime(2010, 2, 5)

});

this.Add(new Course

{

Name = "Learning Expression Blend",

Id = 1003,

StartDate = new DateTime(2010, 2, 8),

EndDate = new DateTime(2010, 2, 19)

});

this.Add(new Course

{

Name = "Learning LINQ",

Id = 1004,

StartDate = new DateTime(2010, 2, 22),

EndDate = new DateTime(2010, 3, 5)

});

}

}

public class Course : IEditableObject, INotifyPropertyChanged

{

private string _name;

public string Name

{

get

{

return _name;

}

set

{

if (_name == value) return;

_name = value;

OnPropertyChanged("Name");

}

}

private int _number;

public int Id

{

get

{

return _number;

}

set

{

if (_number == value) return;

_number = value;

OnPropertyChanged("Id");

}

}

private DateTime _startDate;

public DateTime StartDate

{

get

{

return _startDate;

}

set

{

if (_startDate == value) return;

_startDate = value;

OnPropertyChanged("StartDate");

}

}

private DateTime _endDate;

public DateTime EndDate

{

get

{

return _endDate;

}

set

{

if (_endDate == value) return;

_endDate = value;

OnPropertyChanged("EndDate");

}

}

#region IEditableObject

private Course backupCopy;

private bool inEdit;

public void BeginEdit()

{

if (inEdit) return;

inEdit = true;

backupCopy = this.MemberwiseClone() as Course;

}

public void CancelEdit()

{

if (!inEdit) return;

inEdit = false;

this.Name = backupCopy.Name;

this.Id = backupCopy.Id;

this.StartDate = backupCopy.StartDate;

this.EndDate = backupCopy.EndDate;

}

public void EndEdit()

{

if (!inEdit) return;

inEdit = false;

backupCopy = null;

}

#endregion

#region INotifyPropertyChanged

public event PropertyChangedEventHandler PropertyChanged;

private void OnPropertyChanged(string propertyName)

{

if (PropertyChanged != null)

{

PropertyChanged(this,

new PropertyChangedEventArgs(propertyName));

}

}

#endregion

}

public class CourseValidationRule : ValidationRule

{

public override ValidationResult Validate(object value,

System.Globalization.CultureInfo cultureInfo)

{

Course course = (value as BindingGroup).Items[0] as Course;

if (course.StartDate > course.EndDate)

{

return new ValidationResult(false,

"Start Date must be earlier than End Date.");

}

else

{

return ValidationResult.ValidResult;

}

}

}

}

======================================================================================

<Window x:Class="DataGridValidation.MainWindow"

  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

  xmlns:local="clr-namespace:DataGridValidation"

  Title="DataGrid Validation Example" Height="240" Width="600">

    <Grid>

        <Grid.Resources>

            <local:Courses x:Key="courses"/>

        </Grid.Resources>

        <DataGrid Name="dataGrid1" FontSize="20" RowHeaderWidth="27"

      ItemsSource="{StaticResource courses}"

      AutoGenerateColumns="False">

            <DataGrid.Resources>

                <Style x:Key="errorStyle" TargetType="{x:Type TextBox}">

                    <Setter Property="Padding" Value="-2"/>

                    <Style.Triggers>

                        <Trigger Property="Validation.HasError" Value="True">

                            <Setter Property="Background" Value="Red"/>

                            <Setter Property="ToolTip"

                Value="{Binding RelativeSource={RelativeSource Self},

                  Path=(Validation.Errors)[0].ErrorContent}"/>

                        </Trigger>

                    </Style.Triggers>

                </Style>

            </DataGrid.Resources>

            <DataGrid.Columns>

                <DataGridTextColumn Header="Course Name"

          Binding="{Binding Name, TargetNullValue=(enter a course name)}"/>

                <DataGridTextColumn Header="Course ID"

          EditingElementStyle="{StaticResource errorStyle}"

          Binding="{Binding Id, ValidatesOnExceptions=True}"/>

                <DataGridTextColumn Header="Start Date"

          EditingElementStyle="{StaticResource errorStyle}"

          Binding="{Binding StartDate, ValidatesOnExceptions=True,

            StringFormat=d}"/>

                <DataGridTextColumn Header="End Date"

          EditingElementStyle="{StaticResource errorStyle}"

          Binding="{Binding EndDate, ValidatesOnExceptions=True,

            StringFormat=d}"/>

            </DataGrid.Columns>

            <DataGrid.RowValidationRules>

                <local:CourseValidationRule ValidationStep="UpdatedValue"/>

            </DataGrid.RowValidationRules>

            <DataGrid.RowValidationErrorTemplate>

                <ControlTemplate>

                    <Grid Margin="0,-2,0,-2"

            ToolTip="{Binding RelativeSource={RelativeSource

            FindAncestor, AncestorType={x:Type DataGridRow}},

            Path=(Validation.Errors)[0].ErrorContent}">

                        <Ellipse StrokeThickness="0" Fill="Red"

              Width="{TemplateBinding FontSize}"

              Height="{TemplateBinding FontSize}" />

                        <TextBlock Text="!" FontSize="{TemplateBinding FontSize}"

              FontWeight="Bold" Foreground="White"

              HorizontalAlignment="Center"  />

                    </Grid>

                </ControlTemplate>

            </DataGrid.RowValidationErrorTemplate>

        </DataGrid>

    </Grid>

</Window>

======================================================================================

=============================== < after our changes and additions > ======================================

using System;

using System.Collections.ObjectModel;

using System.ComponentModel;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Data;

namespace DataGridValidation

{

public partial class MainWindow : Window

{

public MainWindow()

{

InitializeComponent();

dataGrid1.InitializingNewItem += (sender, e) =>

{

Course newCourse = e.NewItem as Course;

newCourse.StartDate = newCourse.EndDate = DateTime.Today;

};

}

}

public class Courses : ObservableCollection<Course>

{

public Courses()

{

this.Add(new Course

{

Name = "Learning WPF",

Id = 1001,

StartDate = new DateTime(2010, 1, 11),

EndDate = new DateTime(2010, 1, 22)

});

this.Add(new Course

{

Name = "Learning Silverlight",

Id = 1002,

StartDate = new DateTime(2010, 1, 25),

EndDate = new DateTime(2010, 2, 5)

});

this.Add(new Course

{

Name = "Learning Expression Blend",

Id = 1003,

StartDate = new DateTime(2010, 2, 8),

EndDate = new DateTime(2010, 2, 19)

});

this.Add(new Course

{

Name = "Learning LINQ",

Id = 1004,

StartDate = new DateTime(2010, 2, 22),

EndDate = new DateTime(2010, 3, 5)

});

}

}

public class Course : IEditableObject, INotifyPropertyChanged

{

private string _name;

public string Name

{

get

{

return _name;

}

set

{

if (_name == value) return;

_name = value;

OnPropertyChanged("Name");

}

}

private int _number;

public int Id

{

get

{

return _number;

}

set

{

if (_number == value) return;

_number = value;

OnPropertyChanged("Id");

}

}

private DateTime _startDate;

public DateTime StartDate

{

get

{

return _startDate;

}

set

{

if (_startDate == value) return;

_startDate = value;

OnPropertyChanged("StartDate");

}

}

private DateTime _endDate;

public DateTime EndDate

{

get

{

return _endDate;

}

set

{

if (_endDate == value) return;

_endDate = value;

OnPropertyChanged("EndDate");

}

}

#region IEditableObject

private Course backupCopy;

private bool inEdit;

public void BeginEdit()

{

if (inEdit) return;

inEdit = true;

backupCopy = this.MemberwiseClone() as Course;

}

public void CancelEdit()

{

if (!inEdit) return;

inEdit = false;

this.Name = backupCopy.Name;

this.Id = backupCopy.Id;

this.StartDate = backupCopy.StartDate;

this.EndDate = backupCopy.EndDate;

}

public void EndEdit()

{

if (!inEdit) return;

inEdit = false;

backupCopy = null;

}

#endregion

#region INotifyPropertyChanged

public event PropertyChangedEventHandler PropertyChanged;

private void OnPropertyChanged(string propertyName)

{

if (PropertyChanged != null)

{

PropertyChanged(this,

new PropertyChangedEventArgs(propertyName));

}

}

#endregion

}

public class IdValidationRule : ValidationRule

{

public override ValidationResult Validate(object value,

System.Globalization.CultureInfo cultureInfo)

{

Course course = (value as BindingGroup).Items[0] as Course;

if (course.Id < 100 || course.Id > 3000)

{

return new ValidationResult(false,

"Identity must be between 100 and 3000.");

}

else

{

return ValidationResult.ValidResult;

}

}

}

public class NameValidationRule : ValidationRule

{

public override ValidationResult Validate(object value,

System.Globalization.CultureInfo cultureInfo)

{

Course course = (value as BindingGroup).Items[0] as Course;

if (course.Name.Length < 2 || course.Name.Length > 21 )

{

return new ValidationResult(false,

"Name of course is between 2 & 21 in length.");

}

else

{

return ValidationResult.ValidResult;

}

}

}

public class StartDateValidationRule : ValidationRule

{

public override ValidationResult Validate(object value,

System.Globalization.CultureInfo cultureInfo)

{

Course course = (value as BindingGroup).Items[0] as Course;

if (course.StartDate < DateTime.Now)

{

return new ValidationResult(false,

"Start Date must be after today.");

}

else

{

return ValidationResult.ValidResult;

}

}

}

public class DatesValidationRule : ValidationRule

{

public override ValidationResult Validate(object value,

System.Globalization.CultureInfo cultureInfo)

{

Course course = (value as BindingGroup).Items[0] as Course;

if (course.StartDate > course.EndDate)

{

return new ValidationResult(false,

"Start Date must be earlier than End Date.");

}

else

{

return ValidationResult.ValidResult;

}

}

}

}

======================================================================================

<Window x:Class="DataGridValidation.MainWindow"

  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

  xmlns:local="clr-namespace:DataGridValidation"

  Title="DataGrid Validation Example" Height="240" Width="600">

    <Grid>

        <Grid.Resources>

            <local:Courses x:Key="courses"/>

        </Grid.Resources>

        <DataGrid Name="dataGrid1" FontSize="20" RowHeaderWidth="27"

                  ItemsSource="{StaticResource courses}"

                  AutoGenerateColumns="False">

            <DataGrid.Resources>

                <Style x:Key="errorStyle" TargetType="{x:Type TextBox}">

                    <Setter Property="Padding" Value="-2"/>

                    <Style.Triggers>

                        <Trigger Property="Validation.HasError" Value="True">

                            <Setter Property="Background" Value="Red"/>

                            <Setter Property="Foreground" Value="Gold"/>

                            <Setter Property="ToolTip"

                                    Value="{Binding RelativeSource={RelativeSource Self},

                                    Path=(Validation.Errors)[0].ErrorContent}"/>

                        </Trigger>

                    </Style.Triggers>

                </Style>

            </DataGrid.Resources>

            <DataGrid.Columns>

                <DataGridTextColumn Header="Course Name"

                                    Binding="{Binding Name, TargetNullValue=(enter a course name), ValidatesOnExceptions=True}"/>

                <DataGridTextColumn Header="Course ID"

                                    EditingElementStyle="{StaticResource errorStyle}"

                                    Binding="{Binding Id, ValidatesOnExceptions=True}"/>

                <DataGridTextColumn Header="Start Date"

                                    EditingElementStyle="{StaticResource errorStyle}"

                                    Binding="{Binding StartDate, ValidatesOnExceptions=True,

                                    StringFormat=d }"/>

                <DataGridTextColumn Header="End Date"

                                    EditingElementStyle="{StaticResource errorStyle}"

                                    Binding="{Binding EndDate, ValidatesOnExceptions=True,

                                    StringFormat=d}"/>

            </DataGrid.Columns>

            <DataGrid.RowValidationRules>

                <local:DatesValidationRule ValidationStep="UpdatedValue"/>

                <local:IdValidationRule ValidationStep="UpdatedValue"/>

                <local:NameValidationRule ValidationStep="UpdatedValue"/>

                <local:StartDateValidationRule ValidationStep="UpdatedValue"/>

            </DataGrid.RowValidationRules>

            <DataGrid.RowValidationErrorTemplate>

                <ControlTemplate>

                    <Grid Margin="0,-2,0,-2"

                          ToolTip="{Binding RelativeSource={RelativeSource

                          FindAncestor, AncestorType={x:Type DataGridRow}},

                          Path=(Validation.Errors)[0].ErrorContent}">

                        <Ellipse StrokeThickness="0" Fill="Red"

                                 Width="{TemplateBinding FontSize}"

                                 Height="{TemplateBinding FontSize}" >

                        </Ellipse>

                        <TextBlock Text="!" FontSize="{TemplateBinding FontSize}"

                                   FontWeight="Bold" Foreground="White"

                                   HorizontalAlignment="Center" >

                        </TextBlock>

                    </Grid>

                </ControlTemplate>

            </DataGrid.RowValidationErrorTemplate>

        </DataGrid>

    </Grid>

</Window>

======================================================================================

======================================================================================

======================================================================================

======================================================================================

PHH - 15th. February 2016.


DataGrid - adding Drop Lists / ComboBoxes

DataGrid - adding Drop Lists / ComboBoxes

DataGrid - adding Drop Lists / ComboBoxes

 Previous topic Next topic

Well folks, here is the good news, we already have enough skills and knowledge to be able to validate

data in the format of collections within collections - possibly 'ad infinitum'.

By collections of items I also mean items which are complex business class object items. Like Customers

with Orders, with Order-Items.

Below is an image from the Colgne 2015 Conference, we see that the DataGrid shown has rows of Attendee items. The last column

shows that each Attendee has a public property which is a collection of Sessions they will visit. It is this sort of 'sub-collection'

which we will see how to edit and validate.

Check out the value columns, one for an object and one for a collection :-

In last years conference material we saw how to display editable complex data objects in ListBoxes and ComboBoxes.

As well as how to have a drop list within a drop list, have have then editable if required.

We were focusing on the Data Binding, and showing how to make it all work in a MVVM way (even without a specific

MVVM framework). Remind yourself of what we were exploring :-

And finally, from last year, we see below an editable collection under Attendees which is a drop

list with a data template allowing data binding and data editing.

Check out the details :-

Last year we focused on how to do data binding to public properties displayed in Data Templates

used for items in list and drop lists, as well as a DataGrid column.

This year we will focus on Validating the data in these collections.

Since we already known how to validate on a row basis, the value property data in a DataGrid, we can

use a simple tip or trick from data binding to allow us to easily do a 'second level' item collection validation.

Its all very well showing that we have the skilss to write code and script to have drop list embedded - BUT -

it is quite another thing to be a user trying to manage the data, make editing changes, and not make mistakes.

Also it is often difficult to physically fit the TextBox elements into templates and have these in drop lists.

The simple trick is to use simple data-binding to allow us to have multiple DataGrids, usually just two, but

this can be three or four.

Lets move on ......................

Making use of the 'SelectedItem' Attribute / Property :

In the DataGrid class we see from Help Viewer 2.2 the following about 'SelectedItem' :-

=======================================================================================================================================

And the hot link shows us the next image detail :-

===========================================================================================================

And a few possibly useful remarks as well :-

================================================================================================================================

Okay guys, it feels like time for me to make a sample app to explore what is possible. For this I will take the

previous example of 'Course' items and upgrade it to something more suitable.

For each Course we are going to have a short list (collection) of attendee guys, and each guy may attend more

than one Course.

See you after a coding break ................

The newly updated project - "Attendees for Courses" :

I will share with you some thoughts and facts as I progress with the new app development, just

so you know what I am doing - and for why!

After starting a new project / solution I first needed to define my suitable classes for data and populate

a Model suitable for binding to a View, and hence a DataGrid or two.

Here is my very first attempt with the new data - ListBox is not good as I would need to create a DataTemplate.

Check it out before my better idea :-

===================================================================================================

Better idea is to use a DataGrid, as it sorts out its own template 'stuff' if we elect to have AutoGenerateColumns

set to 'True'.

Here is a quick trial result :-

============================================================================================================

==========================================================================================

========================================================================================================

=========================================================================================================

==========================================================================================

=======================================================================================

=========================================================================================================

=====================================================================================

========================================================================================================

================================================================================================================================================

==================================================================================

=======================================================================================================

============================================================================================================================================================

======================================================================================================================

PHH - 15th. & 17th. & 18th. February 2016.


working script and code

working script and code

working script and code

 Previous topic Next topic

=============================================================================

using System.Windows;

using MyClasses;

using DataGridValidationMVVM.b_Views;

using MVVM_ValidationCologne2016.ViewModels;

namespace DataGridValidationMVVM

{

/// <summary>

/// Interaction logic for MainWindow.xaml

/// </summary>

public partial class MainWindow : Window

{

public MainWindow()

{

InitializeComponent();

}

private void pbSimple_Click(object sender, RoutedEventArgs e)

{

DG_SimpleViewModel oDGSVM = new DG_SimpleViewModel(Courses.GetBunchCourses());

DG_SimpleView oDGSV = new DG_SimpleView();

oDGSV.DataContext = oDGSVM;

oDGSV.Show();

}

private void pbComplex_Click(object sender, RoutedEventArgs e)

{

DG_ComplexViewModel oDGCVM = new DG_ComplexViewModel( Courses.GetBunchCourses() );

DG_ComplexView oComplexView = new DG_ComplexView();

oComplexView.DataContext = oDGCVM;

oComplexView.ShowDialog();

}

private void pbClose_Click(object sender, RoutedEventArgs e)

{

this.Close();

}

}

}

=============================================================================

<Window x:Class="DataGridValidationMVVM.MainWindow"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

        xmlns:local="clr-namespace:DataGridValidationMVVM"

        mc:Ignorable="d"

        Title="MainWindow" Height="350" Width="525" Background="Beige">

    <Grid>

        <Grid.Resources>

            <!--==========================================================-->

            <Style TargetType="Button">

                <Setter Property="HorizontalAlignment" Value="Left" />

                <Setter Property="VerticalAlignment" Value="Top" />

                <Setter Property="Width" Value="160" />

                <Setter Property="Height" Value="30" />

                <Setter Property="FontFamily" Value="centuryschoolbook" />

                <Setter Property="FontSize" Value="18" />

            </Style>

            <Style TargetType="TextBlock">

                <Setter Property="HorizontalAlignment" Value="Left" />

                <Setter Property="VerticalAlignment" Value="Top" />

            </Style>

            <!--==========================================================-->

        </Grid.Resources>

        <Button x:Name="pbSimple" Content="DataGrid Simple" Margin="8,25,0,0"

                Background="LightGreen" Click="pbSimple_Click"/>

        <Button x:Name="pbComplex" Content="DataGrid Complex" Margin="8,72,0,0"

                Background="Yellow" Click="pbComplex_Click"/>

        <Button x:Name="pbClose" Content="Close" Margin="0,279,10,10"

                Background="Tomato" Click="pbClose_Click" HorizontalAlignment="Right"

                Width="93" Height="Auto" VerticalAlignment="Stretch"/>

        <TextBlock x:Name="tblkSay" HorizontalAlignment="Left" Margin="174,25,0,0"

                   TextWrapping="Wrap" Height="77" Width="333" Background="MintCream"

                   Text="Validation of rows in DataGrids - simple and complex.  Collections, and collections within collections." VerticalAlignment="Top"

                   FontSize="16" Foreground="Purple"/>

        <!--<DataGrid x:Name="dataGrid" Margin="10,120,10,54" AutoGenerateColumns="True"

                  ItemsSource="{Binding }"/>   -->

    </Grid>

</Window>

=============================================================================

using System;

using System.Collections.ObjectModel;

namespace MyClasses

{

public static class Courses

{

static Courses()

{

//----- nothing for the moment, later perhaps -----

}

public static ObservableCollection<Course> GetBunchCourses()

{

ObservableCollection<Course> oOC = new ObservableCollection<Course>();

Course oC = new Course

{

Name = "Learning WPF",

Id = 1001,

StartDate = new DateTime(2010, 1, 11),

EndDate = new DateTime(2010, 1, 22),

PostCode = "WD1 7JJ",

oAttendees = Attendees.GetBunchAttendees()

};

oOC.Add(oC);

oC = new Course

{

Name = "Learning Silverlight",

Id = 1002,

StartDate = new DateTime(2010, 1, 25),

EndDate = new DateTime(2010, 2, 5),

PostCode = "SG4 7QR",

oAttendees = Attendees.GetBunchAttendees()

};

oOC.Add(oC);

oC = new Course

{

Name = "Learning Expression Blend",

Id = 1003,

StartDate = new DateTime(2010, 2, 8),

EndDate = new DateTime(2010, 2, 19),

PostCode = "NP19 7QR",

oAttendees = Attendees.GetBunchAttendees()

};

oOC.Add(oC);

oC = new Course

{

Name = "Start PowerShell5",

Id = 1013,

StartDate = new DateTime(2016, 4, 20),

EndDate = new DateTime(2016, 4, 22),

PostCode = "G58 1SB",

oAttendees = Attendees.GetBunchAttendees()

};

oOC.Add(oC);

oC = new Course

{

Name = "Learning LINQ",

Id = 1004,

StartDate = new DateTime(2010, 2, 22),

EndDate = new DateTime(2010, 3, 5),

PostCode = "WD24 4JL",

oAttendees = Attendees.GetBunchAttendees()

};

oOC.Add(oC);

oC = new Course

{

Name = "Advanced PowerShell5",

Id = 1026,

StartDate = new DateTime(2016, 4, 20),

EndDate = new DateTime(2016, 4, 22),

PostCode = "EC2V 7HN",

oAttendees = Attendees.GetBunchAttendees()

};

oOC.Add(oC);

return oOC;

}

}

}

=============================================================================

using System;

using System.Collections.ObjectModel;

namespace MyClasses

{

public static class Attendees

{

static Attendees()

{

//----- nothing for the moment, later perhaps -----

}

public static ObservableCollection<Attendee> GetBunchAttendees()

{

ObservableCollection<Attendee> oOC = new ObservableCollection<Attendee>();

Attendee oA = new Attendee("James","Clark",19,true,DateTime.Now);

oOC.Add(oA);

oA = new Attendee("Mary","Malone",23,true,new DateTime(2016,02,13));

oOC.Add(oA);

oA = new Attendee("David", "Lloyd", 31, false, new DateTime(2016, 02, 28));

oOC.Add(oA);

oA = new Attendee("Ben", "Hepburn", 29, true, new DateTime(2015, 11, 30));

oOC.Add(oA);

oA = new Attendee("Rhian","Lewis",37,false,DateTime.Now);

oOC.Add(oA);

return oOC;

}

}

}

=============================================================================

using System;

using System.ComponentModel;   //----- INotifyPropertyChanged .....

using MyClasses;

using System.Collections.ObjectModel;

namespace MVVM_ValidationCologne2016.ViewModels

{

//===============================================================================

public class DG_ComplexViewModel : INotifyPropertyChanged

{

//----- properties -----

private ObservableCollection<Course> _oInCourses;

private Course _oSelectedCourse;

public Course oSelectedCourse

{

get { return _oSelectedCourse; }

set

{

_oSelectedCourse = value;

NotifyPropertyChanged("oSelectedCourse");

}

}

public ObservableCollection<Course> oInCourses

{

get { return _oInCourses; }

set

{

_oInCourses = value;

NotifyPropertyChanged("oInCourses");

}

}

//----- events -----

public event PropertyChangedEventHandler PropertyChanged;

//----- constructors -----

public DG_ComplexViewModel()

{

oInCourses = new ObservableCollection<Course>();

oInCourses.Add(new Course());

}

public DG_ComplexViewModel(ObservableCollection<Course> oInC)

{

this.oInCourses = oInC;

}

//----- methods -----

public void NotifyPropertyChanged(string propertyName)

{

if (PropertyChanged != null)

{

PropertyChanged(this, new PropertyChangedEventArgs(propertyName));

}

}

}

}

=============================================================================

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Data;

using System.Windows.Documents;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Imaging;

using System.Windows.Shapes;

namespace DataGridValidationMVVM.b_Views

{

/// <summary>

/// Interaction logic for DG_ComplexView.xaml

/// </summary>

public partial class DG_ComplexView : Window

{

public DG_ComplexView()

{

InitializeComponent();

}

}

}

=============================================================================

<Window x:Class="DataGridValidationMVVM.b_Views.DG_ComplexView"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

        xmlns:local="clr-namespace:DataGridValidationMVVM.b_Views"

        xmlns:local2="clr-namespace:DataGridValidation"

        mc:Ignorable="d"

        Title="DG_ComplexView" Height="640" Width="940" Background="AliceBlue">

    <Grid Height="549" VerticalAlignment="Top">

        <Grid.Resources>

            <DataTemplate x:Key="ourStartDate">

                <DatePicker DisplayDate="{Binding StartDate}" SelectedDate="{Binding StartDate}"/>

            </DataTemplate>

            <DataTemplate x:Key="ourEndDate">

                <DatePicker DisplayDate="{Binding EndDate}" SelectedDate="{Binding EndDate}"/>

            </DataTemplate>

            <DataTemplate x:Key="ourRegDate">

                <DatePicker DisplayDate="{Binding RegistrationDate}" SelectedDate="{Binding RegistrationDate}"/>

            </DataTemplate>

            <!--============================================-->

            <Style TargetType="DataGrid">

                <Setter Property="CanUserAddRows" Value="True" />

                <Setter Property="CanUserDeleteRows" Value="True" />

                <Setter Property="CanUserReorderColumns" Value="True" />

                <Setter Property="CanUserResizeRows" Value="True" />

                <Setter Property="CanUserSortColumns" Value="True" />

                <Setter Property="AutoGenerateColumns" Value="False" />

            </Style>

            <!--================================================================-->

            <Style x:Key="errorStyle" TargetType="{x:Type TextBox}">

                <Setter Property="Padding" Value="-2"/>

                <Style.Triggers>

                    <Trigger Property="Validation.HasError" Value="True">

                        <Setter Property="Background" Value="Red"/>

                        <Setter Property="Foreground" Value="Gold"/>

                        <Setter Property="ToolTip"

                                    Value="{Binding RelativeSource={RelativeSource Self},

                                    Path=(Validation.Errors)[0].ErrorContent}"/>

                    </Trigger>

                </Style.Triggers>

            </Style>

            <!--===========================< My Try !>==============================-->

            <Style x:Key="errorStyleMine" TargetType="{x:Type TextBox}">

                <Setter Property="Padding" Value="-4"/>

                <Style.Triggers>

                    <Trigger Property="Validation.HasError" Value="True">

                        <Setter Property="Background" Value="DarkBlue"/>

                        <Setter Property="Foreground" Value="Wheat"/>

                        <Setter Property="FontWeight" Value="Bold" />

                        <Setter Property="FontStyle" Value="Italic" />

                        <Setter Property="ToolTip"

                                    Value="{Binding RelativeSource={RelativeSource Self},

                                    Path=(Validation.Errors)[0].ErrorContent}"/>

                    </Trigger>

                </Style.Triggers>

            </Style>

        </Grid.Resources>

        <!--====================================================================-->

        <DataGrid x:Name="dataGrid" Margin="0,0,0,270"

                  AlternatingRowBackground="Beige"

                  AlternationCount="2"

                  Foreground="DarkBlue"

                  ItemsSource="{Binding oInCourses}"

                  SelectedItem="{Binding oSelectedCourse}"

                  FontSize="20">

            <!--================================================================================-->

            <DataGrid.Columns>

                <DataGridTextColumn Header="Course Name"

                                    Binding="{Binding Name, TargetNullValue=(enter a course name),

                                    ValidatesOnExceptions=True}"/>

                <DataGridTextColumn Header="Course ID"

                                    EditingElementStyle="{StaticResource errorStyleMine}"

                                    Binding="{Binding Id, ValidatesOnExceptions=True}"

                                    FontWeight="Bold"/>

                <DataGridTemplateColumn Header="Start Date" Width="160"

                                        CanUserResize="True"

                                        CellTemplate="{StaticResource ResourceKey=ourStartDate}"/>

                <DataGridTemplateColumn Header="End Date" Width="160"

                                        CanUserResize="True"

                                        CellTemplate="{StaticResource ResourceKey=ourEndDate}"/>

                <DataGridTextColumn Header="Postcode"

                                    EditingElementStyle="{StaticResource errorStyle}"

                                    Binding="{Binding PostCode, ValidatesOnExceptions=True}"

                                    FontStyle="Italic" FontWeight="Bold"/>

                <DataGridTextColumn Header="Attendees"

                                    Binding="{Binding oAttendees, ValidatesOnExceptions=True}"/>

            </DataGrid.Columns>

            <!--================================================================================-->

            <DataGrid.RowValidationRules>

                <local2:DatesValidationRule ValidationStep="UpdatedValue" />

                <local2:IdValidationRule ValidationStep="UpdatedValue"/>

                <local2:NameValidationRule Min="2"

                                           Max="21"

                                           AllowSpaces="True"

                                           AllowEmpty="False"

                                           ValidationStep="UpdatedValue"/>

                <local2:StartDateValidationRule ValidationStep="UpdatedValue"/>

                <local2:PostCodeValidationRule AllowEmpty="True" ValidationStep="UpdatedValue"/>

            </DataGrid.RowValidationRules>

            <!--================================================================================-->

            <DataGrid.RowValidationErrorTemplate>

                <ControlTemplate>

                    <Grid Margin="0,-2,0,-2"

                          ToolTip="{Binding RelativeSource={RelativeSource

                          FindAncestor, AncestorType={x:Type DataGridRow}},

                          Path=(Validation.Errors)[0].ErrorContent}">

                        <Ellipse StrokeThickness="0" Fill="Red"

                                 Width="{TemplateBinding FontSize}"

                                 Height="{TemplateBinding FontSize}" >

                        </Ellipse>

                        <TextBlock Text="!" FontSize="{TemplateBinding FontSize}"

                                   FontWeight="Bold" Foreground="White"

                                   HorizontalAlignment="Center" >

                        </TextBlock>

                    </Grid>

                </ControlTemplate>

            </DataGrid.RowValidationErrorTemplate>

            <!--================================================================================-->

        </DataGrid>

        <!--================================================================================-->

        <Button x:Name="button" Content="Close"

                Margin="0,0,10,8" Height="20" VerticalAlignment="Bottom" HorizontalAlignment="Right" Width="75"/>

        <!--================================================================================-->

        <DataGrid x:Name="dataGrid1"

                  Margin="0,297,99,10"

                  ItemsSource="{Binding oSelectedCourse.oAttendees}"

                  AlternatingRowBackground="LightBlue"

                  AlternationCount="2"

                  FontSize="20" >

            <DataGrid.Columns>

                <DataGridTextColumn Header="Attendee ID"

                                    EditingElementStyle="{StaticResource errorStyleMine}"

                                    Binding="{Binding AttendeeId, ValidatesOnExceptions=True}"

                                    FontWeight="Bold"/>

                <DataGridTextColumn Header="Firstname"

                                    Binding="{Binding FirstName, TargetNullValue=(enter a first name),

                                                      ValidatesOnExceptions=True}"/>

                <DataGridTextColumn Header="Lastname"

                                    Binding="{Binding LastName, TargetNullValue=(enter a last name),

                                                      ValidatesOnExceptions=True}"/>

                <DataGridTemplateColumn Header="Registration Date" Width="180"

                                        CanUserResize="True"

                                        CellTemplate="{StaticResource ResourceKey=ourRegDate}"/>

                <DataGridCheckBoxColumn Header="Has Paid" Binding="{Binding HasPaid}" />

            </DataGrid.Columns>

            <!--================================================================================-->

            <DataGrid.RowValidationRules>

                <local2:AttendeeIdValidationRule ValidationStep="UpdatedValue"/>

                <local2:FirstNameValidationRule Min="2"

                                                Max="21"

                                                AllowSpaces="False"

                                                AllowEmpty="False"

                                                ValidationStep="UpdatedValue"/>

                <local2:LastNameValidationRule  Min="2"

                                                Max="21"

                                                AllowSpaces="False"

                                                AllowEmpty="False"

                                                ValidationStep="UpdatedValue"/>

            </DataGrid.RowValidationRules>

            <!--================================================================================-->

            <DataGrid.RowValidationErrorTemplate>

                <ControlTemplate>

                    <Grid Margin="0,-2,0,-2"

                          ToolTip="{Binding RelativeSource={RelativeSource

                          FindAncestor, AncestorType={x:Type DataGridRow}},

                          Path=(Validation.Errors)[0].ErrorContent}">

                        <Ellipse StrokeThickness="0" Fill="Red"

                                 Width="{TemplateBinding FontSize}"

                                 Height="{TemplateBinding FontSize}" >

                        </Ellipse>

                        <TextBlock Text="!" FontSize="{TemplateBinding FontSize}"

                                   FontWeight="Bold" Foreground="White"

                                   HorizontalAlignment="Center" >

                        </TextBlock>

                    </Grid>

                </ControlTemplate>

            </DataGrid.RowValidationErrorTemplate>

            <!--================================================================================-->

        </DataGrid>

    </Grid>

</Window>

=============================================================================

using System;

using System.Globalization;

using System.Text.RegularExpressions;

using System.Windows.Controls;

using System.Windows.Data;

using MyClasses;

namespace DataGridValidation

{

public class AttendeeIdValidationRule : ValidationRule

{

public override ValidationResult Validate(object value,

System.Globalization.CultureInfo cultureInfo)

{

Attendee att = (value as BindingGroup).Items[0] as Attendee;

if (att.AttendeeId < 900 || att.AttendeeId > 4500)

{

return new ValidationResult(false,

"Identity must be between 900 and 4500.");

}

else

{

return ValidationResult.ValidResult;

}

}

}

//=====================================================================

public class IdValidationRule : ValidationRule

{

public override ValidationResult Validate(object value,

System.Globalization.CultureInfo cultureInfo)

{

Course course = (value as BindingGroup).Items[0] as Course;

if (course.Id < 100 || course.Id > 3000)

{

return new ValidationResult(false,

"Identity must be between 100 and 3000.");

}

else

{

return ValidationResult.ValidResult;

}

}

}

//=====================================================================

public class NameValidationRule : ValidationRule

{

private int _min;

private int _max;

private Boolean _allowEmpty;

private Boolean _allowSpaces;

//----- constructor -----

public NameValidationRule()

{

Min = 0;

Max = 1000;

}

//----- properties -----

public int Min

{ get { return _min; } set { _min = value; } }

public int Max

{ get { return _max; } set { _max = value; } }

public Boolean AllowEmpty

{ get { return _allowEmpty; } set { _allowEmpty = value; } }

public Boolean AllowSpaces

{ get { return _allowSpaces; } set { _allowSpaces = value; } }

public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)

{

Course course = (value as BindingGroup).Items[0] as Course;

string cn = course.Name;

string General = "";

int nLen = cn.Length;

try

{

if (nLen > 0)

{

General = cn.ToString();

}

else

{

if (nLen == 0 && !this.AllowEmpty)

{

return new ValidationResult(false, "String can't be empty : ");

}

if (nLen == 0 && this.AllowEmpty)

{

return new ValidationResult(true, null);

}

General = cn.ToString();

}

}

catch (Exception e)

{

return new ValidationResult(false, "Something wrong or " + e.Message);

}

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

if (!this.AllowSpaces && cn.Contains(" "))

{

return new ValidationResult(false, "String contains space characters : ");

}

if (cn.Length < Min || cn.Length > Max)

{

return new ValidationResult(false,

"Name of course is between "+Min.ToString()+" and "+Max.ToString()+" in length.");

}

else

{

return ValidationResult.ValidResult;

}

}

}

//=====================================================================

public class StartDateValidationRule : ValidationRule

{

public override ValidationResult Validate(object value,

System.Globalization.CultureInfo cultureInfo)

{

Course course = (value as BindingGroup).Items[0] as Course;

if (course.StartDate < DateTime.Now)

{

return new ValidationResult(false,

"Start Date must be after today.");

}

else

{

return ValidationResult.ValidResult;

}

}

}

//=====================================================================

public class DatesValidationRule : ValidationRule

{

public override ValidationResult Validate(object value,

System.Globalization.CultureInfo cultureInfo)

{

Course course = (value as BindingGroup).Items[0] as Course;

if (course.StartDate > course.EndDate)

{

return new ValidationResult(false,

"Start Date must be earlier than End Date.");

}

else

{

return ValidationResult.ValidResult;

}

}

}

//=====================================================================

public class PostCodeValidationRule : ValidationRule

{

private Boolean _allowEmpty = true;

public Boolean AllowEmpty

{

get { return _allowEmpty; }

set

{

if (_allowEmpty == value) return;

_allowEmpty = value;

}

}

public override ValidationResult Validate(object value, CultureInfo cultureInfo)

{

string General = "";

try

{

Course course = (value as BindingGroup).Items[0] as Course;

//---------------------------------------------------------

if (course.PostCode.Length > 0)

{

General = course.PostCode;

}

if (course.PostCode.Trim() == "" && this.AllowEmpty)

{

return new ValidationResult(true, null);

}

}

catch (Exception e)

{

return new ValidationResult(false, "Something wrong or " + e.Message);

}

//----- 'OR' stands for 'Once Removed' - meaning PHH sourced it from elsewhere -----

//----- here is Ben Forta's [poor] effort -----

//----- [A-Z]{1,2}\d[A-Z\d]? \d[ABD-HJLNP-UW-Z]{2} -----

//----- cText := e"[A-Z]{1,2}\\d[A-Z\\d]? \\d[ABD-HJLNP-UW-Z]{2}" -----

//----- cText = "[A-Z]{1,2}\\d[A-Z\\d]? \\d[ABD-HJLNP-UW-Z]{2}";

/* Verifying that the General string contains a valid Post Code. */

string cText = "";

cText = "\\b([A-PR-UWYZ][A-HK-Y]?[0-9]?[0-9]? {1,2}[0-9][ABD-HJLN-UW-Z]{2}|GIR 0AA)\\b";

if (!Regex.IsMatch(General, cText))

{

return new ValidationResult(false, "Not a valid Post Code!");

}

else

{

return new ValidationResult(true, null);

}

}

}

//=====================================================================

public class FirstNameValidationRule : ValidationRule

{

private int _min;

private int _max;

private Boolean _allowEmpty;

private Boolean _allowSpaces;

//----- constructor -----

public FirstNameValidationRule()

{

Min = 0;

Max = 1000;

}

//----- properties -----

public int Min

{ get { return _min; } set { _min = value; } }

public int Max

{ get { return _max; } set { _max = value; } }

public Boolean AllowEmpty

{ get { return _allowEmpty; } set { _allowEmpty = value; } }

public Boolean AllowSpaces

{ get { return _allowSpaces; } set { _allowSpaces = value; } }

public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)

{

Attendee att = (value as BindingGroup).Items[0] as Attendee;

string fn = att.FirstName;

string General = "";

int nLen = fn.Length;

try

{

if (nLen > 0)

{

General = fn.ToString();

}

else

{

if (nLen == 0 && !this.AllowEmpty)

{

return new ValidationResult(false, "Firstname string can't be empty : ");

}

if (nLen == 0 && this.AllowEmpty)

{

return new ValidationResult(true, null);

}

General = fn.ToString();

}

}

catch (Exception e)

{

return new ValidationResult(false, "Something wrong or " + e.Message);

}

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

if (!this.AllowSpaces && fn.Contains(" "))

{

return new ValidationResult(false, "String contains space characters : ");

}

if (fn.Length < Min || fn.Length > Max)

{

return new ValidationResult(false,

"Firstname is between " + Min.ToString() + " and " + Max.ToString() + " in length.");

}

else

{

return ValidationResult.ValidResult;

}

}

//=====================================================================

}

public class LastNameValidationRule : ValidationRule

{

private int _min;

private int _max;

private Boolean _allowEmpty;

private Boolean _allowSpaces;

//----- constructor -----

public LastNameValidationRule()

{

Min = 0;

Max = 1000;

}

//----- properties -----

public int Min

{ get { return _min; } set { _min = value; } }

public int Max

{ get { return _max; } set { _max = value; } }

public Boolean AllowEmpty

{ get { return _allowEmpty; } set { _allowEmpty = value; } }

public Boolean AllowSpaces

{ get { return _allowSpaces; } set { _allowSpaces = value; } }

public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)

{

Attendee att = (value as BindingGroup).Items[0] as Attendee;

string fn = att.LastName;

string General = "";

int nLen = fn.Length;

try

{

if (nLen > 0)

{

General = fn.ToString();

}

else

{

if (nLen == 0 && !this.AllowEmpty)

{

return new ValidationResult(false, "Lastname string can't be empty : ");

}

if (nLen == 0 && this.AllowEmpty)

{

return new ValidationResult(true, null);

}

General = fn.ToString();

}

}

catch (Exception e)

{

return new ValidationResult(false, "Something wrong or " + e.Message);

}

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

if (!this.AllowSpaces && fn.Contains(" "))

{

return new ValidationResult(false, "String contains space characters : ");

}

if (fn.Length < Min || fn.Length > Max)

{

return new ValidationResult(false,

"Lastname is between " + Min.ToString() + " and " + Max.ToString() + " in length.");

}

else

{

return ValidationResult.ValidResult;

}

}

//=====================================================================

}

}

=============================================================================

using System;

using System.Collections.ObjectModel;

using System.ComponentModel;

namespace MyClasses

{

public class Course : IEditableObject, INotifyPropertyChanged

{

private string _name;

public string Name

{

get

{

return _name;

}

set

{

if (_name == value) return;

_name = value;

OnPropertyChanged("Name");

}

}

private string _postCode;

public string PostCode

{

get

{

return _postCode;

}

set

{

if (_postCode == value) return;

_postCode = value;

OnPropertyChanged("PostCode");

}

}

private int _number;

public int Id

{

get

{

return _number;

}

set

{

if (_number == value) return;

_number = value;

OnPropertyChanged("Id");

}

}

private DateTime _startDate;

public DateTime StartDate

{

get

{

return _startDate;

}

set

{

if (_startDate == value) return;

_startDate = value;

OnPropertyChanged("StartDate");

}

}

private DateTime _endDate;

public DateTime EndDate

{

get

{

return _endDate;

}

set

{

if (_endDate == value) return;

_endDate = value;

OnPropertyChanged("EndDate");

}

}

private ObservableCollection<Attendee> _oAttendees;

public ObservableCollection<Attendee> oAttendees

{

get

{

return _oAttendees;

}

set

{

if (_oAttendees == value) return;

_oAttendees = value;

OnPropertyChanged("oAttendees");

}

}

#region IEditableObject

private Course backupCopy;

private bool inEdit;

public void BeginEdit()

{

if (inEdit) return;

inEdit = true;

backupCopy = this.MemberwiseClone() as Course;

}

public void CancelEdit()

{

if (!inEdit) return;

inEdit = false;

this.Name = backupCopy.Name;

this.Id = backupCopy.Id;

this.StartDate = backupCopy.StartDate;

this.EndDate = backupCopy.EndDate;

this.PostCode = backupCopy.PostCode;

}

public void EndEdit()

{

if (!inEdit) return;

inEdit = false;

backupCopy = null;

}

#endregion

//----- constructor -----

public Course()

{

this.Name = "...";

this.Id = -1;

this.PostCode = "...";

this.StartDate = new DateTime(1900,01,01);

this.oAttendees = new ObservableCollection<Attendee>();

this.oAttendees.Add(new Attendee());

}

#region INotifyPropertyChanged

public event PropertyChangedEventHandler PropertyChanged;

private void OnPropertyChanged(string propertyName)

{

if (PropertyChanged != null)

{

PropertyChanged(this,

new PropertyChangedEventArgs(propertyName));

}

}

#endregion

}

//=====================================================================

}

=============================================================================

using System;

using System.Collections.Generic;

using System.ComponentModel;   //----- INotifyPropertyChanged .....

namespace MyClasses

{

public class Attendee : INotifyPropertyChanged

{

//----- fields -----

private int _attendeeId;

private string _firstName;

private string _lastName;

private DateTime _registrationDate;

private Boolean _hasPaid;

//----- events -----

public event PropertyChangedEventHandler PropertyChanged;

//----- properties -----

public int AttendeeId

{

get { return _attendeeId; }

set { _attendeeId = value; NotifyPropertyChanged("AttendeeId"); }

}

public string FirstName

{

get { return _firstName; }

set { _firstName = value; NotifyPropertyChanged("FirstName"); }

}

public string LastName

{

get { return _lastName; }

set { _lastName = value; NotifyPropertyChanged("LastName"); }

}

public DateTime RegistrationDate

{

get { return _registrationDate; }

set { _registrationDate = value; NotifyPropertyChanged("RegistrationDate"); }

}

public Boolean HasPaid

{

get { return _hasPaid; }

set { _hasPaid = value; NotifyPropertyChanged("HasPaid"); }

}

//------------------------------------------------------------------------

//----- constructors -----

public Attendee()

{

this.FirstName = "";

this.LastName = "";

this.AttendeeId = -1;

this.HasPaid = false;

this.RegistrationDate = new DateTime(1900,01,01);

}

public Attendee(string cF, string cL, int nI, Boolean lP, DateTime dtReg)

{

this.FirstName = cF;

this.LastName = cL;

this.AttendeeId = nI;

this.HasPaid = lP;

this.RegistrationDate = dtReg;

}

//----- methods -----

override public string ToString()

{

if (AttendeeId > 0)

{ return AttendeeId.ToString() + " >> " + FirstName.Trim() + " " + LastName.Trim(); }

else

{ return " >> " + FirstName.Trim() + " " + LastName.Trim();}

}

public void NotifyPropertyChanged(string propertyName)

{

if (PropertyChanged != null)

{

PropertyChanged(this, new PropertyChangedEventArgs(propertyName));

}

}

}

}

=============================================================================

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Data;

using System.Windows.Documents;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Imaging;

using System.Windows.Shapes;

namespace DataGridValidationMVVM.b_Views

{

/// <summary>

/// Interaction logic for DG_SimpleView.xaml

/// </summary>

public partial class DG_SimpleView : Window

{

public DG_SimpleView()

{

InitializeComponent();

}

}

}

=============================================================================

<Window x:Class="DataGridValidationMVVM.b_Views.DG_SimpleView"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

        xmlns:local="clr-namespace:DataGridValidationMVVM.b_Views"

        xmlns:local2="clr-namespace:DataGridValidation"

        mc:Ignorable="d"

        Title="DataGrid Validation Example" Height="290" Width="690" Background="Lavender">

    <Grid>

        <DataGrid Name="dataGrid1" FontSize="20" RowHeaderWidth="27"

                  ItemsSource="{Binding oInCourses}"

                  AutoGenerateColumns="False"

                  AlternatingRowBackground="Beige"

                  AlternationCount="2"

                  Foreground="DarkBlue">

            <DataGrid.Resources>

                <Style x:Key="errorStyle" TargetType="{x:Type TextBox}">

                    <Setter Property="Padding" Value="-2"/>

                    <Style.Triggers>

                        <Trigger Property="Validation.HasError" Value="True">

                            <Setter Property="Background" Value="Red"/>

                            <Setter Property="Foreground" Value="Gold"/>

                            <Setter Property="ToolTip"

                                    Value="{Binding RelativeSource={RelativeSource Self},

                                    Path=(Validation.Errors)[0].ErrorContent}"/>

                        </Trigger>

                    </Style.Triggers>

                </Style>

                <!--===========================< My Try !>==============================-->

                <Style x:Key="errorStyleMine" TargetType="{x:Type TextBox}">

                    <Setter Property="Padding" Value="-4"/>

                    <Style.Triggers>

                        <Trigger Property="Validation.HasError" Value="True">

                            <Setter Property="Background" Value="DarkBlue"/>

                            <Setter Property="Foreground" Value="Wheat"/>

                            <Setter Property="FontWeight" Value="Bold" />

                            <Setter Property="FontStyle" Value="Italic" />

                            <Setter Property="ToolTip"

                                    Value="{Binding RelativeSource={RelativeSource Self},

                                    Path=(Validation.Errors)[0].ErrorContent}"/>

                        </Trigger>

                    </Style.Triggers>

                </Style>

            </DataGrid.Resources>

            <!--================================================================================-->

            <DataGrid.Columns>

                <DataGridTextColumn Header="Course Name"

                                    Binding="{Binding Name, TargetNullValue=(enter a course name), ValidatesOnExceptions=True}"/>

                <DataGridTextColumn Header="Course ID"

                                    EditingElementStyle="{StaticResource errorStyleMine}"

                                    Binding="{Binding Id, ValidatesOnExceptions=True}"

                                    FontWeight="Bold"/>

                <DataGridTextColumn Header="Start Date"

                                    EditingElementStyle="{StaticResource errorStyle}"

                                    Binding="{Binding StartDate, ValidatesOnExceptions=True,

                                    StringFormat=d }"/>

                <DataGridTextColumn Header="End Date"

                                    EditingElementStyle="{StaticResource errorStyle}"

                                    Binding="{Binding EndDate, ValidatesOnExceptions=True,

                                    StringFormat=d}"/>

                <DataGridTextColumn Header="Postcode :"

                                    EditingElementStyle="{StaticResource errorStyle}"

                                    Binding="{Binding PostCode, ValidatesOnExceptions=True}"

                                    FontStyle="Italic" FontWeight="Bold"/>

            </DataGrid.Columns>

            <!--================================================================================-->

            <DataGrid.RowValidationRules>

                <local2:DatesValidationRule ValidationStep="UpdatedValue" />

                <local2:IdValidationRule ValidationStep="UpdatedValue"/>

                <local2:NameValidationRule Min="2"

                                           Max="21"

                                           AllowSpaces="True"

                                           AllowEmpty="False"

                                           ValidationStep="UpdatedValue"/>

                <local2:StartDateValidationRule ValidationStep="UpdatedValue"/>

                <local2:PostCodeValidationRule AllowEmpty="True" ValidationStep="UpdatedValue"/>

            </DataGrid.RowValidationRules>

            <!--================================================================================-->

            <DataGrid.RowValidationErrorTemplate>

                <ControlTemplate>

                    <Grid Margin="0,-2,0,-2"

                          ToolTip="{Binding RelativeSource={RelativeSource

                          FindAncestor, AncestorType={x:Type DataGridRow}},

                          Path=(Validation.Errors)[0].ErrorContent}">

                        <Ellipse StrokeThickness="0" Fill="Red"

                                 Width="{TemplateBinding FontSize}"

                                 Height="{TemplateBinding FontSize}" >

                        </Ellipse>

                        <TextBlock Text="!" FontSize="{TemplateBinding FontSize}"

                                   FontWeight="Bold" Foreground="White"

                                   HorizontalAlignment="Center" >

                        </TextBlock>

                    </Grid>

                </ControlTemplate>

            </DataGrid.RowValidationErrorTemplate>

        </DataGrid>

        <!--================================================================================-->

    </Grid>

</Window>

=============================================================================

PHH - 15th. February 2016.


DataGrid with two supporting DataGrids

DataGrid with two supporting DataGrids

DataGrid with two supporting DataGrids

 Previous topic Next topic

Making a start :

Here is my first attempt at functionality - before we get into tidying and improving.

Checkout the following image to see the two RHS columns of complex business objects in collections,

each of these is bound to a DataGrid control below the main DataGrid :-

PHH - 19th. February 2016.


working code and script

working code and script

working code and script

 Previous topic Next topic

PHH - 19th. February 2016.


Tidying up the XAML - more in 'Resources'

Tidying up the XAML - more in 'Resources'

Tidying up the XAML - more in 'Resources'

 Previous topic Next topic

Are you a 'tidy' guy ?

Are you possessed with 'tidiness' ?

Well, if you are somewhere in between these, then you can do a good job with the 'Resources' section of the

XAML script which is defining your 'View'.

For many / most of my WPF forms I use a 'Grid' within a 'Window' or a 'UserControl'.

So the 'Grid.Resources' is the section where I try to put a lot of 'form stuff', so that the body of the form definition

is more readable - this helps me reduce my errors.

Here is a partially collapsed visual display, of the whole XAML script, for the more complex validation input

form we have been developing previously :-

=======================================================================================================

Yes, that's all there is to it - we can mentally digest that I feel ;-0) Three data grids and a button.

Okay then folks, probably a bit too collapsed, so lets open out the 'Resources' section - see below :-

===============================================================================================================

Notice that the Resources seems to be just Styles and Templates, of one sort or another.

Data templates are very commonly used, even by us guys ;-0) Well, so are Styles for that matter.

Control templates are less often seen and used - I copied this one from a web search example, or was

it a Microsoft one from Help Viewer 2.2 ?

By moving XAML script to the Resources section we can simplify and tidy out 'Body' text script. It is

then much easier to study and understand, as well as edit and change.

Let us now look at the expanded view of the Style I have created for any 'DataGrid' within the main 'Grid' :-

==============================================================================================================================================================

So seven visually 'bulky' lines are been moved from each 'DataGrid' that we newly define in the 'body' of the script.

Lets see how this affects the way we then define a new 'DataGrid' UI element. Below is the main one in

the 'Complex' option is our sample application main Form :-

===========================================================================================

And in fact I probably could have gone further with my 'tidying' process, and for my way of doing things, moved

the line of script on line 80 to the Resources as well. I always use a value of two for my alternation setting.

This sort of tidying means that we can then concentrate on the Column definitions and the Validation Rules (VRs).

These are seen as the main parts of the data grid to most of us.

Before we move on, we can see one big benefit of this sort of tidying, in line 30 - of the image two up from here.

We have moved the validation error template definition from the particular 'DataGrid' itself and into the Style in

the Resources section.

Here is what is now in the Resources section, so three copies of these are missing from the 'Body' of script :-

=============================================================================================================

Yes, a big tidy up of our script ;-0)

Remember to take the time to be tidy with your XAML script entries - its does pay off a lot - AND - of course you can

easily copy Resources entries from one Form to another, often without any (or many) changes, because we have

common setters and values.

Time to move on, see you in the next sub-section ....................

PHH - 19th. February 2016.


VRs - 'shell and wrapper' approach

VRs - 'shell and wrapper' approach

VRs - 'shell and wrapper' approach

 Previous topic Next topic

Well guys, the whole point of having a 'wrapper' approach to validation is so that we can have a single 'chunk',

or section of code, which deals with the logical details and conditions in only one part of our application.

If the 'Core' part of the validation rule is the same for all similar VRs then we have only one place to get all our

code right and testing completed etc..

By creating suitable 'Shell' VR classes then we can interface correctly with what is required at the XAML end of things.

I am not an expert on XAML scripting, nor Validation generally, nor messaging between data class objects and

UI elements, so I have based my approach on seeing how a few good examples have handled Validation and the

creation of rules.

I then changed and adapted what these examples showed me, and eventually came up with the idea of a 'Shell' class

to interface correctly with the XAML stuff, and a 'Core' to apply the logical conditions.

There may well be other ways for experienced programmer / developers to do validation - BUT - I have recently had

good success with this approach and with applying VRs at the very 'front' of inputting data at the GUI surface.

I have definitely moved away from trying to validate at the Class level, meaning within the data class object itself.

However, if needs be we could even look at applying the 'Core' VR logic within the class and apply it when a property

changes. I have not looked into this in any depth as yet, but it seems a possibility.

Now then, lets get back to explaining what my idea of a 'Shell' and a 'Core' is for validation.

Let us start at the XAML script end of things, and how our recent Microsoft example applied the VR for the

public property of 'FirstName'.

Below I have left in the original script (commented out) as well as adding the VR for the new approach :-

==========================================================================================================

Okay then, at the XAML script end of things it is simply a different name for the VR defined class.

So lets look at what is different with the 'Shell' named class. Check out the following image and see that

the is very little except within the 'override' method. Lines 363 through 367 are where it's at :-

================================================================================================================================

Okay then, basically we have turned our code into a supplier of a first name text (character) string, and a consumer

of this text string.

The STATIC method which processes the input text string returns a 'ValidationResult' object.

The object returned is then in turn returned by the 'Shell' to satisfy the calling code.

Lets now study the STATIC method where all of the conditional logic is contained - study this below :-

========================================================================================================================================

Quite simply we have a public class called 'GeneralCoreVRs' in which we are to create one or more STATIC methods to accept

input parameters and return a 'ValidationResult' object.

The values of the two properties of the 'ValidationResult' depends on the conditional tests. Basically, it is whether the data is

considered to be valid (or not) and a suitable message to explain what is going on, or nothing.

The result passed back by the 'Core' is then passed straight back by the 'Shell' to the calling XAML object(s).

Lets see if this new structure does as we require :-

[ .. note below that the marked row has an empty 'Firstname' field .. ]

=================================================================================================

The only particular 'thing' to notice is that because the 'Core' code is general for many string properties, we

have had to use 'String' in the name rather than 'Firstname' - but we could even pass that in too if we so wished.

Lets just refresh our minds as to how the data grid column script looks in our form definition :-

=====================================================================================================================

And we then need to see the newly structured (and detailed) SE pane, as far

as the VRs are concerned - the new entries are highlighted below :-

=======================================================

So we could use our general 'Core' as the bases it wrap other 'Shell' VRs around.

We could base a Shell VR for last name on this core code, as too one for Country and Company in

other business classes - like Speaker and Attendee etc., etc..

The reason at the moment that we need different shells for each is because of the XAML scripting of the

way we access the column data item from the row object.

There may be other ways of getting at the row object of even cell object, BUT, the way we have used the

code from the Microsoft sample seems pretty OK to me ;-0)

Moving on a pace or two ...............

Lets move on from the more complex situation of a data row, in a DataGrid control, to a simple TextBox on

a WPF form, which is bound to a public property of a single item which is a complex data type object ..........

The simpler way and situation ....... the next will be based on the VR for an 'Age' value input.

Simpler situation - an Isolated 'TextBox' element :

Here is the result of splitting our VR (validation rule) in to 'shell and core' - it allows some input values, but not others.

Check out this acceptable input :-

===================================================

And now it would be nice to see the XAML script lines which apply the 'shell' VR to our UI element input string :-

[ .. line 87 is the one to particularly study below .. ]

=====================================================================================================================

And does this VR do what it should ? Study the following image to start to find out :-

===============================================================

And with this error message in mind lets see what the code looks like for the 'shell' class :-

===================================================================================================================

As you can see above, it is only line 76 which does the calling of the 'Core' VR code.

Lets now check the top-end range value :-

==============================================================

And with it apparently working okay, lets see the 'Core' code - check below :-

========================================================================================================================

As you can see in the code of the STATIC method above, we have all of the conditional logic which needs to be applied

to any age range for normal use of age data input - two limits lower and upper.

However, we do need to see if we still get correct behaviour when we type text characters into the digit 'space', as

seen in the following image :-

==============================================================================

So all appears well.

And if we wished we could build up a library of Core logic STATIC code, and various 'Shell' VRs which call on

the Core logic.

We could use the same Core logic for the data row 'Shell' as we do for the single TextBox 'Shell'.

Here is what I have at the moment in the SE pane :-

==============================================================================

The above needs to be re-written and re-organised so that we have ONLY 'Shells' and

'Core' STATIC methods.

Note the way I have done things is to have all the STATIC methods in a class called 'GeneralCoreVRs'.

Good Luck in any tidying and rearranging that you do for yourself.

Lets move on to the next sub-section, see you there ...................

PHH - 19th. & 20th. February 2016.


Complex data objects - 'Lists' within 'Lists'

Complex data objects - 'Lists' within 'Lists'

Complex data objects - 'Lists' within 'Lists'

 Previous topic Next topic

PHH - 11th. February 2016.


Chapter 8 - VRs and VR 'wrapper' classes

Chapter 8 - VRs and VR 'wrapper' classes

Chapter 8 - VRs and VR 'wrapper' classes

 Previous topic Next topic

PHH - 19th. February 2016.


Appendix A - 'DatePicker' and a suitable VR

Appendix A - 'DatePicker' and a suitable VR

Appendix A - 'DatePicker' and a suitable VR

 Previous topic Next topic

Lets see where we are headed - hopefully !

Notice that we have dates displayed within DatePicker controls - check below :-

========================================================================================================================

Ideally, we wish to us the DatePicker controls to make sure we can only input valid dates, but then have our own Validation

Rules which can apply another level of validation checking.

Our VRs will have 'Max' and 'Min' dates and possibly allow us to compare across properties within the same row object.

But to do all this we need to work up our skills and knowledge 'step by step'.

Lets start doing that now .................

A single 'isolated' DatePicker control :

Okay then folks, lets start simply, study the following :-

==============================

Yes, we have a 'DatePicker' which is displaying 'signs' that it is holding an invalid date. The actual tooltip

message is from my .NET code.

Here is some XAML script which we used previously with 'TextBox', I have adapted it for our 'DatePicker' example :-

[ .. the following is from the 'Resources' section of the 'Grid' .. ]

=====================================================================================================

As well as the tooltip we get the default red rectangle to flag-up the warning.

Now we need to see the script for the body of our XAML mark-up - check this out below :-

====================================================================================================================

Yes, in the above image it is line 102 where we point the control at the validation rule, which is in .NET code.

So the collection of VRs for this control has a single entry, that entitled "TsetDateVR" - which is in fact a .NET 'Class'.

===================================

We probably need to change the displayed date .... this is still in development ;-0)

And now it is time for us to study the pretty simple .NET code making up the VR itself - check this out :-

======================================================================================================================================

Since we now have public properties for our VR, we can use input parameters within the XAML script.

Here is what I am currently using for testing (results in a little while) - check this carefully :-

[ .. once again it is line 102 which is the most vital / important .. ]

=====================================================================================================================

Lets now try the changed (improved?) VR - see below :-

====================================

And here is the change I made, just so as you know :-

======================================================================================================================================

And if we then clean up the code we see this :-

=================================================================================================================================

And we can change the script to use two parameters as we can see in the following image :-

====================================================================================================================

So if we are providing two values as input parameters we now need to use them both in the .NET code !

In that case we see at runtime the following :-

=========================================

And to generate this we have the following .NET conditional code in use :-

==========================================================================================================================

Yes, above we are doing two tests within one VR - and this may be okay generally - BUT - there is another

way to go about applying many conditions for one input value validation.

Lets try splitting the validation conditions up, so that we test one at a time, so our above VR becomes two - like

this script section shown below :-

======================================================================================================================

Note above, the older line 104 has been commented out.

The code of the two VRs has been simplified to one parameter input per VR - which means that the VR code has

one public property for each - check the following details for one of these VRs :-

==================================================================================================================

Now then, lets go a few steps further and apply three VRs to our DatePicker control.

Although its a bit 'silly' we can trap the condition when the date is that of my son's (Ben) birthday.

Obviously the other two VR parameters need to leave a 'window' of values which includes the birthday.

Here is the runtime result :-

======================================

And here is the XAML script as I have used it to generate the above result :-

[ .. note in particular lines 102 - 104 .. ]

======================================================================================================================

Note that each VR designed, created and used, can have from non parameter up to many of them. It all depends

on our wishes as developers.

Now then, lets see the 'Fun' code for Ben's birthday :-

===================================================================================================================

You can simplify and tidy the above rule code to suit yourself.

For me it works. But only if the Min and Max dates are either side of the birthday date.

============================================================================================================================================

Although it is in many ways nice to have VRs without nested IF statements to check for logical

conditions, there are downsides to splitting up the code. For example how can we test in our VR code

that the Max data is not before the Min date ?

Oh! and probably our biggest problem / issue with DatePicker is that it is sometimes 'tricky' or difficult, to

get the control to fire off the application of the VRs - either one or many.

Here below is the error warning removed after I have clicked outside of the input box on the control surface :-

===========================

Although this all works nicely, I have a fear that when we come to data rows in DataGrid controls we may

get even more trouble (issues) in getting the DatePicker changes to fire-off the notification.

We will see, but we must not forget the possible problem(s).

Time to move on, see you in the next sub-section ................

PHH - 26th. January & 23rd. February 2016.


'DatePicker' in data Rows of a 'DataGrid' control

'DatePicker' in data Rows of a 'DataGrid' control

'DatePicker' in data Rows of a 'DataGrid' control

 Previous topic Next topic

In this sub-section we will look at using a DatePicker control in a column of a DataGrid UI element, in

place of a TextBox input.

I have to admit that I much prefer to see on screen, and use, a DatePicker than a TextBox.

Lets see where we are starting from as a 'DataGrid' layout - see below how it looks with a 'TextBox' based column :-

======================================================================================================

And then how this same example looks when we replace the TextBox based columns with DatePicker controls :-

=========================================================================================================================

When the 'DatePicker' control is in use we see something like that below - yes, it is a different 'DataGrid',

but the calendar input display is the same for all 'DatePickers' - check this below :-

=================================================================

Moving on a pace or two ................

What is the situation ? - What are the issues ? :

Well then guys, from my code and script development, and thorough testing, it would appear that

I can get validation to work at a data 'Row' level and for my personally created VRs to work on any

'DatePicker' I wish to add to the 'DataGrid'.

That's the good news. And here is some supporting information and visual detail :-

[ .. the first four images show correct behaviour as data is corrected in the row's cells - Row with ID 1003 is what we are focusing upon .. ]

==================================================================================================================

So lets change the start date for ID 1003 :-

===============================================================================================================================

So now that the start date below has been changed to 24th. Feb 2016 (tomorrow) check this out for End Date :-

===========================================================================================================================================

Yep! - it looks like the validation rules think both dates are OK now - so what else have we got ? Lets shorten the name :-

===================================================================================================================================

And now that we have "Expression Blend" then the string input length is OK by our rules.

Shown above is a screen display to show no error warning.

So that is good, and what therefore are the problems, if any ?

But guys, we need to be honest with ourselves, the following is also possible if I go in and edit the same two dates in a "certain way",

and while I am careful enough not to click my mouse in the wrong place(s) - wrong for this test to show issues, that is.

Note that below I have changed the two dates to be in 1913, so both are before today, and also the end date is before the start date,

yes, all wrong you would say ;-0((

Study the following carefully :-

=======================================================================================================

But all I need do in order to kick everything into action is change another field - I have changed the ID from 1003 to 1023 and

this is what I got - check it carefully :-

=========================================================================================================================================

So now the dates have been examined and checked out to be incorrect - well invalid to be precise ;-0)

We need to look further at the settings I am using for Display date and Selected date on the controls, as well

as the bindings and their optional settings too.

However, we must be doing something right I feel.

Oh! .. and just so you know, here is an image of the VRs that I am setting 'on' for the Row :-

==============================================================================================================

The Row can have zero to any number of VR in its collection of 'RowValidationRules' - and more than one rule

can refer to the same column data item. So we can have multiple VRs for one property of the row object.

And although I have changed the XAML script just VERY recently to aid better performance and on screen behaviour,

here is what I did first to get my DataGrid to row validate - lines 99 and 102 are what get changed - in the child section

from here.

Study these details carefully - the XAMl syntax is worth learning for the DataTemplate :-

===========================================================================================================

And since the body of the script is expecting STATIC resources for data templates then we had better define some.

Here is what I did which nearly work okay - or did work some of the time ;-0)

Check and learn the syntax, as it is correct and okay :-

=====================================================================================================================

Okay then folks, since I have now identified the behaviour problems, and issues, with the DatePickers in the

'Template Columns', lets leave this sub-section and move to the child section where we can see exactly

what we need change to improve the behaviour to 100%

See you there .................

PHH - 23rd. & 24th. February 2016.


A working solution to the Problem

A working solution to the Problem

A working solution to the Problem

 Previous topic Next topic

Okay guys, first of all let me tell you that I have found an easy solution to the problem I was having with

the DatePicker behaviour in the DataGrid columns.

It took me a while to try out a whole bunch of things before I found the answer.

It is not really possible to show in eNotes what the problem was, you will just have to believe me and take

the story as I tell it ;-0)

Below is an image showing date columns of Start and then End dates. When dates were chosen and then the Row focus

changed, the warning icon for invalid data values did not always show - when indeed it should have.

==================================================================================================================

Lets move on to look at what I checked and tried to change.

It was apparent from the outset that what I was doing with VR script and code was correct, as it worked some of the

time, or even all of the time if I used the controls and my mouse in a certain way.

So know it had something to do with the way I was using the DatePicker control, or some settings related to it, I decided

to revisit the way I doing the whole 'thing' with respect to the date columns.

Here shown below is the data templates I was using in my Resources section :-

============================================================================================================================

If you recall, the data templates are used as Static Resources and call upon in the manner shown below :-

[ .. see line 99 and 102 .. ]

==================================================================================================================

Before I did anything drastic I first tried to make some changes as can be seen by the following script :-

====================================================================================================================================

And still the date changes were spasmodic or intermittent.

I then tried another change as seen below in the next image :-

============================================================================================

By using the 'ConvertedProposedValue' I found that all invalid rows were displayed, even with the original data.

This may be quite useful to know for another time :-

==================================================================================================================================================================================================================

However, it still did not solve the behavioural issues I was having with Start and End dates.

So I thought I had better start all over again - in one sense!

As a 'going back to basics' idea I thought that I should NOT use data templates and do it direct in code,

which is what I have done with the script in the following example :-

============================================================================================================================================

Shown above is the DatePicker specified directly in the column definition, no Static Resources involved at all.

The next image shows I have removed the earlier DataTemplate sections from Resources :-

=================================================================================================

And now we can see a full listing of the columns section, with some parts collapsed as they have not changed :-

==============================================================================================================================================

In the above image study in particular lines 85 through 92.

With extensive testing I found that the messaging or notification from the DatePicker elements was much

better, and seemed to happen immediately. Certainly in the tests I carried out I found that I could not leave the data row

with incorrect values that the validation system seemed not to know about, or recognise.

To my mind the WPF form was now working as I had previously expected it to have done.

I am not quite sure what we should learn from this, except that data binding, and related data validation, is only as good as the

performance of the messaging system underlying it.

We just have to make our property change notification 'spot on', then we are okay ;-0)

Time to move on, see you in the next sub-section .............

PHH - 24th. February 2016.


Appendix B - TextBox and 'Masking' entry

Appendix B - TextBox and 'Masking' entry

Appendix B - TextBox and 'Masking' entry

Previous topic Next topic

*** CARE *** Please be aware that there is much published 'stuff' out there on the web which is not exactly

incorrect, but which for today's purposes is inappropriate and/or unsuitable for our purposes.

We are after modern and appropriate ways of doing MVVM and data binding, as well as validation and the

masking of filtering of input data.

Some stuff which works does not go 'hand-in-hand' with other stuff which works - we need to look at a whole

and consistent pattern of development.

'AND' - embrace Regular Expressions instead of running away from "RegEx" stuff in general - as we do ;-0)

Here is one 'Old fashioned' approach first :-

[ .. I searched on masking in WPF .. ]

http://www.codeproject.com/Tips/1080813/How-To-implement-a-mask-for-a-text-box-in-W-P-F

=====================================================================================================================

As soon as I saw that it was using edit events on the TextBox I knew that there had to be a better way.

Just running your eyes over the code and script in the article tells you that it is a bit of a long-winded and 'brute force' approach.

There are much more elegant, modern, and appropriate ways of doing this. Even if some good ones are indeed published

quite a while ago.

Lets move on .......................

But what about Regular Expressions ? - Aren't they exactly what we are after ? :

Remember that RegExes (Regular Expressions) are about string 'matching' even though we talk about validating

our data input with them (RegExes), they are really about matching one string in another string.

As we input a string to a TextBox for example, the input string gradually grows and changes, a key stroke at a time.

If we set things up right, then the matching enclosed in the VR (validation rule), will be checked for us at each

editing change - we (the user) will get immediate feedback by the red rectangular box shown in the example image

below - note the 'k' is yet to be added, and when it is the red box disappears :-

So while the red rectangle is visible we have an incomplete match, but that does not say what we have already entered

is incorrect. It all depends on how we set-up the 'pattern' or mask.

However, on the other hand, if we have completed our data input and the red rectangle is still visible, then we know

what we have entered is incorrect for some reason or another. We can code our VRs such that they give more meaningful

messages to the user, its up to us as to how much work we do in this Tooltip direction.

Ben Forta's book on Regular Expressions is all about 'matching' - chapters through to the start of 7 are specifically

titled and subtitled about 'Matching'. Take a look at this book for yourself to see what I am talking about.

When we think of input 'masks' of the past we are doing nothing but match an input string with a limited format of

the 'mask' or allowed string.

Yes, I know, there are some 'feel good' factors about old masks, but I feel we can probably do as well in WPF when we

have sufficient skills, and enough knowledge.

For a start to do things a better way lets check-out this URL / live link :-

http://www.codeproject.com/Articles/15610/Regex-Validation-in-WPF

A main part of this article is to create and write a generalised RegEx validation rule, which can then be called

in our XAML script, and bit like the following image shows :-

=====================================================

I like the approach but feel that we can do better than passing in all string type parameters.

In the above image we are assigning a value to each of three properties of the 'RegexValidationRule'.

We can come back later to this generalised RegEx rule for validation.

First lets see what I already have working with Regular Expression stuff in my test application.

Checkout below this string text input box and the supporting VR code :-

===============================================================================================

The call to member 'IsMatch' has an interesting optional third parameter input - notice how we can

choose to ignore any case restrictions in our RegEx matching pattern.

As we read it, our pattern is to look for three successive upper case letters - the addition of the options

parameter, along with the 'IgnoreCase' enumeration, means we can also have lower case input allowed,

see the sample runtime display above.

In fact you can run a test of your own where you mix both upper and lower case input letters.

Now then, below we see that if we type a letter after the point (dot) we get the red invalid warning, even

though we have a complete length of input - we have an alpha character instead of a digit.

Point to notice in particular is that the tooltip reminds us of the general format required :-

==========================================================

So one other correct (or valid) input is seen below :-

============================================

Moving on ... lets go back to the generalised VR for Regex, even though it will have somewhat limited use

simply because the pattern matching string is often so long and complex, and not really that suitable for

passing from XAML script which is in the form editor.

It will be nice to try it our however ;-0)

A 'generalised' Regex VR - based on Josh Smith's 2006 Nov. article :

PHH - 26th. January & 25th. February 2016.


using Data Templates

using Data Templates

using Data Templates

 Previous topic Next topic

PHH - 26th. February 2016.


UserControls

UserControls

UserControls

 Previous topic Next topic

http://www.codeproject.com/Articles/1056014/WPF-Lookless-Controls

Lets get things 'straight', UserControls are 'custom made' by us the developer - BUT - are certainly

not 'Custom Controls'.

Nick Polyak says that the template in VS 2015 for Custom controls is not his favourite 'thing'!

Since he at present knows a lot more than me (us?) them we had better head his warning.

Personally, I can't see why Microsoft would include something which is so useless, but then that's

just how I see things.

==========================================================================

My Personal Experience of UCs :

I have used UserControls (UCs) very successfully with MVVM and samples from Nick Friend, which I then

improved to my liking and to suit my applications.

Basically, I used UCs like Windows or Forms.

However, we can't nest Window UI elements, so we have to put UCs inside Windows (nested) and place

them in a 'ContentCocntrl' UI element.

So in one Form (Window display) we can swap in and out various UCs. My Cologne 2015 app worked

in exactly this way - see below :-

===================================================

===========================================================================================================================================================

=============================================================================================

====================================================================================================================

PHH - 26th. February 2016.


Custom Controls

Custom Controls

Custom Controls

 Previous topic Next topic

It seems to me that we need to be careful when we discuss the various forms of controls - we need to

get our facts straight, and form a simple mental picture of what is going on.

Lets say that a 'CustomControl' is a standard control which we the developer then 'customises'. This

means that we tend to make it visually look different while it (the control) keeps its basic functionality.

A good simple example is the standard one quoted where a button looks quite different. Here is my

working sample of such a situation :-

======================================================================================================================================================

We have written and applied a 'ControlTemplate' which is then applied to the standard button UI element.

It is still a push Button, and still works as such, events and all that jazz.

So lets think of 'Custom Controls' as customised standard UI elements - ones which we have made to different.

When we come to making a new Control and defining both its look and its functionality, we will call the

result a 'Custom Complete Control', or "CCC".

We will do some exploring of this in the next sub-section.

Lets now have a look at what the Microsoft Help Viewer 2.2 has to say :-

==========================================================

And here is the same but expanded to say more :-

==============================================================================================================================

So maybe we should be thinking of 'Owner-drawn' controls as my termed "Custom Complete Control"s.

Why not indeed call them just 'Owner Controls' - where all parts of them are defined by the owner (developer) apart

from the very fundamental UI elements, like TextBoxes, TextBlocks, StackPanels etc..

We will explore all of these options in the following few sub-sections - see Appendic 'C'.

Lets roll up our sleeves ;-0)

PHH - 26th. February 2016.


Appendix C - making Templates and Controls

Appendix C - making Templates and Controls

Appendix C - making Templates and Controls

 Previous topic Next topic

We should all know how the MVVM pattern of design loosely couples a View (visual - form) with supporting

.NET code in a ViewModel. With data-binding being between public properties on the VM with dependency

properties (DPs) of the UI elements (or controls).

Well, we can do exactly the same sort of thing - loose coupling - between a code Class we create with DPs

and the visual part which is a 'ControlTemplate'. We get the same degree of 'separation' as we did with Views

and ViewModels.

Some of you may be aware that we can have more than one visual (View) element capable of being coupled

with a ViewModel (VM), and so we can also have more than one Custom Control ( or as we are going to call

them "Owner Controls") coupled to a supporting .NET code class / type.

We can call the this code part the "Owner Control Model", since it bring data to the "Owner Control" for

suitable display purposes and possibly editing as well.

An "Owner Control" is generally made of a bunch (handful) of standard WPF controls or UI elements.

An "Owner Control" also typically has a bunch of Dependency Properties define and registered, so that we

can have data binding between then and public (data) properties on the "Owner Control Model" (OCM).

You may need to read and study the above clear and simple explanation a few times to start to get the idea

of what I am talking about. And also come back and read it after you have seen my example of "MyContacts",

which I will show you in full detail as I design and create it.

Remember an "Owner Control" is like any other control in that it is drag-able from the Toolbox, and although

it is comprised of many sub-elements it moves and behaves as one.

If you like, think of an "Owner Control" as a complex control - complex meaning that it is made up of many

simple controls.

*** Remember *** Some standard controls, like 'ListBox' are themselves complex controls, but simply have

been made by Microsoft, and included in their Toolbox. Even 'Button' is made up of basic elements. All controls

have a nested structure of 'things within things'.

When we design and make an "Owner Control" we are making something more specific to our business application,

something which Microsoft did not wish to include in their general set of tools.

However, just like the Microsoft controls, we need to provide our own Dependency properties (DPs) so that we

can then use data binding of the ViewModel public properties to the DPs on our specialised complex control.

Yes, we must make DPs so that our "Owner Control" can do data binding. And once we can set up one DP correctly,

we can just as easily add more - so don't stint yourself when it comes to providing DPs for use later. Think ahead.

Also remember that there are far more DPs on each standard WPF control than we usually ever use at once, but

they are there for when we need them. So we would be sensible to do the same thing.

Lets now start to design and create our sample 'OC', "Owner Control". yes, the truth is I have not as yet started ;-0)

............. some time later ...............

Here is an image of where we are headed (now that I have created a working application) :-

We see below that I have placed two copies of the new Control on our form :-

=============================================================

We can also see in the above image that we have some Validation Rules (VRs) in place and working.

Although we have four VRs associated with the input TextBoxes, we can just see the PostCode one in

action in the above image.

In the next image - seen below - we see we have multiple (two) forms instantiated and showing.

The point of having multiple forms is to check that we have data binding in place and working well and correctly.

Also seen below are the results of using some of the delete buttons to empty the 'field' contents.

Remember that the two forms are like mirrors of each other, and that the top controls in both forms are displaying

the same data, as too the two bottoms controls.

So when we clicked the 'X' button for 'eMail' in the lower control, the input 'data' content disappeared from

both of the forms - check this out below :-

========================================================================================================

Note that I have set the 'UppdateSOurceTrigger' to "PropertyChanged" so things should show up straight

away after editing inputs.

Here below is another VR test, that for the Regex version of am eMail validation :-

=============================================================

It will be interesting for you to study how we handle the 'Click' event on the

series of delete buttons, so that the code is in the 'ViewModel' and not the code behind.

To prove this to you here is an image of the code behind the 'MyContactsView' form :-

=========================================================================

PHH - 28th. February 2016.


Custom 'Complete' Controls - "owner controls"

Custom 'Complete' Controls - "owner controls"

Custom 'Complete' Controls - "owner controls"

 Previous topic Next topic

This is the best way I can describe making from scratch a Control of our own.

Custom 'Complete' Control is a title I am using to mean that we, the developer, designs and creates a

visual part of the complex control we require - using the basic UI element building blocks - and then we

also design and create the supporting .NET code to support the visual part.

Defining 'Dependency Properties' as we so wish.

*** Note *** I suppose the title "Owner Control" is also equivalent to "Custom Complete Control".

So we completely design and build the control, both the visual side of it, and the coded functionality as well.

Generally, we have a XAML script part which is the 'ControlTemplate', and a 'back-end' .NET coded supporting

part of the class which is being defined. In the code we include any special functionality that the 'control' is

to possess when it is used for real.

Any and all of our 'Dependency Properties' are defined and kept in the .NET code. We need DPs so that

we can then have data binding to these properties of our control, in our XAML script.

*** Remember, even standard UI elements / controls quite often have quite a number of DPs which can be bound

by the user and scripting time. ***

By doing this (making DPs) our 'ViewModel' public properties can be bound to the DPs we have defined on our CCC.

Think of the DPs as where the binding to the control takes place - NOT - the DPs on the basic UI elements making

up the control.

We need a simple sample to trial, examine, and explore all this I feel.

Making a start to our first "Owner Control" :

Let us look at a simple WPF form - check the following image :-

=============================================================

The above trial WPF form contains two instances of the newly specified 'Owner Control' I have designed.

For the moment don't focus your eyes or attention on the details, just see two complex controls. Yes, in

some ways look at the two controls as if they were two Buttons, or two TextBlocks, or even two ListBoxes.

The new complex control is called (entitled) "MyContactControl", and I called it this because it displays and

handles a complex business data object called a 'MyContact'.

Here below is the code file for 'MyClasses', where the contact related classes are held :-

============================================================

Lets move on ..............

In all I do with WPF forms these days, I like to start with the data and work towards the ViewModel and View.

This invariably means starting with business classes of objects.

If we get our business classes defined in the correct manner, with suitable public properties, and then handle

the notification for properties changing, the rest of the data binding and MVVM stuff will bolt on, and just work,

without too much fuss or messing around.

Often guys don't pay enough attention to getting the basic business classes defined correctly, and I have been

telling everyone for years now.

Getting the 'MyContact' class defined right :

The current situation (defining our business class) is no different, so lets see the 'MyContact' class definition

which can be checked out below :-

[ .. lets do a 'Top Down' and see the structure before any detail .. ]

===========================================================================

The class requires the 'INotifyPropertyChanged' interface.

Basically, the class is a bunch of public properties and their private fields, behind these properties.

There will be about seven in all, but of which only four have so far been fully coded and specified.

Here shown below is an example of the 'PostCode' field and property :-

===============================================================

In line 411 shown above, we send off a notification about property change.

And of course we must NOT forget the all important event and method to handle the changes :-

===================================================================================

And just before we move on the the control itself, lets see some handy action methods which will eventually

allow us to clear the specified input fields with the click of a button. The other three are currently very

similar in detail :-

==================================================================================================================

Finally, the constructor - nothing too clever about this :-

=========================================================================

And that is it for our simple business class.

Lets now move on from the data class of 'MyContact' to the .NET code part of our new "Owner Control".

An "Owner Defined Control" or "Owner Control" has a code part and a visual part. Below we will look at the

overall structure of the supporting 'code' part - study the following details carefully :-

[ .. below is NOT a complicated or complex class .. ]

===========================================================================================

We have nine possible field / property pairings.

As well as these being public properties on private fields, we will also need some (if not all) of them to

be registered as Dependency Properties (DPs). This is so we can use 'Binding' at a XAML / WPF level.

Lets now examine one of these - we will study 'PostCode' which is a string type.

In the code shown below we have the field, then the property and then the DP, and all the code is done

in what is considered a default standard 'style' and format, using well known naming conventions etc. :-

======================================================================================

Lines 143 - 149 are new to many of us and simply create a DP for us and register it with the system.

To make other and similar DPs all we need do is alter the names used in lines 143 and 146, as well

as providing the correct 'Types' in lines 147 and 148 (the class Type which has the property).

There is more to meta data than we need do at the moment, so for now keep it simple and use null.

If I remember right form years back, its when we come to DateTimes that we need to probe a bit further

with Metadata.

Now then, lets see what visual this code is supporting.

Check the next image to see that we have indeed got three elements on our form for PostCode.

The first one is a 'Label', the second a 'TextBox', and the third one a 'Button' :-

===========================================================

All we have done is the code for the second of these. Don't worry, we will understand more as we go along.

Remember that the line shown above is just one of four which are within the new "Owner Control" - here

shown below is the whole control :-

============================================================

Think of an instance of the new control as being a sphere, or football, and that on the surface

of this sphere we have available properties which can be used for binding purposes.

One of our exposed 'surface' properties is a PostCode one. We will make three others in a short

while.

It is quite possible to have public properties which can't be used for binding, but which can still be set in

the XAML script by specifying them as parameters. In fact this is very common.

Also remember that public properties and DPs also, may have nothing directly to do with the data involved,

but could be to do with certain aspects of the control itself, how it looks in some way or another.

PHH - 27th. February & 2nd. March 2016.


Templates - 'Control' compared to 'Data'

Templates - 'Control' compared to 'Data'

Templates - 'Control' compared to 'Data'

 Previous topic Next topic

Control Templates :

========================================================

================================================================================================================================

======================================================================================================================================

============================================

========================================================================================================================================

======================================================================================

Data Templates :

=============================================================================================================================================================

================================================================================================================================================

====================================================================================

====================================================================================

PHH - 26th. February 2016.


'Lookless' controls - instead of 'Custom controls'

'Lookless' controls - instead of 'Custom controls'

'Lookless' controls - instead of 'Custom controls'

 Previous topic Next topic

http://www.codeproject.com/Articles/1056014/WPF-Lookless-Controls

========================================================================================================================

========================================================================================================================

====================================================================================

========================================================================================

========================================================================================

===============================================================================================

=========================================================================================================

PHH - 26th. February 2016.


Appendix D - 'Triggers' in the XAML script

Appendix D - 'Triggers' in the XAML script

 Previous topic This is the last topic

Appendix D - 'Triggers' in the XAML script

 Previous topic This is the last topic

http://www.dotnet-tricks.com/Tutorial/wpf/1P9H091013-Different-Types-of-Triggers-in-WPF.html

http://www.wpf-tutorial.com/styles/trigger-datatrigger-event-trigger/

http://www.codeproject.com/Tips/522041/Triggers-in-WPF

===================================================================================

Keep a calm head - this is not difficult, and explains a lot as far as our understanding is concerned.

We shall see the use of 'Triggers' in the XAML script, of samples that we try out.

=============================================================================================================================

=======================================================================

'Triggers' and their use seem to be much about Properties as anything - well Property values, and changes of value.

Having said that, we can also have triggering caused by data values (conditional), as well as Event triggering.

A bit like 'Super Events' - property values, values changes, data values and value changes, as well as

multiple conditions and events.

In fact Triggering is quite comprehensive - BUT - not that difficult to either understand or apply.

I have already explained in other sections that WPF forms have moved away from Events towards the

use of Property changes, to drive a lot of the functionality of the Form.

So Triggers deal with values and changes of many sorts, as well as values for data. Often a change in visual effects

is brought about by a change in one or more properties.

It all gets a bit easier when we look at come good examples.

Quite often, the attribute values of a control (UI element) are examined in the Trigger definition script,

and if a certain value of the property / attribute is found then we define a consequence.

{ It is possible that a Trigger never gets called. }

Once again the 'consequences' of the triggering are Property value changes, most often temporary

changes. Fonts change colour and style and or weight - then revert back later.

Triggering usually just has a visual effect of what we see on the Form, colours fonts, sizes, cursor shapes etc..

Two cases of Triggers are those on 'Styles' and 'DataTemplates' - there are others, but these will make

a good start to our understanding.

Data Triggers :

====================================================

=================================================================================================

Triggers related to Visual State (was Events !?) :

=====================================================

==========================================================================================================

Multiple Condition Triggers :

================================================================================================================

=====================================================

==================================================================================================================

===================================================

a Microsoft Example :

==================================================================================================

Style Triggers :

==================================================================

================================================================================================

================================================================================

Cursor changes :

======================================================================

Event Triggers :

PHH - 11th. March 2016.